From 3e76493354abbdff3a4299ef4a7672300867d7c5 Mon Sep 17 00:00:00 2001 From: Enrique Ortiz Date: Thu, 3 Dec 2020 14:08:43 -0400 Subject: [PATCH] Console: Improvement round #1 (#233) * console: add action pagination + cosmetic changes * Add formatting for permissions section * chore: remove unnecesary console log * console: small improvements to links and general views * Cosmetic enhancements a-la contraktor * Add permissions filtering * Increase delay for actions scheduled * Handle token approvals * Console demo statte * Sort actions, show human readable execution time * Fix linting / tooling * Remove logging * tooling: revert to React, React DOM & React scripts pre-React 17 While it would be great to update to the latest version of React and all its tooling, there seems to be an issue with typescript versions, react-scripts and the new JSX transform wreaking havoc on the JSX checks. While the issue was supposedly fixed here (https://github.com/facebook/create-react-app/pull/9869), upgrading to react-scripts 4.0.1 doesn't fix the problem. Neither does upgrading AND pinning typescript to 4.0.5 so it doesn't overwrite the tsconfig.json file. Let's wait a few versions ahead for react-scripts so we can be sure this is fixed. --- packages/govern-console/.eslintrc.js | 52 +- packages/govern-console/package.json | 26 +- .../src/components/Entity/Entity.tsx | 5 +- .../FilteredActions/FilteredActions.tsx | 31 +- .../src/components/NewAction/NewAction.tsx | 15 +- .../src/components/ViewAction/ViewAction.tsx | 69 +- .../src/components/ViewDao/ViewDao.tsx | 12 +- packages/govern-console/src/lib/utils.ts | 2 +- packages/govern-console/src/pages/ViewDao.tsx | 7 + packages/govern-console/tsconfig.json | 16 +- yarn.lock | 811 +++++++++++++----- 11 files changed, 734 insertions(+), 312 deletions(-) diff --git a/packages/govern-console/.eslintrc.js b/packages/govern-console/.eslintrc.js index 4910dbde5..29add9b8d 100644 --- a/packages/govern-console/.eslintrc.js +++ b/packages/govern-console/.eslintrc.js @@ -4,53 +4,23 @@ module.exports = { es6: true, }, extends: [ - 'plugin:import/recommended', - 'plugin:promise/recommended', - 'standard', - 'standard-react', - 'prettier/react', + 'eslint:recommended', 'plugin:prettier/recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier/@typescript-eslint', + 'react-app', ], parser: '@typescript-eslint/parser', parserOptions: { - ecmaFeatures: { - experimentalObjectRestSpread: true, - jsx: true, - }, + jsx: true, sourceType: 'module', + project: './tsconfig.json', }, - plugins: ['prettier', 'react', 'react-hooks', 'import', 'promise', 'jest'], + plugins: ['prettier', 'react', 'react-hooks', '@typescript-eslint'], rules: { - 'react/prop-types': 'warn', - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'error', - 'import/no-unresolved': [ - 'error', - { ignore: ['^react(-dom)?$', '^styled-components$'] }, - ], - 'promise/no-nesting': ['off'], - 'valid-jsdoc': 'error', - 'linebreak-style': ['error', 'unix'], - curly: 'error', + '@typescript-eslint/no-explicit-any': 'off', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': ['error'], + '@typescript-eslint/ban-ts-comment': 'off', }, - settings: { - react: { - pragma: 'React', - version: '16.13.1', - }, - 'import/parsers': { - '@typescript-eslint/parser': ['.ts', '.tsx'], - }, - 'import/resolver': { - typescript: { - alwaysTryTypes: true, - }, - }, - }, - overrides: [ - { - files: ['**/*.test.js'], - env: { jest: true }, - }, - ], } diff --git a/packages/govern-console/package.json b/packages/govern-console/package.json index 4afeabff9..5d7106223 100644 --- a/packages/govern-console/package.json +++ b/packages/govern-console/package.json @@ -6,14 +6,6 @@ "@apollo/client": "^3.2.3", "@apollo/react-hooks": "^3.1.5", "@aragon/connect-agreement": "^0.0.0-beta.14", - "@testing-library/jest-dom": "^4.2.4", - "@testing-library/react": "^9.3.2", - "@testing-library/user-event": "^7.1.2", - "@types/jest": "^26.0.14", - "@types/node": "^14.11.2", - "@types/react": "^16.9.49", - "@types/react-dom": "^16.9.8", - "@typescript-eslint/parser": "^4.2.0", "apollo-cache-inmemory": "^1.6.6", "apollo-client": "^2.6.10", "apollo-link-http": "^1.5.17", @@ -21,19 +13,20 @@ "bn.js": "^5.1.3", "clipboard-polyfill": "^3.0.1", "date-fns": "^2.16.1", + "eslint-config-react-app": "^6.0.0", "ethers": "^5.0.14", "graphql": "^15.0.0", "graphql-tag": "^2.10.3", "is-ipfs": "^2.0.0", - "react": "^16.13.1", - "react-dom": "^16.13.1", + "react": "16.13.1", + "react-dom": "16.13.1", "react-query": "^2.26.2", "react-router-dom": "^5.2.0", - "react-scripts": "3.4.1", + "react-scripts": "3.4.4", "react-spring": "^8.0.27", "styled-components": "^5.1.0", "token-amount": "^0.2.0", - "typescript": "^4.0.3", + "typescript": "^4.1.2", "use-viewport": "^0.2.0", "use-wallet": "^0.8.0", "web3-eth-abi": "^1.3.0", @@ -60,10 +53,19 @@ ] }, "devDependencies": { + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@testing-library/user-event": "^7.1.2", + "@types/jest": "^26.0.14", + "@types/node": "^14.11.2", + "@types/react": "^16.9.49", + "@types/react-dom": "^16.9.8", "@types/react-router-dom": "^5.1.5", "@types/styled-components": "^5.1.3", + "@typescript-eslint/eslint-plugin": "^4.9.0", "babel-eslint": "^10.1.0", "babel-plugin-styled-components": "^1.10.7", + "eslint": "^7.14.0", "eslint-config-prettier": "^6.11.0", "eslint-config-standard": "^14.1.1", "eslint-config-standard-react": "^9.2.0", diff --git a/packages/govern-console/src/components/Entity/Entity.tsx b/packages/govern-console/src/components/Entity/Entity.tsx index 8e76671bb..81295c51e 100644 --- a/packages/govern-console/src/components/Entity/Entity.tsx +++ b/packages/govern-console/src/components/Entity/Entity.tsx @@ -56,11 +56,12 @@ function formatAddress( } if (isCid(hexToUtf8(address))) { + return hexToUtf8(address) } } catch (err) { // Won't be a valid string anyway, so we return it - return address + return address.length > 42 ? `${address.slice(0, 43)}...` : address } // In this case, it's possible that it's really just plain text, so we decode it anyways. @@ -79,6 +80,6 @@ export default function Entity({ {formatAddress(address, { shorten })} ) : ( - {address} + {address.length > 42 ? `${address.slice(0, 43)}...` : address} ) } diff --git a/packages/govern-console/src/components/FilteredActions/FilteredActions.tsx b/packages/govern-console/src/components/FilteredActions/FilteredActions.tsx index 808ba1855..2132652e8 100644 --- a/packages/govern-console/src/components/FilteredActions/FilteredActions.tsx +++ b/packages/govern-console/src/components/FilteredActions/FilteredActions.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useMemo, useState } from 'react' import { useHistory, useParams } from 'react-router-dom' +import { fromUnixTime } from 'date-fns' import 'styled-components/macro' import { shortenAddress } from '../../lib/web3-utils' @@ -86,11 +87,19 @@ export default function FilteredActions({ `} > {hasActions && - filteredAndPaginatedActions.map( - ({ id, state }: { id: string; state: string }) => ( - - ), - )} + filteredAndPaginatedActions + .reverse() + .sort((a, b) => { + return b.payload.executionTime - a.payload.executionTime + }) + .map((action: any) => ( + + ))}
{shortenAddress(id)}
- {state} +
+ {state} +
+ {fromUnixTime(Number(executionTime)).toLocaleDateString('en-US')} ) } diff --git a/packages/govern-console/src/components/NewAction/NewAction.tsx b/packages/govern-console/src/components/NewAction/NewAction.tsx index b26500b83..6af1b63a6 100644 --- a/packages/govern-console/src/components/NewAction/NewAction.tsx +++ b/packages/govern-console/src/components/NewAction/NewAction.tsx @@ -13,6 +13,7 @@ import queueAbi from '../../lib/abi/GovernQueue.json' const EMPTY_BYTES = '0x00' const EMPTY_FAILURE_MAP = '0x0000000000000000000000000000000000000000000000000000000000000000' +const NO_TOKEN = `${'0x'.padEnd(42, '0')}` type Input = { name: string | undefined @@ -168,12 +169,14 @@ export default function NewAction({ @@ -193,6 +196,7 @@ type ContractCallHandlerProps = { inputs: Input[] | any[] name: string proof: string + queueAddress: string queueContract: any rawAbiItem: AbiType } @@ -206,6 +210,7 @@ function ContractCallHandler({ inputs, name, proof, + queueAddress, queueContract, rawAbiItem, }: ContractCallHandlerProps) { @@ -249,14 +254,16 @@ function ContractCallHandler({ // 1. The user has more allowance than needed, we can skip. (0 tx) // 2. The user has less allowance than needed, and we need to raise it. (2 tx) // 3. The user has 0 allowance, we just need to approve the needed amount. (1 tx) - - const allowance = await ercContract.getAllowance() - if (allowance.lt(new BN(config.scheduleDeposit.amount))) { + const allowance = await ercContract.allowance(account, queueAddress) + if ( + allowance.lt(config.scheduleDeposit.amount) && + config.scheduleDeposit.token !== NO_TOKEN + ) { if (!allowance.isZero()) { const resetTx = await ercContract.approve(account, '0') await resetTx.wait(1) } - await ercContract.approve(account, config.scheduleDeposit.amount) + await ercContract.approve(queueAddress, config.scheduleDeposit.amount) } const functionValues = values ? values.map((val: any) => val.value) : [] diff --git a/packages/govern-console/src/components/ViewAction/ViewAction.tsx b/packages/govern-console/src/components/ViewAction/ViewAction.tsx index b7b48fe0b..6762f1ed4 100644 --- a/packages/govern-console/src/components/ViewAction/ViewAction.tsx +++ b/packages/govern-console/src/components/ViewAction/ViewAction.tsx @@ -1,6 +1,8 @@ import React, { useMemo, useCallback, useState } from 'react' import { useParams } from 'react-router-dom' import * as clipboard from 'clipboard-polyfill' +import { fromUnixTime } from 'date-fns' +import { toHex } from 'web3-utils' import 'styled-components/macro' import Button from '../Button' import Entity from '../Entity/Entity' @@ -67,7 +69,7 @@ type ContainerEventResolve = { id: string container: any createdAt: string - approved: Boolean + approved: boolean } type ContainerEventRule = { @@ -90,7 +92,7 @@ type ContainerEventSubmitEvidence = { createdAt: string evidence: string submitter: string - finished: Boolean + finished: boolean } type ContainerEventVeto = { @@ -129,12 +131,13 @@ type ViewActionProps = { } function ViewAction({ container, queueAddress }: ViewActionProps) { - const { wallet } = useWallet() - const { status: accountStatus } = wallet const [executionStatus, setExecutionStatus] = useState('') + const [justification, setJustification] = useState('') const [statusType, setStatusType] = useState< 'error' | 'info' | 'success' | '' >('') + const { wallet } = useWallet() + const { status: accountStatus } = wallet const queueContract = useContract(queueAddress, queueAbi) const { permissions } = usePermissions() const { @@ -212,9 +215,13 @@ function ViewAction({ container, queueAddress }: ViewActionProps) { } try { const containerHash = container.id - const tx = await queueContract!.veto(containerHash, '0x00', { - gasLimit: 500000, - }) + const tx = await queueContract!.veto( + containerHash, + justification ? toHex(justification) : '0x00', + { + gasLimit: 500000, + }, + ) handleSetExecutionStatus('info', `Sending transaction.`) await tx.wait(1) handleSetExecutionStatus( @@ -228,7 +235,13 @@ function ViewAction({ container, queueAddress }: ViewActionProps) { `There was an error with the transaction.`, ) } - }, [accountStatus, container, handleSetExecutionStatus, queueContract]) + }, [ + accountStatus, + container, + handleSetExecutionStatus, + queueContract, + justification, + ]) const challenge = useCallback(async () => { if (accountStatus !== 'connected') { @@ -283,18 +296,48 @@ function ViewAction({ container, queueAddress }: ViewActionProps) { ) } }, [accountStatus, container, handleSetExecutionStatus, queueContract]) - console.log(container) + + const vetoEvent = container.history.find( + (event: any) => event.__typename === 'ContainerEventVeto', + ) + // @ts-ignore + const reason = vetoEvent?.reason ?? '' + return ( <>

Action {shortenAddress(container.id)}

Status

{container.state}

+ {container.state === 'Cancelled' && ( + <> +

Reason

+ + + )} {executionStatus && {executionStatus}} - {container.state !== 'Executed' && ( + {container.state !== 'Executed' && container.state !== 'Cancelled' && ( +
+ +

Available actions

{container.state === 'Scheduled' && (