Browse Source

Merge pull request #696 from Lezek123/pioneer-proposals-fixes

Pioneer proposals: spending address validation and council votes fix
Mokhtar Naamani 4 years ago
parent
commit
3868fe0ba2

+ 3 - 3
pioneer/packages/joy-proposals/src/Proposal/ProposalDetails.tsx

@@ -5,7 +5,7 @@ import Body from './Body';
 import VotingSection from './VotingSection';
 import Votes from './Votes';
 import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
-import { ParsedProposal, ProposalVote } from '@polkadot/joy-utils/types/proposals';
+import { ParsedProposal, ProposalVotes } from '@polkadot/joy-utils/types/proposals';
 import { withCalls } from '@polkadot/react-api';
 import { withMulti } from '@polkadot/react-api/with';
 
@@ -115,7 +115,7 @@ export function getExtendedStatus (proposal: ParsedProposal, bestNumber: BlockNu
 type ProposalDetailsProps = MyAccountProps & {
   proposal: ParsedProposal;
   proposalId: ProposalId;
-  votesListState: { data: ProposalVote[]; error: any; loading: boolean };
+  votesListState: { data: ProposalVotes | null; error: any; loading: boolean };
   bestNumber?: BlockNumber;
   council?: Seat[];
 };
@@ -160,7 +160,7 @@ function ProposalDetails ({
             error={votesListState.error}
             loading={votesListState.loading}
             message="Fetching the votes...">
-            <Votes votes={votesListState.data} />
+            <Votes votes={votesListState.data as ProposalVotes} />
           </PromiseComponent>
         </ProposalDetailsVoting>
       </ProposalDetailsMain>

+ 5 - 7
pioneer/packages/joy-proposals/src/Proposal/Votes.tsx

@@ -1,31 +1,29 @@
 import React from 'react';
 import { Header, Divider, Table, Icon } from 'semantic-ui-react';
 import useVoteStyles from './useVoteStyles';
-import { ProposalVote } from '@polkadot/joy-utils/types/proposals';
+import { ProposalVotes } from '@polkadot/joy-utils/types/proposals';
 import { VoteKind } from '@joystream/types/proposals';
 import { VoteKindStr } from './VotingSection';
 import ProfilePreview from '@polkadot/joy-utils/MemberProfilePreview';
 
 type VotesProps = {
-  votes: ProposalVote[];
+  votes: ProposalVotes;
 };
 
 export default function Votes ({ votes }: VotesProps) {
-  const nonEmptyVotes = votes.filter(proposalVote => proposalVote.vote !== null);
-
-  if (!nonEmptyVotes.length) {
+  if (!votes.votes.length) {
     return <Header as="h4">No votes has been submitted!</Header>;
   }
 
   return (
     <>
       <Header as="h3">
-        All Votes: ({nonEmptyVotes.length} / {votes.length})
+        All Votes: ({votes.votes.length}/{votes.councilMembersLength})
       </Header>
       <Divider />
       <Table basic="very">
         <Table.Body>
-          {nonEmptyVotes.map((proposalVote, idx) => {
+          {votes.votes.map((proposalVote, idx) => {
             const { vote, member } = proposalVote;
             const voteStr = (vote as VoteKind).type.toString() as VoteKindStr;
             const { icon, textColor } = useVoteStyles(voteStr);

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

@@ -1,5 +1,4 @@
 import * as Yup from 'yup';
-import { checkAddress } from '@polkadot/util-crypto';
 
 // 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
@@ -242,7 +241,6 @@ const Validation: ValidationType = {
       .required('You need to specify an amount of tokens.'),
     destinationAccount: Yup.string()
       .required('Select a destination account!')
-      .test('address-test', 'Invalid account address.', account => checkAddress(account, 5)[0])
   },
   SetLead: {
     workingGroupLead: Yup.string().required('Select a proposed lead!')

+ 3 - 3
pioneer/packages/joy-utils/src/react/hooks/proposals/useProposalSubscription.tsx

@@ -1,5 +1,5 @@
 import { useState, useEffect } from 'react';
-import { ParsedProposal, ProposalVote } from '../../../types/proposals';
+import { ParsedProposal, ProposalVotes } from '../../../types/proposals';
 import { useTransport, usePromise } from '../';
 import { ProposalId } from '@joystream/types/proposals';
 
@@ -15,9 +15,9 @@ const useProposalSubscription = (id: ProposalId) => {
     {} as ParsedProposal
   );
 
-  const [votes, votesError, votesLoading, refreshVotes] = usePromise<ProposalVote[]>(
+  const [votes, votesError, votesLoading, refreshVotes] = usePromise<ProposalVotes | null>(
     () => transport.proposals.votes(id),
-    []
+    null
   );
 
   // Function to re-fetch the data using transport

+ 13 - 1
pioneer/packages/joy-utils/src/transport/council.ts

@@ -7,13 +7,25 @@ import { Balance, BlockNumber } from '@polkadot/types/interfaces';
 import { FIRST_MEMBER_ID } from '../consts/members';
 import { ApiPromise } from '@polkadot/api';
 import MembersTransport from './members';
+import ChainTransport from './chain';
 
 export default class CouncilTransport extends BaseTransport {
   private membersT: MembersTransport;
+  private chainT: ChainTransport;
 
-  constructor (api: ApiPromise, membersTransport: MembersTransport) {
+  constructor (api: ApiPromise, membersTransport: MembersTransport, chainTransport: ChainTransport) {
     super(api);
     this.membersT = membersTransport;
+    this.chainT = chainTransport;
+  }
+
+  async councilMembersLength (atBlock?: number): Promise<number> {
+    if (atBlock) {
+      const blockHash = await this.chainT.blockHash(atBlock);
+      return ((await this.council.activeCouncil.at(blockHash)) as Seats).length;
+    }
+
+    return ((await this.council.activeCouncil()) as Seats).length;
   }
 
   async councilMembers (): Promise<(ParsedMember & { memberId: MemberId })[]> {

+ 1 - 1
pioneer/packages/joy-utils/src/transport/index.ts

@@ -24,7 +24,7 @@ export default class Transport {
     this.members = new MembersTransport(api);
     this.storageProviders = new StorageProvidersTransport(api);
     this.validators = new ValidatorsTransport(api);
-    this.council = new CouncilTransport(api, this.members);
+    this.council = new CouncilTransport(api, this.members, this.chain);
     this.contentWorkingGroup = new ContentWorkingGroupTransport(api, this.members);
     this.proposals = new ProposalsTransport(api, this.members, this.chain, this.council);
   }

+ 4 - 0
pioneer/packages/joy-utils/src/transport/members.ts

@@ -6,4 +6,8 @@ export default class MembersTransport extends BaseTransport {
   memberProfile (id: MemberId | number): Promise<Option<Profile>> {
     return this.members.memberProfile(id) as Promise<Option<Profile>>;
   }
+
+  async membersCreated (): Promise<number> {
+    return (await this.members.membersCreated() as MemberId).toNumber();
+  }
 }

+ 30 - 10
pioneer/packages/joy-utils/src/transport/proposals.ts

@@ -3,6 +3,7 @@ import {
   ProposalType,
   ProposalTypes,
   ProposalVote,
+  ProposalVotes,
   ParsedPost,
   ParsedDiscussion,
   DiscussionContraints
@@ -20,6 +21,7 @@ import { BalanceOf } from '@polkadot/types/interfaces';
 import { includeKeys, bytesToString } from '../functions/misc';
 import _ from 'lodash';
 import proposalsConsts from '../consts/proposals';
+import { FIRST_MEMBER_ID } from '../consts/members';
 
 import { ApiPromise } from '@polkadot/api';
 import MembersTransport from './members';
@@ -120,17 +122,35 @@ export default class ProposalsTransport extends BaseTransport {
     return hasVoted ? vote : null;
   }
 
-  async votes (proposalId: ProposalId): Promise<ProposalVote[]> {
-    const councilMembers = await this.councilT.councilMembers();
-    return Promise.all(
-      councilMembers.map(async member => {
-        const vote = await this.voteByProposalAndMember(proposalId, member.memberId);
-        return {
-          vote,
-          member
-        };
-      })
+  async votes (proposalId: ProposalId): Promise<ProposalVotes> {
+    const voteEntries = await this.doubleMapEntries(
+      'proposalsEngine.voteExistsByProposalByVoter', // Double map of intrest
+      proposalId, // First double-map key value
+      (v) => new VoteKind(v), // Converter from hex
+      async () => (await this.membersT.membersCreated()), // A function that returns the number of iterations to go through when chekcing possible values for the second double-map key (memberId)
+      FIRST_MEMBER_ID.toNumber() // Min. possible value for second double-map key (memberId)
     );
+
+    const votesWithMembers: ProposalVote[] = [];
+    for (const voteEntry of voteEntries) {
+      const memberId = voteEntry.secondKey;
+      const vote = voteEntry.value;
+      const parsedMember = (await this.membersT.memberProfile(memberId)).toJSON() as ParsedMember;
+      votesWithMembers.push({
+        vote,
+        member: {
+          memberId: new MemberId(memberId),
+          ...parsedMember
+        }
+      });
+    }
+
+    const proposal = await this.rawProposalById(proposalId);
+
+    return {
+      councilMembersLength: await this.councilT.councilMembersLength(proposal.createdAt.toNumber()),
+      votes: votesWithMembers
+    };
   }
 
   async fetchProposalMethodsFromCodex (includeKey: string) {

+ 5 - 0
pioneer/packages/joy-utils/src/types/proposals.ts

@@ -46,6 +46,11 @@ export type ProposalVote = {
   member: ParsedMember & { memberId: MemberId };
 };
 
+export type ProposalVotes = {
+  councilMembersLength: number;
+  votes: ProposalVote[];
+};
+
 export const Categories = {
   storage: 'Storage',
   council: 'Council',