proposal arbiter BE basics + initial admin work
This commit is contained in:
parent
6310e7ca90
commit
2868d93fa1
|
@ -7,6 +7,10 @@
|
|||
margin-bottom: 3rem;
|
||||
font-size: 1rem;
|
||||
|
||||
& > * + * {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
color: #ffaa00;
|
||||
}
|
||||
|
|
|
@ -11,17 +11,39 @@ class Home extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { userCount, proposalCount, proposalPendingCount } = store.stats;
|
||||
const {
|
||||
userCount,
|
||||
proposalCount,
|
||||
proposalPendingCount,
|
||||
proposalNoArbiterCount,
|
||||
} = store.stats;
|
||||
|
||||
const actionItems = [
|
||||
!!proposalPendingCount && (
|
||||
<div>
|
||||
<Icon type="exclamation-circle" /> There are <b>{proposalPendingCount}</b>{' '}
|
||||
proposals <b>waiting for review</b>.{' '}
|
||||
<Link to="/proposals?status=PENDING">Click here</Link> to view them.
|
||||
</div>
|
||||
),
|
||||
!!proposalNoArbiterCount && (
|
||||
<div>
|
||||
<Icon type="exclamation-circle" /> There are <b>{proposalNoArbiterCount}</b>{' '}
|
||||
live proposals <b>without an arbitor</b>. No one can approve their payout
|
||||
requests! <Link to="/proposals?status=LIVE&arbiter=false">Click here</Link> to
|
||||
view them.
|
||||
</div>
|
||||
),
|
||||
].filter(Boolean);
|
||||
|
||||
return (
|
||||
<div className="Home">
|
||||
{!!proposalPendingCount && (
|
||||
{!!actionItems.length && (
|
||||
<div className="Home-actionItems">
|
||||
<Divider orientation="left">Action Items</Divider>
|
||||
<div>
|
||||
<Icon type="exclamation-circle" /> There are <b>{proposalPendingCount}</b>{' '}
|
||||
proposals waiting for review.{' '}
|
||||
<Link to="/proposals?status=PENDING">Click here</Link> to view them.
|
||||
</div>
|
||||
{actionItems.map((ai, i) => (
|
||||
<div key={i}>{ai}</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -197,6 +197,29 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
|||
/>
|
||||
);
|
||||
|
||||
const renderSetArbiter = () =>
|
||||
!p.arbiter &&
|
||||
p.status === PROPOSAL_STATUS.LIVE && (
|
||||
<Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message="No Arbiter on Live Proposal"
|
||||
description={
|
||||
<div>
|
||||
<p>An arbiter is required to review milestone payout requests.</p>
|
||||
<Button
|
||||
loading={store.proposalDetailApproving}
|
||||
icon="crown"
|
||||
type="primary"
|
||||
onClick={this.handleApprove}
|
||||
>
|
||||
Set arbiter
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderDeetItem = (name: string, val: any) => (
|
||||
<div className="ProposalDetail-deet">
|
||||
<span>{name}</span>
|
||||
|
@ -214,6 +237,7 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
|||
{renderApproved()}
|
||||
{renderReview()}
|
||||
{renderRejected()}
|
||||
{renderSetArbiter()}
|
||||
<Collapse defaultActiveKey={['brief', 'content']}>
|
||||
<Collapse.Panel key="brief" header="brief">
|
||||
{p.brief}
|
||||
|
@ -250,8 +274,10 @@ class ProposalDetailNaked extends React.Component<Props, State> {
|
|||
{renderDeetItem('funded (inc. matching)', p.funded)}
|
||||
{renderDeetItem('matching', p.contributionMatching)}
|
||||
{p.rfp &&
|
||||
renderDeetItem('rfp', <Link to={`/rfps/${p.rfp.id}`}>{p.rfp.title}</Link>)
|
||||
}
|
||||
renderDeetItem(
|
||||
'rfp',
|
||||
<Link to={`/rfps/${p.rfp.id}`}>{p.rfp.title}</Link>,
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* TEAM */}
|
||||
|
|
|
@ -112,6 +112,7 @@ const app = store({
|
|||
userCount: 0,
|
||||
proposalCount: 0,
|
||||
proposalPendingCount: 0,
|
||||
proposalNoArbiterCount: 0,
|
||||
},
|
||||
|
||||
usersFetching: false,
|
||||
|
|
|
@ -68,6 +68,7 @@ export interface Proposal {
|
|||
rejectReason: string;
|
||||
contributionMatching: number;
|
||||
rfp?: RFP;
|
||||
arbiter?: User;
|
||||
}
|
||||
export interface Comment {
|
||||
commentId: string;
|
||||
|
|
|
@ -56,10 +56,15 @@ def stats():
|
|||
proposal_pending_count = db.session.query(func.count(Proposal.id)) \
|
||||
.filter(Proposal.status == ProposalStatus.PENDING) \
|
||||
.scalar()
|
||||
proposal_no_arbiter_count = db.session.query(func.count(Proposal.id)) \
|
||||
.filter(Proposal.status == ProposalStatus.LIVE) \
|
||||
.filter(Proposal.arbiter_id == None) \
|
||||
.scalar()
|
||||
return {
|
||||
"userCount": user_count,
|
||||
"proposalCount": proposal_count,
|
||||
"proposalPendingCount": proposal_pending_count,
|
||||
"proposalNoArbiterCount": proposal_no_arbiter_count,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -115,6 +115,7 @@ class Proposal(db.Model):
|
|||
id = db.Column(db.Integer(), primary_key=True)
|
||||
date_created = db.Column(db.DateTime)
|
||||
rfp_id = db.Column(db.Integer(), db.ForeignKey('rfp.id'), nullable=True)
|
||||
arbiter_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=True)
|
||||
|
||||
# Content info
|
||||
status = db.Column(db.String(255), nullable=False)
|
||||
|
@ -141,6 +142,7 @@ class Proposal(db.Model):
|
|||
contributions = db.relationship(ProposalContribution, backref="proposal", lazy=True, cascade="all, delete-orphan")
|
||||
milestones = db.relationship("Milestone", backref="proposal", lazy=True, cascade="all, delete-orphan")
|
||||
invites = db.relationship(ProposalTeamInvite, backref="proposal", lazy=True, cascade="all, delete-orphan")
|
||||
arbiter = db.relationship("User", lazy=True, back_populates="arbitrated_proposals")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -359,7 +361,8 @@ class ProposalSchema(ma.Schema):
|
|||
"deadline_duration",
|
||||
"contribution_matching",
|
||||
"invites",
|
||||
"rfp"
|
||||
"rfp",
|
||||
"arbiter"
|
||||
)
|
||||
|
||||
date_created = ma.Method("get_date_created")
|
||||
|
@ -373,6 +376,7 @@ class ProposalSchema(ma.Schema):
|
|||
milestones = ma.Nested("MilestoneSchema", many=True)
|
||||
invites = ma.Nested("ProposalTeamInviteSchema", many=True)
|
||||
rfp = ma.Nested("RFPSchema", exclude=["accepted_proposals"])
|
||||
arbiter = ma.Nested("UserSchema") # exclude=["arbitrated_proposals"])
|
||||
|
||||
def get_proposal_id(self, obj):
|
||||
return obj.id
|
||||
|
|
|
@ -18,7 +18,7 @@ from sqlalchemy.ext.hybrid import hybrid_property
|
|||
|
||||
def is_current_authed_user_id(user_id):
|
||||
return current_user.is_authenticated and \
|
||||
current_user.id == user_id
|
||||
current_user.id == user_id
|
||||
|
||||
|
||||
class RolesUsers(db.Model):
|
||||
|
@ -118,6 +118,7 @@ class User(db.Model, UserMixin):
|
|||
lazy=True, cascade="all, delete-orphan")
|
||||
roles = db.relationship('Role', secondary='roles_users',
|
||||
backref=db.backref('users', lazy='dynamic'))
|
||||
arbitrated_proposals = db.relationship("Proposal", lazy=True, back_populates="arbiter")
|
||||
|
||||
# TODO - add create and validate methods
|
||||
|
||||
|
@ -235,13 +236,15 @@ class SelfUserSchema(ma.Schema):
|
|||
"avatar",
|
||||
"display_name",
|
||||
"userid",
|
||||
"email_verified"
|
||||
"email_verified",
|
||||
"arbitrated_proposals"
|
||||
)
|
||||
|
||||
social_medias = ma.Nested("SocialMediaSchema", many=True)
|
||||
avatar = ma.Nested("AvatarSchema")
|
||||
userid = ma.Method("get_userid")
|
||||
email_verified = ma.Method("get_email_verified")
|
||||
arbitrated_proposals = ma.Nested("Proposals", many=True, exclude=["arbitor"])
|
||||
|
||||
def get_userid(self, obj):
|
||||
return obj.id
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 310dca400b81
|
||||
Revises: fa1fedf4ca08
|
||||
Create Date: 2019-02-05 11:24:11.291158
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '310dca400b81'
|
||||
down_revision = 'fa1fedf4ca08'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('proposal', sa.Column('arbiter_id', sa.Integer(), nullable=True))
|
||||
op.create_foreign_key(None, 'proposal', 'user', ['arbiter_id'], ['id'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'proposal', type_='foreignkey')
|
||||
op.drop_column('proposal', 'arbiter_id')
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in New Issue