Browse Source

TerminateWorkingGroupLeaderRole proposal

Leszek Wiesner 4 years ago
parent
commit
8c3c6d0088

+ 12 - 2
pioneer/packages/joy-proposals/src/Proposal/Body.tsx

@@ -7,7 +7,7 @@ import { blake2AsHex } from '@polkadot/util-crypto';
 import styled from 'styled-components';
 import AddressMini from '@polkadot/react-components/AddressMiniJoy';
 import TxButton from '@polkadot/joy-utils/TxButton';
-import { ProposalId } from '@joystream/types/proposals';
+import { ProposalId, TerminateRoleParameters } from '@joystream/types/proposals';
 import { MemberId, Profile } from '@joystream/types/members';
 import ProfilePreview from '@polkadot/joy-utils/MemberProfilePreview';
 import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks';
@@ -225,7 +225,17 @@ const paramParsers: { [x in ProposalType]: (params: any[]) => ParsedParams} = {
     'Working group': (new WorkingGroup(group)).type,
     'New reward amount': formatBalance(amount),
     Lead: new FullWidthParam(<LeadInfoFromId group={(new WorkingGroup(group).type as WorkingGroupKeys)} leadId={leadId}/>)
-  })
+  }),
+  TerminateWorkingGroupLeaderRole: ([params]) => {
+    const paramsObj = new TerminateRoleParameters(params);
+    const { working_group: workingGroup, rationale, worker_id: leadId, slash } = paramsObj;
+    return {
+      'Working group': workingGroup.type,
+      Rationale: new FullWidthParam(bytesToString(rationale)),
+      'Slash stake': slash.isTrue ? 'YES' : 'NO',
+      Lead: new FullWidthParam(<LeadInfoFromId group={workingGroup.type as WorkingGroupKeys} leadId={leadId.toNumber()}/>)
+    };
+  }
 };
 
 const StyledProposalDescription = styled(Card.Description)`

+ 125 - 0
pioneer/packages/joy-proposals/src/forms/TerminateWorkingGroupLeaderForm.tsx

@@ -0,0 +1,125 @@
+import React, { useState } from 'react';
+import * as Yup from 'yup';
+import {
+  withProposalFormData,
+  ProposalFormExportProps,
+  ProposalFormContainerProps,
+  ProposalFormInnerProps,
+  genericFormDefaultOptions
+} from './GenericProposalForm';
+import {
+  GenericWorkingGroupProposalForm,
+  FormValues as WGFormValues,
+  defaultValues as wgFromDefaultValues
+} from './GenericWorkingGroupProposalForm';
+import { withFormContainer } from './FormContainer';
+import './forms.css';
+import _ from 'lodash';
+import Validation from '../validationSchema';
+import { WorkerData } from '@polkadot/joy-utils/types/workingGroups';
+import { getFormErrorLabelsProps } from './errorHandling';
+import FormField, { TextareaFormField } from './FormFields';
+import { Checkbox } from 'semantic-ui-react';
+import { TerminateRoleParameters } from '@joystream/types/proposals';
+import { WorkerId } from '@joystream/types/working-group';
+import { Bytes } from '@polkadot/types';
+import { WorkingGroup, InputValidationLengthConstraint } from '@joystream/types/common';
+import { bool as Bool, u16 as U16 } from '@polkadot/types/primitive';
+import { withCalls } from '@polkadot/react-api';
+
+export type FormValues = WGFormValues & {
+  terminationRationale: string;
+  slashStake: boolean;
+};
+
+const defaultValues: FormValues = {
+  ...wgFromDefaultValues,
+  terminationRationale: '',
+  slashStake: false
+};
+
+type FormAdditionalProps = {}; // Aditional props coming all the way from export component into the inner form.
+type ExportComponentProps = ProposalFormExportProps<FormAdditionalProps, FormValues>;
+type FormContainerProps = ProposalFormContainerProps<ExportComponentProps> & {
+  terminationRationaleConstraint?: InputValidationLengthConstraint;
+};
+type FormInnerProps = ProposalFormInnerProps<FormContainerProps, FormValues>;
+
+const valuesToTerminateRoleParams = (values: FormValues, leadId: number): TerminateRoleParameters => {
+  return new TerminateRoleParameters({
+    worker_id: new WorkerId(leadId),
+    rationale: new Bytes(values.terminationRationale),
+    slash: new Bool(values.slashStake),
+    working_group: new WorkingGroup(values.workingGroup)
+  });
+};
+
+const TerminateWorkingGroupLeaderForm: React.FunctionComponent<FormInnerProps> = props => {
+  const { handleChange, errors, touched, values, myMemberId, setFieldValue } = props;
+  const errorLabelsProps = getFormErrorLabelsProps<FormValues>(errors, touched);
+  const [lead, setLead] = useState<WorkerData | null>(null);
+
+  return (
+    <GenericWorkingGroupProposalForm
+      {...props}
+      txMethod="createTerminateWorkingGroupLeaderRoleProposal"
+      proposalType="TerminateWorkingGroupLeaderRole"
+      leadRequired={true}
+      onLeadChange={(lead: WorkerData | null) => setLead(lead)}
+      submitParams={[
+        myMemberId,
+        values.title,
+        values.rationale,
+        '{STAKE}',
+        valuesToTerminateRoleParams(values, lead?.workerId!)
+      ]}
+    >
+      { lead && (<>
+        <TextareaFormField
+          label="Termination rationale"
+          help={
+            'This rationale is an required argument of "terminateWorkerRole" extrinsic, ' +
+            'it may differ from proposal rationale and has different length constraints. ' +
+            'If the propsal gets executed, this rationale will become part of "TerminatedLeader" event.'
+          }
+          onChange={handleChange}
+          name="terminationRationale"
+          placeholder="Provide a clear rationale for terminating the leader role..."
+          error={errorLabelsProps.terminationRationale}
+          value={values.terminationRationale}
+        />
+        <FormField>
+          <Checkbox
+            toggle
+            onChange={(e, data) => { setFieldValue('slashStake', data.checked); }}
+            label="Slash leader stake"
+            checked={values.slashStake}/>
+        </FormField>
+      </>) }
+    </GenericWorkingGroupProposalForm>
+  );
+};
+
+const TERMINATION_RATIONALE_CONSTRAINT_FALLBACK = new InputValidationLengthConstraint({
+  min: new U16(1),
+  max_min_diff: new U16(65534) // max possible value that won't cause overflow
+});
+
+const FormContainer = withFormContainer<FormContainerProps, FormValues>({
+  mapPropsToValues: (props: FormContainerProps) => ({
+    ...defaultValues,
+    ...(props.initialData || {})
+  }),
+  validationSchema: (props: FormContainerProps) => Yup.object().shape({
+    ...genericFormDefaultOptions.validationSchema,
+    ...Validation.TerminateWorkingGroupLeaderRole(props.terminationRationaleConstraint || TERMINATION_RATIONALE_CONSTRAINT_FALLBACK)
+  }),
+  handleSubmit: genericFormDefaultOptions.handleSubmit,
+  displayName: 'TerminateWorkingGroupLeaderForm'
+})(TerminateWorkingGroupLeaderForm);
+
+export default withCalls<ExportComponentProps>(
+  ['query.storageWorkingGroup.workerExitRationaleText', { propName: 'terminationRationaleConstraint' }]
+)(
+  withProposalFormData<FormContainerProps, ExportComponentProps>(FormContainer)
+);

+ 1 - 0
pioneer/packages/joy-proposals/src/forms/index.ts

@@ -14,3 +14,4 @@ export { default as FillWorkingGroupLeaderOpeningForm } from './FillWorkingGroup
 export { default as DecreaseWorkingGroupLeadStakeFrom } from './DecreaseWorkingGroupLeadStakeForm';
 export { default as SlashWorkingGroupLeadStakeForm } from './SlashWorkingGroupLeadStakeForm';
 export { default as SetWorkingGroupLeadRewardForm } from './SetWorkingGroupLeadRewardForm';
+export { default as TerminateWorkingGroupLeaderForm } from './TerminateWorkingGroupLeaderForm';

+ 3 - 1
pioneer/packages/joy-proposals/src/index.tsx

@@ -26,7 +26,8 @@ import {
   FillWorkingGroupLeaderOpeningForm,
   DecreaseWorkingGroupLeadStakeFrom,
   SlashWorkingGroupLeadStakeForm,
-  SetWorkingGroupLeadRewardForm
+  SetWorkingGroupLeadRewardForm,
+  TerminateWorkingGroupLeaderForm
 } from './forms';
 
 interface Props extends AppProps, I18nProps {}
@@ -80,6 +81,7 @@ function App (props: Props): React.ReactElement<Props> {
           <Route exact path={`${basePath}/new/decrease-working-group-leader-stake`} component={DecreaseWorkingGroupLeadStakeFrom} />
           <Route exact path={`${basePath}/new/slash-working-group-leader-stake`} component={SlashWorkingGroupLeadStakeForm} />
           <Route exact path={`${basePath}/new/set-working-group-leader-reward`} component={SetWorkingGroupLeadRewardForm} />
+          <Route exact path={`${basePath}/new/terminate-working-group-leader-role`} component={TerminateWorkingGroupLeaderForm} />
           <Route exact path={`${basePath}/active`} component={NotDone} />
           <Route exact path={`${basePath}/finalized`} component={NotDone} />
           <Route exact path={`${basePath}/:id`} component={ProposalFromId} />

+ 11 - 0
pioneer/packages/joy-proposals/src/validationSchema.ts

@@ -2,6 +2,7 @@ import * as Yup from 'yup';
 import { schemaValidator, ActivateOpeningAtKeys } from '@joystream/types/hiring';
 import { ProposalTypes } from '@polkadot/joy-utils/types/proposals';
 import { GenericFormValues } from './forms/GenericProposalForm';
+import { InputValidationLengthConstraint } from '@joystream/types/common';
 import { FormValues as SignalFormValues } from './forms/SignalForm';
 import { FormValues as RuntimeUpgradeFormValues } from './forms/RuntimeUpgradeForm';
 import { FormValues as SetCouncilParamsFormValues } from './forms/SetCouncilParamsForm';
@@ -16,6 +17,7 @@ import { FormValues as FillWorkingGroupLeaderOpeningFormValues } from './forms/F
 import { FormValues as DecreaseWorkingGroupLeadStakeFormValues } from './forms/DecreaseWorkingGroupLeadStakeForm';
 import { FormValues as SlashWorkingGroupLeadStakeFormValues } from './forms/SlashWorkingGroupLeadStakeForm';
 import { FormValues as SetWorkingGroupLeadRewardFormValues } from './forms/SetWorkingGroupLeadRewardForm';
+import { FormValues as TerminateWorkingGroupLeaderFormValues } from './forms/TerminateWorkingGroupLeaderForm';
 
 // TODO: If we really need this (currency unit) we can we make "Validation" a functiction that returns an object.
 // We could then "instantialize" it in "withFormContainer" where instead of passing
@@ -146,6 +148,7 @@ type FormValuesByType<T extends ValidationTypeKeys> =
   T extends 'DecreaseWorkingGroupLeaderStake' ? Omit<DecreaseWorkingGroupLeadStakeFormValues, keyof GenericFormValues> :
   T extends 'SlashWorkingGroupLeaderStake' ? Omit<SlashWorkingGroupLeadStakeFormValues, keyof GenericFormValues> :
   T extends 'SetWorkingGroupLeaderReward' ? Omit<SetWorkingGroupLeadRewardFormValues, keyof GenericFormValues> :
+  T extends 'TerminateWorkingGroupLeaderRole' ? Omit<TerminateWorkingGroupLeaderFormValues, keyof GenericFormValues> :
   never;
 /* eslint-enable @typescript-eslint/indent */
 
@@ -404,6 +407,14 @@ const Validation: ValidationType = {
   SetWorkingGroupLeaderReward: () => ({
     workingGroup: Yup.string(),
     amount: minMaxInt(MIN_REWARD_AMOUNT, MAX_REWARD_AMOUNT, 'Reward amount')
+  }),
+  TerminateWorkingGroupLeaderRole: ({ min, max }: InputValidationLengthConstraint) => ({
+    workingGroup: Yup.string(),
+    terminationRationale: Yup.string()
+      .required('Termination rationale is required')
+      .min(min.toNumber(), `Termination rationale must be at least ${min.toNumber()} character(s) long`)
+      .max(max.toNumber(), `Termination rationale cannot be more than ${max.toNumber()} character(s) long`),
+    slashStake: Yup.boolean()
   })
 };
 

+ 13 - 0
pioneer/packages/joy-utils/src/consts/proposals.ts

@@ -146,6 +146,15 @@ export const metadata: { [k in ProposalType]: ProposalMeta } = {
     approvalThreshold: 75,
     slashingQuorum: 60,
     slashingThreshold: 80
+  },
+  TerminateWorkingGroupLeaderRole: {
+    description: 'Terminate Working Group Leader Role Proposal',
+    category: 'Working Groups',
+    stake: 100000,
+    approvalQuorum: 66,
+    approvalThreshold: 80,
+    slashingQuorum: 60,
+    slashingThreshold: 80
   }
 };
 
@@ -209,6 +218,10 @@ export const apiMethods: { [k in ProposalType]?: ProposalsApiMethodNames } = {
   SetWorkingGroupLeaderReward: {
     votingPeriod: 'setWorkingGroupLeaderRewardProposalVotingPeriod',
     gracePeriod: 'setWorkingGroupLeaderRewardProposalGracePeriod'
+  },
+  TerminateWorkingGroupLeaderRole: {
+    votingPeriod: 'terminateWorkingGroupLeaderRoleProposalVotingPeriod',
+    gracePeriod: 'terminateWorkingGroupLeaderRoleProposalGracePeriod'
   }
 } as const;
 

+ 2 - 1
pioneer/packages/joy-utils/src/types/proposals.ts

@@ -19,7 +19,8 @@ export const ProposalTypes = [
   'FillWorkingGroupLeaderOpening',
   'SlashWorkingGroupLeaderStake',
   'DecreaseWorkingGroupLeaderStake',
-  'SetWorkingGroupLeaderReward'
+  'SetWorkingGroupLeaderReward',
+  'TerminateWorkingGroupLeaderRole'
 ] as const;
 
 export type ProposalType = typeof ProposalTypes[number];