Skip to content

Commit

Permalink
Add support for searching for network proposal by name
Browse files Browse the repository at this point in the history
  • Loading branch information
csillag committed Jan 30, 2024
1 parent d0dd9da commit 35b1bd5
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 2 deletions.
1 change: 1 addition & 0 deletions .changelog/1192.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for searching for network proposal by name
51 changes: 51 additions & 0 deletions src/app/components/NetworkProposalsList/ProposalDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { FC } from 'react'
import { Proposal } from '../../../oasis-nexus/api'
import { StyledDescriptionList } from '../StyledDescriptionList'
import { useScreenSize } from '../../hooks/useScreensize'
import { DashboardLink } from '../../pages/ParatimeDashboardPage/DashboardLink'
import { useTranslation } from 'react-i18next'
import { RoundedBalance } from '../RoundedBalance'
import { ProposalStatusIcon } from '../ProposalStatusIcon'

export const ProposalDetails: FC<{ proposal: Proposal; showLayer?: boolean; standalone?: boolean }> = ({
proposal,
showLayer = false,
standalone = false,
}) => {
const { t } = useTranslation()
const { isMobile } = useScreenSize()
return (
<StyledDescriptionList titleWidth={isMobile ? '100px' : '200px'} standalone={standalone}>
{showLayer && (
<>
<dt>{t('common.network')}</dt>
<dd>
<DashboardLink scope={{ network: proposal.network, layer: proposal.layer }} />
</dd>
</>
)}

<dt>{t('networkProposal.id')}</dt>
<dd>{proposal.id}</dd>

<dt>{t('networkProposal.handler')}</dt>
<dd>{proposal.handler}</dd>

<dt>{t('networkProposal.deposit')}</dt>
<dd>
<RoundedBalance value={proposal.deposit} />
</dd>

<dt>{t('networkProposal.create')}</dt>
<dd>{proposal.created_at}</dd>

<dt>{t('networkProposal.close')}</dt>
<dd>{proposal.closes_at}</dd>

<dt>{t('common.status')}</dt>
<dd>
<ProposalStatusIcon status={proposal.state} />
</dd>
</StyledDescriptionList>
)
}
5 changes: 5 additions & 0 deletions src/app/components/Search/search-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ export const validateAndNormalize = {
return searchTerm.toLowerCase()
}
},
networkProposalNameFragment: (searchTerm: string) => {
if (searchTerm?.length >= textSearchMininumLength) {
return searchTerm.toLowerCase()
}
},
} satisfies { [name: string]: (searchTerm: string) => string | undefined }

export function isSearchValid(searchTerm: string) {
Expand Down
10 changes: 10 additions & 0 deletions src/app/pages/SearchResultsPage/SearchResultsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
AccountResult,
BlockResult,
ContractResult,
ProposalResult,
SearchResults,
TokenResult,
TransactionResult,
Expand All @@ -19,6 +20,7 @@ import { SubPageCard } from '../../components/SubPageCard'
import { AllTokenPrices } from '../../../coin-gecko/api'
import { ResultListFrame } from './ResultListFrame'
import { TokenDetails } from '../../components/Tokens/TokenDetails'
import { ProposalDetails } from '../../components/NetworkProposalsList/ProposalDetails'

/**
* Component for displaying a list of search results
Expand Down Expand Up @@ -114,6 +116,14 @@ export const SearchResultsList: FC<{
link={token => RouteUtils.getTokenRoute(token, token.eth_contract_addr ?? token.contract_addr)}
linkLabel={t('search.results.tokens.viewLink')}
/>

<ResultsGroupByType
title={t('search.results.proposals.title')}
results={searchResults.filter((item): item is ProposalResult => item.resultType === 'proposal')}
resultComponent={item => <ProposalDetails proposal={item} showLayer />}
link={() => ''}
linkLabel=""
/>
</SubPageCard>
</ResultListFrame>
)
Expand Down
33 changes: 31 additions & 2 deletions src/app/pages/SearchResultsPage/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
EvmTokenList,
EvmToken,
useGetRuntimeBlockByHash,
Proposal,
useGetConsensusProposalsByName,
} from '../../../oasis-nexus/api'
import { RouteUtils } from '../../utils/route-utils'
import { SearchParams } from '../../components/Search/search-utils'
Expand All @@ -24,7 +26,7 @@ function isDefined<T>(item: T): item is NonNullable<T> {
export type ConditionalResults<T> = { isLoading: boolean; results: T[] }

type SearchResultItemCore = HasScope & {
resultType: 'block' | 'transaction' | 'account' | 'contract' | 'token'
resultType: 'block' | 'transaction' | 'account' | 'contract' | 'token' | 'proposal'
}

export type BlockResult = SearchResultItemCore & RuntimeBlock & { resultType: 'block' }
Expand All @@ -37,7 +39,15 @@ export type ContractResult = SearchResultItemCore & RuntimeAccount & { resultTyp

export type TokenResult = SearchResultItemCore & EvmToken & { resultType: 'token' }

export type SearchResultItem = BlockResult | TransactionResult | AccountResult | ContractResult | TokenResult
export type ProposalResult = SearchResultItemCore & Proposal & { resultType: 'proposal' }

export type SearchResultItem =
| BlockResult
| TransactionResult
| AccountResult
| ContractResult
| TokenResult
| ProposalResult

export type SearchResults = SearchResultItem[]

Expand Down Expand Up @@ -161,6 +171,22 @@ export function useRuntimeTokenConditionally(
}
}

export function useNetworkProposalsConditionally(
nameFragment: string | undefined,
): ConditionalResults<Proposal> {
const queries = RouteUtils.getEnabledNetworks().map(network =>
// eslint-disable-next-line react-hooks/rules-of-hooks
useGetConsensusProposalsByName(network, nameFragment),
)
return {
isLoading: queries.some(query => query.isInitialLoading),
results: queries
.map(query => query.results)
.filter(isDefined)
.flat(),
}
}

export const useSearch = (q: SearchParams) => {
const queries = {
blockHeight: useBlocksByHeightConditionally(q.blockHeight),
Expand All @@ -170,6 +196,7 @@ export const useSearch = (q: SearchParams) => {
// TODO: remove evmBech32Account and use evmAccount when API is ready
evmBech32Account: useRuntimeAccountConditionally(q.evmBech32Account),
tokens: useRuntimeTokenConditionally(q.evmTokenNameFragment),
proposals: useNetworkProposalsConditionally(q.networkProposalNameFragment),
}
const isLoading = Object.values(queries).some(query => query.isLoading)
const blocks = [...queries.blockHeight.results, ...queries.blockHash.results]
Expand All @@ -182,6 +209,7 @@ export const useSearch = (q: SearchParams) => {
.map(l => l.evm_tokens)
.flat()
.sort((t1, t2) => t2.num_holders - t1.num_holders)
const proposals = queries.proposals.results

const results: SearchResultItem[] = isLoading
? []
Expand All @@ -195,6 +223,7 @@ export const useSearch = (q: SearchParams) => {
.filter(account => account.evm_contract)
.map((account): ContractResult => ({ ...account, resultType: 'contract' })),
...tokens.map((token): TokenResult => ({ ...token, resultType: 'token' })),
...proposals.map((proposal): ProposalResult => ({ ...proposal, resultType: 'proposal' })),
]
return {
isLoading,
Expand Down
3 changes: 3 additions & 0 deletions src/app/pages/SearchResultsPage/useRedirectIfSingleResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export function useRedirectIfSingleResult(scope: SearchScope | undefined, result
case 'token':
redirectTo = RouteUtils.getTokenRoute(item, item.eth_contract_addr || item.contract_addr)
break
case 'proposal':
// We don't want to redirect to proposals
break
default:
exhaustedTypeWarning('Unexpected result type', item)
}
Expand Down
3 changes: 3 additions & 0 deletions src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,9 @@
"title": "Transactions",
"viewLink": "View Transaction"
},
"proposals": {
"title": "Proposals"
},
"count_one": "1 result",
"count_other": "{{ count }} results",
"moreCount_one": "1 more result",
Expand Down
18 changes: 18 additions & 0 deletions src/oasis-nexus/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,7 @@ export const useGetConsensusProposals: typeof generated.useGetConsensusProposals
return {
...proposal,
network,
layer: Layer.consensus,
deposit: fromBaseUnits(proposal.deposit, consensusDecimals),
}
}),
Expand Down Expand Up @@ -854,6 +855,23 @@ export const useGetConsensusProposalsProposalId: typeof generated.useGetConsensu
})
}

export const useGetConsensusProposalsByName = (network: Network, nameFragment: string | undefined) => {
const query = useGetConsensusProposals(network, {}, { query: { enabled: !!nameFragment } })
const { isLoading, isInitialLoading, data, status, error } = query
const textMatcher = nameFragment
? (proposal: generated.Proposal): boolean =>
!!proposal.handler && proposal.handler?.includes(nameFragment)
: () => false
const results = data ? query.data.data.proposals.filter(textMatcher) : undefined
return {
isLoading,
isInitialLoading,
status,
error,
results,
}
}

export const useGetConsensusValidators: typeof generated.useGetConsensusValidators = (
network,
params?,
Expand Down

0 comments on commit 35b1bd5

Please sign in to comment.