From 5b44f5dcd7cf2c85de48106a4f0c7fcd2832d9b5 Mon Sep 17 00:00:00 2001 From: Aaron Date: Sat, 9 Feb 2019 21:00:49 -0600 Subject: [PATCH] admin: more robust arbiter --- admin/src/components/ArbiterControl/index.tsx | 17 +++++-- admin/src/components/Home/index.tsx | 2 +- admin/src/components/ProposalDetail/index.tsx | 47 +++++++++++++++---- admin/src/types.ts | 13 ++++- admin/src/util/filters.ts | 23 +++++---- admin/src/util/statuses.ts | 28 ++++++++++- 6 files changed, 103 insertions(+), 27 deletions(-) diff --git a/admin/src/components/ArbiterControl/index.tsx b/admin/src/components/ArbiterControl/index.tsx index 70a414e0..54a9217a 100644 --- a/admin/src/components/ArbiterControl/index.tsx +++ b/admin/src/components/ArbiterControl/index.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { view } from 'react-easy-state'; import { Button, Modal, Input, Icon, List, Avatar, message } from 'antd'; import store from 'src/store'; -import { Proposal, User } from 'src/types'; +import { Proposal, User, PROPOSAL_ARBITER_STATUS } from 'src/types'; import Search from 'antd/lib/input/Search'; import { ButtonProps } from 'antd/lib/button'; import './index.less'; @@ -34,6 +34,13 @@ class ArbiterControlNaked extends React.Component { const { showSearch, searching } = this.state; const { results, search, error } = store.arbitersSearch; const showEmpty = !results.length && !searching; + + const disp = { + [PROPOSAL_ARBITER_STATUS.MISSING]: 'Nominate arbiter', + [PROPOSAL_ARBITER_STATUS.NOMINATED]: 'Change nomination', + [PROPOSAL_ARBITER_STATUS.ACCEPTED]: 'Change arbiter', + }; + return ( <> {/* CONTROL */} @@ -45,14 +52,14 @@ class ArbiterControlNaked extends React.Component { onClick={this.handleShowSearch} {...this.props.buttonProps} > - {arbiter ? 'Change arbiter' : 'Set arbiter'} + {disp[arbiter.status]} {/* SEARCH MODAL */} {showSearch && ( - Select an arbiter + Nominate an arbiter } visible={true} @@ -96,7 +103,7 @@ class ArbiterControlNaked extends React.Component { key="select" onClick={() => this.handleSelect(u)} > - Select + Nominate , ]} > @@ -143,7 +150,7 @@ class ArbiterControlNaked extends React.Component { await store.setArbiter(this.props.proposalId, user.userid); message.success( <> - Arbiter set for {this.props.title} + Arbiter nominated for {this.props.title} , ); } catch (e) { diff --git a/admin/src/components/Home/index.tsx b/admin/src/components/Home/index.tsx index 8bba0039..b6a08aa4 100644 --- a/admin/src/components/Home/index.tsx +++ b/admin/src/components/Home/index.tsx @@ -30,7 +30,7 @@ class Home extends React.Component {
There are {proposalNoArbiterCount}{' '} live proposals without an arbiter.{' '} - + Click here {' '} to view them. diff --git a/admin/src/components/ProposalDetail/index.tsx b/admin/src/components/ProposalDetail/index.tsx index cbabaee4..fb9b1002 100644 --- a/admin/src/components/ProposalDetail/index.tsx +++ b/admin/src/components/ProposalDetail/index.tsx @@ -16,7 +16,7 @@ import { import TextArea from 'antd/lib/input/TextArea'; import store from 'src/store'; import { formatDateSeconds } from 'util/time'; -import { PROPOSAL_STATUS } from 'src/types'; +import { PROPOSAL_STATUS, PROPOSAL_ARBITER_STATUS } from 'src/types'; import { Link } from 'react-router-dom'; import Back from 'components/Back'; import Info from 'components/Info'; @@ -209,13 +209,13 @@ class ProposalDetailNaked extends React.Component { /> ); - const renderSetArbiter = () => - !p.arbiter && + const renderNominateArbiter = () => + PROPOSAL_ARBITER_STATUS.MISSING === p.arbiter.status && p.status === PROPOSAL_STATUS.LIVE && (

An arbiter is required to review milestone payout requests.

@@ -225,6 +225,25 @@ class ProposalDetailNaked extends React.Component { /> ); + const renderNominatedArbiter = () => + PROPOSAL_ARBITER_STATUS.NOMINATED === p.arbiter.status && + p.status === PROPOSAL_STATUS.LIVE && ( + +

+ {p.arbiter.user!.displayName} has been nominated for arbiter of + this proposal but has not yet accepted. +

+ +
+ } + /> + ); + const renderDeetItem = (name: string, val: any) => (
{name} @@ -242,7 +261,8 @@ class ProposalDetailNaked extends React.Component { {renderApproved()} {renderReview()} {renderRejected()} - {renderSetArbiter()} + {renderNominateArbiter()} + {renderNominatedArbiter()} {p.brief} @@ -279,11 +299,18 @@ class ProposalDetailNaked extends React.Component { {renderDeetItem('contributed', p.contributed)} {renderDeetItem('funded (inc. matching)', p.funded)} {renderDeetItem('matching', p.contributionMatching)} - {p.arbiter && - renderDeetItem( - 'arbiter', - {p.arbiter.displayName}, - )} + + {renderDeetItem( + 'arbiter', + <> + {p.arbiter.user && ( + + {p.arbiter.user.displayName} + + )} + ({p.arbiter.status}) + , + )} {p.rfp && renderDeetItem( 'rfp', diff --git a/admin/src/types.ts b/admin/src/types.ts index eb634d3b..0473d561 100644 --- a/admin/src/types.ts +++ b/admin/src/types.ts @@ -36,6 +36,17 @@ export interface RFPArgs { category: string; status?: string; } +// NOTE: sync with backend/grant/utils/enums.py ProposalArbiterStatus +export enum PROPOSAL_ARBITER_STATUS { + MISSING = 'MISSING', + NOMINATED = 'NOMINATED', + ACCEPTED = 'ACCEPTED', +} +export interface ProposalArbiter { + user?: User; + proposal: Proposal; + status: PROPOSAL_ARBITER_STATUS; +} // NOTE: sync with backend/grant/utils/enums.py ProposalStatus export enum PROPOSAL_STATUS { DRAFT = 'DRAFT', @@ -68,7 +79,7 @@ export interface Proposal { rejectReason: string; contributionMatching: number; rfp?: RFP; - arbiter?: User; + arbiter: ProposalArbiter; } export interface Comment { commentId: string; diff --git a/admin/src/util/filters.ts b/admin/src/util/filters.ts index 4f05b92c..df91f8d2 100644 --- a/admin/src/util/filters.ts +++ b/admin/src/util/filters.ts @@ -1,4 +1,9 @@ -import { PROPOSAL_STATUSES, RFP_STATUSES, CONTRIBUTION_STATUSES } from './statuses'; +import { + PROPOSAL_STATUSES, + RFP_STATUSES, + CONTRIBUTION_STATUSES, + PROPOSAL_ARBITER_STATUSES, +} from './statuses'; export interface Filter { id: string; @@ -29,14 +34,14 @@ const PROPOSAL_FILTERS = PROPOSAL_STATUSES.map(s => ({ group: 'Status', })) // proposal has extra filters - .concat([ - { - id: `OTHER_ARBITER`, - display: `Other: Arbiter`, - color: '#cf00d5', - group: 'Other', - }, - ]); + .concat( + PROPOSAL_ARBITER_STATUSES.map(s => ({ + id: `ARBITER_${s.id}`, + display: `Arbiter: ${s.tagDisplay}`, + color: s.tagColor, + group: 'Arbiter', + })), + ); export const proposalFilters: Filters = { list: PROPOSAL_FILTERS, diff --git a/admin/src/util/statuses.ts b/admin/src/util/statuses.ts index 1c786855..c9e461f0 100644 --- a/admin/src/util/statuses.ts +++ b/admin/src/util/statuses.ts @@ -1,4 +1,9 @@ -import { PROPOSAL_STATUS, RFP_STATUS, CONTRIBUTION_STATUS } from 'src/types'; +import { + PROPOSAL_STATUS, + RFP_STATUS, + CONTRIBUTION_STATUS, + PROPOSAL_ARBITER_STATUS, +} from 'src/types'; export interface StatusSoT { id: E; @@ -53,6 +58,27 @@ export const PROPOSAL_STATUSES: Array> = [ }, ]; +export const PROPOSAL_ARBITER_STATUSES: Array> = [ + { + id: PROPOSAL_ARBITER_STATUS.MISSING, + tagDisplay: 'Missing', + tagColor: '#cf00d5', + hint: 'Proposal does not have an arbiter.', + }, + { + id: PROPOSAL_ARBITER_STATUS.NOMINATED, + tagDisplay: 'Nominated', + tagColor: '#cf00d5', + hint: 'An arbiter has been nominated for this proposal.', + }, + { + id: PROPOSAL_ARBITER_STATUS.ACCEPTED, + tagDisplay: 'Accepted', + tagColor: '#cf00d5', + hint: 'Proposal has an arbiter.', + }, +]; + export const RFP_STATUSES: Array> = [ { id: RFP_STATUS.DRAFT,