Bounties - Extract "give up curator" role modal, prioritize unhappy path motions to display and organize files into folders (#4596)

This commit is contained in:
Ewa Kowalska 2021-02-04 20:25:49 +01:00 committed by GitHub
parent bcadef3868
commit 36eb0b2e6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 109 additions and 48 deletions

View File

@ -402,7 +402,7 @@ describe('Bounties', () => {
});
});
describe('slash curator modal', () => {
describe('give up curator modal', () => {
it('gives up on the Curator role of an Active bounty', async () => {
const bounty = aBounty({ status: bountyStatusWith({ curator: alice }) });
@ -410,12 +410,14 @@ describe('Bounties', () => {
await bountiesPage.openGiveUpCuratorsRole();
bountiesPage.enterProposingAccount(alice);
await bountiesPage.clickButton('Approve');
await bountiesPage.clickButton('Give Up');
bountiesPage.expectExtrinsicQueued({ accountId: alice, extrinsic: 'mockUnassignExtrinsic' });
expect(mockBountyHooks.bountyApi.unassignCurator).toHaveBeenCalledWith(aBountyIndex(0));
});
});
describe('slash curator modal', () => {
it('creates a motion when slashing a PendingPayout bounty', async () => {
bountiesPage.renderOne(bountyWith({ status: 'PendingPayout' }));

View File

@ -14,7 +14,7 @@ import { BlockToTime, FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
import { getProposalToDisplay } from './helpers/extendedStatuses';
import VotingResultsColumn from './Voting/VotersColumn';
import VotersColumn from './Voting/VotersColumn';
import { BountyActions } from './BountyActions';
import BountyExtraActions from './BountyExtraActions';
import BountyInfos from './BountyInfos';
@ -190,12 +190,12 @@ function Bounty ({ bestNumber, bounty, className = '', description, index, propo
<td>
{proposals && (
<div className='votes-table'>
<VotingResultsColumn
<VotersColumn
option={'ayes'}
proposals={proposals}
status={status}
/>
<VotingResultsColumn
<VotersColumn
option={'nays'}
proposals={proposals}
status={status}

View File

@ -8,7 +8,7 @@ import React from 'react';
import { useBounties } from '@polkadot/app-bounties/hooks';
import { InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useTranslation } from './translate';
import { useTranslation } from '../translate';
interface Props {
curatorId: AccountId;

View File

@ -0,0 +1,62 @@
// Copyright 2017-2021 @polkadot/app-bounties authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AccountId, BountyIndex } from '@polkadot/types/interfaces';
import React from 'react';
import { useBounties } from '@polkadot/app-bounties/hooks';
import { InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useTranslation } from '../translate';
interface Props {
curatorId: AccountId;
index: BountyIndex;
toggleOpen: () => void;
}
function BountyGiveUpCurator ({ curatorId, index, toggleOpen }: Props) {
const { t } = useTranslation();
const { unassignCurator } = useBounties();
return (
<Modal
header={t<string>("Give up curator's role")}
size='large'
>
<Modal.Content>
<Modal.Column>
<p>{t<string>('This action will unassign you from the curator role.')}</p>
</Modal.Column>
<Modal.Columns>
<Modal.Column>
<InputAddress
help={t<string>('The Curator account that will give up on it\'s role.')}
isDisabled
label={t<string>('curator account')}
type='account'
value={curatorId.toString()}
withLabel
/>
</Modal.Column>
<Modal.Column>
<p>{t<string>('You are giving up your curator role, the bounty will return to the Funded state. You will get your deposit back.')}</p>
</Modal.Column>
</Modal.Columns>
</Modal.Content>
<Modal.Actions onCancel={toggleOpen}>
<TxButton
accountId={curatorId}
icon='check'
label={t<string>('Give Up')}
onStart={toggleOpen}
params={[index]}
tx={unassignCurator}
/>
</Modal.Actions>
</Modal>
);
}
export default React.memo(BountyGiveUpCurator);

View File

@ -53,16 +53,6 @@ function SlashCurator ({ action, curatorId, description, index, toggleOpen }: Pr
const unassignCuratorProposal = useMemo(() => unassignCurator(index), [index, unassignCurator]);
const actionProperties = useMemo<Record<ValidUnassignCuratorAction, ActionProperties>>(() => ({
GiveUp: {
filter: [curatorId.toString()],
header: t('This action will unassign you from a curator role.'),
helpMessage: t("The Curator account that will give up on it's role"),
params: [index],
proposingAccountTip: t('The account that will create the transaction.'),
tip: t('You are giving up your curator role, the bounty will return to the Funded state. You will get your deposit back.'),
title: t("Give up curator's role"),
tx: unassignCurator
},
SlashCuratorAction: {
filter: allAccounts,
header: t('This action will Slash the Curator.'),
@ -93,7 +83,7 @@ function SlashCurator ({ action, curatorId, description, index, toggleOpen }: Pr
title: t('Unassign curator'),
tx: api.tx.council.propose
}
}), [t, curatorId, index, unassignCurator, api.tx.council.propose, allAccounts, members, threshold, unassignCuratorProposal]);
}), [t, index, unassignCurator, api.tx.council.propose, allAccounts, members, threshold, unassignCuratorProposal]);
const { filter, header, helpMessage, params, proposingAccountTip, tip, title, tx } = actionProperties[action];

View File

@ -10,15 +10,16 @@ import { ThemeProps } from '@polkadot/react-components/types';
import { useMembers, useToggle } from '@polkadot/react-hooks';
import { BlockNumber, BountyIndex, BountyStatus } from '@polkadot/types/interfaces';
import CloseBounty from './BountyActions/CloseBounty';
import ExtendBountyExpiryAction from './BountyActions/ExtendBountyExpiryAction';
import SlashCurator from './BountyActions/SlashCurator';
import { determineUnassignCuratorAction } from '../helpers';
import { useBountyStatus, useUserRole } from '../hooks';
import { bountySvgColor } from '../theme';
import { useTranslation } from '../translate';
import { ValidUnassignCuratorAction } from '../types';
import BountyRejectCurator from './BountyRejectCurator';
import { determineUnassignCuratorAction } from './helpers';
import { useBountyStatus, useUserRole } from './hooks';
import { bountySvgColor } from './theme';
import { useTranslation } from './translate';
import { ValidUnassignCuratorAction } from './types';
import CloseBounty from './CloseBounty';
import ExtendBountyExpiryAction from './ExtendBountyExpiryAction';
import GiveUp from './GiveUp';
import SlashCurator from './SlashCurator';
interface Props {
bestNumber: BlockNumber;
@ -29,12 +30,13 @@ interface Props {
status: BountyStatus;
}
function BountyExtraActions ({ bestNumber, className, description, index, proposals, status }: Props): React.ReactElement<Props> | null {
function Index ({ bestNumber, className, description, index, proposals, status }: Props): React.ReactElement<Props> | null {
const [isPopupOpen, togglePopupOpen] = useToggle();
const [isCloseBountyOpen, toggleCloseBounty] = useToggle();
const [isRejectCuratorOpen, toggleRejectCurator] = useToggle();
const [isSlashCuratorOpen, toggleSlashCurator] = useToggle();
const [isExtendExpiryOpen, toggleExtendExpiry] = useToggle();
const [isGiveUpCuratorOpen, toggleGiveUpCurator] = useToggle();
const [selectedAction, setSlashAction] = useState<ValidUnassignCuratorAction>();
const { t } = useTranslation();
const { isMember } = useMembers();
@ -46,7 +48,6 @@ function BountyExtraActions ({ bestNumber, className, description, index, propos
const availableSlashActions = determineUnassignCuratorAction(roles, status, blocksUntilUpdate);
const slashCuratorActionNames = useRef<Record<ValidUnassignCuratorAction, string>>({
GiveUp: t('Give Up'),
SlashCuratorAction: t('Slash Curator'),
SlashCuratorMotion: t('Slash Curator (Council)'),
UnassignCurator: t('Unassign Curator')
@ -57,10 +58,11 @@ function BountyExtraActions ({ bestNumber, className, description, index, propos
const showCloseBounty = (status.isFunded || status.isActive || status.isCuratorProposed) && isMember && !existingCloseBountyProposal;
const showRejectCurator = status.isCuratorProposed && isCurator;
const showGiveUpCurator = status.isActive && isCurator;
const showExtendExpiry = status.isActive && isCurator;
const showSlashCurator = (status.isCuratorProposed || status.isActive || status.isPendingPayout) && !existingUnassignCuratorProposal && availableSlashActions.length !== 0;
const hasNoItems = !(showCloseBounty || showRejectCurator || showExtendExpiry || showSlashCurator);
const hasNoItems = !(showCloseBounty || showRejectCurator || showExtendExpiry || showSlashCurator || showGiveUpCurator);
function slashCuratorClicked (action: ValidUnassignCuratorAction) {
setSlashAction(action);
@ -91,6 +93,13 @@ function BountyExtraActions ({ bestNumber, className, description, index, propos
toggleOpen={toggleExtendExpiry}
/>
}
{isGiveUpCuratorOpen && curator &&
<GiveUp
curatorId={curator}
index={index}
toggleOpen={toggleGiveUpCurator}
/>
}
{isSlashCuratorOpen && curator && selectedAction &&
<SlashCurator
action={selectedAction}
@ -142,6 +151,14 @@ function BountyExtraActions ({ bestNumber, className, description, index, propos
{t<string>('Extend Expiry')}
</Menu.Item>
}
{showGiveUpCurator &&
<Menu.Item
key='giveUpCurator'
onClick={toggleGiveUpCurator}
>
{t<string>('Give Up')}
</Menu.Item>
}
{showSlashCurator && availableSlashActions.map((actionName) =>
<Menu.Item
key={actionName}
@ -157,7 +174,7 @@ function BountyExtraActions ({ bestNumber, className, description, index, propos
: null;
}
export default React.memo(styled(BountyExtraActions)(({ theme }: ThemeProps) => `
export default React.memo(styled(Index)(({ theme }: ThemeProps) => `
&& .ui--Button:not(.isDisabled):not(.isIcon).settings-button {
width: 24px;
height: 24px;

View File

@ -7,8 +7,8 @@ import styled from 'styled-components';
import { Icon } from '@polkadot/react-components';
import { ThemeProps } from '@polkadot/react-components/types';
import { bountyColor, bountyLabelColor, bountySvgColor } from './theme';
import { useTranslation } from './translate';
import { bountyColor, bountyLabelColor, bountySvgColor } from '../theme';
import { useTranslation } from '../translate';
interface Props {
className: '';

View File

@ -10,11 +10,11 @@ import React from 'react';
import { AddressSmall } from '@polkadot/react-components';
import { BN_HUNDRED } from '@polkadot/util';
import VotingSummary from './Voting/VotingSummary';
import Description from '../Description';
import { useBounties } from '../hooks';
import { useTranslation } from '../translate';
import BountyInfo from './BountyInfo';
import Description from './Description';
import { useBounties } from './hooks';
import { useTranslation } from './translate';
import VotingSummary from './VotingSummary';
interface Props {
beneficiary?: AccountId;

View File

@ -29,12 +29,6 @@ describe('adjust slash curator component for', () => {
expect(displayAs).toEqual(['SlashCuratorMotion']);
});
it('Curator in Active state', () => {
const displayAs = determineUnassignCuratorAction(['Curator'], aBountyStatus('Active'));
expect(displayAs).toEqual(['GiveUp']);
});
it('User in Active state with update due blocks remaining', () => {
const displayAs = determineUnassignCuratorAction(['User'], aBountyStatus('Active'), new BN('1'));

View File

@ -17,10 +17,6 @@ export function determineUnassignCuratorAction (roles: UserRole[], status: Bount
}
if (status.isActive) {
if (roles.includes('Curator')) {
actions.push('GiveUp');
}
if (roles.includes('Member')) {
actions.push('SlashCuratorMotion');
}

View File

@ -9,7 +9,7 @@ import { BountyVotingStatuses, StatusName } from '../types';
const validProposalNames: BountyVotingStatuses = {
Active: ['closeBounty', 'unassignCurator'],
Approved: [],
CuratorProposed: ['unassignCurator', 'closeBounty'],
CuratorProposed: ['closeBounty', 'unassignCurator'],
Funded: ['proposeCurator', 'closeBounty'],
PendingPayout: ['unassignCurator'],
Proposed: ['approveBounty', 'closeBounty']

View File

@ -15,6 +15,6 @@ export type HelpMessages = Record<StatusName, string>;
export type StatusName = 'Active' | 'Approved' | 'CuratorProposed' | 'Funded' | 'PendingPayout' | 'Proposed';
export type BountyVotingStatuses = { [status in StatusName]: string[] };
export type ValidUnassignCuratorAction = 'GiveUp' | 'UnassignCurator' | 'SlashCuratorMotion' | 'SlashCuratorAction';
export type ValidUnassignCuratorAction = 'UnassignCurator' | 'SlashCuratorMotion' | 'SlashCuratorAction';
export type UnassignCuratorAction = ValidUnassignCuratorAction | 'None';
export type UserRole = 'User' | 'Member' | 'Curator' | 'None';

View File

@ -281,7 +281,7 @@ export class BountiesPage {
async openGiveUpCuratorsRole (): Promise<void> {
await this.openExtraActions();
await this.clickButtonByText('Give Up');
await this.expectText('This action will unassign you from a curator role.');
await this.expectText('This action will unassign you from the curator role.');
}
async openSlashCuratorByCouncil (): Promise<void> {