fix(governance): update market proposal diff (#5842)
This commit is contained in:
parent
c4a56e0de3
commit
042919eca9
@ -33,7 +33,7 @@ LC_ALL="en_US.UTF-8"
|
|||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
NX_PRODUCT_PERPETUALS=false
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=false
|
NX_UPDATE_MARKET_STATE=true
|
||||||
NX_REFERRALS=true
|
NX_REFERRALS=true
|
||||||
NX_GOVERNANCE_TRANSFERS=false
|
NX_GOVERNANCE_TRANSFERS=true
|
||||||
|
@ -3,16 +3,8 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||||
import { SubHeading } from '../../../../components/heading';
|
import { SubHeading } from '../../../../components/heading';
|
||||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||||
import {
|
|
||||||
type BatchProposalFieldsFragment,
|
|
||||||
type ProposalFieldsFragment,
|
|
||||||
} from '../../__generated__/Proposals';
|
|
||||||
|
|
||||||
export const ProposalJson = ({
|
export const ProposalJson = ({ proposal }: { proposal?: unknown }) => {
|
||||||
proposal,
|
|
||||||
}: {
|
|
||||||
proposal: ProposalFieldsFragment | BatchProposalFieldsFragment;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [showDetails, setShowDetails] = useState(false);
|
const [showDetails, setShowDetails] = useState(false);
|
||||||
|
|
||||||
|
@ -57,21 +57,21 @@ describe('applyImmutableKeysFromEarlierVersion', () => {
|
|||||||
describe('ProposalMarketChanges', () => {
|
describe('ProposalMarketChanges', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<ProposalMarketChanges marketId="market-id" updatedProposal={{}} />
|
<ProposalMarketChanges marketId="market-id" updateProposalNode={null} />
|
||||||
);
|
);
|
||||||
expect(getByTestId('proposal-market-changes')).toBeInTheDocument();
|
expect(getByTestId('proposal-market-changes')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('JsonDiff is not visible when showChanges is false', () => {
|
it('JsonDiff is not visible when showChanges is false', () => {
|
||||||
const { queryByTestId } = render(
|
const { queryByTestId } = render(
|
||||||
<ProposalMarketChanges marketId="market-id" updatedProposal={{}} />
|
<ProposalMarketChanges marketId="market-id" updateProposalNode={null} />
|
||||||
);
|
);
|
||||||
expect(queryByTestId('json-diff')).not.toBeInTheDocument();
|
expect(queryByTestId('json-diff')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('JsonDiff is visible when showChanges is true', async () => {
|
it('JsonDiff is visible when showChanges is true', async () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<ProposalMarketChanges marketId="market-id" updatedProposal={{}} />
|
<ProposalMarketChanges marketId="market-id" updateProposalNode={null} />
|
||||||
);
|
);
|
||||||
fireEvent.click(getByTestId('proposal-market-changes-toggle'));
|
fireEvent.click(getByTestId('proposal-market-changes-toggle'));
|
||||||
expect(getByTestId('json-diff')).toBeInTheDocument();
|
expect(getByTestId('json-diff')).toBeInTheDocument();
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import set from 'lodash/set';
|
import set from 'lodash/set';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { JsonDiff } from '../../../../components/json-diff';
|
import compact from 'lodash/compact';
|
||||||
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import { JsonDiff, type JsonValue } from '../../../../components/json-diff';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||||
import { SubHeading } from '../../../../components/heading';
|
import { SubHeading } from '../../../../components/heading';
|
||||||
import type { JsonValue } from '../../../../components/json-diff';
|
import {
|
||||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
useFetchProposal,
|
||||||
import { ENV } from '../../../../config';
|
useFetchProposals,
|
||||||
|
flatten,
|
||||||
|
isBatchProposalNode,
|
||||||
|
isSingleProposalNode,
|
||||||
|
type ProposalNode,
|
||||||
|
type SingleProposalData,
|
||||||
|
type SubProposalData,
|
||||||
|
} from '../proposal/proposal-utils';
|
||||||
|
|
||||||
const immutableKeys = [
|
const immutableKeys = [
|
||||||
'decimalPlaces',
|
'decimalPlaces',
|
||||||
@ -18,8 +27,8 @@ const immutableKeys = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const applyImmutableKeysFromEarlierVersion = (
|
export const applyImmutableKeysFromEarlierVersion = (
|
||||||
earlierVersion: JsonValue,
|
earlierVersion: unknown,
|
||||||
updatedVersion: JsonValue
|
updatedVersion: unknown
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
typeof earlierVersion !== 'object' ||
|
typeof earlierVersion !== 'object' ||
|
||||||
@ -35,7 +44,8 @@ export const applyImmutableKeysFromEarlierVersion = (
|
|||||||
|
|
||||||
// Overwrite the immutable keys in the updatedVersionCopy with the earlier values
|
// Overwrite the immutable keys in the updatedVersionCopy with the earlier values
|
||||||
immutableKeys.forEach((key) => {
|
immutableKeys.forEach((key) => {
|
||||||
set(updatedVersionCopy, key, get(earlierVersion, key));
|
const earlier = get(earlierVersion, key);
|
||||||
|
if (earlier) set(updatedVersionCopy, key, earlier);
|
||||||
});
|
});
|
||||||
|
|
||||||
return updatedVersionCopy;
|
return updatedVersionCopy;
|
||||||
@ -43,49 +53,84 @@ export const applyImmutableKeysFromEarlierVersion = (
|
|||||||
|
|
||||||
interface ProposalMarketChangesProps {
|
interface ProposalMarketChangesProps {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
updatedProposal: JsonValue;
|
/** This are the changes from proposal */
|
||||||
|
updateProposalNode: ProposalNode | null;
|
||||||
|
indicator?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProposalMarketChanges = ({
|
export const ProposalMarketChanges = ({
|
||||||
marketId,
|
marketId,
|
||||||
updatedProposal,
|
updateProposalNode,
|
||||||
|
indicator,
|
||||||
}: ProposalMarketChangesProps) => {
|
}: ProposalMarketChangesProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [showChanges, setShowChanges] = useState(false);
|
const [showChanges, setShowChanges] = useState(false);
|
||||||
|
|
||||||
const {
|
const { data: originalProposalData } = useFetchProposal({
|
||||||
state: { data },
|
proposalId: marketId,
|
||||||
} = useFetch(`${ENV.rest}governance?proposalId=${marketId}`, undefined, true);
|
});
|
||||||
|
|
||||||
const {
|
const { data: enactedProposalsData } = useFetchProposals({
|
||||||
state: { data: enactedProposalData },
|
proposalState: 'STATE_ENACTED',
|
||||||
} = useFetch(
|
proposalType: 'TYPE_UPDATE_MARKET',
|
||||||
`${ENV.rest}governances?proposalState=STATE_ENACTED&proposalType=TYPE_UPDATE_MARKET`,
|
});
|
||||||
undefined,
|
|
||||||
true
|
let updateProposal: SingleProposalData | SubProposalData | undefined;
|
||||||
|
if (isBatchProposalNode(updateProposalNode)) {
|
||||||
|
updateProposal = updateProposalNode.proposals.find(
|
||||||
|
(p, i) =>
|
||||||
|
p.terms.updateMarket?.marketId === marketId &&
|
||||||
|
(indicator != null ? i === indicator - 1 : true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isSingleProposalNode(updateProposalNode)) {
|
||||||
|
updateProposal = updateProposalNode.proposal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this should get the proposal before the current one
|
||||||
|
const enactedUpdateMarketProposals = orderBy(
|
||||||
|
compact(
|
||||||
|
flatten(enactedProposalsData).filter((enacted) => {
|
||||||
|
const related = enacted.terms.updateMarket?.marketId === marketId;
|
||||||
|
const notCurrent =
|
||||||
|
enacted.id !== updateProposal?.id ||
|
||||||
|
('batchId' in enacted && enacted.batchId !== updateProposal.id);
|
||||||
|
const beforeCurrent =
|
||||||
|
Number(enacted.terms.enactmentTimestamp) <
|
||||||
|
Number(updateProposal?.terms.enactmentTimestamp);
|
||||||
|
return related && notCurrent && beforeCurrent;
|
||||||
|
})
|
||||||
|
),
|
||||||
|
[(proposal) => Number(proposal.terms.enactmentTimestamp)],
|
||||||
|
'desc'
|
||||||
);
|
);
|
||||||
|
|
||||||
// @ts-ignore no types here :-/
|
const latestEnactedProposal =
|
||||||
const enacted = enactedProposalData?.connection?.edges
|
enactedUpdateMarketProposals.length > 0
|
||||||
.filter(
|
? enactedUpdateMarketProposals[0]
|
||||||
// @ts-ignore no type here
|
: undefined;
|
||||||
({ node }) => node?.proposal?.terms?.updateMarket?.marketId === marketId
|
|
||||||
)
|
|
||||||
// @ts-ignore no type here
|
|
||||||
.sort((a, b) => {
|
|
||||||
return (
|
|
||||||
new Date(a?.node?.terms?.enactmentTimestamp).getTime() -
|
|
||||||
new Date(b?.node?.terms?.enactmentTimestamp).getTime()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const latestEnactedProposal = enacted?.length
|
let originalProposal;
|
||||||
? enacted[enacted.length - 1]
|
if (isBatchProposalNode(originalProposalData)) {
|
||||||
: undefined;
|
originalProposal = originalProposalData.proposals.find(
|
||||||
|
(proposal) => proposal.id === marketId && proposal.terms.newMarket != null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isSingleProposalNode(originalProposalData)) {
|
||||||
|
originalProposal = originalProposalData.proposal;
|
||||||
|
}
|
||||||
|
|
||||||
const originalProposal =
|
// LEFT SIDE: update market proposal enacted just before this one
|
||||||
// @ts-ignore no types with useFetch TODO: check this is good
|
// or original new market proposal
|
||||||
data?.data?.proposal?.terms?.newMarket?.changes;
|
const left =
|
||||||
|
latestEnactedProposal?.terms.updateMarket?.changes ||
|
||||||
|
originalProposal?.terms.newMarket?.changes;
|
||||||
|
|
||||||
|
// RIGHT SIDE: this update market proposal
|
||||||
|
const right = applyImmutableKeysFromEarlierVersion(
|
||||||
|
left,
|
||||||
|
updateProposal?.terms.updateMarket?.changes
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section data-testid="proposal-market-changes">
|
<section data-testid="proposal-market-changes">
|
||||||
@ -99,20 +144,7 @@ export const ProposalMarketChanges = ({
|
|||||||
|
|
||||||
{showChanges && (
|
{showChanges && (
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<JsonDiff
|
<JsonDiff left={left as JsonValue} right={right as JsonValue} />
|
||||||
left={latestEnactedProposal || originalProposal}
|
|
||||||
right={
|
|
||||||
latestEnactedProposal
|
|
||||||
? applyImmutableKeysFromEarlierVersion(
|
|
||||||
latestEnactedProposal,
|
|
||||||
updatedProposal
|
|
||||||
)
|
|
||||||
: applyImmutableKeysFromEarlierVersion(
|
|
||||||
originalProposal,
|
|
||||||
updatedProposal
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
@ -13,6 +13,7 @@ import { ProposalUpdateBenefitTiers } from '../proposal-update-benefit-tiers';
|
|||||||
import { ProposalUpdateMarketState } from '../proposal-update-market-state';
|
import { ProposalUpdateMarketState } from '../proposal-update-market-state';
|
||||||
import { ProposalVolumeDiscountProgramDetails } from '../proposal-volume-discount-program-details';
|
import { ProposalVolumeDiscountProgramDetails } from '../proposal-volume-discount-program-details';
|
||||||
import { getIndicatorStyle } from './colours';
|
import { getIndicatorStyle } from './colours';
|
||||||
|
import { type ProposalNode } from './proposal-utils';
|
||||||
|
|
||||||
export const ProposalChangeDetails = ({
|
export const ProposalChangeDetails = ({
|
||||||
proposal,
|
proposal,
|
||||||
@ -22,8 +23,7 @@ export const ProposalChangeDetails = ({
|
|||||||
}: {
|
}: {
|
||||||
proposal: Proposal | BatchProposal;
|
proposal: Proposal | BatchProposal;
|
||||||
terms: ProposalTermsFieldsFragment;
|
terms: ProposalTermsFieldsFragment;
|
||||||
// eslint-disable-next-line
|
restData: ProposalNode | null;
|
||||||
restData: any;
|
|
||||||
indicator?: number;
|
indicator?: number;
|
||||||
}) => {
|
}) => {
|
||||||
let details = null;
|
let details = null;
|
||||||
@ -63,30 +63,13 @@ export const ProposalChangeDetails = ({
|
|||||||
}
|
}
|
||||||
case 'UpdateMarket': {
|
case 'UpdateMarket': {
|
||||||
if (proposal.id) {
|
if (proposal.id) {
|
||||||
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 = (
|
details = (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<ProposalMarketData proposalId={proposal.id} />
|
<ProposalMarketData proposalId={proposal.id} />
|
||||||
<ProposalMarketChanges
|
<ProposalMarketChanges
|
||||||
|
indicator={indicator}
|
||||||
marketId={terms.change.marketId}
|
marketId={terms.change.marketId}
|
||||||
updatedProposal={updatedProposal}
|
updateProposalNode={restData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,261 @@
|
|||||||
|
import compact from 'lodash/compact';
|
||||||
|
import { ENV } from '../../../../config';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
type Maybe<T> = T | null | undefined;
|
||||||
|
|
||||||
|
type ProposalState =
|
||||||
|
| 'STATE_UNSPECIFIED'
|
||||||
|
| 'STATE_FAILED'
|
||||||
|
| 'STATE_OPEN'
|
||||||
|
| 'STATE_PASSED'
|
||||||
|
| 'STATE_REJECTED'
|
||||||
|
| 'STATE_DECLINED'
|
||||||
|
| 'STATE_ENACTED'
|
||||||
|
| 'STATE_WAITING_FOR_NODE_VOTE';
|
||||||
|
|
||||||
|
type ProposalType =
|
||||||
|
| 'TYPE_UNSPECIFIED'
|
||||||
|
| 'TYPE_ALL'
|
||||||
|
| 'TYPE_NEW_MARKET'
|
||||||
|
| 'TYPE_UPDATE_MARKET'
|
||||||
|
| 'TYPE_NETWORK_PARAMETERS'
|
||||||
|
| 'TYPE_NEW_ASSET'
|
||||||
|
| 'TYPE_NEW_FREE_FORM'
|
||||||
|
| 'TYPE_UPDATE_ASSET'
|
||||||
|
| 'TYPE_NEW_SPOT_MARKET'
|
||||||
|
| 'TYPE_UPDATE_SPOT_MARKET'
|
||||||
|
| 'TYPE_NEW_TRANSFER'
|
||||||
|
| 'TYPE_CANCEL_TRANSFER'
|
||||||
|
| 'TYPE_UPDATE_MARKET_STATE'
|
||||||
|
| 'TYPE_UPDATE_REFERRAL_PROGRAM'
|
||||||
|
| 'TYPE_UPDATE_VOLUME_DISCOUNT_PROGRAM';
|
||||||
|
|
||||||
|
type ProposalNodeType = 'TYPE_SINGLE_OR_UNSPECIFIED' | 'TYPE_BATCH';
|
||||||
|
|
||||||
|
type ProposalData = {
|
||||||
|
id: string;
|
||||||
|
rationale: {
|
||||||
|
description: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
state: ProposalState;
|
||||||
|
timestamp: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Terms = {
|
||||||
|
cancelTransfer?: { changes: unknown };
|
||||||
|
enactmentTimestamp: string;
|
||||||
|
newAsset?: { changes: unknown };
|
||||||
|
newFreeform: object;
|
||||||
|
newMarket?: { changes: unknown };
|
||||||
|
newSpotMarket?: { changes: unknown };
|
||||||
|
newTransfer?: { changes: unknown };
|
||||||
|
updateAsset?: { assetId: string; changes: unknown };
|
||||||
|
updateMarket?: { marketId: string; changes: unknown };
|
||||||
|
updateMarketState?: {
|
||||||
|
changes: {
|
||||||
|
marketId: string;
|
||||||
|
price: string;
|
||||||
|
updateType:
|
||||||
|
| 'MARKET_STATE_UPDATE_TYPE_UNSPECIFIED'
|
||||||
|
| 'MARKET_STATE_UPDATE_TYPE_TERMINATE'
|
||||||
|
| 'MARKET_STATE_UPDATE_TYPE_SUSPEND'
|
||||||
|
| 'MARKET_STATE_UPDATE_TYPE_RESUME';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
updateNetworkParameter?: { changes: unknown };
|
||||||
|
updateReferralProgram?: { changes: unknown };
|
||||||
|
updateSpotMarket?: { marketId: string; changes: unknown };
|
||||||
|
updateVolumeDiscountProgram?: { changes: unknown };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SingleProposalData = ProposalData & {
|
||||||
|
terms: Terms & {
|
||||||
|
closingTimestamp: string;
|
||||||
|
validationTimestamp: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type BatchProposalData = ProposalData & {
|
||||||
|
batchTerms: {
|
||||||
|
changes: Terms[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SubProposalData = SingleProposalData & {
|
||||||
|
batchId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProposalNode = {
|
||||||
|
proposal: ProposalData;
|
||||||
|
proposalType: ProposalNodeType;
|
||||||
|
proposals: SubProposalData[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type SingleProposalNode = ProposalNode & {
|
||||||
|
proposal: SingleProposalData;
|
||||||
|
proposalType: 'TYPE_SINGLE_OR_UNSPECIFIED';
|
||||||
|
proposals: [];
|
||||||
|
};
|
||||||
|
|
||||||
|
type BatchProposalNode = ProposalNode & {
|
||||||
|
proposal: BatchProposalData;
|
||||||
|
proposalType: 'TYPE_BATCH';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isProposalNode = (node: unknown): node is ProposalNode =>
|
||||||
|
Boolean(
|
||||||
|
typeof node === 'object' &&
|
||||||
|
node &&
|
||||||
|
'proposal' in node &&
|
||||||
|
typeof node.proposal === 'object' &&
|
||||||
|
node?.proposal &&
|
||||||
|
'id' in node.proposal &&
|
||||||
|
node?.proposal?.id
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isSingleProposalNode = (
|
||||||
|
node: Maybe<ProposalNode>
|
||||||
|
): node is SingleProposalNode =>
|
||||||
|
Boolean(
|
||||||
|
node &&
|
||||||
|
node?.proposalType === 'TYPE_SINGLE_OR_UNSPECIFIED' &&
|
||||||
|
node?.proposal
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isBatchProposalNode = (
|
||||||
|
node: Maybe<ProposalNode>
|
||||||
|
): node is BatchProposalNode =>
|
||||||
|
Boolean(
|
||||||
|
node &&
|
||||||
|
node?.proposalType === 'TYPE_BATCH' &&
|
||||||
|
node?.proposal &&
|
||||||
|
'batchTerms' in node.proposal &&
|
||||||
|
node?.proposals?.length > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
// this includes also batch proposals with `updateMarket`s 👍
|
||||||
|
const PROPOSALS_ENDPOINT = `${ENV.rest}governances?proposalState=:proposalState&proposalType=:proposalType`;
|
||||||
|
|
||||||
|
// this can be queried also by sub proposal id as `proposalId` and it will
|
||||||
|
// return full batch proposal data with all of its sub proposals including
|
||||||
|
// the requested one inside `proposals` array.
|
||||||
|
const PROPOSAL_ENDPOINT = `${ENV.rest}governance?proposalId=:proposalId`;
|
||||||
|
|
||||||
|
export const getProposals = async ({
|
||||||
|
proposalState,
|
||||||
|
proposalType,
|
||||||
|
}: {
|
||||||
|
proposalState: ProposalState;
|
||||||
|
proposalType: ProposalType;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
PROPOSALS_ENDPOINT.replace(':proposalState', proposalState).replace(
|
||||||
|
':proposalType',
|
||||||
|
proposalType
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (
|
||||||
|
data &&
|
||||||
|
'connection' in data &&
|
||||||
|
data.connection &&
|
||||||
|
'edges' in data.connection &&
|
||||||
|
data.connection.edges?.length > 0
|
||||||
|
) {
|
||||||
|
const nodes = compact(
|
||||||
|
data.connection.edges.map((e: { node?: object }) => e?.node)
|
||||||
|
).filter(isProposalNode);
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// NOOP - ignore errors
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProposal = async ({ proposalId }: { proposalId: string }) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
PROPOSAL_ENDPOINT.replace(':proposalId', proposalId)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data && 'data' in data && isProposalNode(data.data)) {
|
||||||
|
return data.data as ProposalNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// NOOP - ignore errors
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFetchProposal = ({ proposalId }: { proposalId?: string }) => {
|
||||||
|
const [data, setData] = useState<ProposalNode | null>(null);
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const cb = async () => {
|
||||||
|
if (!proposalId) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
const data = await getProposal({ proposalId });
|
||||||
|
setLoading(false);
|
||||||
|
if (data) {
|
||||||
|
setData(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cb();
|
||||||
|
}, [proposalId]);
|
||||||
|
|
||||||
|
return { data, loading };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFetchProposals = ({
|
||||||
|
proposalState,
|
||||||
|
proposalType,
|
||||||
|
}: {
|
||||||
|
proposalState: ProposalState;
|
||||||
|
proposalType: ProposalType;
|
||||||
|
}) => {
|
||||||
|
const [data, setData] = useState<ProposalNode[]>([]);
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const cb = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const data = await getProposals({ proposalState, proposalType });
|
||||||
|
setLoading(false);
|
||||||
|
if (data) {
|
||||||
|
setData(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cb();
|
||||||
|
}, [proposalState, proposalType]);
|
||||||
|
|
||||||
|
return { data, loading };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const flatten = (
|
||||||
|
nodes: ProposalNode[]
|
||||||
|
): (SingleProposalData | SubProposalData)[] => {
|
||||||
|
const flattenNodes = [];
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (isSingleProposalNode(node)) {
|
||||||
|
flattenNodes.push(node.proposal);
|
||||||
|
}
|
||||||
|
if (isBatchProposalNode(node)) {
|
||||||
|
for (const sub of node.proposals) {
|
||||||
|
flattenNodes.push(sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flattenNodes;
|
||||||
|
};
|
@ -61,7 +61,7 @@ const renderComponent = (proposal: IProposal) => {
|
|||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MockedProvider>
|
<MockedProvider>
|
||||||
<VegaWalletProvider config={vegaWalletConfig}>
|
<VegaWalletProvider config={vegaWalletConfig}>
|
||||||
<Proposal restData={{}} proposal={proposal} />
|
<Proposal restData={null} proposal={proposal} />
|
||||||
</VegaWalletProvider>
|
</VegaWalletProvider>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
|
@ -8,15 +8,17 @@ import { ProposalJson } from '../proposal-json';
|
|||||||
import { UserVote } from '../vote-details';
|
import { UserVote } from '../vote-details';
|
||||||
import Routes from '../../../routes';
|
import Routes from '../../../routes';
|
||||||
import { ProposalState } from '@vegaprotocol/types';
|
import { ProposalState } from '@vegaprotocol/types';
|
||||||
|
import { type ProposalNode } from './proposal-utils';
|
||||||
import { useVoteSubmit } from '@vegaprotocol/proposals';
|
import { useVoteSubmit } from '@vegaprotocol/proposals';
|
||||||
import { useUserVote } from '../vote-details/use-user-vote';
|
import { useUserVote } from '../vote-details/use-user-vote';
|
||||||
import { type Proposal as IProposal, type BatchProposal } from '../../types';
|
import { type Proposal as IProposal, type BatchProposal } from '../../types';
|
||||||
import { ProposalChangeDetails } from './proposal-change-details';
|
import { ProposalChangeDetails } from './proposal-change-details';
|
||||||
|
import { type JsonValue } from 'type-fest';
|
||||||
|
|
||||||
export interface ProposalProps {
|
export interface ProposalProps {
|
||||||
proposal: IProposal | BatchProposal;
|
proposal: IProposal | BatchProposal;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
restData: any;
|
restData: ProposalNode | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||||
@ -95,7 +97,7 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<ProposalJson proposal={restData?.data?.proposal} />
|
<ProposalJson proposal={restData?.proposal as unknown as JsonValue} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
|
||||||
import { ENV } from '../../../config';
|
|
||||||
import { Proposal } from '../components/proposal';
|
import { Proposal } from '../components/proposal';
|
||||||
import { ProposalNotFound } from '../components/proposal-not-found';
|
import { ProposalNotFound } from '../components/proposal-not-found';
|
||||||
import { useProposalQuery } from '../__generated__/Proposals';
|
import { useProposalQuery } from '../__generated__/Proposals';
|
||||||
|
import { useFetchProposal } from '../components/proposal/proposal-utils';
|
||||||
|
|
||||||
export const ProposalContainer = () => {
|
export const ProposalContainer = () => {
|
||||||
const params = useParams<{ proposalId: string }>();
|
const params = useParams<{ proposalId: string }>();
|
||||||
|
|
||||||
const {
|
const { data: restData, loading: restLoading } = useFetchProposal({
|
||||||
state: { data: restData, loading: restLoading, error: restError },
|
proposalId: params.proposalId,
|
||||||
} = useFetch(`${ENV.rest}governance?proposalId=${params.proposalId}`);
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useProposalQuery({
|
const { data, loading, error } = useProposalQuery({
|
||||||
fetchPolicy: 'network-only',
|
fetchPolicy: 'network-only',
|
||||||
@ -26,7 +25,7 @@ export const ProposalContainer = () => {
|
|||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
loading={Boolean(loading || restLoading)}
|
loading={Boolean(loading || restLoading)}
|
||||||
error={error || restError}
|
error={error}
|
||||||
data={{
|
data={{
|
||||||
...data,
|
...data,
|
||||||
...(restData ? { restData } : {}),
|
...(restData ? { restData } : {}),
|
||||||
|
Loading…
Reference in New Issue
Block a user