Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: filter proposals #5664

Merged
merged 11 commits into from Jan 26, 2023
12 changes: 9 additions & 3 deletions packages/shared/components/Proposals.svelte
@@ -1,20 +1,26 @@
<script lang="typescript">
import { Text, ProposalCard } from 'shared/components'
import { Text, ProposalCard, Filter } from 'shared/components'
import { localize } from '@core/i18n'
import { FontWeight } from './enums'

import { IProposal } from '@contexts/governance/interfaces'
import { proposalFilter } from '@contexts/governance'
import { isVisibleProposal } from '@contexts/governance/utils/isVisibleProposal'

export let proposals: IProposal[] = []

$: sortedProposals = proposals.sort((a, b) => (a.id < b.id ? -1 : 1))
$: sortedProposals = proposals
.filter((proposal) => isVisibleProposal(proposal, $proposalFilter))
.sort((a, b) => (a.id < b.id ? -1 : 1))
</script>

<proposals-container class="flex flex-col h-full">
<header-container class="flex justify-between items-center mb-4">
<Text fontSize="14" fontWeight={FontWeight.semibold}>
{localize('views.governance.proposals.title')}
</Text>
<div class="flex flex-row">
<Filter filterStore={proposalFilter} />
</div>
</header-container>
<ul class="grid grid-cols-2 auto-rows-min gap-6 flex-1 overflow-y-scroll">
{#each sortedProposals as proposal}
Expand Down
7 changes: 4 additions & 3 deletions packages/shared/components/filters/Filter.svelte
@@ -1,12 +1,13 @@
<script lang="typescript">
import { TogglableButton, FilterModal, FilterItem, Modal } from 'shared/components'
import type { Filter } from '@core/wallet/interfaces/filter/filter.interface'
import type { ActivityFilter, AssetFilter } from '@core/wallet/interfaces'
import { deepCopy } from '@core/utils'
import type { Writable } from 'svelte/store'
import { ProposalFilter } from '@contexts/governance'

export let filterStore: Writable<Filter>
export let filterStore: Writable<ActivityFilter | AssetFilter | ProposalFilter>

let filter: Filter = deepCopy($filterStore)
let filter: ActivityFilter | AssetFilter | ProposalFilter = deepCopy($filterStore)
Tuditi marked this conversation as resolved.
Show resolved Hide resolved

let filterActive = false
let modal: Modal
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/components/filters/FilterItem.svelte
@@ -1,9 +1,9 @@
<script lang="typescript">
import type { FilterUnit } from '@core/wallet/interfaces'
import { Checkbox, Icon } from 'shared/components'
import { localize } from '@core/i18n'
import { DateFilterItem, NumberFilterItem, SelectionFilterItem, AssetFilterItem, OrderFilterItem } from './items'
import { createEventDispatcher } from 'svelte'
import { FilterUnit } from '@core/utils/interfaces/filter'

export let filterUnit: FilterUnit
export let isOpen: boolean
Expand Down
@@ -1,8 +1,8 @@
<script lang="typescript">
import type { AssetFilterUnit } from '@core/wallet/interfaces'
import { Dropdown } from 'shared/components'
import type { IDropdownChoice } from '@core/utils'
import { visibleSelectedAccountAssets } from '@core/wallet'
import { AssetFilterUnit } from '@core/utils/interfaces/filter'

export let filterUnit: AssetFilterUnit
const { baseCoin, nativeTokens } = $visibleSelectedAccountAssets
Expand Down
@@ -1,9 +1,9 @@
<script lang="typescript">
import type { DateFilterUnit } from '@core/wallet/interfaces'
import { DateInputButton, Dropdown, Icon, Text, NumberInput } from 'shared/components'
import { localize } from '@core/i18n'
import type { IDropdownChoice } from '@core/utils'
import { DateFilterOption, DateUnit } from '@core/wallet'
import { DateFilterUnit } from '@core/utils/interfaces/filter'
import { DateFilterOption, DateUnit } from '@core/utils/enums/filters'

export let filterUnit: DateFilterUnit

Expand Down
@@ -1,9 +1,9 @@
<script lang="typescript">
import type { NumberFilterUnit } from '@core/wallet/interfaces'
import { Dropdown, Icon, Text, NumberInput } from 'shared/components'
import { localize } from '@core/i18n'
import type { IDropdownChoice } from '@core/utils'
import { NumberFilterOption } from '@core/wallet'
import { NumberFilterUnit } from '@core/utils/interfaces/filter'
import { NumberFilterOption } from '@core/utils/enums/filters'

export let filterUnit: NumberFilterUnit

Expand Down
@@ -1,9 +1,9 @@
<script lang="typescript">
import type { OrderFilterUnit } from '@core/wallet/interfaces'
import { Dropdown } from 'shared/components'
import { localize } from '@core/i18n'
import type { IDropdownChoice } from '@core/utils'
import { OrderOption } from '@core/wallet'
import { OrderFilterUnit } from '@core/utils/interfaces/filter'
import { OrderOption } from '@core/utils/enums/filters'

export let filterUnit: OrderFilterUnit

Expand Down
@@ -1,8 +1,8 @@
<script lang="typescript">
import type { SelectionFilterUnit } from '@core/wallet/interfaces'
import { Dropdown } from 'shared/components'
import { localize } from '@core/i18n'
import type { IDropdownChoice } from '@core/utils'
import { SelectionFilterUnit } from '@core/utils/interfaces/filter'

export let filterUnit: SelectionFilterUnit

Expand Down
1 change: 1 addition & 0 deletions packages/shared/lib/contexts/governance/enums/index.ts
@@ -1 +1,2 @@
export * from './proposal-type.enum'
export * from './proposal-status.enum'
@@ -0,0 +1,4 @@
export enum ProposalType {
Official = 'official',
Custom = 'custom',
}
@@ -1,4 +1,5 @@
export * from './organization.interface'
export * from './proposal-filter.interface'
export * from './proposal-state.interface'
export * from './proposal.interface'
export * from './proposals-details.interface'
@@ -0,0 +1,7 @@
import { SelectionFilterUnit } from '@core/utils/interfaces/filter/filter-unit.interface'

export interface ProposalFilter {
phase: SelectionFilterUnit
type: SelectionFilterUnit
participated: SelectionFilterUnit
}
@@ -1,12 +1,14 @@
import { INode } from '@core/network'
import { ProposalStatus } from '../enums'
import { ProposalStatus, ProposalType } from '../enums'
import { IOrganization } from './organization.interface'

export interface IProposal {
id: string
milestones?: Record<ProposalStatus, number>
organization?: IOrganization
status: ProposalStatus
type: ProposalType
title: string
participated: boolean
nodeUrls: INode[]
}
1 change: 1 addition & 0 deletions packages/shared/lib/contexts/governance/stores/index.ts
@@ -1,5 +1,6 @@
export * from './has-to-revote.constant'
export * from './participation-overview.store'
export * from './proposal-filter.store'
export * from './proposals-state.store'
export * from './registered-event-ids.store'
export * from './selected-proposal.store'
@@ -0,0 +1,27 @@
import { BooleanFilterOption } from '@core/utils/enums/filters'
import { writable, Writable } from 'svelte/store'
import { ProposalFilter, ProposalStatus, ProposalType } from '..'

export const proposalFilter: Writable<ProposalFilter> = writable({
phase: {
active: false,
type: 'selection',
localeKey: 'filters.phase',
Copy link
Contributor

@Tuditi Tuditi Jan 25, 2023

Choose a reason for hiding this comment

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

Can we update this then to alleviate the burden on the translaters and to keep sure it's consistent across all languages? I guess the only thing that would need add is a label property to proposalStatus?

Suggested change
localeKey: 'filters.phase',
localeKey: 'proposalStatus',

selected: ProposalStatus.Commencing,
choices: [ProposalStatus.Commencing, ProposalStatus.Upcoming, ProposalStatus.Holding, ProposalStatus.Ended],
},
type: {
active: false,
type: 'selection',
localeKey: 'filters.proposalType',
selected: ProposalType.Official,
choices: [ProposalType.Official, ProposalType.Custom],
},
participated: {
active: false,
type: 'selection',
localeKey: 'filters.participated',
selected: BooleanFilterOption.Yes,
choices: [BooleanFilterOption.Yes, BooleanFilterOption.No],
},
})
Expand Up @@ -2,44 +2,54 @@ import { get } from 'svelte/store'

import { Event } from '@iota/wallet'

import { ProposalStatus } from '@contexts/governance/enums'
import { ProposalStatus, ProposalType } from '@contexts/governance/enums'
import { IProposal } from '@contexts/governance/interfaces'
import { nodeInfo } from '@core/network'
import { activeProfile } from '@core/profile'
import { nodeInfo, OFFICIAL_NODE_URLS } from '@core/network'
import { activeProfile, activeProfileId } from '@core/profile'
import { getVotingEvents } from '@core/profile-manager'
import { getParticipationsForProposal, proposalsState } from '..'

export async function createProposals(): Promise<IProposal[]> {
const events = await getVotingEvents()
const proposals: IProposal[] = events?.map(createProposalFromEvent)
const proposals: IProposal[] = await Promise.all(events?.map(async (event) => createProposalFromEvent(event)))
return proposals
}

function createProposalFromEvent(event: Event): IProposal {
async function createProposalFromEvent(event: Event): Promise<IProposal> {
const { data, id } = event
const proposal = {

const officialNodeUrls = OFFICIAL_NODE_URLS[get(activeProfile).networkProtocol][get(activeProfile).networkType]
const proposalNodeUrl = get(proposalsState)[get(activeProfileId)]?.[id].nodeUrl
const isOfficialNetwork = officialNodeUrls.includes(proposalNodeUrl)

const participated = (await getParticipationsForProposal(id)) !== undefined

const milestones = {
[ProposalStatus.Upcoming]: 0, // TODO: fix this
[ProposalStatus.Commencing]: data.milestoneIndexCommence,
[ProposalStatus.Holding]: data.milestoneIndexStart,
[ProposalStatus.Ended]: data.milestoneIndexEnd,
}

const status = getLatestStatus(milestones)

const proposal: IProposal = {
id,
title: event.data.name,
status: ProposalStatus.Upcoming,
milestones: {
[ProposalStatus.Upcoming]: 0, // TODO: fix this
[ProposalStatus.Commencing]: data.milestoneIndexCommence,
[ProposalStatus.Holding]: data.milestoneIndexStart,
[ProposalStatus.Ended]: data.milestoneIndexEnd,
},
status: status ?? ProposalStatus.Upcoming,
milestones,
// TODO: figure out a better way to get the node URLs
nodeUrls: get(activeProfile)?.clientOptions?.nodes,
type: isOfficialNetwork ? ProposalType.Official : ProposalType.Custom,
participated,
}

const status = getLatestStatus(proposal)
if (status) {
proposal.status = status
}
return proposal
}

function getLatestStatus(proposal: IProposal): ProposalStatus {
function getLatestStatus(milestones: Record<ProposalStatus, number>): ProposalStatus {
const latestMilestoneIndex = get(nodeInfo)?.status?.latestMilestone.index
const milestoneDifferences = Object.entries(proposal?.milestones).map(([status, milestone]) => ({
const milestoneDifferences = Object.entries(milestones).map(([status, milestone]) => ({
status,
milestoneDifference: latestMilestoneIndex - milestone,
}))
Expand Down
@@ -0,0 +1,20 @@
import { get } from 'svelte/store'

import type { ParticipationOverview, TrackedParticipationOverview } from '@iota/wallet'

import { participationOverview } from '@contexts/governance/stores'
import { getParticipationOverview, selectedAccountIndex } from '@core/account'

export async function getParticipationsForProposal(
proposalId: string,
accountIndex = get(selectedAccountIndex)
): Promise<{ [outputId: string]: TrackedParticipationOverview }> {
let overview: ParticipationOverview
if (accountIndex === get(selectedAccountIndex)) {
overview = get(participationOverview)
} else {
overview = await getParticipationOverview(accountIndex)
}

return overview?.participations?.[proposalId]
}
1 change: 1 addition & 0 deletions packages/shared/lib/contexts/governance/utils/index.ts
Expand Up @@ -2,6 +2,7 @@ export * from './calculateWeightedVotes'
export * from './createProposalsFromEvents'
export * from './getNumberOfActiveProposals'
export * from './getNumberOfVotingProposals'
export * from './getParticipationsForProposal'
export * from './getTotalNumberOfProposals'
export * from './isAnyAccountVotingForSelectedProposal'
export * from './isParticipationOutput'
Expand Down
44 changes: 44 additions & 0 deletions packages/shared/lib/contexts/governance/utils/isVisibleProposal.ts
@@ -0,0 +1,44 @@
import { IProposal, ProposalFilter } from '../interfaces'
import { BooleanFilterOption } from '@core/utils/enums/filters'

export function isVisibleProposal(proposal: IProposal, filter: ProposalFilter): boolean {
if (!isVisibleWithActivePhaseFilter(proposal, filter)) {
return false
}
if (!isVisibleWithActiveTypeFilter(proposal, filter)) {
return false
}
if (!isVisibleWithActivePraticipatedFilter(proposal, filter)) {
return false
}
return true
}

function isVisibleWithActivePraticipatedFilter(proposal: IProposal, filter: ProposalFilter): boolean {
if (
filter.participated.active &&
((filter.participated.selected === BooleanFilterOption.No && proposal.participated) ||
(filter.participated.selected === BooleanFilterOption.Yes && !proposal.participated))
) {
return false
}
return true
}

function isVisibleWithActiveTypeFilter(proposal: IProposal, filter: ProposalFilter): boolean {
if (filter.type.active && filter.type.selected) {
if (filter.type.selected !== proposal.type) {
return false
}
}
return true
}

function isVisibleWithActivePhaseFilter(proposal: IProposal, filter: ProposalFilter): boolean {
if (filter.phase.active && filter.phase.selected) {
if (filter.phase.selected !== proposal.status) {
return false
}
}
return true
}
@@ -1,22 +1,13 @@
import { get } from 'svelte/store'

import type { ParticipationOverview, TrackedParticipationOverview } from '@iota/wallet'

import { participationOverview } from '@contexts/governance/stores'
import { getParticipationOverview, selectedAccountIndex } from '@core/account'
import type { TrackedParticipationOverview } from '@iota/wallet'
import { selectedAccountIndex } from '@core/account'
import { getParticipationsForProposal } from './getParticipationsForProposal'

export async function isVotingForProposal(
proposalId: string,
accountIndex = get(selectedAccountIndex)
): Promise<boolean> {
let overview: ParticipationOverview
if (accountIndex === get(selectedAccountIndex)) {
overview = get(participationOverview)
} else {
overview = await getParticipationOverview(accountIndex)
}
Tuditi marked this conversation as resolved.
Show resolved Hide resolved

const participations = overview?.participations?.[proposalId] ?? {}
const participations = (await getParticipationsForProposal(proposalId, accountIndex)) ?? {}
const participationOutputs: TrackedParticipationOverview[] = Object.values(participations)
return participationOutputs.some((output) => output?.endMilestoneIndex === 0)
}
@@ -1,4 +1,4 @@
import { DateUnit } from '@core/wallet/enums'
import { DateUnit } from '@core/utils/enums/filters'

export type DateFilterInput = SingleDateFilterInput | RangeDateFilterInput | UnitDateFilterInput

Expand Down
@@ -1,4 +1,4 @@
import { NumberFilterOption, DateFilterOption, OrderOption } from '../../enums'
import { DateFilterOption, NumberFilterOption, OrderOption } from '@core/utils/enums/filters'
import { DateFilterInput } from './date-filter-input.interface'
import { NumberFilterInput } from './number-filter-input.interface'

Expand Down
@@ -1,4 +1,3 @@
export * from './date-filter-input.interface'
export * from './filter-unit.interface'
export * from './filter.interface'
export * from './number-filter-input.interface'
1 change: 0 additions & 1 deletion packages/shared/lib/core/wallet/enums/index.ts
Expand Up @@ -8,6 +8,5 @@ export * from './governance-action.enum'
export * from './not-verified-status.enum'
export * from './token-standard.enum'
export * from './verified-status.enum'
export * from './filters'
export * from './inclusion-state.enum'
export * from './irc27-version.enum'