Skip to content

Commit

Permalink
Release Stabilisation Bugfixing 10 (#3641)
Browse files Browse the repository at this point in the history
* 🪅 add word break (#3326)

* add word break

* add word-wrap for title

Co-authored-by: Pasha Hliebov <p.hliebov@bsg.world>

* 🥩 Fix Candidates Card "Staked" copy (#3338)

* Fix: #2795

* Fix: Test Fix

* 🔌 Refactor the api to easily switch between `ProxyApi` and `ApiRx` (#3385)

Refactor `@/api`

* ⏲️ Fix election/council remaining period timer (#3444)

* Fix election/council remaining period clock

* Fix tests

* Fix one more test

* 👮 Show moderated posts (#3420)

* feat: Show moderated posts

* fix: Moderated post stories args

* fix: useForumThreadPosts error

* ⚖️ Fix available staking balances (#3408)

* New function to calc available balance for staking account

* Apply of the new function

* Small UI tweaks

* Fix for same keys in bounty actors list

* Update the SelectAccount balances

* Update the getStakingBalance logic

* Speed up `useMyBalances`

* Fix the validation

* Updating logic and corresponding UI

* Always validate `balances.total` for staking

* Validate AnnounceCandidacy including binding fees

* Validate staking amounts including binding fee

* Fix the ApplyForRoleModal tests

Co-authored-by: WRadoslaw <r.wyszynski00@gmail.com>

* 🗳️ Add a `node-mocks council:elect` for testing node (#3410)

Add a `node-mocks council:elect` for testing node

* 📄 Signal description copy change (#3193)

* Update for staking amount validate

* Fiexed:issue 3091

* update for issue 2770

* Reverse #2601 and #2770 fixes

Co-authored-by: Theophile Sandoz <theophile.sandoz@gmail.com>

* 🧵 Fixed datetime is not displayed for Forum thread (#3574)

* Fixed datetime is not displayed for Forum thread

* changed graphql for forumthreadinitialpost

* removed unnecessary fields

* changed quorum bar with council's size (#3595)

* changed quorum bar with council's size

fixed concilsize to denominator

fixed

used abstain for quorum & thresold

added remain and abstain bar

* added abstain slider in storybook

Co-authored-by: Palllke2015 <palllke2015@gmail.com>
Co-authored-by: Pasha Hliebov <p.hliebov@bsg.world>
Co-authored-by: Akinsuyi Joshua <akinsuyi.joshua84@gmail.com>
Co-authored-by: gyroflaw <83718263+gyroflaw@users.noreply.github.com>
Co-authored-by: WRadoslaw <r.wyszynski00@gmail.com>
Co-authored-by: Ken-tech-max <81464477+Ken-tech-max@users.noreply.github.com>
Co-authored-by: Software Developer | Web, Blockchain & Desktop <69617937+mkbeefcake@users.noreply.github.com>
  • Loading branch information
8 people authored Oct 6, 2022
1 parent e2fa256 commit 0214ffa
Show file tree
Hide file tree
Showing 177 changed files with 986 additions and 448 deletions.
61 changes: 31 additions & 30 deletions packages/ui/dev/helpers/nextCouncilStage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ApiPromise } from '@polkadot/api'

import { AN_HOUR, A_MINUTE, A_SECOND } from '../../src/common/constants'
import { durationFormatter, MILLISECONDS_PER_BLOCK } from '../../src/common/model/formatters'
import { withApi } from '../node-mocks/lib/api'
Expand All @@ -8,42 +10,41 @@ const etaFormat = durationFormatter([
[A_SECOND, 'second'],
])

const handler = () =>
withApi(async (api) => {
const initialStageEnd = await stageEnd()
let remaining: number
while ((remaining = await getRemaining()) > 0) {
process.stdout.write(`\nWait ${etaFormat(remaining)} for the current stage to end\n`)
await new Promise<void>((resolve) => setTimeout(() => resolve(), remaining))
}
export const nextCouncilStageCommand = async (api: ApiPromise) => {
const initialStageEnd = await stageEnd()
let remaining: number
while ((remaining = await getRemaining()) > 0) {
process.stdout.write(`\nWait ${etaFormat(remaining)} for the current stage to end\n`)
await new Promise<void>((resolve) => setTimeout(() => resolve(), remaining))
}

async function stageEnd(): Promise<number> {
const currentCouncilStage = await api.query.council.stage()
async function stageEnd(): Promise<number> {
const currentCouncilStage = await api.query.council.stage()

if (currentCouncilStage.stage.type === 'Election') {
const currentElectionStage = await api.query.referendum.stage()
const type = currentElectionStage.type as 'Voting' | 'Revealing'
const periodName = type === 'Voting' ? 'voteStageDuration' : 'revealStageDuration'
const duration = api.consts.referendum[periodName]
const started = currentElectionStage[`as${type}`].started
return started.add(duration).toNumber()
} else {
const type = currentCouncilStage.stage.type as 'Idle' | 'Announcing'
const periodName = type === 'Idle' ? 'idlePeriodDuration' : 'announcingPeriodDuration'
const duration = api.consts.council[periodName]
const started = currentCouncilStage.changed_at
return started.add(duration).toNumber()
}
if (currentCouncilStage.stage.type === 'Election') {
const currentElectionStage = await api.query.referendum.stage()
const type = currentElectionStage.type as 'Voting' | 'Revealing'
const periodName = type === 'Voting' ? 'voteStageDuration' : 'revealStageDuration'
const duration = api.consts.referendum[periodName]
const started = currentElectionStage[`as${type}`].started
return started.add(duration).toNumber()
} else {
const type = currentCouncilStage.stage.type as 'Idle' | 'Announcing'
const periodName = type === 'Idle' ? 'idlePeriodDuration' : 'announcingPeriodDuration'
const duration = api.consts.council[periodName]
const started = currentCouncilStage.changed_at
return started.add(duration).toNumber()
}
}

async function getRemaining(): Promise<number> {
const currentBlock = (await api.rpc.chain.getHeader()).number.toNumber()
return (initialStageEnd - currentBlock) * MILLISECONDS_PER_BLOCK
}
})
async function getRemaining(): Promise<number> {
const currentBlock = (await api.rpc.chain.getHeader()).number.toNumber()
return (initialStageEnd - currentBlock) * MILLISECONDS_PER_BLOCK
}
}

export const nextCouncilStageModule = {
command: 'nextCouncilStage',
describe: 'Wait until the next council stage start',
handler,
handler: () => withApi(nextCouncilStageCommand),
}
81 changes: 42 additions & 39 deletions packages/ui/dev/node-mocks/commands/council/announce.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,59 @@
import { ApiPromise } from '@polkadot/api'

import { lockLookup } from '../../../../src/accounts/model/lockTypes'
import { flatMapP, mapP } from '../../../../src/common/utils'
import memberData from '../../../../src/mocks/data/raw/members.json'
import { accountsMap } from '../../data/addresses'
import { signAndSend, withApi } from '../../lib/api'
import { createMembersCommand } from '../members/create'

const announceCandidacies = async () => {
await createMembersCommand()

withApi(async (api) => {
const candidateCount = api.consts.council.councilSize.toNumber() + 1
const announceStake = api.consts.council.minCandidateStake
const members = memberData.slice(0, candidateCount)

// Fund the empty accounts
const requiredBalance = announceStake.add(api.consts.referendum.minimumStake).muln(1.5) // 1.5 extra margin for potential transaction fees
const memberToFund = members.filter(({ boundAccounts }) => !boundAccounts?.length)
const fundingTx = await flatMapP(memberToFund, async ({ controllerAccount: address }) => {
const { data } = await api.query.system.account(address)
return data.free.lt(requiredBalance) ? [api.tx.balances.transfer(address, requiredBalance)] : []
})

if (fundingTx.length > 0) {
await signAndSend(api.tx.utility.batch(fundingTx), accountsMap.alice)
}
export const announceCandidaciesCommand = async (api: ApiPromise) => {
const candidateCount = api.consts.council.councilSize.toNumber() + 1
const announceStake = api.consts.council.minCandidateStake
const members = memberData.slice(0, candidateCount)

// Fund the empty accounts
const requiredBalance = announceStake.add(api.consts.referendum.minimumStake).muln(1.5) // 1.5 extra margin for potential transaction fees
const memberToFund = members.filter(({ boundAccounts }) => !boundAccounts?.length)
const fundingTx = await flatMapP(memberToFund, async ({ controllerAccount: address }) => {
const { data } = await api.query.system.account(address)
return data.free.lt(requiredBalance) ? [api.tx.balances.transfer(address, requiredBalance)] : []
})

// Announce candidacies
await mapP(members, async ({ id, controllerAccount: address }) => {
const stakingAccountInfoSize = await api.query.members.stakingAccountIdMemberStatus.size(address)

if (stakingAccountInfoSize.isEmpty) {
// Bind staking account
await signAndSend(api.tx.members.addStakingAccountCandidate(id), address)
// Confirm staking account
await signAndSend(api.tx.members.confirmStakingAccount(id, address), address)
} else {
const locks = await api.query.balances.locks(address)

if (locks.some(({ id }) => lockLookup(id) === 'Council Candidate')) {
// Release stakes
await signAndSend(api.tx.council.releaseCandidacyStake(id), address)
}
if (fundingTx.length > 0) {
await signAndSend(api.tx.utility.batch(fundingTx), accountsMap.alice)
}

// Announce candidacies
await mapP(members, async ({ id, controllerAccount: address }) => {
const stakingAccountInfoSize = await api.query.members.stakingAccountIdMemberStatus.size(address)

if (stakingAccountInfoSize.isEmpty) {
// Bind staking account
await signAndSend(api.tx.members.addStakingAccountCandidate(id), address)
// Confirm staking account
await signAndSend(api.tx.members.confirmStakingAccount(id, address), address)
} else {
const locks = await api.query.balances.locks(address)

if (locks.some(({ id }) => lockLookup(id) === 'Council Candidate')) {
// Release stakes
await signAndSend(api.tx.council.releaseCandidacyStake(id), address)
}
}

// Announce candidacy
await signAndSend(api.tx.council.announceCandidacy(id, address, address, announceStake), address)
})
// Announce candidacy
await signAndSend(api.tx.council.announceCandidacy(id, address, address, announceStake), address)
})
}

const handler = async () => {
await createMembersCommand()
await withApi(announceCandidaciesCommand)
}

export const announceCandidaciesModule = {
command: 'council:announce',
describe: 'Announce council candidates',
handler: announceCandidacies,
handler: handler,
}
22 changes: 22 additions & 0 deletions packages/ui/dev/node-mocks/commands/council/elect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { nextCouncilStageCommand } from '../../../helpers/nextCouncilStage'
import { withApi } from '../../lib/api'
import { createMembersCommand } from '../members/create'

import { announceCandidaciesCommand } from './announce'
import { revealVotesCommand } from './reveal'
import { castVotesCommand } from './vote'

export const electCouncilModule = {
command: 'council:elect',
describe: 'Elect a full council',
handler: async () => {
await createMembersCommand()
await withApi(async (api) => {
await announceCandidaciesCommand(api)
await nextCouncilStageCommand(api)
await castVotesCommand(api)
await nextCouncilStageCommand(api)
await revealVotesCommand(api)
})
},
}
15 changes: 8 additions & 7 deletions packages/ui/dev/node-mocks/commands/council/reveal.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { ApiPromise } from '@polkadot/api'

import { mapP } from '../../../../src/common/utils'
import { signAndSend, withApi } from '../../lib/api'

import { votes } from './vote'

const revealVotes = () =>
withApi(async (api) => {
await mapP(votes(api), ({ accountId, optionsId, salt }) =>
signAndSend(api.tx.referendum.revealVote(salt, optionsId), accountId)
)
})
export const revealVotesCommand = async (api: ApiPromise) => {
await mapP(votes(api), ({ accountId, optionsId, salt }) =>
signAndSend(api.tx.referendum.revealVote(salt, optionsId), accountId)
)
}

export const revealVotesModule = {
command: 'council:reveal',
describe: 'Reveal votes',
handler: revealVotes,
handler: () => withApi(revealVotesCommand),
}
22 changes: 11 additions & 11 deletions packages/ui/dev/node-mocks/commands/council/vote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@ import memberData from '../../../../src/mocks/data/raw/members.json'
import { signAndSend, withApi } from '../../lib/api'

const SALT = '0x0000000000000000000000000000000000000000000000000000000000000001'

export const castVotesCommand = async (api: ApiPromise) => {
const stage = await api.query.referendum.stage()
const cycleId = stage.asVoting.current_cycle_id.toNumber()
await mapP(votes(api), async ({ accountId, optionsId, salt }) => {
const commitment = calculateCommitment(accountId, optionsId, salt, cycleId)
await signAndSend(api.tx.referendum.vote(commitment, 10_000), accountId)
})
}

export const votes = (api: ApiPromise) => {
const councilSize = api.consts.council.councilSize.toNumber()
return memberData
.slice(0, councilSize)
.map(({ id: optionsId, controllerAccount: accountId }) => ({ accountId, optionsId, salt: SALT }))
}

const castVotes = () =>
withApi(async (api) => {
const stage = await api.query.referendum.stage()
const cycleId = stage.asVoting.current_cycle_id.toNumber()
await mapP(votes(api), async ({ accountId, optionsId, salt }) => {
const commitment = calculateCommitment(accountId, optionsId, salt, cycleId)
await signAndSend(api.tx.referendum.vote(commitment, 10_000), accountId)
})
})

export const castVotesModule = {
command: 'council:vote',
describe: 'Votes for candidates',
handler: castVotes,
handler: () => withApi(castVotesCommand),
}
2 changes: 2 additions & 0 deletions packages/ui/dev/node-mocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import yargs from 'yargs'

import { createBountyModule } from './commands/bounty/create'
import { announceCandidaciesModule } from './commands/council/announce'
import { electCouncilModule } from './commands/council/elect'
import { revealVotesModule } from './commands/council/reveal'
import { castVotesModule } from './commands/council/vote'
import { createForumCategoryModule } from './commands/forumCategory/create'
Expand All @@ -22,6 +23,7 @@ yargs(process.argv.slice(2))
.command(castVotesModule)
.command(createForumCategoryModule)
.command(revealVotesModule)
.command(electCouncilModule)
.command(createMembersModule)
.command(setBudgetModule)
.command(createOpeningModule)
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"lint:post-codegen": "eslint \"./{src,test}/**/*.generated.{ts,tsx}\" --fix && yarn prettier \"./{src,test}/**/*.generated.{ts,tsx,html,graphql}\" --write --loglevel warn",
"node-mocks": "ts-node --transpile-only dev/node-mocks/index.ts",
"node-mocks:announce-vote": "yarn node-mocks council:announce && yarn helpers nextCouncilStage && yarn node-mocks council:vote",
"node-mocks:announce-vote-reveal": "yarn node-mocks:announce-vote && yarn helpers nextCouncilStage && yarn node-mocks council:reveal",
"node-mocks:announce-vote-reveal": "yarn node-mocks council:elect",
"queries:generate": "graphql-codegen --config codegen.config.yml && yarn lint:post-codegen",
"query-node-mocks": "ts-node --transpile-only dev/query-node-mocks/generateMocks.ts",
"helpers": "ts-node --transpile-only dev/helpers/index.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react'
import { generatePath } from 'react-router-dom'

import { useApi } from '@/common/hooks/useApi'
import { useApi } from '@/api/hooks/useApi'
import { useModal } from '@/common/hooks/useModal'
import { MILLISECONDS_PER_BLOCK } from '@/common/model/formatters'
import { asBlock } from '@/common/types'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React, { useMemo } from 'react'
import { generatePath } from 'react-router-dom'

import { useApi } from '@/common/hooks/useApi'
import { useApi } from '@/api/hooks/useApi'
import { MILLISECONDS_PER_BLOCK } from '@/common/model/formatters'
import { asBlock } from '@/common/types'
import { CouncilRoutes } from '@/council/constants'
import { useElectionRemainingPeriod } from '@/council/hooks/useElectionRemainingPeriod'
import { useElectionStage } from '@/council/hooks/useElectionStage'
import { useCouncilRemainingPeriod } from '@/council/hooks/useCouncilRemainingPeriod'
import { useGetCouncilorElectionEventQuery } from '@/council/queries'
import { useMember } from '@/memberships/hooks/useMembership'
import { useMyMemberships } from '@/memberships/hooks/useMyMemberships'
Expand All @@ -33,8 +32,7 @@ export const CouncilorLockItem = ({ lock, address, isRecoverable }: LockDetailsP
network: eventData.electedAtNetwork,
})
const idlePeriodDuration = api?.consts.council.idlePeriodDuration.toNumber()
const { stage } = useElectionStage()
const remainingPeriod = useElectionRemainingPeriod(stage)
const remainingPeriod = useCouncilRemainingPeriod()

const recoveryTime = useMemo(() => {
if (!eventData || !idlePeriodDuration) {
Expand All @@ -47,7 +45,7 @@ export const CouncilorLockItem = ({ lock, address, isRecoverable }: LockDetailsP
const endTime =
councilEnd > Date.now()
? new Date(councilEnd).toISOString()
: new Date(Date.now() + (remainingPeriod?.toNumber() ?? 0) * MILLISECONDS_PER_BLOCK).toISOString()
: new Date(Date.now() + (remainingPeriod ?? 0) * MILLISECONDS_PER_BLOCK).toISOString()

return { time: endTime, tooltipLabel: 'Recoverable after not re-elected' }
}, [eventData?.electedAtTime, idlePeriodDuration])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,24 @@ import { Colors } from '@/common/constants'

interface Props {
option: AccountOption
isForStaking?: boolean
}

export const OptionAccount = ({ option }: Props) => {
const balance = useBalance(option.address)
export const OptionAccount = ({ option, isForStaking }: Props) => {
const balances = useBalance(option.address)
const balance = isForStaking ? balances?.total : balances?.transferable
const balanceType = isForStaking ? 'Total' : 'Transferable'
const locks = option.optionLocks
const isLocked = !!locks?.length

return (
<>
<AccountInfo account={option} locked={isLocked} />
<BalanceInfoInRow>
<InfoTitle>Transferable balance</InfoTitle>
<InfoTitle>{balanceType} balance</InfoTitle>
<InfoValueWithLocks>
<Value value={balance?.transferable} locked={isLocked} />
<AccountLocks locks={balance?.locks} />
<Value value={balance} locked={isLocked} />
<AccountLocks locks={balances?.locks} />
</InfoValueWithLocks>
</BalanceInfoInRow>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'

import { Option, OptionsListComponent } from '../../../common/components/selects'
import { Option, OptionsListComponent } from '@/common/components/selects'

import { AccountOption } from '../../types'

import { AccountLockTooltip } from './AccountLockTooltip'
Expand All @@ -10,22 +11,23 @@ interface Props {
options: AccountOption[]
onChange: (option: AccountOption) => void
className?: string
isForStaking?: boolean
}

export const OptionListAccount = React.memo(({ options, onChange, className }: Props) => {
export const OptionListAccount = React.memo(({ options, onChange, className, isForStaking }: Props) => {
const freeAccounts = options.filter((option) => (option.optionLocks ? option.optionLocks?.length === 0 : true))
const lockedAccounts = options.filter((option) => !!option.optionLocks?.length)
return (
<OptionsListComponent className={className}>
{freeAccounts.map((option) => (
<Option key={option.address} onClick={() => onChange && onChange(option)}>
<OptionAccount option={option} />
<OptionAccount option={option} isForStaking={isForStaking} />
</Option>
))}
{lockedAccounts.map((option) => (
<AccountLockTooltip key={option.address} locks={option.optionLocks}>
<Option key={option.address} onClick={() => onChange && onChange(option)} disabled>
<OptionAccount option={option} />
<OptionAccount option={option} isForStaking={isForStaking} />
</Option>
</AccountLockTooltip>
))}
Expand Down
Loading

1 comment on commit 0214ffa

@vercel
Copy link

@vercel vercel bot commented on 0214ffa Oct 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

dao – ./

dao-joystream.vercel.app
dao.joystream.org
dao-git-main-joystream.vercel.app

Please sign in to comment.