Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/execute state #21

Merged
merged 41 commits into from Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
f5b7fa3
feat(guilds/hook): create executeProposal hook
hhamud Apr 28, 2022
e438d13
fix(guilds/proposalPage): implement endProposal button
hhamud Apr 28, 2022
4647244
fix(hook/useProposalState): clean up hook
hhamud Apr 28, 2022
4e60174
feat(husky/precommit): ensure that formatting is added into git
hhamud Apr 28, 2022
3812e97
fix(hooks/useProposalState): standardise returns and simplify ternary
hhamud Apr 29, 2022
3ce0060
feat(guilds/executeButton): add tests and refactor component
hhamud May 6, 2022
ee48cdb
fix(guilds/executeState): fix breaking tests
hhamud May 6, 2022
0f650a7
feat(guilds/useProposalState): move useProposalState and add tests
hhamud May 9, 2022
8d0a90d
fix(hooks/useProposalMetadata): move file
hhamud May 9, 2022
f812734
fix(guilds/executeButton): fix failing test
hhamud May 9, 2022
5503323
fix(guilds/useProposalState): fix failing tests
hhamud May 9, 2022
0ac7ab1
Merge branch 'v1.1-beta' into feat/executeState
hhamud May 10, 2022
98d168b
fix(hooks/useProposalMetadata): fix imports
hhamud May 10, 2022
f7a7a14
fix(useProposalCalls): fixed merge conflicts
dcrescimbeni May 26, 2022
3eeba2e
Merge branch 'v1.1-beta' into feature/executeState
dcrescimbeni May 27, 2022
8e9c098
feat(useProposalState): deleted useProposalState duplicated hook
dcrescimbeni May 27, 2022
e318eac
Merge branch 'v1.1-beta' into feature/executeState
dcrescimbeni May 27, 2022
2139920
feat(useProposal): added cases in middleware to map state numbers to …
dcrescimbeni May 28, 2022
c310858
feat: added logic to show ExecuteButton component and fixed minor bugs
dcrescimbeni May 28, 2022
63cbc39
feat: added new enum ContractState, updated state => contractState ma…
dcrescimbeni May 30, 2022
59ecc0a
feat: replaced imports of ContractState from Components/Types to type…
dcrescimbeni May 30, 2022
37c0276
refactor(useProposalState): renamed useProposalState to useExecutable…
dcrescimbeni May 30, 2022
37e359a
feat(useProposalState): extracted logic to custom hook
dcrescimbeni May 30, 2022
460b791
style(useProposalState): format
dcrescimbeni May 30, 2022
b32173d
refactor: extracted timeDetail property into useProposal middleware
dcrescimbeni May 30, 2022
9696e2c
refactor(useProposalState): deleted unused useMemo
dcrescimbeni May 30, 2022
749fb76
test(useProposalState): added tests
dcrescimbeni May 30, 2022
7f83b5d
style: format
dcrescimbeni May 30, 2022
9255010
feat(ExecuteButton,-Proposal-page): moved ExecuteButton to Components…
dcrescimbeni May 30, 2022
9aada35
feat(useExecutableState): fixed bug regarding isExecutable validation
dcrescimbeni May 30, 2022
7da940d
feat(ExecuteButton): changed button import to use new Guilds button
dcrescimbeni May 30, 2022
55de34a
feat(Proposal-page): added guild name to back button in proposal view
dcrescimbeni May 31, 2022
8b0166b
refactor: changed name from useExecutableState to useExecutable and f…
dcrescimbeni May 31, 2022
6de595f
feat(useProposalState): moved executable logic from useExecutable to …
dcrescimbeni May 31, 2022
2af075d
fix: fixed and made more clear the time left for the proposal to end
dcrescimbeni May 31, 2022
52049d1
test(useExecutable): changed tests to reflect new functionality
dcrescimbeni May 31, 2022
1ce5599
feat(useProposalState): added tests for useProposalState and fixed mi…
dcrescimbeni May 31, 2022
7bea8f9
fix(dev-script): fixed substract time to match with current time
dcrescimbeni May 31, 2022
09aa75a
feat(ExecuteButton): added i18n to ExecuteButton component
dcrescimbeni May 31, 2022
962f555
Merge branch 'develop' into feature/executeState
dcrescimbeni May 31, 2022
01c37f4
feat: added explanatory comments and renamed data variable to guildCo…
dcrescimbeni Jun 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -134,6 +134,7 @@
"@storybook/testing-library": "^0.0.11",
"@synthetixio/synpress": "^1.2.0",
"@testing-library/cypress": "^8.0.2",
"@testing-library/react-hooks": "^8.0.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react-hooks": "^8.0.0",
"@types/jest": "^27.4.0",
Expand Down
3 changes: 2 additions & 1 deletion scripts/dev.ts
Expand Up @@ -303,7 +303,8 @@ async function main() {
},
{
type: 'transfer',
timestamp: moment().subtract(26, 'minutes').unix(),
// We use this to reset to local time, in future we should ensure the automatic process works
timestamp: moment().subtract(46, 'minutes').unix(),
dcrescimbeni marked this conversation as resolved.
Show resolved Hide resolved
from: accounts[0],
data: {
asset: ZERO_ADDRESS,
Expand Down
32 changes: 32 additions & 0 deletions src/Components/ExecuteButton/index.test.tsx
@@ -0,0 +1,32 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import ExecuteButton from '.';
import { render } from 'utils/tests';

let mockedIsExecutable = true;
let mockedExecuteProposal = jest.fn();
jest.mock('hooks/Guilds/useExecutable', () => ({
__esModule: true,
default: () => ({
data: {
isExecutable: mockedIsExecutable,
executeProposal: mockedExecuteProposal,
},
loading: false,
error: null,
}),
}));

describe('Execute Button', () => {
beforeAll(() => {
render(<ExecuteButton executeProposal={mockedExecuteProposal} />);
});

it('User is able to click button to execute', async () => {
const button = screen.queryByTestId('execute-btn');
fireEvent.click(button);
await waitFor(() => {
expect(mockedExecuteProposal).toHaveBeenCalledTimes(1);
});
});
});
19 changes: 19 additions & 0 deletions src/Components/ExecuteButton/index.tsx
@@ -0,0 +1,19 @@
import { Button } from 'old-components/Guilds/common/Button';
import React from 'react';
import { useTranslation } from 'react-i18next';

interface ExecuteButtonProps {
executeProposal: () => void;
}

const ExecuteButton: React.FC<ExecuteButtonProps> = ({ executeProposal }) => {
const { t } = useTranslation();

return (
<Button data-testid="execute-btn" onClick={() => executeProposal()}>
{t('execute')}
</Button>
);
};

export default ExecuteButton;
8 changes: 5 additions & 3 deletions src/Components/Fixtures/index.ts
@@ -1,6 +1,7 @@
import { BigNumber } from 'ethers';
import moment from 'moment';
import { Proposal, ENSAvatar, ProposalState } from '../Types';
import { Proposal, ContractState } from 'types/types.guilds.d';
import { ENSAvatar } from '../Types';

export const proposalMock: Proposal = {
id: '0x1234567890123456789012345678901234567890',
Expand All @@ -12,7 +13,8 @@ export const proposalMock: Proposal = {
value: [],
startTime: moment(),
endTime: moment(),
state: ProposalState.Active,
timeDetail: '',
contractState: ContractState.Active,
totalActions: BigNumber.from(0),
totalVotes: [],
};
Expand All @@ -24,6 +26,6 @@ export const ensAvatarMock: ENSAvatar = {

export const proposalStatusPropsMock = {
timeDetail: 'Time',
status: ProposalState.Active,
status: ContractState.Active,
endTime: moment('2022-05-09T08:00:00'),
};
Expand Up @@ -83,7 +83,7 @@ exports[`ProposalCard ProposalCard Renders properly with data 1`] = `
class="sc-hOqruk cHwGYV"
>
<div
class="sc-jrAFXE sc-dacGJm jLAWlL dTZYfv"
class="sc-jrAFXE sc-dacGJm jLAWlL jqIJTf"
>
<span
title="May 9th, 2022 - 8:00 am"
Expand Down Expand Up @@ -229,7 +229,7 @@ exports[`ProposalCard ProposalCard loading 1`] = `
class="sc-hOqruk cHwGYV"
>
<div
class="sc-jrAFXE sc-dacGJm jLAWlL dTZYfv"
class="sc-jrAFXE sc-dacGJm jLAWlL jqIJTf"
>
<div
class="sc-bdfBQB"
Expand Down
3 changes: 2 additions & 1 deletion src/Components/ProposalCard/types.ts
@@ -1,5 +1,6 @@
import { ProposalStatusProps } from 'Components/ProposalStatus/types';
import { Proposal, ENSAvatar } from '../Types';
import { Proposal } from 'types/types.guilds.d';
import { ENSAvatar } from '../Types';
import { DecodedAction } from 'old-components/Guilds/ActionsBuilder/types';

export interface ProposalCardProps {
Expand Down
4 changes: 2 additions & 2 deletions src/Components/ProposalStatus/ProposalStatus.test.tsx
@@ -1,12 +1,12 @@
import ProposalStatus from './ProposalStatus';
import { render } from '../../utils/tests';
import { ProposalStatusProps } from './types';
import { ProposalState } from 'Components/Types';
import { ContractState } from 'types/types.guilds.d';
import moment from 'moment';

const validProps: ProposalStatusProps = {
timeDetail: 'Time',
status: ProposalState.Active,
status: ContractState.Active,
endTime: moment('2022-05-09T08:00:00'),
};

Expand Down
10 changes: 5 additions & 5 deletions src/Components/ProposalStatus/ProposalStatus.tsx
@@ -1,7 +1,7 @@
import styled, { css } from 'styled-components';
import { Box } from 'Components/Primitives/Layout';
import { Loading } from 'Components/Primitives/Loading';
import { ProposalState } from 'Components/Types';
import { ProposalState } from 'types/types.guilds.d';
import { ProposalStatusProps } from './types';

const ProposalStatusWrapper = styled.div`
Expand Down Expand Up @@ -41,11 +41,11 @@ const ProposalStatusDetail = styled(Box)<{ statusDetail?: ProposalState }>`
`;

const DetailText = styled(Box)`
padding: 0 0.2rem;
padding: 0 0.2rem;

@media only screen and (min-width: 768px) {
padding - right: 0.5rem;
}
@media only screen and (min-width: 768px) {
padding-right: 0.5rem;
}
`;

const ProposalStatus: React.FC<ProposalStatusProps> = ({
Expand Down
Expand Up @@ -10,7 +10,7 @@ exports[`ProposalStatus loading 1`] = `
class="sc-iBPTik gOtsle"
>
<div
class="sc-bdfBQB sc-pGacB bmhbKs bjIKgW"
class="sc-bdfBQB sc-pGacB bmhbKs bDHuHu"
>
<div
class="sc-eCstlR"
Expand Down Expand Up @@ -61,7 +61,7 @@ exports[`ProposalStatus votes 1`] = `
class="sc-iBPTik gOtsle"
>
<div
class="sc-bdfBQB sc-pGacB bmhbKs bjIKgW"
class="sc-bdfBQB sc-pGacB bmhbKs bDHuHu"
>
<span
title="May 9th, 2022 - 8:00 am"
Expand Down
2 changes: 1 addition & 1 deletion src/Components/ProposalStatus/fixtures.ts
@@ -1,4 +1,4 @@
import { ProposalState } from 'Components/Types';
import { ProposalState } from 'types/types.guilds.d';
import moment from 'moment';
import { ProposalStatusProps } from './types';

Expand Down
12 changes: 10 additions & 2 deletions src/Components/Types/index.ts
Expand Up @@ -3,7 +3,15 @@ import { Moment } from 'moment';

export enum ProposalState {
Active = 'Active',
Passed = 'Passed',
Rejected = 'Rejected',
Executed = 'Executed',
Failed = 'Failed',
Finished = 'Finished',
}

export enum ContractState {
Active = 'Active',
Rejected = 'Rejected',
Executed = 'Executed',
Failed = 'Failed',
}
Expand All @@ -19,7 +27,7 @@ export interface Proposal {
totalActions: BigNumber;
title: string;
contentHash: string;
state: ProposalState;
contractState: ContractState;
totalVotes: BigNumber[];
}

Expand Down
44 changes: 7 additions & 37 deletions src/Modules/Guilds/Wrappers/ProposalCardWrapper.tsx
Expand Up @@ -4,10 +4,8 @@ import useENSAvatar from 'hooks/Guilds/ether-swr/ens/useENSAvatar';
import { useProposal } from 'hooks/Guilds/ether-swr/guild/useProposal';
import useVoteSummary from 'hooks/Guilds/useVoteSummary';
import { MAINNET_ID } from 'utils/constants';
import { useMemo } from 'react';
import { ProposalState } from 'Components/Types';
import { useProposalSummaryActions } from 'hooks/Guilds/guild/useProposalSummaryActions';
import moment from 'moment';
import useProposalState from 'hooks/Guilds/useProposalState';

interface ProposalCardWrapperProps {
proposalId?: string;
Expand All @@ -22,47 +20,19 @@ const ProposalCardWrapper: React.FC<ProposalCardWrapperProps> = ({

const ensAvatar = useENSAvatar(proposal?.creator, MAINNET_ID);

// Make into hooks
const timeDetail = useMemo(() => {
if (!proposal?.endTime) return null;

const currentTime = moment();
if (proposal.endTime?.isBefore(currentTime)) {
return proposal.endTime.fromNow();
} else {
return proposal.endTime.toNow();
}
}, [proposal]);

// Make into singular guild state hook
const status = useMemo(() => {
if (!proposal?.endTime) return null;
switch (proposal.state) {
case ProposalState.Active:
const currentTime = moment();
if (currentTime.isSameOrAfter(proposal.endTime)) {
return ProposalState.Failed;
} else {
return ProposalState.Active;
}
case ProposalState.Executed:
return ProposalState.Executed;
case ProposalState.Passed:
return ProposalState.Passed;
case ProposalState.Failed:
return ProposalState.Failed;
default:
return proposal.state;
}
}, [proposal]);
const status = useProposalState(proposal);

return (
<ProposalCard
proposal={{ ...proposal, id: proposalId }}
ensAvatar={ensAvatar}
votes={votes}
href={`/${chainName}/${guildId}/proposal/${proposalId}`}
statusProps={{ timeDetail, status, endTime: proposal?.endTime }}
statusProps={{
timeDetail: proposal?.timeDetail,
status,
endTime: proposal?.endTime,
}}
summaryActions={summaryActions}
/>
);
Expand Down
38 changes: 34 additions & 4 deletions src/hooks/Guilds/ether-swr/guild/useProposal.ts
@@ -1,34 +1,64 @@
import { unix } from 'moment';
import moment, { unix } from 'moment';
import { Middleware, SWRHook } from 'swr';
import { Proposal } from '../../../../types/types.guilds';
import useEtherSWR from '../useEtherSWR';
import ERC20GuildContract from 'contracts/ERC20Guild.json';
import { Proposal, ContractState } from 'types/types.guilds.d';

const formatterMiddleware: Middleware =
(useSWRNext: SWRHook) => (key, fetcher, config) => {
const swr = useSWRNext(key, fetcher, config);
if (swr.data) {
const original = swr.data as any;

const clone: any = Object.assign({}, swr.data);

//rename state to contractState
clone.contractState = clone.state;
delete clone.state;

switch (clone.contractState) {
case 1:
clone.contractState = ContractState.Active;
break;
case 2:
clone.contractState = ContractState.Rejected;
break;
case 3:
clone.contractState = ContractState.Executed;
break;
case 4:
clone.contractState = ContractState.Failed;
break;
}

clone.startTime = original.startTime
? unix(original.startTime.toNumber())
: null;
clone.endTime = original.endTime
? unix(original.endTime.toNumber())
: null;

// Add timeDetail
const currentTime = moment();
let differenceInMilliseconds = currentTime.diff(clone.endTime);
let timeDifference = moment.duration(differenceInMilliseconds).humanize();
if (clone.endTime.isBefore(currentTime)) {
clone.timeDetail = `ended ${timeDifference} ago`;
} else {
clone.timeDetail = `${timeDifference} left`;
}

return { ...swr, data: clone };
}
return swr;
};

export const useProposal = (guildId: string, proposalId: string) => {
return useEtherSWR<Proposal>(
let result = useEtherSWR<Proposal>(
guildId && proposalId ? [guildId, 'getProposal', proposalId] : [],
{
use: [formatterMiddleware],
ABIs: new Map([[guildId, ERC20GuildContract.abi]]),
}
);
return result;
};
2 changes: 1 addition & 1 deletion src/hooks/Guilds/guild/useProposalCalls.ts
Expand Up @@ -2,12 +2,12 @@ import { useState, useEffect, useMemo } from 'react';
import { useTheme } from 'styled-components';
import { useWeb3React } from '@web3-react/core';
import { bulkDecodeCallsFromOptions } from '../contracts/useDecodedCall';
import useProposalMetadata from 'hooks/Guilds/ether-swr/guild/useProposalMetadata';
import { decodeCall } from 'hooks/Guilds/contracts/useDecodedCall';
import { useProposal } from '../ether-swr/guild/useProposal';
import { useVotingResults } from 'hooks/Guilds/ether-swr/guild/useVotingResults';
import { Call, Option } from 'old-components/Guilds/ActionsBuilder/types';
import { ZERO_HASH } from 'utils';
import useProposalMetadata from '../useProposalMetadata';
import { useRichContractRegistry } from '../contracts/useRichContractRegistry';
import { ERC20_APPROVE_SIGNATURE } from 'utils';
import useGuildImplementationTypeConfig from './useGuildImplementationType';
Expand Down