admin: more robust arbiter

This commit is contained in:
Aaron 2019-02-09 21:00:49 -06:00
parent 9831bc03db
commit 5b44f5dcd7
No known key found for this signature in database
GPG Key ID: 3B5B7597106F0A0E
6 changed files with 103 additions and 27 deletions

View File

@ -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<Props, State> {
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<Props, State> {
onClick={this.handleShowSearch}
{...this.props.buttonProps}
>
{arbiter ? 'Change arbiter' : 'Set arbiter'}
{disp[arbiter.status]}
</Button>
{/* SEARCH MODAL */}
{showSearch && (
<Modal
title={
<>
<Icon type="crown" /> Select an arbiter
<Icon type="crown" /> Nominate an arbiter
</>
}
visible={true}
@ -96,7 +103,7 @@ class ArbiterControlNaked extends React.Component<Props, State> {
key="select"
onClick={() => this.handleSelect(u)}
>
Select
Nominate
</Button>,
]}
>
@ -143,7 +150,7 @@ class ArbiterControlNaked extends React.Component<Props, State> {
await store.setArbiter(this.props.proposalId, user.userid);
message.success(
<>
Arbiter set for <b>{this.props.title}</b>
Arbiter nominated for <b>{this.props.title}</b>
</>,
);
} catch (e) {

View File

@ -30,7 +30,7 @@ class Home extends React.Component {
<div>
<Icon type="exclamation-circle" /> There are <b>{proposalNoArbiterCount}</b>{' '}
live proposals <b>without an arbiter</b>.{' '}
<Link to="/proposals?filters[]=STATUS_LIVE&filters[]=OTHER_ARBITER">
<Link to="/proposals?filters[]=STATUS_LIVE&filters[]=ARBITER_MISSING">
Click here
</Link>{' '}
to view them.

View File

@ -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<Props, State> {
/>
);
const renderSetArbiter = () =>
!p.arbiter &&
const renderNominateArbiter = () =>
PROPOSAL_ARBITER_STATUS.MISSING === p.arbiter.status &&
p.status === PROPOSAL_STATUS.LIVE && (
<Alert
showIcon
type="warning"
message="No Arbiter on Live Proposal"
message="No arbiter on live proposal"
description={
<div>
<p>An arbiter is required to review milestone payout requests.</p>
@ -225,6 +225,25 @@ class ProposalDetailNaked extends React.Component<Props, State> {
/>
);
const renderNominatedArbiter = () =>
PROPOSAL_ARBITER_STATUS.NOMINATED === p.arbiter.status &&
p.status === PROPOSAL_STATUS.LIVE && (
<Alert
showIcon
type="info"
message="Arbiter has been nominated"
description={
<div>
<p>
<b>{p.arbiter.user!.displayName}</b> has been nominated for arbiter of
this proposal but has not yet accepted.
</p>
<ArbiterControl {...p} />
</div>
}
/>
);
const renderDeetItem = (name: string, val: any) => (
<div className="ProposalDetail-deet">
<span>{name}</span>
@ -242,7 +261,8 @@ class ProposalDetailNaked extends React.Component<Props, State> {
{renderApproved()}
{renderReview()}
{renderRejected()}
{renderSetArbiter()}
{renderNominateArbiter()}
{renderNominatedArbiter()}
<Collapse defaultActiveKey={['brief', 'content']}>
<Collapse.Panel key="brief" header="brief">
{p.brief}
@ -279,11 +299,18 @@ class ProposalDetailNaked extends React.Component<Props, State> {
{renderDeetItem('contributed', p.contributed)}
{renderDeetItem('funded (inc. matching)', p.funded)}
{renderDeetItem('matching', p.contributionMatching)}
{p.arbiter &&
renderDeetItem(
'arbiter',
<Link to={`/users/${p.arbiter.userid}`}>{p.arbiter.displayName}</Link>,
)}
{renderDeetItem(
'arbiter',
<>
{p.arbiter.user && (
<Link to={`/users/${p.arbiter.user.userid}`}>
{p.arbiter.user.displayName}
</Link>
)}
({p.arbiter.status})
</>,
)}
{p.rfp &&
renderDeetItem(
'rfp',

View File

@ -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;

View File

@ -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,

View File

@ -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<E> {
id: E;
@ -53,6 +58,27 @@ export const PROPOSAL_STATUSES: Array<StatusSoT<PROPOSAL_STATUS>> = [
},
];
export const PROPOSAL_ARBITER_STATUSES: Array<StatusSoT<PROPOSAL_ARBITER_STATUS>> = [
{
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<StatusSoT<RFP_STATUS>> = [
{
id: RFP_STATUS.DRAFT,