chore: proposal schema change (1057) (#1169)

* chore: moved proposal queries to lib/governance

* chore: used new rationale title and description

* chore: addressed PR comments, refactored

* chore: moved proposal queries to lib/governance

* chore: used new rationale title and description

* chore: addressed PR comments, refactored

* fix: dropped s after merge

* fix: fixed lodash imports
This commit is contained in:
Art 2022-09-07 18:35:29 +02:00 committed by GitHub
parent 9de3683bf3
commit d2791f2e59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 610 additions and 466 deletions

View File

@ -9,7 +9,23 @@ import { ProposalState, ProposalRejectionReason, VoteValue } from "@vegaprotocol
// GraphQL query operation: ProposalsQuery
// ====================================================
export interface ProposalsQuery_proposals_party {
export interface ProposalsQuery_proposalsConnection_edges_node_rationale {
__typename: "ProposalRationale";
/**
* Title to be used to give a short description of the proposal in lists.
* This is to be between 0 and 100 unicode characters.
* This is mandatory for all proposals.
*/
title: string;
/**
* Description to show a short title / something in case the link goes offline.
* This is to be between 0 and 20k unicode characters.
* This is mandatory for all proposals.
*/
description: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_party {
__typename: "Party";
/**
* Party identifier
@ -17,11 +33,11 @@ export interface ProposalsQuery_proposals_party {
id: string;
}
export interface ProposalsQuery_proposals_terms_change_UpdateAsset {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateAsset {
__typename: "UpdateAsset" | "NewFreeform";
}
export interface ProposalsQuery_proposals_terms_change_NewMarket_instrument {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket_instrument {
__typename: "InstrumentConfiguration";
/**
* Full and fairly descriptive name for the instrument
@ -29,20 +45,20 @@ export interface ProposalsQuery_proposals_terms_change_NewMarket_instrument {
name: string;
}
export interface ProposalsQuery_proposals_terms_change_NewMarket {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket {
__typename: "NewMarket";
/**
* New market instrument configuration
*/
instrument: ProposalsQuery_proposals_terms_change_NewMarket_instrument;
instrument: ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket_instrument;
}
export interface ProposalsQuery_proposals_terms_change_UpdateMarket {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateMarket {
__typename: "UpdateMarket";
marketId: string;
}
export interface ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAsset {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source_BuiltinAsset {
__typename: "BuiltinAsset";
/**
* Maximum amount that can be requested by a party through the built-in asset faucet at a time
@ -50,7 +66,7 @@ export interface ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAs
maxFaucetAmountMint: string;
}
export interface ProposalsQuery_proposals_terms_change_NewAsset_source_ERC20 {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source_ERC20 {
__typename: "ERC20";
/**
* The address of the ERC20 contract
@ -58,9 +74,9 @@ export interface ProposalsQuery_proposals_terms_change_NewAsset_source_ERC20 {
contractAddress: string;
}
export type ProposalsQuery_proposals_terms_change_NewAsset_source = ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAsset | ProposalsQuery_proposals_terms_change_NewAsset_source_ERC20;
export type ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source = ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source_BuiltinAsset | ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source_ERC20;
export interface ProposalsQuery_proposals_terms_change_NewAsset {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset {
__typename: "NewAsset";
/**
* The symbol of the asset (e.g: GBP)
@ -69,10 +85,10 @@ export interface ProposalsQuery_proposals_terms_change_NewAsset {
/**
* The source of the new asset
*/
source: ProposalsQuery_proposals_terms_change_NewAsset_source;
source: ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source;
}
export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_networkParameter {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter_networkParameter {
__typename: "NetworkParameter";
/**
* The name of the network parameter
@ -84,14 +100,14 @@ export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_ne
value: string;
}
export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter {
__typename: "UpdateNetworkParameter";
networkParameter: ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_networkParameter;
networkParameter: ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter_networkParameter;
}
export type ProposalsQuery_proposals_terms_change = ProposalsQuery_proposals_terms_change_UpdateAsset | ProposalsQuery_proposals_terms_change_NewMarket | ProposalsQuery_proposals_terms_change_UpdateMarket | ProposalsQuery_proposals_terms_change_NewAsset | ProposalsQuery_proposals_terms_change_UpdateNetworkParameter;
export type ProposalsQuery_proposalsConnection_edges_node_terms_change = ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateAsset | ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket | ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateMarket | ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset | ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter;
export interface ProposalsQuery_proposals_terms {
export interface ProposalsQuery_proposalsConnection_edges_node_terms {
__typename: "ProposalTerms";
/**
* RFC3339Nano time and date when voting closes for this proposal.
@ -107,18 +123,18 @@ export interface ProposalsQuery_proposals_terms {
/**
* Actual change being introduced by the proposal - action the proposal triggers if passed and enacted.
*/
change: ProposalsQuery_proposals_terms_change;
change: ProposalsQuery_proposalsConnection_edges_node_terms_change;
}
export interface ProposalsQuery_proposals_votes_yes_votes_party_stake {
__typename: "PartyStake";
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
currentStakeAvailable: string;
}
export interface ProposalsQuery_proposals_votes_yes_votes_party {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party {
__typename: "Party";
/**
* Party identifier
@ -127,10 +143,10 @@ export interface ProposalsQuery_proposals_votes_yes_votes_party {
/**
* The staking information for this Party
*/
stake: ProposalsQuery_proposals_votes_yes_votes_party_stake;
stakingSummary: ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party_stakingSummary;
}
export interface ProposalsQuery_proposals_votes_yes_votes {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes {
__typename: "Vote";
/**
* The vote value cast
@ -139,14 +155,14 @@ export interface ProposalsQuery_proposals_votes_yes_votes {
/**
* The party casting the vote
*/
party: ProposalsQuery_proposals_votes_yes_votes_party;
party: ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party;
/**
* RFC3339Nano time and date when the vote reached Vega network
*/
datetime: string;
}
export interface ProposalsQuery_proposals_votes_yes {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes {
__typename: "ProposalVoteSide";
/**
* Total number of governance tokens from the votes cast for this side
@ -159,18 +175,18 @@ export interface ProposalsQuery_proposals_votes_yes {
/**
* All votes cast for this side
*/
votes: ProposalsQuery_proposals_votes_yes_votes[] | null;
votes: ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes[] | null;
}
export interface ProposalsQuery_proposals_votes_no_votes_party_stake {
__typename: "PartyStake";
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
currentStakeAvailable: string;
}
export interface ProposalsQuery_proposals_votes_no_votes_party {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party {
__typename: "Party";
/**
* Party identifier
@ -179,10 +195,10 @@ export interface ProposalsQuery_proposals_votes_no_votes_party {
/**
* The staking information for this Party
*/
stake: ProposalsQuery_proposals_votes_no_votes_party_stake;
stakingSummary: ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party_stakingSummary;
}
export interface ProposalsQuery_proposals_votes_no_votes {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes {
__typename: "Vote";
/**
* The vote value cast
@ -191,14 +207,14 @@ export interface ProposalsQuery_proposals_votes_no_votes {
/**
* The party casting the vote
*/
party: ProposalsQuery_proposals_votes_no_votes_party;
party: ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party;
/**
* RFC3339Nano time and date when the vote reached Vega network
*/
datetime: string;
}
export interface ProposalsQuery_proposals_votes_no {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no {
__typename: "ProposalVoteSide";
/**
* Total number of governance tokens from the votes cast for this side
@ -211,27 +227,31 @@ export interface ProposalsQuery_proposals_votes_no {
/**
* All votes cast for this side
*/
votes: ProposalsQuery_proposals_votes_no_votes[] | null;
votes: ProposalsQuery_proposalsConnection_edges_node_votes_no_votes[] | null;
}
export interface ProposalsQuery_proposals_votes {
export interface ProposalsQuery_proposalsConnection_edges_node_votes {
__typename: "ProposalVotes";
/**
* Yes votes cast for this proposal
*/
yes: ProposalsQuery_proposals_votes_yes;
yes: ProposalsQuery_proposalsConnection_edges_node_votes_yes;
/**
* No votes cast for this proposal
*/
no: ProposalsQuery_proposals_votes_no;
no: ProposalsQuery_proposalsConnection_edges_node_votes_no;
}
export interface ProposalsQuery_proposals {
export interface ProposalsQuery_proposalsConnection_edges_node {
__typename: "Proposal";
/**
* Proposal ID that is filled by Vega once proposal reaches the network
*/
id: string | null;
/**
* Rationale behind the proposal
*/
rationale: ProposalsQuery_proposalsConnection_edges_node_rationale;
/**
* A UUID reference to aid tracking proposals on Vega
*/
@ -251,20 +271,36 @@ export interface ProposalsQuery_proposals {
/**
* Party that prepared the proposal
*/
party: ProposalsQuery_proposals_party;
party: ProposalsQuery_proposalsConnection_edges_node_party;
/**
* Terms of the proposal
*/
terms: ProposalsQuery_proposals_terms;
terms: ProposalsQuery_proposalsConnection_edges_node_terms;
/**
* Votes cast for this proposal
*/
votes: ProposalsQuery_proposals_votes;
votes: ProposalsQuery_proposalsConnection_edges_node_votes;
}
export interface ProposalsQuery_proposalsConnection_edges {
__typename: "ProposalEdge";
/**
* The proposal data
*/
node: ProposalsQuery_proposalsConnection_edges_node;
}
export interface ProposalsQuery_proposalsConnection {
__typename: "ProposalsConnection";
/**
* List of proposals available for the connection
*/
edges: (ProposalsQuery_proposalsConnection_edges | null)[] | null;
}
export interface ProposalsQuery {
/**
* All governance proposals in the Vega network
*/
proposals: ProposalsQuery_proposals[] | null;
proposalsConnection: ProposalsQuery_proposalsConnection;
}

View File

@ -4,29 +4,22 @@ import React from 'react';
import { RouteTitle } from '../../components/route-title';
import { SubHeading } from '../../components/sub-heading';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { getProposals } from '@vegaprotocol/governance';
import type {
ProposalsQuery,
ProposalsQuery_proposals_terms_change,
ProposalsQuery_proposalsConnection_edges_node,
} from './__generated__/ProposalsQuery';
export function getProposalName(change: ProposalsQuery_proposals_terms_change) {
if (change.__typename === 'NewAsset') {
return t(`New asset: ${change.symbol}`);
} else if (change.__typename === 'NewMarket') {
return t(`New market: ${change.instrument.name}`);
} else if (change.__typename === 'UpdateMarket') {
return t(`Update market: ${change.marketId}`);
} else if (change.__typename === 'UpdateNetworkParameter') {
return t(`Update network: ${change.networkParameter.key}`);
}
return t('Unknown proposal');
}
const PROPOSAL_QUERY = gql`
const PROPOSALS_QUERY = gql`
query ProposalsQuery {
proposals {
proposalsConnection {
edges {
node {
id
rationale {
title
description
}
reference
state
datetime
@ -74,7 +67,7 @@ const PROPOSAL_QUERY = gql`
value
party {
id
stake {
stakingSummary {
currentStakeAvailable
}
}
@ -88,7 +81,7 @@ const PROPOSAL_QUERY = gql`
value
party {
id
stake {
stakingSummary {
currentStakeAvailable
}
}
@ -98,12 +91,17 @@ const PROPOSAL_QUERY = gql`
}
}
}
}
}
`;
const Governance = () => {
const { data } = useQuery<ProposalsQuery>(PROPOSAL_QUERY, {
const { data } = useQuery<ProposalsQuery>(PROPOSALS_QUERY, {
errorPolicy: 'ignore',
});
const proposals = getProposals(
data
) as ProposalsQuery_proposalsConnection_edges_node[];
if (!data) return null;
return (
@ -111,9 +109,11 @@ const Governance = () => {
<RouteTitle data-testid="governance-header">
{t('Governance Proposals')}
</RouteTitle>
{data.proposals?.map((p) => (
{proposals.map((p) => (
<React.Fragment key={p.id}>
<SubHeading>{getProposalName(p.terms.change)}</SubHeading>
<SubHeading>
{p.rationale.title || p.rationale.description}
</SubHeading>
<SyntaxHighlighter data={p} />
</React.Fragment>
))}

View File

@ -9,6 +9,22 @@ import { ProposalState, ProposalRejectionReason, VoteValue } from "@vegaprotocol
// GraphQL fragment: ProposalFields
// ====================================================
export interface ProposalFields_rationale {
__typename: "ProposalRationale";
/**
* Title to be used to give a short description of the proposal in lists.
* This is to be between 0 and 100 unicode characters.
* This is mandatory for all proposals.
*/
title: string;
/**
* Description to show a short title / something in case the link goes offline.
* This is to be between 0 and 20k unicode characters.
* This is mandatory for all proposals.
*/
description: string;
}
export interface ProposalFields_party {
__typename: "Party";
/**
@ -55,14 +71,6 @@ export interface ProposalFields_terms_change_NewMarket_instrument {
export interface ProposalFields_terms_change_NewMarket {
__typename: "NewMarket";
/**
* Decimal places used for the new market, sets the smallest price increment on the book
*/
decimalPlaces: number;
/**
* Metadata for this instrument, tags
*/
metadata: string[] | null;
/**
* New market instrument configuration
*/
@ -146,8 +154,8 @@ export interface ProposalFields_terms {
change: ProposalFields_terms_change;
}
export interface ProposalFields_votes_yes_votes_party_stake {
__typename: "PartyStake";
export interface ProposalFields_votes_yes_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
@ -163,7 +171,7 @@ export interface ProposalFields_votes_yes_votes_party {
/**
* The staking information for this Party
*/
stake: ProposalFields_votes_yes_votes_party_stake;
stakingSummary: ProposalFields_votes_yes_votes_party_stakingSummary;
}
export interface ProposalFields_votes_yes_votes {
@ -198,8 +206,8 @@ export interface ProposalFields_votes_yes {
votes: ProposalFields_votes_yes_votes[] | null;
}
export interface ProposalFields_votes_no_votes_party_stake {
__typename: "PartyStake";
export interface ProposalFields_votes_no_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
@ -215,7 +223,7 @@ export interface ProposalFields_votes_no_votes_party {
/**
* The staking information for this Party
*/
stake: ProposalFields_votes_no_votes_party_stake;
stakingSummary: ProposalFields_votes_no_votes_party_stakingSummary;
}
export interface ProposalFields_votes_no_votes {
@ -268,6 +276,10 @@ export interface ProposalFields {
* Proposal ID that is filled by Vega once proposal reaches the network
*/
id: string | null;
/**
* Rationale behind the proposal
*/
rationale: ProposalFields_rationale;
/**
* A UUID reference to aid tracking proposals on Vega
*/
@ -284,14 +296,14 @@ export interface ProposalFields {
* Reason for the proposal to be rejected by the core
*/
rejectionReason: ProposalRejectionReason | null;
/**
* Error details of the rejectionReason
*/
errorDetails: string | null;
/**
* Party that prepared the proposal
*/
party: ProposalFields_party;
/**
* Error details of the rejectionReason
*/
errorDetails: string | null;
/**
* Terms of the proposal
*/

View File

@ -1,10 +1,10 @@
import { ProposalState } from '@vegaprotocol/types';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import type { ProposalFields } from '../../__generated__/ProposalFields';
export const CurrentProposalState = ({
proposal,
}: {
proposal: Proposals_proposals;
proposal: ProposalFields;
}) => {
const { state } = proposal;
let className = 'text-white';

View File

@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { ProposalState } from '@vegaprotocol/types';
import { useVoteInformation } from '../../hooks';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import type { ProposalFields } from '../../__generated__/ProposalFields';
export const StatusPass = ({ children }: { children: React.ReactNode }) => (
<span className="text-vega-green">{children}</span>
@ -17,7 +17,7 @@ export const StatusFail = ({ children }: { children: React.ReactNode }) => (
export const CurrentProposalStatus = ({
proposal,
}: {
proposal: Proposals_proposals;
proposal: ProposalFields;
}) => {
const { willPass, majorityMet, participationMet } = useVoteInformation({
proposal,

View File

@ -3,11 +3,11 @@ import { useTranslation } from 'react-i18next';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import { CurrentProposalState } from '../current-proposal-state';
import type { ProposalFields } from '../../__generated__/ProposalFields';
interface ProposalChangeTableProps {
proposal: Proposals_proposals;
proposal: ProposalFields;
}
export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {

View File

@ -1,9 +1,9 @@
import { render, screen } from '@testing-library/react';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { ProposalHeader } from './proposal-header';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import type { Proposal_proposal } from '@vegaprotocol/governance';
const renderComponent = (proposal: Proposals_proposals) => (
const renderComponent = (proposal: Proposal_proposal) => (
<ProposalHeader proposal={proposal} />
);
@ -12,10 +12,13 @@ describe('Proposal header', () => {
render(
renderComponent(
generateProposal({
rationale: {
title: 'New some market',
description: 'A new some market',
},
terms: {
change: {
__typename: 'NewMarket',
decimalPlaces: 1,
instrument: {
__typename: 'InstrumentConfiguration',
name: 'Some market',
@ -28,16 +31,18 @@ describe('Proposal header', () => {
},
},
},
metadata: [],
},
},
})
)
);
expect(screen.getByTestId('proposal-header')).toHaveTextContent(
'New market: Some market'
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'New some market'
);
expect(screen.getByTestId('proposal-details-one')).toHaveTextContent(
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
'A new some market'
);
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'tGBP settled future.'
);
});
@ -46,6 +51,9 @@ describe('Proposal header', () => {
render(
renderComponent(
generateProposal({
rationale: {
title: 'New market id',
},
terms: {
change: {
__typename: 'UpdateMarket',
@ -55,7 +63,13 @@ describe('Proposal header', () => {
})
)
);
expect(screen.getByTestId('proposal-header')).toHaveTextContent(
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'New market id'
);
expect(
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Market change: MarketId'
);
});
@ -64,6 +78,10 @@ describe('Proposal header', () => {
render(
renderComponent(
generateProposal({
rationale: {
title: 'New asset: Fake currency',
description: '',
},
terms: {
change: {
__typename: 'NewAsset',
@ -78,10 +96,10 @@ describe('Proposal header', () => {
})
)
);
expect(screen.getByTestId('proposal-header')).toHaveTextContent(
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'New asset: Fake currency'
);
expect(screen.getByTestId('proposal-details-one')).toHaveTextContent(
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Symbol: FAKE. ERC20 0x0'
);
});
@ -104,10 +122,10 @@ describe('Proposal header', () => {
})
)
);
expect(screen.getByTestId('proposal-header')).toHaveTextContent(
'New asset: Fake currency'
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Unknown proposal'
);
expect(screen.getByTestId('proposal-details-one')).toHaveTextContent(
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Symbol: BIA. Max faucet amount mint: 300'
);
});
@ -116,6 +134,9 @@ describe('Proposal header', () => {
render(
renderComponent(
generateProposal({
rationale: {
title: 'Network parameter',
},
terms: {
change: {
__typename: 'UpdateNetworkParameter',
@ -129,25 +150,22 @@ describe('Proposal header', () => {
})
)
);
expect(screen.getByTestId('proposal-header')).toHaveTextContent(
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Network parameter'
);
expect(screen.getByTestId('proposal-details-one')).toHaveTextContent(
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Network key to Network value'
);
});
// Skipped until proposals have rationale - https://github.com/vegaprotocol/frontend-monorepo/issues/824
// eslint-disable-next-line jest/no-disabled-tests
it.skip('Renders Freeform network - short rationale', () => {
it('Renders Freeform network - short rationale', () => {
render(
renderComponent(
generateProposal({
id: 'short',
// rationale: {
// hash: '0x0',
// description: 'freeform description',
// },
rationale: {
title: '0x0',
},
terms: {
change: {
__typename: 'NewFreeform',
@ -156,27 +174,23 @@ describe('Proposal header', () => {
})
)
);
expect(screen.getByTestId('proposal-header')).toHaveTextContent(
'freeform description'
);
expect(screen.getByTestId('proposal-details-one')).toBeEmptyDOMElement();
expect(screen.getByTestId('proposal-details-two')).toHaveTextContent(
'short'
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent('0x0');
expect(
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
expect(screen.getByTestId('proposal-details')).toHaveTextContent('short');
});
// Skipped until proposals have rationale
// eslint-disable-next-line jest/no-disabled-tests
it.skip('Renders Freeform proposal - long rationale (105 chars)', () => {
it('Renders Freeform proposal - long rationale (105 chars)', () => {
render(
renderComponent(
generateProposal({
id: 'long',
// rationale: {
// hash: '0x0',
// description:
// 'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
// },
rationale: {
title: '0x0',
description:
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
},
terms: {
change: {
__typename: 'NewFreeform',
@ -187,29 +201,24 @@ describe('Proposal header', () => {
);
// For a rationale over 100 chars, we expect the header to be truncated at
// 100 chars with ellipsis and the details-one element to contain the rest.
expect(screen.getByTestId('proposal-header')).toHaveTextContent(
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean…'
);
expect(screen.getByTestId('proposal-details-one')).toHaveTextContent(
'dolor'
);
expect(screen.getByTestId('proposal-details-two')).toHaveTextContent(
'long'
expect(screen.getByTestId('proposal-title')).toHaveTextContent('0x0');
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
'Class aptent taciti sociosqu ad litora torquent per conubia'
);
expect(screen.getByTestId('proposal-details')).toHaveTextContent('long');
});
// Skipped until proposals have rationale
// eslint-disable-next-line jest/no-disabled-tests
it.skip('Renders Freeform proposal - extra long rationale (165 chars)', () => {
it('Renders Freeform proposal - extra long rationale (165 chars)', () => {
render(
renderComponent(
generateProposal({
id: 'extraLong',
// rationale: {
// hash: '0x0',
// description:
// 'Aenean sem odio, eleifend non sodales vitae, porttitor eu ex. Aliquam erat volutpat. Fusce pharetra libero quis risus lobortis, sed ornare leo efficitur turpis duis.',
// },
rationale: {
title:
'Aenean sem odio, eleifend non sodales vitae, porttitor eu ex. Aliquam erat volutpat. Fusce pharetra libero quis risus lobortis, sed ornare leo efficitur turpis duis.',
description:
'Aenean sem odio, eleifend non sodales vitae, porttitor eu ex. Aliquam erat volutpat. Fusce pharetra libero quis risus lobortis, sed ornare leo efficitur turpis duis.',
},
terms: {
change: {
__typename: 'NewFreeform',
@ -221,13 +230,13 @@ describe('Proposal header', () => {
// For a rationale over 160 chars, we expect the header to be truncated at 100
// chars with ellipsis and the details-one element to contain 60 chars and also
// be truncated with an ellipsis.
expect(screen.getByTestId('proposal-header')).toHaveTextContent(
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Aenean sem odio, eleifend non sodales vitae, porttitor eu ex. Aliquam erat volutpat. Fusce pharetra…'
);
expect(screen.getByTestId('proposal-details-one')).toHaveTextContent(
'libero quis risus lobortis, sed ornare leo efficitur turpis…'
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
'Aenean sem odio, eleifend non sodales vitae, porttitor eu e…'
);
expect(screen.getByTestId('proposal-details-two')).toHaveTextContent(
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'extraLong'
);
});
@ -238,6 +247,9 @@ describe('Proposal header', () => {
renderComponent(
generateProposal({
id: 'freeform id',
rationale: {
title: 'freeform',
},
terms: {
change: {
__typename: 'NewFreeform',
@ -246,15 +258,13 @@ describe('Proposal header', () => {
})
)
);
expect(screen.getByTestId('proposal-header')).toHaveTextContent(
'Freeform proposal: freeform id'
expect(screen.getByTestId('proposal-title')).toHaveTextContent('freeform');
expect(
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
expect(screen.queryByTestId('proposal-details')).toHaveTextContent(
'freeform id'
);
expect(
screen.queryByTestId('proposal-details-one')
).not.toBeInTheDocument();
expect(
screen.queryByTestId('proposal-details-two')
).not.toBeInTheDocument();
});
it("Renders unknown proposal if it's a different proposal type", () => {
@ -270,7 +280,7 @@ describe('Proposal header', () => {
})
)
);
expect(screen.getByTestId('proposal-header')).toHaveTextContent(
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Unknown proposal'
);
});

View File

@ -1,24 +1,28 @@
import { useTranslation } from 'react-i18next';
import { Lozenge } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import { shorten } from '@vegaprotocol/react-helpers';
import type { ProposalFields } from '../../__generated__/ProposalFields';
export const ProposalHeader = ({
proposal,
}: {
proposal: Proposals_proposals;
}) => {
export const ProposalHeader = ({ proposal }: { proposal: ProposalFields }) => {
const { t } = useTranslation();
const { change } = proposal.terms;
let headerText: string;
let detailsOne: ReactNode;
let detailsTwo: ReactNode;
let details: ReactNode;
let title = proposal.rationale.title.trim();
let description = proposal.rationale.description.trim();
if (title.length === 0 && description.length > 0) {
title = description;
description = '';
}
const titleContent = shorten(title, 100);
const descriptionContent = shorten(description, 60);
switch (change.__typename) {
case 'NewMarket': {
headerText = `${t('New market')}: ${change.instrument.name}`;
detailsOne = (
details = (
<>
{t('Code')}: {change.instrument.code}.{' '}
{change.instrument.futureProduct?.settlementAsset.symbol ? (
@ -36,12 +40,11 @@ export const ProposalHeader = ({
break;
}
case 'UpdateMarket': {
headerText = `${t('Market change')}: ${change.marketId}`;
details = `${t('Market change')}: ${change.marketId}`;
break;
}
case 'NewAsset': {
headerText = `${t('New asset')}: ${change.name}`;
detailsOne = (
details = (
<>
{t('Symbol')}: {change.symbol}.{' '}
<Lozenge>
@ -57,8 +60,7 @@ export const ProposalHeader = ({
}
case 'UpdateNetworkParameter': {
const parametersClasses = 'font-mono leading-none';
headerText = `${t('Network parameter')}`;
detailsOne = (
details = (
<>
<span className={`${parametersClasses} mr-2`}>
{change.networkParameter.key}
@ -72,44 +74,34 @@ export const ProposalHeader = ({
break;
}
case 'NewFreeform': {
// When rationale exists (https://github.com/vegaprotocol/frontend-monorepo/issues/824):
// const description = proposal.rationale.description.trim();
// const headerMaxLength = 100;
// const descriptionOneMaxLength = 60;
// const headerOverflow = description.length > headerMaxLength;
// const descriptionOneOverflow =
// description.length > headerMaxLength + descriptionOneMaxLength;
//
// headerText = `${description.substring(0, headerMaxLength - 1).trim()}${
// headerOverflow ? '…' : ''
// }`;
// detailsOne = headerOverflow
// ? `${description
// .substring(
// headerMaxLength - 1,
// headerMaxLength + descriptionOneMaxLength - 1
// )
// .trim()}${descriptionOneOverflow ? '…' : ''}`
// : '';
// detailsTwo = `${proposal.id}`;
headerText = proposal.id
? `${t('Freeform proposal')}: ${proposal.id.trim()}`
: `${t('Unknown proposal')}`;
details = `${proposal.id}`;
break;
}
default: {
headerText = `${t('Unknown proposal')}`;
}
}
return (
<div className="text-sm mb-2">
<header data-testid="proposal-header">
<h2 className="text-lg mx-0 mt-0 mb-1 font-semibold">{headerText}</h2>
<header data-testid="proposal-title">
<h2
{...(title && title.length > titleContent.length && { title: title })}
className="text-lg mx-0 mt-0 mb-1 font-semibold"
>
{titleContent || t('Unknown proposal')}
</h2>
</header>
{detailsOne && <div data-testid="proposal-details-one">{detailsOne}</div>}
{detailsTwo && <div data-testid="proposal-details-two">{detailsTwo}</div>}
{descriptionContent && (
<div
className="mb-4"
{...(description.length > descriptionContent.length && {
title: description,
})}
data-testid="proposal-description"
>
{descriptionContent}
</div>
)}
{details && <div data-testid="proposal-details">{details}</div>}
</div>
);
};

View File

@ -1,11 +1,11 @@
import { useTranslation } from 'react-i18next';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import type { Proposal_proposal_terms } from '../../proposal/__generated__/Proposal';
import type { ProposalFields_terms } from '../../__generated__/ProposalFields';
export const ProposalTermsJson = ({
terms,
}: {
terms: Proposal_proposal_terms;
terms: ProposalFields_terms;
}) => {
const { t } = useTranslation();
return (

View File

@ -6,11 +6,11 @@ import {
formatNumberPercentage,
} from '@vegaprotocol/react-helpers';
import { useVoteInformation } from '../../hooks';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import type { ProposalFields } from '../../__generated__/ProposalFields';
interface ProposalVotesTableProps {
proposal: Proposals_proposals;
proposal: ProposalFields;
}
export const ProposalVotesTable = ({ proposal }: ProposalVotesTableProps) => {

View File

@ -26,10 +26,10 @@ import {
lastWeek,
nextWeek,
} from '../../test-helpers/mocks';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import type { ProposalsConnection_proposalsConnection_edges_node as ProposalNode } from '@vegaprotocol/governance';
const renderComponent = (
proposal: Proposals_proposals,
proposal: ProposalNode,
mock = networkParamsQueryMock
) => (
<Router>
@ -173,8 +173,8 @@ describe('Proposals list item details', () => {
party: {
__typename: 'Party',
id: mockPubkey,
stake: {
__typename: 'PartyStake',
stakingSummary: {
__typename: 'StakingSummary',
currentStakeAvailable: '1000',
},
},
@ -211,8 +211,8 @@ describe('Proposals list item details', () => {
party: {
__typename: 'Party',
id: mockPubkey,
stake: {
__typename: 'PartyStake',
stakingSummary: {
__typename: 'StakingSummary',
currentStakeAvailable: '1000',
},
},

View File

@ -10,12 +10,12 @@ import { format, formatDistanceToNowStrict } from 'date-fns';
import { useTranslation } from 'react-i18next';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import type { ReactNode } from 'react';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import {
ProposalRejectionReasonMapping,
ProposalState,
} from '@vegaprotocol/types';
import Routes from '../../../routes';
import type { ProposalFields } from '../../__generated__/ProposalFields';
const MajorityNotReached = () => {
const { t } = useTranslation();
@ -37,7 +37,7 @@ const ParticipationNotReached = () => {
export const ProposalsListItemDetails = ({
proposal,
}: {
proposal: Proposals_proposals;
proposal: ProposalFields;
}) => {
const { state } = proposal;
const { willPass, majorityMet, participationMet } = useVoteInformation({

View File

@ -1,9 +1,9 @@
import type { ProposalFields } from '../../__generated__/ProposalFields';
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import { ProposalsListItemDetails } from './proposals-list-item-details';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
interface ProposalsListItemProps {
proposal: Proposals_proposals;
proposal: ProposalFields;
}
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {

View File

@ -14,7 +14,7 @@ import {
lastMonth,
nextMonth,
} from '../../test-helpers/mocks';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import type { ProposalsConnection_proposalsConnection_edges_node as ProposalNode } from '@vegaprotocol/governance';
const openProposalClosesNextMonth = generateProposal({
id: 'proposal1',
@ -58,7 +58,7 @@ const failedProposalClosedLastMonth = generateProposal({
},
});
const renderComponent = (proposals: Proposals_proposals[]) => (
const renderComponent = (proposals: ProposalNode[]) => (
<Router>
<MockedProvider mocks={[networkParamsQueryMock]}>
<AppStateProvider>

View File

@ -4,20 +4,20 @@ import { useTranslation } from 'react-i18next';
import { Heading } from '../../../../components/heading';
import { ProposalsListItem } from '../proposals-list-item';
import { ProposalsListFilter } from '../proposals-list-filter';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import Routes from '../../../routes';
import { Button } from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom';
import type { ProposalFields } from '../../__generated__/ProposalFields';
import { Links } from '../../../../config';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
interface ProposalsListProps {
proposals: Proposals_proposals[];
proposals: ProposalFields[];
}
interface SortedProposalsProps {
open: Proposals_proposals[];
closed: Proposals_proposals[];
open: ProposalFields[];
closed: ProposalFields[];
}
export const ProposalsList = ({ proposals }: ProposalsListProps) => {
@ -39,7 +39,7 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
}
);
const filterPredicate = (p: Proposals_proposals) =>
const filterPredicate = (p: ProposalFields) =>
p.id?.includes(filterString) ||
p.party?.id?.toString().includes(filterString);

View File

@ -12,11 +12,11 @@ import {
nextWeek,
lastMonth,
} from '../../test-helpers/mocks';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import type { ProposalsConnection_proposalsConnection_edges_node as ProposalNode } from '@vegaprotocol/governance';
const rejectedProposalClosesNextWeek = generateProposal({
id: 'rejected1',
state: ProposalState.Open,
state: ProposalState.STATE_OPEN,
party: {
id: 'bvcx',
},
@ -28,14 +28,14 @@ const rejectedProposalClosesNextWeek = generateProposal({
const rejectedProposalClosedLastMonth = generateProposal({
id: 'rejected2',
state: ProposalState.Rejected,
state: ProposalState.STATE_REJECTED,
terms: {
closingDatetime: lastMonth.toString(),
enactmentDatetime: lastMonth.toString(),
},
});
const renderComponent = (proposals: Proposals_proposals[]) => (
const renderComponent = (proposals: ProposalNode[]) => (
<Router>
<MockedProvider mocks={[networkParamsQueryMock]}>
<AppStateProvider>

View File

@ -3,17 +3,17 @@ import { useTranslation } from 'react-i18next';
import { Heading } from '../../../../components/heading';
import { ProposalsListItem } from '../proposals-list-item';
import { ProposalsListFilter } from '../proposals-list-filter';
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
import type { Proposals_proposalsConnection_edges_node } from '../../proposals/__generated__/Proposals';
interface ProposalsListProps {
proposals: Proposals_proposals[];
proposals: Proposals_proposalsConnection_edges_node[];
}
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
const { t } = useTranslation();
const [filterString, setFilterString] = useState('');
const filterPredicate = (p: Proposals_proposals) =>
const filterPredicate = (p: Proposals_proposalsConnection_edges_node) =>
p.id?.includes(filterString) ||
p.party?.id?.toString().includes(filterString);

View File

@ -4,13 +4,13 @@ import { useTranslation } from 'react-i18next';
import { formatNumber } from '../../../../lib/format-number';
import { ConnectToVega } from '../../../staking/connect-to-vega';
import { useVoteInformation } from '../../hooks';
import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
import { CurrentProposalStatus } from '../current-proposal-status';
import { useUserVote } from './use-user-vote';
import { VoteButtonsContainer } from './vote-buttons';
import { VoteProgress } from './vote-progress';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { ProposalState } from '@vegaprotocol/types';
import type { Proposal_proposal } from '../../proposal/__generated__/Proposal';
interface VoteDetailsProps {
proposal: Proposal_proposal;

View File

@ -6,15 +6,15 @@ import { useAppState } from '../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../lib/bignumber';
import { addDecimal } from '../../../lib/decimals';
import type {
Proposal_proposal_votes_no_votes,
Proposal_proposal_votes_yes_votes,
} from '../proposal/__generated__/Proposal';
import type { Proposals_proposals } from '../proposals/__generated__/Proposals';
ProposalFields,
ProposalFields_votes_no_votes,
ProposalFields_votes_yes_votes,
} from '../__generated__/ProposalFields';
const useProposalNetworkParams = ({
proposal,
}: {
proposal: Proposals_proposals;
proposal: ProposalFields;
}) => {
const { data, loading } = useNetworkParams([
NetworkParams.GOV_UPDATE_MARKET_REQUIRED_MAJORITY,
@ -82,7 +82,7 @@ const useProposalNetworkParams = ({
export const useVoteInformation = ({
proposal,
}: {
proposal: Proposals_proposals;
proposal: ProposalFields;
}) => {
const {
appState: { totalSupply },
@ -105,10 +105,10 @@ export const useVoteInformation = ({
return new BigNumber(0);
}
const totalNoVotes = proposal.votes.no.votes.reduce(
(prevValue: BigNumber, newValue: Proposal_proposal_votes_no_votes) => {
return new BigNumber(newValue.party.stake.currentStakeAvailable).plus(
prevValue
);
(prevValue: BigNumber, newValue: ProposalFields_votes_no_votes) => {
return new BigNumber(
newValue.party.stakingSummary.currentStakeAvailable
).plus(prevValue);
},
new BigNumber(0)
);
@ -120,10 +120,10 @@ export const useVoteInformation = ({
return new BigNumber(0);
}
const totalYesVotes = proposal.votes.yes.votes.reduce(
(prevValue: BigNumber, newValue: Proposal_proposal_votes_yes_votes) => {
return new BigNumber(newValue.party.stake.currentStakeAvailable).plus(
prevValue
);
(prevValue: BigNumber, newValue: ProposalFields_votes_yes_votes) => {
return new BigNumber(
newValue.party.stakingSummary.currentStakeAvailable
).plus(prevValue);
},
new BigNumber(0)
);

View File

@ -1,23 +1,25 @@
import { gql } from '@apollo/client';
export const PROPOSALS_FRAGMENT = gql`
export const PROPOSAL_FRAGMENT = gql`
fragment ProposalFields on Proposal {
id
rationale {
title
description
}
reference
state
datetime
rejectionReason
errorDetails
party {
id
}
errorDetails
terms {
closingDatetime
enactmentDatetime
change {
... on NewMarket {
decimalPlaces
metadata
instrument {
name
code
@ -37,11 +39,9 @@ export const PROPOSALS_FRAGMENT = gql`
symbol
source {
... on BuiltinAsset {
__typename
maxFaucetAmountMint
}
... on ERC20 {
__typename
contractAddress
}
}
@ -62,7 +62,7 @@ export const PROPOSALS_FRAGMENT = gql`
value
party {
id
stake {
stakingSummary {
currentStakeAvailable
}
}
@ -76,7 +76,7 @@ export const PROPOSALS_FRAGMENT = gql`
value
party {
id
stake {
stakingSummary {
currentStakeAvailable
}
}

View File

@ -9,6 +9,22 @@ import { ProposalState, ProposalRejectionReason, VoteValue } from "@vegaprotocol
// GraphQL query operation: Proposal
// ====================================================
export interface Proposal_proposal_rationale {
__typename: "ProposalRationale";
/**
* Title to be used to give a short description of the proposal in lists.
* This is to be between 0 and 100 unicode characters.
* This is mandatory for all proposals.
*/
title: string;
/**
* Description to show a short title / something in case the link goes offline.
* This is to be between 0 and 20k unicode characters.
* This is mandatory for all proposals.
*/
description: string;
}
export interface Proposal_proposal_party {
__typename: "Party";
/**
@ -55,14 +71,6 @@ export interface Proposal_proposal_terms_change_NewMarket_instrument {
export interface Proposal_proposal_terms_change_NewMarket {
__typename: "NewMarket";
/**
* Decimal places used for the new market, sets the smallest price increment on the book
*/
decimalPlaces: number;
/**
* Metadata for this instrument, tags
*/
metadata: string[] | null;
/**
* New market instrument configuration
*/
@ -146,8 +154,8 @@ export interface Proposal_proposal_terms {
change: Proposal_proposal_terms_change;
}
export interface Proposal_proposal_votes_yes_votes_party_stake {
__typename: "PartyStake";
export interface Proposal_proposal_votes_yes_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
@ -163,7 +171,7 @@ export interface Proposal_proposal_votes_yes_votes_party {
/**
* The staking information for this Party
*/
stake: Proposal_proposal_votes_yes_votes_party_stake;
stakingSummary: Proposal_proposal_votes_yes_votes_party_stakingSummary;
}
export interface Proposal_proposal_votes_yes_votes {
@ -198,8 +206,8 @@ export interface Proposal_proposal_votes_yes {
votes: Proposal_proposal_votes_yes_votes[] | null;
}
export interface Proposal_proposal_votes_no_votes_party_stake {
__typename: "PartyStake";
export interface Proposal_proposal_votes_no_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
@ -215,7 +223,7 @@ export interface Proposal_proposal_votes_no_votes_party {
/**
* The staking information for this Party
*/
stake: Proposal_proposal_votes_no_votes_party_stake;
stakingSummary: Proposal_proposal_votes_no_votes_party_stakingSummary;
}
export interface Proposal_proposal_votes_no_votes {
@ -268,6 +276,10 @@ export interface Proposal_proposal {
* Proposal ID that is filled by Vega once proposal reaches the network
*/
id: string | null;
/**
* Rationale behind the proposal
*/
rationale: Proposal_proposal_rationale;
/**
* A UUID reference to aid tracking proposals on Vega
*/
@ -284,14 +296,14 @@ export interface Proposal_proposal {
* Reason for the proposal to be rejected by the core
*/
rejectionReason: ProposalRejectionReason | null;
/**
* Error details of the rejectionReason
*/
errorDetails: string | null;
/**
* Party that prepared the proposal
*/
party: Proposal_proposal_party;
/**
* Error details of the rejectionReason
*/
errorDetails: string | null;
/**
* Terms of the proposal
*/

View File

@ -1,5 +1,4 @@
export {
ProposalContainer,
PROPOSAL_QUERY,
ProposalContainer as default,
} from './proposal-container';

View File

@ -4,14 +4,14 @@ import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Proposal } from '../components/proposal';
import { PROPOSALS_FRAGMENT } from '../proposal-fragment';
import { PROPOSAL_FRAGMENT } from '../proposal-fragment';
import type {
Proposal as ProposalQueryResult,
ProposalVariables,
} from './__generated__/Proposal';
export const PROPOSAL_QUERY = gql`
${PROPOSALS_FRAGMENT}
${PROPOSAL_FRAGMENT}
query Proposal($proposalId: ID!) {
proposal(id: $proposalId) {
...ProposalFields

View File

@ -9,7 +9,23 @@ import { ProposalState, ProposalRejectionReason, VoteValue } from "@vegaprotocol
// GraphQL query operation: Proposals
// ====================================================
export interface Proposals_proposals_party {
export interface Proposals_proposalsConnection_edges_node_rationale {
__typename: "ProposalRationale";
/**
* Title to be used to give a short description of the proposal in lists.
* This is to be between 0 and 100 unicode characters.
* This is mandatory for all proposals.
*/
title: string;
/**
* Description to show a short title / something in case the link goes offline.
* This is to be between 0 and 20k unicode characters.
* This is mandatory for all proposals.
*/
description: string;
}
export interface Proposals_proposalsConnection_edges_node_party {
__typename: "Party";
/**
* Party identifier
@ -17,11 +33,11 @@ export interface Proposals_proposals_party {
id: string;
}
export interface Proposals_proposals_terms_change_UpdateAsset {
export interface Proposals_proposalsConnection_edges_node_terms_change_UpdateAsset {
__typename: "UpdateAsset" | "NewFreeform";
}
export interface Proposals_proposals_terms_change_NewMarket_instrument_futureProduct_settlementAsset {
export interface Proposals_proposalsConnection_edges_node_terms_change_NewMarket_instrument_futureProduct_settlementAsset {
__typename: "Asset";
/**
* The symbol of the asset (e.g: GBP)
@ -29,15 +45,15 @@ export interface Proposals_proposals_terms_change_NewMarket_instrument_futurePro
symbol: string;
}
export interface Proposals_proposals_terms_change_NewMarket_instrument_futureProduct {
export interface Proposals_proposalsConnection_edges_node_terms_change_NewMarket_instrument_futureProduct {
__typename: "FutureProduct";
/**
* Product asset ID
*/
settlementAsset: Proposals_proposals_terms_change_NewMarket_instrument_futureProduct_settlementAsset;
settlementAsset: Proposals_proposalsConnection_edges_node_terms_change_NewMarket_instrument_futureProduct_settlementAsset;
}
export interface Proposals_proposals_terms_change_NewMarket_instrument {
export interface Proposals_proposalsConnection_edges_node_terms_change_NewMarket_instrument {
__typename: "InstrumentConfiguration";
/**
* Full and fairly descriptive name for the instrument
@ -50,31 +66,23 @@ export interface Proposals_proposals_terms_change_NewMarket_instrument {
/**
* Future product specification
*/
futureProduct: Proposals_proposals_terms_change_NewMarket_instrument_futureProduct | null;
futureProduct: Proposals_proposalsConnection_edges_node_terms_change_NewMarket_instrument_futureProduct | null;
}
export interface Proposals_proposals_terms_change_NewMarket {
export interface Proposals_proposalsConnection_edges_node_terms_change_NewMarket {
__typename: "NewMarket";
/**
* Decimal places used for the new market, sets the smallest price increment on the book
*/
decimalPlaces: number;
/**
* Metadata for this instrument, tags
*/
metadata: string[] | null;
/**
* New market instrument configuration
*/
instrument: Proposals_proposals_terms_change_NewMarket_instrument;
instrument: Proposals_proposalsConnection_edges_node_terms_change_NewMarket_instrument;
}
export interface Proposals_proposals_terms_change_UpdateMarket {
export interface Proposals_proposalsConnection_edges_node_terms_change_UpdateMarket {
__typename: "UpdateMarket";
marketId: string;
}
export interface Proposals_proposals_terms_change_NewAsset_source_BuiltinAsset {
export interface Proposals_proposalsConnection_edges_node_terms_change_NewAsset_source_BuiltinAsset {
__typename: "BuiltinAsset";
/**
* Maximum amount that can be requested by a party through the built-in asset faucet at a time
@ -82,7 +90,7 @@ export interface Proposals_proposals_terms_change_NewAsset_source_BuiltinAsset {
maxFaucetAmountMint: string;
}
export interface Proposals_proposals_terms_change_NewAsset_source_ERC20 {
export interface Proposals_proposalsConnection_edges_node_terms_change_NewAsset_source_ERC20 {
__typename: "ERC20";
/**
* The address of the ERC20 contract
@ -90,9 +98,9 @@ export interface Proposals_proposals_terms_change_NewAsset_source_ERC20 {
contractAddress: string;
}
export type Proposals_proposals_terms_change_NewAsset_source = Proposals_proposals_terms_change_NewAsset_source_BuiltinAsset | Proposals_proposals_terms_change_NewAsset_source_ERC20;
export type Proposals_proposalsConnection_edges_node_terms_change_NewAsset_source = Proposals_proposalsConnection_edges_node_terms_change_NewAsset_source_BuiltinAsset | Proposals_proposalsConnection_edges_node_terms_change_NewAsset_source_ERC20;
export interface Proposals_proposals_terms_change_NewAsset {
export interface Proposals_proposalsConnection_edges_node_terms_change_NewAsset {
__typename: "NewAsset";
/**
* The full name of the asset (e.g: Great British Pound)
@ -105,10 +113,10 @@ export interface Proposals_proposals_terms_change_NewAsset {
/**
* The source of the new asset
*/
source: Proposals_proposals_terms_change_NewAsset_source;
source: Proposals_proposalsConnection_edges_node_terms_change_NewAsset_source;
}
export interface Proposals_proposals_terms_change_UpdateNetworkParameter_networkParameter {
export interface Proposals_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter_networkParameter {
__typename: "NetworkParameter";
/**
* The name of the network parameter
@ -120,14 +128,14 @@ export interface Proposals_proposals_terms_change_UpdateNetworkParameter_network
value: string;
}
export interface Proposals_proposals_terms_change_UpdateNetworkParameter {
export interface Proposals_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter {
__typename: "UpdateNetworkParameter";
networkParameter: Proposals_proposals_terms_change_UpdateNetworkParameter_networkParameter;
networkParameter: Proposals_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter_networkParameter;
}
export type Proposals_proposals_terms_change = Proposals_proposals_terms_change_UpdateAsset | Proposals_proposals_terms_change_NewMarket | Proposals_proposals_terms_change_UpdateMarket | Proposals_proposals_terms_change_NewAsset | Proposals_proposals_terms_change_UpdateNetworkParameter;
export type Proposals_proposalsConnection_edges_node_terms_change = Proposals_proposalsConnection_edges_node_terms_change_UpdateAsset | Proposals_proposalsConnection_edges_node_terms_change_NewMarket | Proposals_proposalsConnection_edges_node_terms_change_UpdateMarket | Proposals_proposalsConnection_edges_node_terms_change_NewAsset | Proposals_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter;
export interface Proposals_proposals_terms {
export interface Proposals_proposalsConnection_edges_node_terms {
__typename: "ProposalTerms";
/**
* RFC3339Nano time and date when voting closes for this proposal.
@ -143,18 +151,18 @@ export interface Proposals_proposals_terms {
/**
* Actual change being introduced by the proposal - action the proposal triggers if passed and enacted.
*/
change: Proposals_proposals_terms_change;
change: Proposals_proposalsConnection_edges_node_terms_change;
}
export interface Proposals_proposals_votes_yes_votes_party_stake {
__typename: "PartyStake";
export interface Proposals_proposalsConnection_edges_node_votes_yes_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
currentStakeAvailable: string;
}
export interface Proposals_proposals_votes_yes_votes_party {
export interface Proposals_proposalsConnection_edges_node_votes_yes_votes_party {
__typename: "Party";
/**
* Party identifier
@ -163,10 +171,10 @@ export interface Proposals_proposals_votes_yes_votes_party {
/**
* The staking information for this Party
*/
stake: Proposals_proposals_votes_yes_votes_party_stake;
stakingSummary: Proposals_proposalsConnection_edges_node_votes_yes_votes_party_stakingSummary;
}
export interface Proposals_proposals_votes_yes_votes {
export interface Proposals_proposalsConnection_edges_node_votes_yes_votes {
__typename: "Vote";
/**
* The vote value cast
@ -175,14 +183,14 @@ export interface Proposals_proposals_votes_yes_votes {
/**
* The party casting the vote
*/
party: Proposals_proposals_votes_yes_votes_party;
party: Proposals_proposalsConnection_edges_node_votes_yes_votes_party;
/**
* RFC3339Nano time and date when the vote reached Vega network
*/
datetime: string;
}
export interface Proposals_proposals_votes_yes {
export interface Proposals_proposalsConnection_edges_node_votes_yes {
__typename: "ProposalVoteSide";
/**
* Total number of governance tokens from the votes cast for this side
@ -195,18 +203,18 @@ export interface Proposals_proposals_votes_yes {
/**
* All votes cast for this side
*/
votes: Proposals_proposals_votes_yes_votes[] | null;
votes: Proposals_proposalsConnection_edges_node_votes_yes_votes[] | null;
}
export interface Proposals_proposals_votes_no_votes_party_stake {
__typename: "PartyStake";
export interface Proposals_proposalsConnection_edges_node_votes_no_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
currentStakeAvailable: string;
}
export interface Proposals_proposals_votes_no_votes_party {
export interface Proposals_proposalsConnection_edges_node_votes_no_votes_party {
__typename: "Party";
/**
* Party identifier
@ -215,10 +223,10 @@ export interface Proposals_proposals_votes_no_votes_party {
/**
* The staking information for this Party
*/
stake: Proposals_proposals_votes_no_votes_party_stake;
stakingSummary: Proposals_proposalsConnection_edges_node_votes_no_votes_party_stakingSummary;
}
export interface Proposals_proposals_votes_no_votes {
export interface Proposals_proposalsConnection_edges_node_votes_no_votes {
__typename: "Vote";
/**
* The vote value cast
@ -227,14 +235,14 @@ export interface Proposals_proposals_votes_no_votes {
/**
* The party casting the vote
*/
party: Proposals_proposals_votes_no_votes_party;
party: Proposals_proposalsConnection_edges_node_votes_no_votes_party;
/**
* RFC3339Nano time and date when the vote reached Vega network
*/
datetime: string;
}
export interface Proposals_proposals_votes_no {
export interface Proposals_proposalsConnection_edges_node_votes_no {
__typename: "ProposalVoteSide";
/**
* Total number of governance tokens from the votes cast for this side
@ -247,27 +255,31 @@ export interface Proposals_proposals_votes_no {
/**
* All votes cast for this side
*/
votes: Proposals_proposals_votes_no_votes[] | null;
votes: Proposals_proposalsConnection_edges_node_votes_no_votes[] | null;
}
export interface Proposals_proposals_votes {
export interface Proposals_proposalsConnection_edges_node_votes {
__typename: "ProposalVotes";
/**
* Yes votes cast for this proposal
*/
yes: Proposals_proposals_votes_yes;
yes: Proposals_proposalsConnection_edges_node_votes_yes;
/**
* No votes cast for this proposal
*/
no: Proposals_proposals_votes_no;
no: Proposals_proposalsConnection_edges_node_votes_no;
}
export interface Proposals_proposals {
export interface Proposals_proposalsConnection_edges_node {
__typename: "Proposal";
/**
* Proposal ID that is filled by Vega once proposal reaches the network
*/
id: string | null;
/**
* Rationale behind the proposal
*/
rationale: Proposals_proposalsConnection_edges_node_rationale;
/**
* A UUID reference to aid tracking proposals on Vega
*/
@ -284,27 +296,43 @@ export interface Proposals_proposals {
* Reason for the proposal to be rejected by the core
*/
rejectionReason: ProposalRejectionReason | null;
/**
* Party that prepared the proposal
*/
party: Proposals_proposalsConnection_edges_node_party;
/**
* Error details of the rejectionReason
*/
errorDetails: string | null;
/**
* Party that prepared the proposal
*/
party: Proposals_proposals_party;
/**
* Terms of the proposal
*/
terms: Proposals_proposals_terms;
terms: Proposals_proposalsConnection_edges_node_terms;
/**
* Votes cast for this proposal
*/
votes: Proposals_proposals_votes;
votes: Proposals_proposalsConnection_edges_node_votes;
}
export interface Proposals_proposalsConnection_edges {
__typename: "ProposalEdge";
/**
* The proposal data
*/
node: Proposals_proposalsConnection_edges_node;
}
export interface Proposals_proposalsConnection {
__typename: "ProposalsConnection";
/**
* List of proposals available for the connection
*/
edges: (Proposals_proposalsConnection_edges | null)[] | null;
}
export interface Proposals {
/**
* All governance proposals in the Vega network
*/
proposals: Proposals_proposals[] | null;
proposalsConnection: Proposals_proposalsConnection;
}

View File

@ -1,5 +1,5 @@
export {
ProposalsContainer,
PROPOSALS_QUERY,
ProposalsContainer as default,
PROPOSALS_QUERY,
} from './proposals-container';

View File

@ -1,56 +1,36 @@
import { gql, useQuery } from '@apollo/client';
import { ProposalState } from '@vegaprotocol/types';
import { getNotRejectedProposals } from '@vegaprotocol/governance';
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import compact from 'lodash/compact';
import filter from 'lodash/filter';
import flow from 'lodash/flow';
import orderBy from 'lodash/orderBy';
import React from 'react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { SplashLoader } from '../../../components/splash-loader';
import { ProposalsList } from '../components/proposals-list';
import { PROPOSALS_FRAGMENT } from '../proposal-fragment';
import { PROPOSAL_FRAGMENT } from '../proposal-fragment';
import type { Proposals } from './__generated__/Proposals';
export const PROPOSALS_QUERY = gql`
${PROPOSALS_FRAGMENT}
${PROPOSAL_FRAGMENT}
query Proposals {
proposals {
proposalsConnection {
edges {
node {
...ProposalFields
}
}
}
}
`;
export const ProposalsContainer = () => {
const { t } = useTranslation();
const { data, loading, error } = useQuery<Proposals, never>(PROPOSALS_QUERY, {
const { data, loading, error } = useQuery<Proposals>(PROPOSALS_QUERY, {
pollInterval: 5000,
fetchPolicy: 'network-only',
errorPolicy: 'ignore', // this is to get around some backend issues and should be removed in future
errorPolicy: 'ignore',
});
const proposals = React.useMemo(() => {
if (!data?.proposals?.length) {
return [];
}
return flow([
compact,
(arr) =>
filter(arr, ({ state }) => state !== ProposalState.STATE_REJECTED),
(arr) =>
orderBy(
arr,
[
(p) => new Date(p.terms.enactmentDatetime).getTime(),
(p) => new Date(p.terms.closingDatetime).getTime(),
(p) => p.id,
],
['desc', 'desc', 'desc']
),
])(data.proposals);
}, [data]);
const proposals = useMemo(() => getNotRejectedProposals(data), [data]);
if (error) {
return (

View File

@ -1,46 +1,19 @@
import { useQuery } from '@apollo/client';
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import compact from 'lodash/compact';
import filter from 'lodash/filter';
import flow from 'lodash/flow';
import orderBy from 'lodash/orderBy';
import React from 'react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PROPOSALS_QUERY } from '../proposals';
import { SplashLoader } from '../../../components/splash-loader';
import { RejectedProposalsList } from '../components/proposals-list';
import { getRejectedProposals } from '@vegaprotocol/governance';
import { PROPOSALS_QUERY } from '../proposals';
import type { Proposals } from '../proposals/__generated__/Proposals';
import { ProposalState } from '@vegaprotocol/types';
export const RejectedProposalsContainer = () => {
const { t } = useTranslation();
const { data, loading, error } = useQuery<Proposals, never>(PROPOSALS_QUERY, {
pollInterval: 5000,
errorPolicy: 'ignore', // this is to get around some backend issues and should be removed in future
});
const { data, loading, error } = useQuery<Proposals>(PROPOSALS_QUERY);
const proposals = React.useMemo(() => {
if (!data?.proposals?.length) {
return [];
}
return flow([
compact,
(arr) =>
filter(arr, ({ state }) => state === ProposalState.STATE_REJECTED),
(arr) =>
orderBy(
arr,
[
(p) => new Date(p.terms.enactmentDatetime).getTime(),
(p) => new Date(p.terms.closingDatetime).getTime(),
(p) => p.id,
],
['desc', 'desc', 'desc']
),
])(data.proposals);
}, [data]);
const proposals = useMemo(() => getRejectedProposals(data), [data]);
if (error) {
return (

View File

@ -16,6 +16,11 @@ export function generateProposal(
const defaultProposal: ProposalFields = {
__typename: 'Proposal',
id: faker.datatype.uuid(),
rationale: {
__typename: 'ProposalRationale',
title: '',
description: '',
},
reference: 'ref' + faker.datatype.uuid(),
state: ProposalState.STATE_OPEN,
datetime: faker.date.past().toISOString(),
@ -84,8 +89,8 @@ export const generateYesVotes = (
party: {
id: faker.datatype.uuid(),
__typename: 'Party',
stake: {
__typename: 'PartyStake',
stakingSummary: {
__typename: 'StakingSummary',
currentStakeAvailable: fixedTokenValue
? fixedTokenValue.toString()
: faker.datatype
@ -119,8 +124,8 @@ export const generateNoVotes = (
party: {
id: faker.datatype.uuid(),
__typename: 'Party',
stake: {
__typename: 'PartyStake',
stakingSummary: {
__typename: 'StakingSummary',
currentStakeAvailable: fixedTokenValue
? fixedTokenValue.toString()
: faker.datatype

View File

@ -1,2 +1,3 @@
export * from './proposals-hooks';
export * from './proposal-form';
export * from './proposals-queries';

View File

@ -0,0 +1 @@
export * from './proposals-queries';

View File

@ -0,0 +1,61 @@
import compact from 'lodash/compact';
import filter from 'lodash/filter';
import flow from 'lodash/flow';
import orderBy from 'lodash/orderBy';
import { ProposalState } from '@vegaprotocol/types';
type Proposal = {
__typename: 'Proposal';
id: string | null;
state: ProposalState;
terms: {
enactmentDatetime: string | null;
closingDatetime: string;
};
};
type ProposalEdge = {
node: Proposal;
};
type ProposalsConnection = {
proposalsConnection: {
edges: (ProposalEdge | null)[] | null;
};
};
export const getProposals = (data?: ProposalsConnection) => {
const proposals = data?.proposalsConnection.edges
?.filter((e) => e?.node)
.map((e) => e?.node);
return proposals ? (proposals as Proposal[]) : [];
};
const orderByDate = (arr: Proposal[]) =>
orderBy(
arr,
[
(p) => new Date(p.terms.enactmentDatetime || 0).getTime(), // has to be defaulted to 0 because new Date(null).getTime() -> NaN which is first when ordered.
(p) => new Date(p.terms.closingDatetime).getTime(),
(p) => p.id,
],
['desc', 'desc', 'desc']
);
export const getNotRejectedProposals = (data?: ProposalsConnection) => {
const proposals = getProposals(data);
return flow([
compact,
(arr: Proposal[]) =>
filter(arr, ({ state }) => state !== ProposalState.STATE_REJECTED),
orderByDate,
])(proposals);
};
export const getRejectedProposals = (data?: ProposalsConnection) => {
const proposals = getProposals(data);
return flow([
compact,
(arr: Proposal[]) =>
filter(arr, ({ state }) => state === ProposalState.STATE_REJECTED),
orderByDate,
])(proposals);
};

View File

@ -1,5 +1,5 @@
export * from './date';
export * from './number';
export * from './size';
export * from './truncate';
export * from './strings';
export * from './utils';

View File

@ -0,0 +1,29 @@
import { truncateByChars, ELLIPSIS, shorten } from './strings';
describe('truncateByChars', () => {
it.each([
{
i: '12345678901234567890',
s: undefined,
e: undefined,
o: `123456${ELLIPSIS}567890`,
},
{ i: '12345678901234567890', s: 0, e: 10, o: `${ELLIPSIS}1234567890` },
{ i: '123', s: 0, e: 4, o: '123' },
])('should truncate given string by specific chars', ({ i, s, e, o }) => {
expect(truncateByChars(i, s, e)).toStrictEqual(o);
});
});
describe('shorten', () => {
it.each([
{ i: '12345678901234567890', l: undefined, o: '12345678901234567890' },
{ i: '12345678901234567890', l: 0, o: `12345678901234567890` },
{ i: '12345678901234567890', l: 10, o: `123456789${ELLIPSIS}` },
{ i: '12345678901234567890', l: 20, o: `1234567890123456789${ELLIPSIS}` },
{ i: '12345678901234567890', l: 30, o: `12345678901234567890` },
])('should shorten given string by specific limit', ({ i, l, o }) => {
const output = shorten(i, l);
expect(output).toStrictEqual(o);
});
});

View File

@ -0,0 +1,19 @@
export const ELLIPSIS = '\u2026';
export function truncateByChars(input: string, start = 6, end = 6) {
// if the text is shorted than the total number of chars to show
// no truncation is needed. Plus one is to account for the ellipsis
if (input.length <= start + end + 1) {
return input;
}
return input.slice(0, start) + ELLIPSIS + input.slice(-end);
}
export function shorten(input: string, limit?: number) {
if (!input || !limit || input.length < limit || limit <= 0) {
return input;
}
const output = input.substring(0, limit - 1);
const suffix = output.length < limit ? ELLIPSIS : '';
return input.substring(0, limit - 1) + suffix;
}

View File

@ -1,14 +0,0 @@
export function truncateByChars(s: string, startChars = 6, endChars = 6) {
const ELLIPSIS = '\u2026';
// if the text is shorted than the total number of chars to show
// no truncation is needed. Plus one is to account for the ellipsis
if (s.length <= startChars + endChars + 1) {
return s;
}
const start = s.slice(0, startChars);
const end = s.slice(-endChars);
return start + ELLIPSIS + end;
}