-
Notifications
You must be signed in to change notification settings - Fork 479
feat: show voted indicator on governance proposals list #2950
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -118,13 +118,21 @@ async function fetchSubgraphVotes(proposalId: number, votingChainId: ChainId) { | |
| */ | ||
| export const useGovernanceProposals = () => { | ||
| const { votingMachineSerivce, governanceV3Service } = useSharedDependencies(); | ||
| const user = useRootStore((store) => store.account); | ||
|
|
||
| const cacheResult = useInfiniteQuery({ | ||
| queryFn: async ({ pageParam = 0 }) => { | ||
| const proposals = await getProposalsFromCache(PAGE_SIZE, pageParam * PAGE_SIZE); | ||
| return { proposals: proposals.map(adaptCacheProposalToListItem) }; | ||
| const userVotes = user | ||
| ? await Promise.all( | ||
| proposals.map((p) => getUserVoteFromCache(p.id, user).catch(() => null)) | ||
| ) | ||
| : proposals.map(() => null); | ||
| return { | ||
| proposals: proposals.map((p, i) => adaptCacheProposalToListItem(p, userVotes[i])), | ||
| }; | ||
| }, | ||
| queryKey: ['governance-proposals-cache'], | ||
| queryKey: ['governance-proposals-cache', user], | ||
| enabled: USE_GOVERNANCE_CACHE, | ||
| refetchOnMount: false, | ||
| refetchOnReconnect: false, | ||
|
|
@@ -141,11 +149,12 @@ export const useGovernanceProposals = () => { | |
| const enriched = await fetchProposals( | ||
| result.proposals, | ||
| votingMachineSerivce, | ||
| governanceV3Service | ||
| governanceV3Service, | ||
| user | ||
| ); | ||
| return { proposals: enriched.proposals.map(adaptGraphProposalToListItem) }; | ||
| }, | ||
| queryKey: ['governance-proposals-graph'], | ||
| queryKey: ['governance-proposals-graph', user], | ||
| enabled: !USE_GOVERNANCE_CACHE, | ||
| refetchOnMount: false, | ||
| refetchOnReconnect: false, | ||
|
|
@@ -164,15 +173,19 @@ export const useGovernanceProposals = () => { | |
| */ | ||
| export const useGovernanceProposalsSearch = (query: string) => { | ||
| const { votingMachineSerivce, governanceV3Service } = useSharedDependencies(); | ||
| const user = useRootStore((store) => store.account); | ||
| const formattedQuery = query.trim().split(' ').join(' & '); | ||
|
|
||
| const { data: cacheData, isFetching: cacheFetching } = useQuery({ | ||
| queryFn: async () => { | ||
| const results = await searchProposalsFromCache(query, SEARCH_RESULTS_LIMIT); | ||
| return results.map(adaptCacheProposalToListItem); | ||
| const userVotes = user | ||
| ? await Promise.all(results.map((p) => getUserVoteFromCache(p.id, user).catch(() => null))) | ||
| : results.map(() => null); | ||
| return results.map((p, i) => adaptCacheProposalToListItem(p, userVotes[i])); | ||
|
Comment on lines
+182
to
+185
|
||
| }, | ||
| enabled: USE_GOVERNANCE_CACHE && query.trim() !== '', | ||
| queryKey: ['governance-search-cache', query], | ||
| queryKey: ['governance-search-cache', query, user], | ||
| }); | ||
|
|
||
| const { data: graphIds, isFetching: graphIdsFetching } = useQuery({ | ||
|
|
@@ -185,10 +198,15 @@ export const useGovernanceProposalsSearch = (query: string) => { | |
| const { data: graphData, isFetching: graphProposalsFetching } = useQuery({ | ||
| queryFn: async () => { | ||
| const proposals = await fetchSubgraphProposalsByIds(graphIds || []); | ||
| const enriched = await fetchProposals(proposals, votingMachineSerivce, governanceV3Service); | ||
| const enriched = await fetchProposals( | ||
| proposals, | ||
| votingMachineSerivce, | ||
| governanceV3Service, | ||
| user | ||
| ); | ||
| return enriched.proposals.map(adaptGraphProposalToListItem); | ||
| }, | ||
| queryKey: ['governance-search-graph-proposals', graphIds], | ||
| queryKey: ['governance-search-graph-proposals', graphIds, user], | ||
| enabled: !USE_GOVERNANCE_CACHE && graphIds !== undefined && graphIds.length > 0, | ||
| }); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| import { Box, Paper, Skeleton, Stack, Typography } from '@mui/material'; | ||
| import { CheckCircleIcon } from '@heroicons/react/solid'; | ||
| import { Trans } from '@lingui/macro'; | ||
| import { Box, Paper, Skeleton, Stack, SvgIcon, Typography } from '@mui/material'; | ||
| import { useState } from 'react'; | ||
| import InfiniteScroll from 'react-infinite-scroller'; | ||
| import { NoSearchResults } from 'src/components/NoSearchResults'; | ||
|
|
@@ -12,9 +14,30 @@ import { GOVERNANCE_PAGE } from 'src/utils/events'; | |
|
|
||
| import { ProposalListHeader } from './ProposalListHeader'; | ||
| import { StateBadge, stringToState } from './StateBadge'; | ||
| import { ProposalListItem } from './types'; | ||
| import { ProposalListItem, UserVoteInfo } from './types'; | ||
| import { VoteBar } from './VoteBar'; | ||
|
|
||
| const VotedIndicator = ({ userVote }: { userVote: UserVoteInfo }) => { | ||
| const paletteKey = userVote.support ? 'success' : 'error'; | ||
| return ( | ||
| <Box | ||
| sx={(theme) => ({ | ||
| color: theme.palette[paletteKey].main, | ||
| display: 'inline-flex', | ||
| alignItems: 'center', | ||
| gap: 0.5, | ||
| })} | ||
| > | ||
| <SvgIcon sx={{ fontSize: 14 }}> | ||
| <CheckCircleIcon /> | ||
| </SvgIcon> | ||
|
Comment on lines
+31
to
+33
|
||
| <Typography variant="subheader2" color="inherit"> | ||
| <Trans>You voted {userVote.support ? 'YAE' : 'NAY'}</Trans> | ||
| </Typography> | ||
| </Box> | ||
| ); | ||
| }; | ||
|
|
||
| const ProposalListItemRow = ({ proposal }: { proposal: ProposalListItem }) => { | ||
| const trackEvent = useRootStore((store) => store.trackEvent); | ||
|
|
||
|
|
@@ -45,8 +68,9 @@ const ProposalListItemRow = ({ proposal }: { proposal: ProposalListItem }) => { | |
| justifyContent: 'space-between', | ||
| }} | ||
| > | ||
| <Stack direction="row" gap={3} alignItems="center"> | ||
| <Stack direction="row" gap={3} alignItems="center" flexWrap="wrap"> | ||
| <StateBadge state={proposal.badgeState} loading={false} /> | ||
| {proposal.userVote && <VotedIndicator userVote={proposal.userVote} />} | ||
| </Stack> | ||
| <Typography variant="h3" sx={{ overflow: 'hidden', textOverflow: 'ellipsis' }}> | ||
| {proposal.title} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This introduces an N+1 pattern: for each proposals page (PAGE_SIZE=10) it makes one
getUserVoteFromCacherequest per proposal, which can add significant latency and load on the cache backend. Consider batching these lookups into a single GraphQL request (e.g., a bulk endpoint, or a single query using field aliases for each proposalId) so the page fetch remains O(1) requests.