From 7ee315dad98315a3f2ab2e459bc666884678cfaf Mon Sep 17 00:00:00 2001 From: Jaco Date: Tue, 6 Dec 2022 14:42:33 +0200 Subject: [PATCH] Disable ranked voting (based on compares) (#8493) --- .../src/api/params/tracks/kusama.ts | 18 ++++- .../src/api/params/tracks/types.ts | 4 + packages/page-ranked/src/App.tsx | 1 + packages/page-ranked/src/useMembers.ts | 8 +- .../page-referenda/src/Referenda/Group.tsx | 74 +++++++++++++++++++ .../src/Referenda/RefOngoing.tsx | 4 +- .../page-referenda/src/Referenda/Vote.tsx | 45 +++++++++-- .../page-referenda/src/Referenda/index.tsx | 56 +++++--------- packages/page-referenda/src/types.ts | 9 +++ packages/page-referenda/src/useReferenda.ts | 9 ++- packages/page-referenda/src/util.ts | 5 +- 11 files changed, 178 insertions(+), 55 deletions(-) create mode 100644 packages/page-referenda/src/Referenda/Group.tsx diff --git a/packages/apps-config/src/api/params/tracks/kusama.ts b/packages/apps-config/src/api/params/tracks/kusama.ts index 52d28f22a0..c9e1a296a1 100644 --- a/packages/apps-config/src/api/params/tracks/kusama.ts +++ b/packages/apps-config/src/api/params/tracks/kusama.ts @@ -1,30 +1,40 @@ // Copyright 2017-2022 @polkadot/app-config authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { BN } from '@polkadot/util'; import type { TrackInfo } from './types'; +function compareFellowshipRank (trackId: number): (rank: BN) => boolean { + return (rank: BN): boolean => + rank.gten(trackId); +} + export const kusama: Record = { fellowshipReferenda: [ { - + compare: compareFellowshipRank(0), id: 0, name: 'candidates', origin: { Origins: 'FellowshipInitiates' }, text: 'Origin commanded by any members of the Polkadot Fellowship (no Dan grade needed)' }, { + compare: compareFellowshipRank(1), id: 1, name: 'members', origin: { Origins: 'Fellowship1Dan' }, text: 'Origin commanded by rank 1 of the Polkadot Fellowship and with a success of 1' }, { + compare: compareFellowshipRank(2), id: 2, name: 'proficients', origin: { Origins: 'Fellowship2Dan' }, text: 'Origin commanded by rank 2 of the Polkadot Fellowship and with a success of 2' }, { + + compare: compareFellowshipRank(3), id: 3, name: 'fellows', origin: [ @@ -34,12 +44,14 @@ export const kusama: Record = { text: 'Origin commanded by Polkadot Fellows (3rd Dan fellows or greater)' }, { + compare: compareFellowshipRank(4), id: 4, name: 'senior fellows', origin: { Origins: 'Fellowship4Dan' }, text: 'Origin commanded by rank 4 of the Polkadot Fellowship and with a success of 4' }, { + compare: compareFellowshipRank(5), id: 5, name: 'experts', origin: [ @@ -49,12 +61,14 @@ export const kusama: Record = { text: 'Origin commanded by Polkadot Experts (5th Dan fellows or greater)' }, { + compare: compareFellowshipRank(6), id: 6, name: 'senior experts', origin: { Origins: 'Fellowship6Dan' }, text: 'Origin commanded by rank 6 of the Polkadot Fellowship and with a success of 6' }, { + compare: compareFellowshipRank(7), id: 7, name: 'masters', origin: [ @@ -64,12 +78,14 @@ export const kusama: Record = { text: 'Origin commanded by Polkadot Masters (7th Dan fellows of greater)' }, { + compare: compareFellowshipRank(8), id: 8, name: 'senior masters', origin: { Origins: 'Fellowship8Dan' }, text: 'Origin commanded by rank 8 of the Polkadot Fellowship and with a success of 8' }, { + compare: compareFellowshipRank(9), id: 9, name: 'grand masters', origin: { Origins: 'Fellowship9Dan' }, diff --git a/packages/apps-config/src/api/params/tracks/types.ts b/packages/apps-config/src/api/params/tracks/types.ts index 6d3f67aa74..3c13acc8eb 100644 --- a/packages/apps-config/src/api/params/tracks/types.ts +++ b/packages/apps-config/src/api/params/tracks/types.ts @@ -1,9 +1,13 @@ // Copyright 2017-2022 @polkadot/app-config authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { BN } from '@polkadot/util'; + export type Origin = { system: string } | { Origins: string }; export interface TrackInfo { + // TODO We would want to make this generic for non-fellowship + compare?: (memberInfo: BN) => boolean; id: number; name: string; origin: Origin | Origin[]; diff --git a/packages/page-ranked/src/App.tsx b/packages/page-ranked/src/App.tsx index c85c1ff5d3..94cdbe4893 100644 --- a/packages/page-ranked/src/App.tsx +++ b/packages/page-ranked/src/App.tsx @@ -52,6 +52,7 @@ function App ({ basePath, className, palletColl, palletPoll }: Props): React.Rea members={members && members.memberIds} palletReferenda={palletPoll} palletVote={palletColl} + ranks={members && members.memberRanks} /> diff --git a/packages/page-ranked/src/useMembers.ts b/packages/page-ranked/src/useMembers.ts index d61b3d546c..df0877317a 100644 --- a/packages/page-ranked/src/useMembers.ts +++ b/packages/page-ranked/src/useMembers.ts @@ -4,6 +4,7 @@ import type { Option } from '@polkadot/types'; import type { AccountId32 } from '@polkadot/types/interfaces'; import type { PalletRankedCollectiveMemberRecord } from '@polkadot/types/lookup'; +import type { BN } from '@polkadot/util'; import type { Member, PalletColl } from './types'; import { useMemo } from 'react'; @@ -14,6 +15,7 @@ import useMembersIds from './useMemberIds'; interface Result { memberIds: string[]; + memberRanks: BN[]; members: Member[]; } @@ -27,10 +29,10 @@ const OPT_MEM = { accountId: accountId.toString(), info })); - const memberIds = members.map(({ accountId }) => accountId); return { - memberIds, + memberIds: members.map(({ accountId }) => accountId), + memberRanks: members.map(({ info }) => info.rank), members }; }, @@ -44,7 +46,7 @@ function useMembersImpl (collective: PalletColl): Result | undefined { return useMemo( () => ids && ids.length === 0 - ? { memberIds: [], members: [] } + ? { memberIds: [], memberRanks: [], members: [] } : result, [ids, result] ); diff --git a/packages/page-referenda/src/Referenda/Group.tsx b/packages/page-referenda/src/Referenda/Group.tsx new file mode 100644 index 0000000000..8ca72e2aec --- /dev/null +++ b/packages/page-referenda/src/Referenda/Group.tsx @@ -0,0 +1,74 @@ +// Copyright 2017-2022 @polkadot/app-referenda authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { PalletReferendaTrackInfo } from '@polkadot/types/lookup'; +import type { BN } from '@polkadot/util'; +import type { PalletReferenda, PalletVote, ReferendaGroup } from '../types'; + +import React, { useMemo } from 'react'; +import styled from 'styled-components'; + +import { Table } from '@polkadot/react-components'; +import { useApi } from '@polkadot/react-hooks'; + +import { useTranslation } from '../translate'; +import { getTrackInfo } from '../util'; +import Referendum from './Referendum'; + +interface Props extends ReferendaGroup { + className?: string; + isMember: boolean; + members?: string[]; + palletReferenda: PalletReferenda; + palletVote: PalletVote; + ranks?: BN[]; + tracks?: [BN, PalletReferendaTrackInfo][] | undefined; +} + +function Group ({ className, isMember, members, palletReferenda, palletVote, ranks, referenda, trackId, trackName, tracks }: Props): React.ReactElement { + const { t } = useTranslation(); + const { api, specName } = useApi(); + + const trackInfo = useMemo( + () => getTrackInfo(api, specName, palletReferenda, tracks, trackId?.toNumber()), + [api, specName, palletReferenda, tracks, trackId] + ); + + return ( + ('No active referenda')} + header={[ + [trackName ? <>{trackName}
{trackInfo?.text}
: t('referenda'), 'start', 7], + [undefined, undefined, 1] + ]} + key={ + trackName + ? `track:${trackName}` + : 'untracked' + } + > + {referenda && referenda.map((r) => ( + + ))} +
+ ); +} + +export default React.memo(styled(Group)` + th > h1 > div { + display: inline-block; + font-size: 1rem; + padding-left: 1.5rem; + text-overflow: ellipsis; + } +`); diff --git a/packages/page-referenda/src/Referenda/RefOngoing.tsx b/packages/page-referenda/src/Referenda/RefOngoing.tsx index 84b13f363e..491a7832f7 100644 --- a/packages/page-referenda/src/Referenda/RefOngoing.tsx +++ b/packages/page-referenda/src/Referenda/RefOngoing.tsx @@ -71,7 +71,7 @@ function expandOngoing (info: Referendum['info'], track?: PalletReferendaTrackIn }; } -function Ongoing ({ isMember, members, palletReferenda, palletVote, value: { id, info, isConvictionVote, track } }: Props): React.ReactElement { +function Ongoing ({ isMember, members, palletReferenda, palletVote, ranks, trackInfo, value: { id, info, isConvictionVote, track } }: Props): React.ReactElement { const { t } = useTranslation(); const { ongoing: { decisionDeposit, submissionDeposit, tally }, periods: { confirmEnd, decideEnd, periodEnd }, proposalHash, shortHash, tallyTotal } = useMemo( @@ -133,6 +133,8 @@ function Ongoing ({ isMember, members, palletReferenda, palletVote, value: { id, members={members} palletVote={palletVote} preimage={preimage} + ranks={ranks} + trackInfo={trackInfo} /> diff --git a/packages/page-referenda/src/Referenda/Vote.tsx b/packages/page-referenda/src/Referenda/Vote.tsx index 9bedee4a3b..292b270853 100644 --- a/packages/page-referenda/src/Referenda/Vote.tsx +++ b/packages/page-referenda/src/Referenda/Vote.tsx @@ -3,9 +3,9 @@ import type { Preimage } from '@polkadot/app-preimages/types'; import type { BN } from '@polkadot/util'; -import type { PalletVote } from '../types'; +import type { PalletVote, TrackInfo } from '../types'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { Button, ConvictionDropdown, Modal, ProposedAction, TxButton, VoteAccount, VoteValue } from '@polkadot/react-components'; import { useAccounts, useApi, useToggle } from '@polkadot/react-hooks'; @@ -19,17 +19,49 @@ interface Props { members?: string[]; palletVote: PalletVote; preimage?: Preimage; + ranks?: BN[]; + trackInfo?: TrackInfo; } -function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage }: Props): React.ReactElement | null { +function filterMembers (allAccounts: string[], members?: string[], ranks?: BN[], trackInfo?: TrackInfo): string[] | undefined { + if (members) { + const accounts = members.filter((a) => allAccounts.includes(a)); + + if (ranks && trackInfo && trackInfo.compare) { + const cmp = trackInfo.compare; + + return accounts.filter((_, i) => cmp(ranks[i])); + } + + return accounts; + } + + return members; +} + +function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage, ranks, trackInfo }: Props): React.ReactElement | null { const { t } = useTranslation(); const { api } = useApi(); - const { hasAccounts } = useAccounts(); + const { allAccounts, hasAccounts } = useAccounts(); const [accountId, setAccountId] = useState(null); const [balance, setBalance] = useState(); const [conviction, setConviction] = useState(0); const [isVotingOpen, toggleVoting] = useToggle(); + const filteredMembers = useMemo( + () => filterMembers(allAccounts, members, ranks, trackInfo), + [allAccounts, members, ranks, trackInfo] + ); + + const isDisabled = useMemo( + () => !isMember || ( + members && filteredMembers + ? !filteredMembers.length + : false + ), + [filteredMembers, isMember, members] + ); + if (!hasAccounts) { return null; } @@ -53,7 +85,7 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage )} ('The vote will be recorded for this account. If another account delegated to this one, the delegated votes will also be counted.')}> @@ -84,7 +116,6 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage ('Vote Nay')} onStart={toggleVoting} params={ @@ -97,7 +128,6 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage ('Vote Aye')} onStart={toggleVoting} params={ @@ -112,6 +142,7 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage )}