From c7b79f5832b3497d1d83dd786cfc58301193779d Mon Sep 17 00:00:00 2001 From: Michal Zielenkiewicz Date: Wed, 13 Nov 2024 16:59:46 +0100 Subject: [PATCH] Enable search by validator name --- .changelog/1633.trivial.md | 1 + src/app/components/Search/search-utils.ts | 5 +++ src/app/hooks/useSearchForValidatorByName.ts | 46 ++++++++++++++++++++ src/app/pages/SearchResultsPage/hooks.ts | 18 ++++++++ 4 files changed, 70 insertions(+) create mode 100644 .changelog/1633.trivial.md create mode 100644 src/app/hooks/useSearchForValidatorByName.ts diff --git a/.changelog/1633.trivial.md b/.changelog/1633.trivial.md new file mode 100644 index 000000000..92f1add73 --- /dev/null +++ b/.changelog/1633.trivial.md @@ -0,0 +1 @@ +Enable search by validator name diff --git a/src/app/components/Search/search-utils.ts b/src/app/components/Search/search-utils.ts index e2966ad6b..cd4112c70 100644 --- a/src/app/components/Search/search-utils.ts +++ b/src/app/components/Search/search-utils.ts @@ -146,6 +146,11 @@ export const validateAndNormalize = { return searchTerm.toLowerCase() } }, + validatorNameFragment: (searchTerm: string) => { + if (searchTerm?.length >= textSearchMinimumLength) { + return searchTerm.toLowerCase() + } + }, } satisfies { [name: string]: (searchTerm: string) => string | undefined } export function isSearchValid(searchTerm: string) { diff --git a/src/app/hooks/useSearchForValidatorByName.ts b/src/app/hooks/useSearchForValidatorByName.ts new file mode 100644 index 000000000..15e0205bf --- /dev/null +++ b/src/app/hooks/useSearchForValidatorByName.ts @@ -0,0 +1,46 @@ +import { hasTextMatch } from 'app/components/HighlightedText/text-matching' +import { + Layer, + useGetConsensusValidatorsAddressNameMap, + useGetConsensusAccountsAddresses, + ValidatorAddressNameMap, +} from 'oasis-nexus/api' +import { Network } from 'types/network' +import { AccountNameSearchResults, AccountNameSearchConsensusMatch } from '../data/named-accounts' + +function findAddressesWithMatch(addressMap: ValidatorAddressNameMap, nameFragment: string, network: Network) { + const matchedAddresses: AccountNameSearchConsensusMatch[] = [] + + for (const [address, name] of Object.entries(addressMap)) { + if (hasTextMatch(name, [nameFragment])) { + matchedAddresses.push({ address, layer: Layer.consensus, network }) + } + } + + return matchedAddresses +} + +export const useSearchForValidatorByName = ( + network: Network, + nameFragment: string | undefined, +): AccountNameSearchResults => { + const { isLoading, isError, error, data } = useGetConsensusValidatorsAddressNameMap(network) + if (isError) { + console.log('Failed to load Validator data', error) + } + const matches = data?.data && nameFragment ? findAddressesWithMatch(data?.data, nameFragment, network) : [] + const consensusMatches = matches as AccountNameSearchConsensusMatch[] + const { + isLoading: areConsensusAccountsLoading, + isError: areConsensusAccountsError, + data: consensusResults, + } = useGetConsensusAccountsAddresses(consensusMatches, { + enabled: !isLoading && !isError, + }) + + return { + isLoading: isLoading || areConsensusAccountsLoading, + isError: isError || areConsensusAccountsError, + results: [...consensusResults], + } +} diff --git a/src/app/pages/SearchResultsPage/hooks.ts b/src/app/pages/SearchResultsPage/hooks.ts index 8328613f3..752909c57 100644 --- a/src/app/pages/SearchResultsPage/hooks.ts +++ b/src/app/pages/SearchResultsPage/hooks.ts @@ -22,6 +22,7 @@ import { RouteUtils } from '../../utils/route-utils' import { SearchParams } from '../../components/Search/search-utils' import { SearchScope } from '../../../types/searchScope' import { useSearchForAccountsByName } from '../../hooks/useAccountMetadata' +import { useSearchForValidatorByName } from '../../hooks/useSearchForValidatorByName' function isDefined(item: T): item is NonNullable { return item != null @@ -234,6 +235,21 @@ export function useNamedAccountConditionally( } } +export function useNamedValidatorConditionally(nameFragment: string | undefined) { + const queries = RouteUtils.getEnabledNetworksForLayer(Layer.consensus).map(network => + // eslint-disable-next-line react-hooks/rules-of-hooks + useSearchForValidatorByName(network, nameFragment), + ) + return { + isLoading: queries.some(query => query.isLoading), + isError: queries.some(query => query.isError), + results: queries + .map(query => query.results) + .filter(isDefined) + .flat(), + } +} + export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams) => { const queries = { blockHeight: useBlocksByHeightConditionally(currentScope, q.blockHeight), @@ -243,6 +259,7 @@ export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams oasisRuntimeAccount: useRuntimeAccountConditionally(currentScope, q.consensusAccount), evmAccount: useRuntimeAccountConditionally(currentScope, q.evmAccount), accountsByName: useNamedAccountConditionally(currentScope, q.accountNameFragment), + validatorByName: useNamedValidatorConditionally(q.validatorNameFragment), tokens: useRuntimeTokenConditionally(currentScope, q.evmTokenNameFragment), proposals: useNetworkProposalsConditionally(q.networkProposalNameFragment), } @@ -255,6 +272,7 @@ export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams ...(queries.oasisRuntimeAccount.results || []), ...(queries.evmAccount.results || []), ...(queries.accountsByName.results || []), + ...(queries.validatorByName.results || []), ].filter(isAccountNonEmpty) const tokens = queries.tokens.results .map(l => l.evm_tokens)