Fix/1622 proposal vote deadline fixes (#1730)

* Fix/1622: Partial progress

* Fix/1622: Proposal forms fields now correctly updating when vote deadline 'use min' and 'use max' are clicked

* Fix/1622: Removing some unused imports

* Fix/1622: WIP commit for generics for deadline component

* Fix/1622: WIP commit for generics for deadline component

* Fix/1622: Separate minMax functions

* Fix/1622: Updated unit tests

* Update apps/token/src/routes/governance/components/propose/proposal-form-vote-and-enactment-deadline.spec.tsx

Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>

* Fix/1622: Tweaks from PR comments

* Fix/1622: Tests fixes

* chore: fix min proposer change for tests

* frontend-monorepo-1622 Removed unused value in governance-flow.cy.js

Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>
Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
Sam Keen 2022-10-21 13:47:46 +01:00 committed by GitHub
parent 2fa640a467
commit 269d3820dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 282 additions and 65 deletions

View File

@ -61,7 +61,7 @@ context(
cy.verify_page_header('The $VEGA token'); cy.verify_page_header('The $VEGA token');
cy.get_network_parameters().then((network_parameters) => { cy.get_network_parameters().then((network_parameters) => {
cy.wrap( cy.wrap(
network_parameters['spam.protection.proposal.min.tokens'] / network_parameters['spam.protection.voting.min.tokens'] /
1000000000000000000 1000000000000000000
).as('minProposerBalance'); ).as('minProposerBalance');
cy.wrap( cy.wrap(
@ -115,11 +115,6 @@ context(
0.00001, 0.00001,
'Asserting that value is at least 0.00001 for network parameter minProposerBalance' 'Asserting that value is at least 0.00001 for network parameter minProposerBalance'
); );
assert.isAtLeast(
parseInt(this.minVoterBalance),
0.00001,
'Asserting that value is at least 0.00001 for network parameter minVoterBalance'
);
assert.isAtLeast( assert.isAtLeast(
parseFloat(this.requiredParticipation), parseFloat(this.requiredParticipation),
0.00001, 0.00001,

View File

@ -23,16 +23,20 @@ const expectedDate = (expected: string) => new Date(expected).toLocaleString();
const renderComponent = () => { const renderComponent = () => {
const register = jest.fn(); const register = jest.fn();
const setValue = jest.fn();
render( render(
<ProposalFormVoteAndEnactmentDeadline <ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline')} voteRegister={register('proposalVoteDeadline')}
voteErrorMessage={undefined} voteErrorMessage={undefined}
voteMinClose={minVoteDeadline} voteMinClose={minVoteDeadline}
voteMaxClose={maxVoteDeadline} voteMaxClose={maxVoteDeadline}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline')} enactmentRegister={register('proposalEnactmentDeadline')}
enactmentErrorMessage={undefined} enactmentErrorMessage={undefined}
enactmentMinClose={minEnactDeadline} enactmentMinClose={minEnactDeadline}
enactmentMaxClose={maxEnactDeadline} enactmentMaxClose={maxEnactDeadline}
onValidationMinMax={setValue}
validationRequired={true} validationRequired={true}
validationRegister={register('proposalValidationDeadline')} validationRegister={register('proposalValidationDeadline')}
validationErrorMessage={undefined} validationErrorMessage={undefined}

View File

@ -1,6 +1,5 @@
import { useEffect, useState, useMemo } from 'react'; import { useEffect, useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { parse as ISO8601Parse, toSeconds } from 'iso8601-duration';
import { import {
ButtonLink, ButtonLink,
FormGroup, FormGroup,
@ -8,22 +7,29 @@ import {
InputError, InputError,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { addHours, addMinutes } from 'date-fns'; import { addHours, addMinutes } from 'date-fns';
import {
deadlineToSeconds,
secondsToRoundedHours,
} from '@vegaprotocol/governance';
import { ProposalFormSubheader } from './proposal-form-subheader'; import { ProposalFormSubheader } from './proposal-form-subheader';
import type { UseFormRegisterReturn } from 'react-hook-form'; import type { UseFormRegisterReturn } from 'react-hook-form';
interface DeadlineProps { interface DeadlineProps {
vote: number; vote: number;
enactment: number; enactment: number | undefined;
validation: number; validation: number;
} }
interface DeadlineDatesProps { interface DeadlineDatesProps {
vote: Date; vote: Date;
enactment: Date; enactment: Date | undefined;
validation: Date; validation: Date;
} }
interface ValidationFormProps { interface ValidationFormProps {
onValidationMinMax:
| ((field: 'proposalValidationDeadline', value: string) => void)
| undefined;
validationRegister: validationRegister:
| UseFormRegisterReturn<'proposalValidationDeadline'> | UseFormRegisterReturn<'proposalValidationDeadline'>
| undefined; | undefined;
@ -34,6 +40,7 @@ interface ValidationFormProps {
} }
const ValidationForm = ({ const ValidationForm = ({
onValidationMinMax,
validationRegister, validationRegister,
deadlines, deadlines,
deadlineDates, deadlineDates,
@ -69,13 +76,24 @@ const ValidationForm = ({
<div className="flex items-center gap-4 text-sm"> <div className="flex items-center gap-4 text-sm">
<ButtonLink <ButtonLink
data-testid="min-validation" data-testid="min-validation"
onClick={() => updateValidationDeadlineAndDate(0)} onClick={() => {
onValidationMinMax &&
onValidationMinMax('proposalValidationDeadline', '0');
updateValidationDeadlineAndDate(0);
}}
> >
{t('UseMin')} {t('UseMin')}
</ButtonLink> </ButtonLink>
<ButtonLink <ButtonLink
data-testid="max-validation" data-testid="max-validation"
onClick={() => updateValidationDeadlineAndDate(deadlines.vote)} onClick={() => {
onValidationMinMax &&
onValidationMinMax(
'proposalValidationDeadline',
deadlines.vote.toString()
);
updateValidationDeadlineAndDate(deadlines.vote);
}}
> >
{t('UseMax')} {t('UseMax')}
</ButtonLink> </ButtonLink>
@ -108,6 +126,9 @@ const ValidationForm = ({
}; };
interface EnactmentFormProps { interface EnactmentFormProps {
onEnactMinMax:
| ((field: 'proposalEnactmentDeadline', value: string) => void)
| undefined;
enactmentRegister: enactmentRegister:
| UseFormRegisterReturn<'proposalEnactmentDeadline'> | UseFormRegisterReturn<'proposalEnactmentDeadline'>
| undefined; | undefined;
@ -120,6 +141,7 @@ interface EnactmentFormProps {
} }
const EnactmentForm = ({ const EnactmentForm = ({
onEnactMinMax,
enactmentRegister, enactmentRegister,
deadlines, deadlines,
deadlineDates, deadlineDates,
@ -157,13 +179,27 @@ const EnactmentForm = ({
<div className="flex items-center gap-4 text-sm"> <div className="flex items-center gap-4 text-sm">
<ButtonLink <ButtonLink
data-testid="min-enactment" data-testid="min-enactment"
onClick={() => updateEnactmentDeadlineAndDate(minEnactmentHours)} onClick={() => {
onEnactMinMax &&
onEnactMinMax(
'proposalEnactmentDeadline',
minEnactmentHours.toString()
);
updateEnactmentDeadlineAndDate(minEnactmentHours);
}}
> >
{t('UseMin')} {t('UseMin')}
</ButtonLink> </ButtonLink>
<ButtonLink <ButtonLink
data-testid="max-enactment" data-testid="max-enactment"
onClick={() => updateEnactmentDeadlineAndDate(maxEnactmentHours)} onClick={() => {
onEnactMinMax &&
onEnactMinMax(
'proposalEnactmentDeadline',
maxEnactmentHours.toString()
);
updateEnactmentDeadlineAndDate(maxEnactmentHours);
}}
> >
{t('UseMax')} {t('UseMax')}
</ButtonLink> </ButtonLink>
@ -188,33 +224,42 @@ const EnactmentForm = ({
}; };
export interface ProposalFormVoteAndEnactmentDeadlineProps { export interface ProposalFormVoteAndEnactmentDeadlineProps {
onVoteMinMax: (field: 'proposalVoteDeadline', value: string) => void;
voteRegister: UseFormRegisterReturn<'proposalVoteDeadline'>; voteRegister: UseFormRegisterReturn<'proposalVoteDeadline'>;
voteErrorMessage: string | undefined; voteErrorMessage: string | undefined;
voteMinClose: string; voteMinClose: string;
voteMaxClose: string; voteMaxClose: string;
enactmentRequired?: boolean; enactmentRequired?: boolean;
onEnactMinMax?: (field: 'proposalEnactmentDeadline', value: string) => void;
enactmentRegister?: UseFormRegisterReturn<'proposalEnactmentDeadline'>; enactmentRegister?: UseFormRegisterReturn<'proposalEnactmentDeadline'>;
enactmentErrorMessage?: string; enactmentErrorMessage?: string;
enactmentMinClose?: string; enactmentMinClose?: string;
enactmentMaxClose?: string; enactmentMaxClose?: string;
validationRequired?: boolean; validationRequired?: boolean;
onValidationMinMax?: (
field: 'proposalValidationDeadline',
value: string
) => void;
validationRegister?: UseFormRegisterReturn<'proposalValidationDeadline'>; validationRegister?: UseFormRegisterReturn<'proposalValidationDeadline'>;
validationErrorMessage?: string; validationErrorMessage?: string;
} }
export const ProposalFormVoteAndEnactmentDeadline = ({ export function ProposalFormVoteAndEnactmentDeadline({
onVoteMinMax,
voteRegister, voteRegister,
voteErrorMessage, voteErrorMessage,
voteMinClose, voteMinClose,
voteMaxClose, voteMaxClose,
onEnactMinMax,
enactmentRegister, enactmentRegister,
enactmentErrorMessage, enactmentErrorMessage,
enactmentMinClose, enactmentMinClose,
enactmentMaxClose, enactmentMaxClose,
validationRequired, validationRequired,
onValidationMinMax,
validationRegister, validationRegister,
validationErrorMessage, validationErrorMessage,
}: ProposalFormVoteAndEnactmentDeadlineProps) => { }: ProposalFormVoteAndEnactmentDeadlineProps) {
const { const {
minVoteSeconds, minVoteSeconds,
maxVoteSeconds, maxVoteSeconds,
@ -222,18 +267,12 @@ export const ProposalFormVoteAndEnactmentDeadline = ({
maxEnactmentSeconds, maxEnactmentSeconds,
} = useMemo( } = useMemo(
() => ({ () => ({
minVoteSeconds: toSeconds( minVoteSeconds: deadlineToSeconds(voteMinClose),
ISO8601Parse(`PT${voteMinClose.toUpperCase()}`) maxVoteSeconds: deadlineToSeconds(voteMaxClose),
),
maxVoteSeconds: toSeconds(
ISO8601Parse(`PT${voteMaxClose.toUpperCase()}`)
),
minEnactmentSeconds: minEnactmentSeconds:
enactmentMinClose && enactmentMinClose && deadlineToSeconds(enactmentMinClose),
toSeconds(ISO8601Parse(`PT${enactmentMinClose.toUpperCase()}`)),
maxEnactmentSeconds: maxEnactmentSeconds:
enactmentMaxClose && enactmentMaxClose && deadlineToSeconds(enactmentMaxClose),
toSeconds(ISO8601Parse(`PT${enactmentMaxClose.toUpperCase()}`)),
}), }),
[voteMinClose, voteMaxClose, enactmentMinClose, enactmentMaxClose] [voteMinClose, voteMaxClose, enactmentMinClose, enactmentMaxClose]
); );
@ -243,17 +282,14 @@ export const ProposalFormVoteAndEnactmentDeadline = ({
const { minVoteHours, maxVoteHours, minEnactmentHours, maxEnactmentHours } = const { minVoteHours, maxVoteHours, minEnactmentHours, maxEnactmentHours } =
useMemo( useMemo(
() => ({ () => ({
minVoteHours: minVoteHours: secondsToRoundedHours(minVoteSeconds),
Math.floor(minVoteSeconds / 3600) > 1 maxVoteHours: secondsToRoundedHours(maxVoteSeconds),
? Math.floor(minVoteSeconds / 3600) minEnactmentHours: minEnactmentSeconds
: 1, ? secondsToRoundedHours(minEnactmentSeconds)
maxVoteHours: Math.floor(maxVoteSeconds / 3600), : undefined,
minEnactmentHours: maxEnactmentHours: maxEnactmentSeconds
minEnactmentSeconds && Math.floor(minEnactmentSeconds / 3600) > 1 ? secondsToRoundedHours(maxEnactmentSeconds)
? Math.floor(minEnactmentSeconds / 3600) : undefined,
: 1,
maxEnactmentHours:
maxEnactmentSeconds && Math.floor(maxEnactmentSeconds / 3600),
}), }),
[minVoteSeconds, maxVoteSeconds, minEnactmentSeconds, maxEnactmentSeconds] [minVoteSeconds, maxVoteSeconds, minEnactmentSeconds, maxEnactmentSeconds]
); );
@ -269,7 +305,9 @@ export const ProposalFormVoteAndEnactmentDeadline = ({
deadlines.vote === minVoteHours deadlines.vote === minVoteHours
? addHours(addMinutes(new Date(), 2), deadlines.vote) ? addHours(addMinutes(new Date(), 2), deadlines.vote)
: addHours(new Date(), deadlines.vote), : addHours(new Date(), deadlines.vote),
enactment: addHours(new Date(), deadlines.vote + deadlines.enactment), enactment: deadlines.enactment
? addHours(new Date(), deadlines.vote + deadlines.enactment)
: undefined,
validation: validation:
deadlines.validation === 0 deadlines.validation === 0
? addHours(addMinutes(new Date(), 2), deadlines.validation) ? addHours(addMinutes(new Date(), 2), deadlines.validation)
@ -284,7 +322,9 @@ export const ProposalFormVoteAndEnactmentDeadline = ({
deadlines.vote === minVoteHours deadlines.vote === minVoteHours
? addHours(addMinutes(new Date(), 2), deadlines.vote) ? addHours(addMinutes(new Date(), 2), deadlines.vote)
: addHours(new Date(), deadlines.vote), : addHours(new Date(), deadlines.vote),
enactment: addHours(new Date(), deadlines.vote + deadlines.enactment), enactment: deadlines.enactment
? addHours(new Date(), deadlines.vote + deadlines.enactment)
: undefined,
validation: validation:
deadlines.validation === 0 deadlines.validation === 0
? addHours(addMinutes(new Date(), 2), deadlines.validation) ? addHours(addMinutes(new Date(), 2), deadlines.validation)
@ -301,7 +341,7 @@ export const ProposalFormVoteAndEnactmentDeadline = ({
setDeadlines((prev) => ({ setDeadlines((prev) => ({
...prev, ...prev,
vote: hours, vote: hours,
validation: Math.min(prev.validation, hours), validation: prev.validation && Math.min(prev.validation, hours),
})); }));
// If the vote deadline is set to minimum, add 2 mins to the date as we do // If the vote deadline is set to minimum, add 2 mins to the date as we do
@ -317,7 +357,9 @@ export const ProposalFormVoteAndEnactmentDeadline = ({
hours === minVoteHours hours === minVoteHours
? addHours(addMinutes(new Date(), 2), hours) ? addHours(addMinutes(new Date(), 2), hours)
: addHours(new Date(), hours), : addHours(new Date(), hours),
enactment: addHours(new Date(), hours + deadlines.enactment), enactment: deadlines.enactment
? addHours(new Date(), hours + deadlines.enactment)
: undefined,
validation: addHours(new Date(), Math.min(hours, deadlines.validation)), validation: addHours(new Date(), Math.min(hours, deadlines.validation)),
})); }));
}; };
@ -384,13 +426,19 @@ export const ProposalFormVoteAndEnactmentDeadline = ({
<div className="flex items-center gap-4 text-sm"> <div className="flex items-center gap-4 text-sm">
<ButtonLink <ButtonLink
data-testid="min-vote" data-testid="min-vote"
onClick={() => updateVoteDeadlineAndDate(minVoteHours)} onClick={() => {
onVoteMinMax('proposalVoteDeadline', minVoteHours.toString());
updateVoteDeadlineAndDate(minVoteHours);
}}
> >
{t('UseMin')} {t('UseMin')}
</ButtonLink> </ButtonLink>
<ButtonLink <ButtonLink
data-testid="max-vote" data-testid="max-vote"
onClick={() => updateVoteDeadlineAndDate(maxVoteHours)} onClick={() => {
onVoteMinMax('proposalVoteDeadline', maxVoteHours.toString());
updateVoteDeadlineAndDate(maxVoteHours);
}}
> >
{t('UseMax')} {t('UseMax')}
</ButtonLink> </ButtonLink>
@ -422,6 +470,7 @@ export const ProposalFormVoteAndEnactmentDeadline = ({
{validationRequired && ( {validationRequired && (
<ValidationForm <ValidationForm
onValidationMinMax={onValidationMinMax}
validationRegister={validationRegister} validationRegister={validationRegister}
deadlines={deadlines} deadlines={deadlines}
deadlineDates={deadlineDates} deadlineDates={deadlineDates}
@ -430,8 +479,9 @@ export const ProposalFormVoteAndEnactmentDeadline = ({
/> />
)} )}
{enactmentMinClose && enactmentMaxClose && maxEnactmentHours && ( {minEnactmentHours && maxEnactmentHours && (
<EnactmentForm <EnactmentForm
onEnactMinMax={onEnactMinMax}
enactmentRegister={enactmentRegister} enactmentRegister={enactmentRegister}
deadlines={deadlines} deadlines={deadlines}
deadlineDates={deadlineDates} deadlineDates={deadlineDates}
@ -443,4 +493,4 @@ export const ProposalFormVoteAndEnactmentDeadline = ({
)} )}
</> </>
); );
}; }

View File

@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import { import {
getClosingTimestamp, getClosingTimestamp,
useProposalSubmit, useProposalSubmit,
deadlineToRoundedHours,
} from '@vegaprotocol/governance'; } from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import {
@ -44,10 +45,17 @@ export const ProposeFreeform = () => {
register, register,
handleSubmit, handleSubmit,
formState: { isSubmitting, errors }, formState: { isSubmitting, errors },
setValue,
} = useForm<FreeformProposalFormFields>(); } = useForm<FreeformProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: FreeformProposalFormFields) => { const onSubmit = async (fields: FreeformProposalFormFields) => {
const isVoteDeadlineAtMinimum =
fields.proposalVoteDeadline ===
deadlineToRoundedHours(
params.governance_proposal_freeform_minClose
).toString();
await submit({ await submit({
rationale: { rationale: {
title: fields.proposalTitle, title: fields.proposalTitle,
@ -55,7 +63,10 @@ export const ProposeFreeform = () => {
}, },
terms: { terms: {
newFreeform: {}, newFreeform: {},
closingTimestamp: getClosingTimestamp(fields.proposalVoteDeadline), closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
isVoteDeadlineAtMinimum
),
}, },
}); });
}; };
@ -114,6 +125,7 @@ export const ProposeFreeform = () => {
/> />
<ProposalFormVoteAndEnactmentDeadline <ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', { voteRegister={register('proposalVoteDeadline', {
required: t('Required'), required: t('Required'),
})} })}

View File

@ -9,6 +9,7 @@ import {
getClosingTimestamp, getClosingTimestamp,
getEnactmentTimestamp, getEnactmentTimestamp,
useProposalSubmit, useProposalSubmit,
deadlineToRoundedHours,
} from '@vegaprotocol/governance'; } from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import {
@ -89,6 +90,7 @@ export const ProposeNetworkParameter = () => {
register, register,
handleSubmit, handleSubmit,
formState: { isSubmitting, errors }, formState: { isSubmitting, errors },
setValue,
} = useForm<NetworkParameterProposalFormFields>(); } = useForm<NetworkParameterProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
@ -100,6 +102,13 @@ export const ProposeNetworkParameter = () => {
const acutalNetworkParamKey = fields.proposalNetworkParameterKey const acutalNetworkParamKey = fields.proposalNetworkParameterKey
.split('_') .split('_')
.join('.'); .join('.');
const isVoteDeadlineAtMinimum =
fields.proposalVoteDeadline ===
deadlineToRoundedHours(
params.governance_proposal_updateNetParam_minClose
).toString();
await submit({ await submit({
rationale: { rationale: {
title: fields.proposalTitle, title: fields.proposalTitle,
@ -112,10 +121,14 @@ export const ProposeNetworkParameter = () => {
value: fields.proposalNetworkParameterValue, value: fields.proposalNetworkParameterValue,
}, },
}, },
closingTimestamp: getClosingTimestamp(fields.proposalVoteDeadline), closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
isVoteDeadlineAtMinimum
),
enactmentTimestamp: getEnactmentTimestamp( enactmentTimestamp: getEnactmentTimestamp(
fields.proposalVoteDeadline, fields.proposalVoteDeadline,
fields.proposalEnactmentDeadline fields.proposalEnactmentDeadline,
isVoteDeadlineAtMinimum
), ),
}, },
}); });
@ -243,6 +256,7 @@ export const ProposeNetworkParameter = () => {
)} )}
<ProposalFormVoteAndEnactmentDeadline <ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', { voteRegister={register('proposalVoteDeadline', {
required: t('Required'), required: t('Required'),
})} })}
@ -253,6 +267,7 @@ export const ProposeNetworkParameter = () => {
voteMaxClose={ voteMaxClose={
params.governance_proposal_updateNetParam_maxClose params.governance_proposal_updateNetParam_maxClose
} }
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', { enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'), required: t('Required'),
})} })}

View File

@ -5,6 +5,7 @@ import {
getEnactmentTimestamp, getEnactmentTimestamp,
getValidationTimestamp, getValidationTimestamp,
useProposalSubmit, useProposalSubmit,
deadlineToRoundedHours,
} from '@vegaprotocol/governance'; } from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import {
@ -59,10 +60,17 @@ export const ProposeNewAsset = () => {
register, register,
handleSubmit, handleSubmit,
formState: { isSubmitting, errors }, formState: { isSubmitting, errors },
setValue,
} = useForm<NewAssetProposalFormFields>(); } = useForm<NewAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: NewAssetProposalFormFields) => { const onSubmit = async (fields: NewAssetProposalFormFields) => {
const isVoteDeadlineAtMinimum =
fields.proposalVoteDeadline ===
deadlineToRoundedHours(
params.governance_proposal_asset_minClose
).toString();
await submit({ await submit({
rationale: { rationale: {
title: fields.proposalTitle, title: fields.proposalTitle,
@ -72,10 +80,14 @@ export const ProposeNewAsset = () => {
newAsset: { newAsset: {
...JSON.parse(fields.proposalTerms), ...JSON.parse(fields.proposalTerms),
}, },
closingTimestamp: getClosingTimestamp(fields.proposalVoteDeadline), closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
isVoteDeadlineAtMinimum
),
enactmentTimestamp: getEnactmentTimestamp( enactmentTimestamp: getEnactmentTimestamp(
fields.proposalVoteDeadline, fields.proposalVoteDeadline,
fields.proposalEnactmentDeadline fields.proposalEnactmentDeadline,
isVoteDeadlineAtMinimum
), ),
validationTimestamp: getValidationTimestamp( validationTimestamp: getValidationTimestamp(
fields.proposalValidationDeadline fields.proposalValidationDeadline
@ -155,12 +167,14 @@ export const ProposeNewAsset = () => {
/> />
<ProposalFormVoteAndEnactmentDeadline <ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', { voteRegister={register('proposalVoteDeadline', {
required: t('Required'), required: t('Required'),
})} })}
voteErrorMessage={errors?.proposalVoteDeadline?.message} voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_asset_minClose} voteMinClose={params.governance_proposal_asset_minClose}
voteMaxClose={params.governance_proposal_asset_maxClose} voteMaxClose={params.governance_proposal_asset_maxClose}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', { enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'), required: t('Required'),
})} })}
@ -170,6 +184,7 @@ export const ProposeNewAsset = () => {
enactmentMinClose={params.governance_proposal_asset_minEnact} enactmentMinClose={params.governance_proposal_asset_minEnact}
enactmentMaxClose={params.governance_proposal_asset_maxEnact} enactmentMaxClose={params.governance_proposal_asset_maxEnact}
validationRequired={true} validationRequired={true}
onValidationMinMax={setValue}
validationRegister={register('proposalValidationDeadline', { validationRegister={register('proposalValidationDeadline', {
required: t('Required'), required: t('Required'),
})} })}

View File

@ -4,6 +4,7 @@ import {
getClosingTimestamp, getClosingTimestamp,
getEnactmentTimestamp, getEnactmentTimestamp,
useProposalSubmit, useProposalSubmit,
deadlineToRoundedHours,
} from '@vegaprotocol/governance'; } from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import {
@ -57,10 +58,17 @@ export const ProposeNewMarket = () => {
register, register,
handleSubmit, handleSubmit,
formState: { isSubmitting, errors }, formState: { isSubmitting, errors },
setValue,
} = useForm<NewMarketProposalFormFields>(); } = useForm<NewMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: NewMarketProposalFormFields) => { const onSubmit = async (fields: NewMarketProposalFormFields) => {
const isVoteDeadlineAtMinimum =
fields.proposalVoteDeadline ===
deadlineToRoundedHours(
params.governance_proposal_market_minClose
).toString();
await submit({ await submit({
rationale: { rationale: {
title: fields.proposalTitle, title: fields.proposalTitle,
@ -70,10 +78,14 @@ export const ProposeNewMarket = () => {
newMarket: { newMarket: {
...JSON.parse(fields.proposalTerms), ...JSON.parse(fields.proposalTerms),
}, },
closingTimestamp: getClosingTimestamp(fields.proposalVoteDeadline), closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
isVoteDeadlineAtMinimum
),
enactmentTimestamp: getEnactmentTimestamp( enactmentTimestamp: getEnactmentTimestamp(
fields.proposalVoteDeadline, fields.proposalVoteDeadline,
fields.proposalEnactmentDeadline fields.proposalEnactmentDeadline,
isVoteDeadlineAtMinimum
), ),
}, },
}); });
@ -150,12 +162,14 @@ export const ProposeNewMarket = () => {
/> />
<ProposalFormVoteAndEnactmentDeadline <ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', { voteRegister={register('proposalVoteDeadline', {
required: t('Required'), required: t('Required'),
})} })}
voteErrorMessage={errors?.proposalVoteDeadline?.message} voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_market_minClose} voteMinClose={params.governance_proposal_market_minClose}
voteMaxClose={params.governance_proposal_market_maxClose} voteMaxClose={params.governance_proposal_market_maxClose}
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', { enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'), required: t('Required'),
})} })}

View File

@ -4,6 +4,7 @@ import {
getClosingTimestamp, getClosingTimestamp,
getEnactmentTimestamp, getEnactmentTimestamp,
useProposalSubmit, useProposalSubmit,
deadlineToRoundedHours,
} from '@vegaprotocol/governance'; } from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import {
@ -57,10 +58,17 @@ export const ProposeUpdateAsset = () => {
register, register,
handleSubmit, handleSubmit,
formState: { isSubmitting, errors }, formState: { isSubmitting, errors },
setValue,
} = useForm<UpdateAssetProposalFormFields>(); } = useForm<UpdateAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: UpdateAssetProposalFormFields) => { const onSubmit = async (fields: UpdateAssetProposalFormFields) => {
const isVoteDeadlineAtMinimum =
fields.proposalVoteDeadline ===
deadlineToRoundedHours(
params.governance_proposal_updateAsset_minClose
).toString();
await submit({ await submit({
rationale: { rationale: {
title: fields.proposalTitle, title: fields.proposalTitle,
@ -70,10 +78,14 @@ export const ProposeUpdateAsset = () => {
updateAsset: { updateAsset: {
...JSON.parse(fields.proposalTerms), ...JSON.parse(fields.proposalTerms),
}, },
closingTimestamp: getClosingTimestamp(fields.proposalVoteDeadline), closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
isVoteDeadlineAtMinimum
),
enactmentTimestamp: getEnactmentTimestamp( enactmentTimestamp: getEnactmentTimestamp(
fields.proposalVoteDeadline, fields.proposalVoteDeadline,
fields.proposalEnactmentDeadline fields.proposalEnactmentDeadline,
isVoteDeadlineAtMinimum
), ),
}, },
}); });
@ -152,6 +164,7 @@ export const ProposeUpdateAsset = () => {
/> />
<ProposalFormVoteAndEnactmentDeadline <ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', { voteRegister={register('proposalVoteDeadline', {
required: t('Required'), required: t('Required'),
})} })}

View File

@ -6,6 +6,7 @@ import {
getClosingTimestamp, getClosingTimestamp,
getEnactmentTimestamp, getEnactmentTimestamp,
useProposalSubmit, useProposalSubmit,
deadlineToRoundedHours,
} from '@vegaprotocol/governance'; } from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { import {
@ -117,10 +118,17 @@ export const ProposeUpdateMarket = () => {
register, register,
handleSubmit, handleSubmit,
formState: { isSubmitting, errors }, formState: { isSubmitting, errors },
setValue,
} = useForm<UpdateMarketProposalFormFields>(); } = useForm<UpdateMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit(); const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: UpdateMarketProposalFormFields) => { const onSubmit = async (fields: UpdateMarketProposalFormFields) => {
const isVoteDeadlineAtMinimum =
fields.proposalVoteDeadline ===
deadlineToRoundedHours(
params.governance_proposal_updateMarket_minClose
).toString();
await submit({ await submit({
rationale: { rationale: {
title: fields.proposalTitle, title: fields.proposalTitle,
@ -133,10 +141,14 @@ export const ProposeUpdateMarket = () => {
...JSON.parse(fields.proposalTerms), ...JSON.parse(fields.proposalTerms),
}, },
}, },
closingTimestamp: getClosingTimestamp(fields.proposalVoteDeadline), closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
isVoteDeadlineAtMinimum
),
enactmentTimestamp: getEnactmentTimestamp( enactmentTimestamp: getEnactmentTimestamp(
fields.proposalVoteDeadline, fields.proposalVoteDeadline,
fields.proposalEnactmentDeadline fields.proposalEnactmentDeadline,
isVoteDeadlineAtMinimum
), ),
}, },
}); });
@ -269,6 +281,7 @@ export const ProposeUpdateMarket = () => {
/> />
<ProposalFormVoteAndEnactmentDeadline <ProposalFormVoteAndEnactmentDeadline
onVoteMinMax={setValue}
voteRegister={register('proposalVoteDeadline', { voteRegister={register('proposalVoteDeadline', {
required: t('Required'), required: t('Required'),
})} })}
@ -279,6 +292,7 @@ export const ProposeUpdateMarket = () => {
voteMaxClose={ voteMaxClose={
params.governance_proposal_updateMarket_maxClose params.governance_proposal_updateMarket_maxClose
} }
onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', { enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'), required: t('Required'),
})} })}

View File

@ -0,0 +1,41 @@
import { deadlineToSeconds, secondsToRoundedHours } from './deadline-helpers';
describe('deadlineToSeconds', () => {
it('should throw an error if the deadline does not match the format "XhXmXs"', () => {
expect(() => deadlineToSeconds('abcde')).toThrowError(
'Invalid deadline format, expected format "XhXmXs", got "abcde"'
);
});
it('should convert "0h0m1s" to 1', () => {
expect(deadlineToSeconds('0h0m1s')).toEqual(1);
});
it('should convert "0h1m0s" to 60', () => {
expect(deadlineToSeconds('0h1m0s')).toEqual(60);
});
it('should convert "1h0m0s" to 3600', () => {
expect(deadlineToSeconds('1h0m0s')).toEqual(3600);
});
it('should convert "1h1m1s" to 3661', () => {
expect(deadlineToSeconds('1h1m1s')).toEqual(3661);
});
});
describe('secondsToRoundedHours', () => {
it('should return 1 hour for anything up to 3600 seconds', () => {
expect(secondsToRoundedHours(0)).toEqual(1);
expect(secondsToRoundedHours(3600)).toEqual(1);
});
it('should round to the nearest hour for anything more than 3600 seconds', () => {
// 5399 seconds is 1 hour 29 minutes 59 seconds
expect(secondsToRoundedHours(5399)).toEqual(1);
expect(secondsToRoundedHours(5400)).toEqual(2);
// 8999 seconds is 2 hours 29 minutes 59 seconds
expect(secondsToRoundedHours(8999)).toEqual(2);
expect(secondsToRoundedHours(9000)).toEqual(3);
});
});

View File

@ -0,0 +1,22 @@
import { parse as ISO8601Parse, toSeconds } from 'iso8601-duration';
// Converts API deadlines ("XhXmXs") to seconds
export const deadlineToSeconds = (deadline: string) => {
// check that the deadline string matches the format "XhXmXs"
const regex = /^(\d+h)?(\d+m)?(\d+s)?$/;
if (!regex.test(deadline)) {
throw new Error(
`Invalid deadline format, expected format "XhXmXs", got "${deadline}"`
);
}
return toSeconds(ISO8601Parse(`PT${deadline.toUpperCase()}`));
};
// Converts seconds to rounded hours, min 1 hour
export const secondsToRoundedHours = (seconds: number) => {
const hours = Math.round(seconds / 3600);
return hours < 1 ? 1 : hours;
};
export const deadlineToRoundedHours = (deadline: string) =>
secondsToRoundedHours(deadlineToSeconds(deadline));

View File

@ -11,23 +11,31 @@ afterEach(() => {
}); });
describe('getClosingTimestamp', () => { describe('getClosingTimestamp', () => {
it('should return the correct timestamp if the proposalVoteDeadline is 1 (when 2 mins are added)', () => { it('should return the correct timestamp if the proposalVoteDeadline is set to minimum (when 2 mins are added)', () => {
const proposalVoteDeadline = '1'; const proposalVoteDeadline = '1';
const isMinimumDeadlineSelected = true;
const expected = Math.floor( const expected = Math.floor(
getTime( getTime(
addHours(addMinutes(new Date(), 2), Number(proposalVoteDeadline)) addHours(addMinutes(new Date(), 2), Number(proposalVoteDeadline))
) / 1000 ) / 1000
); );
const actual = getClosingTimestamp(proposalVoteDeadline); const actual = getClosingTimestamp(
proposalVoteDeadline,
isMinimumDeadlineSelected
);
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });
it('should return the correct timestamp if the proposalVoteDeadline is 2 (when no extra mins are added)', () => { it('should return the correct timestamp if the proposalVoteDeadline is not set to minimum (when no extra mins are added)', () => {
const proposalVoteDeadline = '2'; const proposalVoteDeadline = '2';
const isMinimumDeadlineSelected = false;
const expected = Math.floor( const expected = Math.floor(
getTime(addHours(new Date(), Number(proposalVoteDeadline))) / 1000 getTime(addHours(new Date(), Number(proposalVoteDeadline))) / 1000
); );
const actual = getClosingTimestamp(proposalVoteDeadline); const actual = getClosingTimestamp(
proposalVoteDeadline,
isMinimumDeadlineSelected
);
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });
}); });

View File

@ -1,13 +1,16 @@
import { addHours, addMinutes, getTime } from 'date-fns'; import { addHours, addMinutes, getTime } from 'date-fns';
// If proposaVoteDeadline is at its minimum of 1 hour, then we add // If proposaVoteDeadline is at its minimum, then we add
// 2 extra minutes to the closing timestamp to ensure that there's time // 2 extra minutes to the closing timestamp to ensure that there's time
// to confirm in the wallet. // to confirm in the wallet.
export const getClosingTimestamp = (proposalVoteDeadline: string) => export const getClosingTimestamp = (
proposalVoteDeadline: string,
minimumDeadlineSelected: boolean
) =>
Math.floor( Math.floor(
getTime( getTime(
proposalVoteDeadline === '1' minimumDeadlineSelected
? addHours(addMinutes(new Date(), 2), Number(proposalVoteDeadline)) ? addHours(addMinutes(new Date(), 2), Number(proposalVoteDeadline))
: addHours(new Date(), Number(proposalVoteDeadline)) : addHours(new Date(), Number(proposalVoteDeadline))
) / 1000 ) / 1000

View File

@ -13,6 +13,7 @@ afterEach(() => {
describe('getEnactmentTimestamp', () => { describe('getEnactmentTimestamp', () => {
it('should return the correct timestamp', () => { it('should return the correct timestamp', () => {
const proposalVoteDeadline = '2'; const proposalVoteDeadline = '2';
const isMinimumVoteDeadlineSelected = false;
const enactmentDeadline = '1'; const enactmentDeadline = '1';
const expected = Math.floor( const expected = Math.floor(
getTime( getTime(
@ -24,7 +25,8 @@ describe('getEnactmentTimestamp', () => {
); );
const actual = getEnactmentTimestamp( const actual = getEnactmentTimestamp(
proposalVoteDeadline, proposalVoteDeadline,
enactmentDeadline enactmentDeadline,
isMinimumVoteDeadlineSelected
); );
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });

View File

@ -3,12 +3,20 @@ import { getClosingTimestamp } from './get-closing-timestamp';
export const getEnactmentTimestamp = ( export const getEnactmentTimestamp = (
proposalVoteDeadline: string, proposalVoteDeadline: string,
enactmentDeadline: string enactmentDeadline: string,
minimumVoteDeadlineSelected: boolean
) => ) =>
Math.floor( Math.floor(
getTime( getTime(
addHours( addHours(
new Date(fromUnixTime(getClosingTimestamp(proposalVoteDeadline))), new Date(
fromUnixTime(
getClosingTimestamp(
proposalVoteDeadline,
minimumVoteDeadlineSelected
)
)
),
Number(enactmentDeadline) Number(enactmentDeadline)
) )
) / 1000 ) / 1000

View File

@ -2,3 +2,4 @@ export * from './proposal-dialog-helpers';
export * from './get-closing-timestamp'; export * from './get-closing-timestamp';
export * from './get-enactment-timestamp'; export * from './get-enactment-timestamp';
export * from './get-validation-timestamp'; export * from './get-validation-timestamp';
export * from './deadline-helpers';