From b1996041d95fb191a27df586f4569fc2b8b8a2c5 Mon Sep 17 00:00:00 2001 From: Michal Zielenkiewicz Date: Wed, 13 Nov 2024 16:52:13 +0100 Subject: [PATCH 1/3] Allow to render validator links in ConsensusAccountDetailsView --- src/app/components/Account/ConsensusAccountDetailsView.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/components/Account/ConsensusAccountDetailsView.tsx b/src/app/components/Account/ConsensusAccountDetailsView.tsx index da57e9b4c..6d10ee8bc 100644 --- a/src/app/components/Account/ConsensusAccountDetailsView.tsx +++ b/src/app/components/Account/ConsensusAccountDetailsView.tsx @@ -11,7 +11,7 @@ import { styled } from '@mui/material/styles' import { useFormattedTimestampStringWithDistance } from '../../hooks/useFormattedTimestamp' import { AccountAvatar } from '../AccountAvatar' import { AccountSizeBadge } from '../AccountSizeBadge' -import { AccountLink } from './AccountLink' +import { ConsensusAccountLink } from './ConsensusAccountLink' import { CopyToClipboard } from '../CopyToClipboard' import { getPreciseNumberFormat } from '../../../locales/getPreciseNumberFormat' @@ -60,8 +60,9 @@ export const ConsensusAccountDetailsView: FC =
- From 82d0b473acce7d5326ef3675d027ae9d142b7fdf Mon Sep 17 00:00:00 2001 From: Michal Zielenkiewicz Date: Wed, 13 Nov 2024 16:54:19 +0100 Subject: [PATCH 2/3] Fix highlighted text in ConsensusAccountLink --- src/app/components/Account/ConsensusAccountLink.tsx | 12 +++++++++++- .../components/Validators/DeferredValidatorLink.tsx | 2 +- src/app/components/Validators/ValidatorLink.tsx | 8 ++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/app/components/Account/ConsensusAccountLink.tsx b/src/app/components/Account/ConsensusAccountLink.tsx index 16f60252d..8d2470295 100644 --- a/src/app/components/Account/ConsensusAccountLink.tsx +++ b/src/app/components/Account/ConsensusAccountLink.tsx @@ -9,6 +9,7 @@ type ConsensusAccountLinkProps = { alwaysTrim?: boolean labelOnly?: boolean network: Network + highlightedPartOfName?: string | undefined } export const ConsensusAccountLink: FC = ({ @@ -16,11 +17,19 @@ export const ConsensusAccountLink: FC = ({ alwaysTrim = true, labelOnly, network, + highlightedPartOfName, }) => { const { data } = useGetConsensusValidatorsAddressNameMap(network) if (data?.data?.[address]) { - return + return ( + + ) } return ( @@ -29,6 +38,7 @@ export const ConsensusAccountLink: FC = ({ scope={{ network, layer: Layer.consensus }} address={address} alwaysTrim={alwaysTrim} + highlightedPartOfName={highlightedPartOfName} /> ) } diff --git a/src/app/components/Validators/DeferredValidatorLink.tsx b/src/app/components/Validators/DeferredValidatorLink.tsx index 79df09789..aef73260c 100644 --- a/src/app/components/Validators/DeferredValidatorLink.tsx +++ b/src/app/components/Validators/DeferredValidatorLink.tsx @@ -22,7 +22,7 @@ export const DeferredValidatorLink: FC<{ address={address} network={scope.network} name={validator?.media?.name} - highlightedPart={highlightedPart} + highlightedPartOfName={highlightedPart} /> ) } diff --git a/src/app/components/Validators/ValidatorLink.tsx b/src/app/components/Validators/ValidatorLink.tsx index add3f1bcd..2d6c0a529 100644 --- a/src/app/components/Validators/ValidatorLink.tsx +++ b/src/app/components/Validators/ValidatorLink.tsx @@ -15,7 +15,7 @@ type ValidatorLinkProps = { name?: string network: Network alwaysTrim?: boolean - highlightedPart?: string + highlightedPartOfName?: string } export const ValidatorLink: FC = ({ @@ -23,7 +23,7 @@ export const ValidatorLink: FC = ({ name, network, alwaysTrim, - highlightedPart, + highlightedPartOfName, }) => { const { isTablet } = useScreenSize() const to = RouteUtils.getValidatorRoute(network, address) @@ -36,7 +36,7 @@ export const ValidatorLink: FC = ({ address={address} name={name || validatorName} to={to} - highlightedPart={highlightedPart} + highlightedPart={highlightedPartOfName} /> ) : ( = ({ alwaysTrim={alwaysTrim} name={name || validatorName} to={to} - highlightedPart={highlightedPart} + highlightedPart={highlightedPartOfName} /> )} From 10e8c50ead04c084cd07e24018889d96dfb682d5 Mon Sep 17 00:00:00 2001 From: Michal Zielenkiewicz Date: Wed, 13 Nov 2024 16:59:46 +0100 Subject: [PATCH 3/3] Enable search by validator name --- .changelog/1633.trivial.md | 1 + src/app/components/Search/search-utils.ts | 5 +++ src/app/hooks/useSearchForValidatorsByName.ts | 42 +++++++++++++++++++ src/app/pages/SearchResultsPage/hooks.ts | 18 ++++++++ 4 files changed, 66 insertions(+) create mode 100644 .changelog/1633.trivial.md create mode 100644 src/app/hooks/useSearchForValidatorsByName.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/useSearchForValidatorsByName.ts b/src/app/hooks/useSearchForValidatorsByName.ts new file mode 100644 index 000000000..1d5600ea2 --- /dev/null +++ b/src/app/hooks/useSearchForValidatorsByName.ts @@ -0,0 +1,42 @@ +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 useSearchForValidatorsByName = ( + network: Network, + nameFragment: string | undefined, +): AccountNameSearchResults => { + const { isLoading, isError, data } = useGetConsensusValidatorsAddressNameMap(network) + const matches = data?.data && nameFragment ? findAddressesWithMatch(data?.data, nameFragment, network) : [] + const { + isLoading: areConsensusAccountsLoading, + isError: areConsensusAccountsError, + data: consensusResults, + } = useGetConsensusAccountsAddresses(matches, { + 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..1f8bad3e2 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 { useSearchForValidatorsByName } from '../../hooks/useSearchForValidatorsByName' 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 + useSearchForValidatorsByName(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)