Skip to content

Commit

Permalink
Support metadata_tag type in search (#2559)
Browse files Browse the repository at this point in the history
* Support metadata_tag type in search

* Update ui/searchResults/SearchResultListItem.tsx

Co-authored-by: tom goriunov <tom@ohhhh.me>

---------

Co-authored-by: tom goriunov <tom@ohhhh.me>
  • Loading branch information
isstuev and tom2drum authored Feb 13, 2025
1 parent bcd5c2b commit c8d6980
Show file tree
Hide file tree
Showing 24 changed files with 228 additions and 65 deletions.
39 changes: 39 additions & 0 deletions mocks/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
SearchResultUserOp,
SearchResultBlob,
SearchResultDomain,
SearchResultMetadataTag,
} from 'types/api/search';

export const token1: SearchResultToken = {
Expand Down Expand Up @@ -147,6 +148,42 @@ export const domain1: SearchResultDomain = {
url: '/address/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
};

export const metatag1: SearchResultMetadataTag = {
...address1,
type: 'metadata_tag',
metadata: {
name: 'utko',
slug: 'utko',
meta: {},
tagType: 'name',
ordinal: 1,
},
};

export const metatag2: SearchResultMetadataTag = {
...address2,
type: 'metadata_tag',
metadata: {
name: 'utko',
slug: 'utko',
meta: {},
tagType: 'name',
ordinal: 1,
},
};

export const metatag3: SearchResultMetadataTag = {
...contract2,
type: 'metadata_tag',
metadata: {
name: 'super utko',
slug: 'super-utko',
meta: {},
tagType: 'protocol',
ordinal: 1,
},
};

export const baseResponse: SearchResult = {
items: [
token1,
Expand All @@ -157,6 +194,8 @@ export const baseResponse: SearchResult = {
tx1,
blob1,
domain1,
metatag1,

],
next_page_params: null,
};
63 changes: 46 additions & 17 deletions types/api/search.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import type * as bens from '@blockscout/bens-types';
import type { TokenType } from 'types/api/token';

export type SearchResultType = 'token' | 'address' | 'block' | 'transaction' | 'contract';
import type { AddressMetadataTagApi } from './addressMetadata';

export const SEARCH_RESULT_TYPES = {
token: 'token',
address: 'address',
block: 'block',
transaction: 'transaction',
contract: 'contract',
ens_domain: 'ens_domain',
label: 'label',
user_operation: 'user_operation',
blob: 'blob',
metadata_tag: 'metadata_tag',
} as const;

export type SearchResultType = typeof SEARCH_RESULT_TYPES[keyof typeof SEARCH_RESULT_TYPES];

export interface SearchResultToken {
type: 'token';
Expand All @@ -20,29 +35,35 @@ export interface SearchResultToken {
certified?: boolean;
}

export interface SearchResultAddressOrContract {
type: 'address' | 'contract';
type SearchResultEnsInfo = {
address_hash: string;
expiry_date?: string;
name: string;
names_count: number;
} | null;

interface SearchResultAddressData {
name: string | null;
address: string;
is_smart_contract_verified: boolean;
certified?: true;
filecoin_robust_address?: string | null;
url?: string; // not used by the frontend, we build the url ourselves
ens_info?: {
address_hash: string;
expiry_date?: string;
name: string;
names_count: number;
};
}

export interface SearchResultDomain {
export interface SearchResultAddressOrContract extends SearchResultAddressData {
type: 'address' | 'contract';
ens_info?: SearchResultEnsInfo;
}

export interface SearchResultMetadataTag extends SearchResultAddressData {
type: 'metadata_tag';
ens_info?: SearchResultEnsInfo;
metadata: AddressMetadataTagApi;
}

export interface SearchResultDomain extends SearchResultAddressData {
type: 'ens_domain';
name: string | null;
address: string;
filecoin_robust_address?: string | null;
is_smart_contract_verified: boolean;
url?: string; // not used by the frontend, we build the url ourselves
ens_info: {
address_hash: string;
expiry_date?: string;
Expand Down Expand Up @@ -90,8 +111,16 @@ export interface SearchResultUserOp {
url?: string; // not used by the frontend, we build the url ourselves
}

export type SearchResultItem = SearchResultToken | SearchResultAddressOrContract | SearchResultBlock | SearchResultTx | SearchResultLabel | SearchResultUserOp |
SearchResultBlob | SearchResultDomain;
export type SearchResultItem =
SearchResultToken |
SearchResultAddressOrContract |
SearchResultBlock |
SearchResultTx |
SearchResultLabel |
SearchResultUserOp |
SearchResultBlob |
SearchResultDomain |
SearchResultMetadataTag;

export interface SearchResult {
items: Array<SearchResultItem>;
Expand Down
18 changes: 18 additions & 0 deletions ui/pages/SearchResults.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ test.describe('search by name', () => {
searchMock.token2,
searchMock.contract1,
searchMock.address2,
searchMock.metatag1,
searchMock.label1,
],
next_page_params: null,
Expand Down Expand Up @@ -52,6 +53,23 @@ test('search by address hash +@mobile', async({ render, mockApiResponse }) => {
await expect(component.locator('main')).toHaveScreenshot();
});

test('search by meta tag +@mobile', async({ render, mockApiResponse }) => {
const hooksConfig = {
router: {
query: { q: 'utko' },
},
};
const data = {
items: [ searchMock.metatag1, searchMock.metatag2, searchMock.metatag3 ],
next_page_params: null,
};
await mockApiResponse('search', data, { queryParams: { q: 'utko' } });

const component = await render(<SearchResults/>, { hooksConfig });

await expect(component.locator('main')).toHaveScreenshot();
});

test('search by block number +@mobile', async({ render, mockApiResponse }) => {
const hooksConfig = {
router: {
Expand Down
4 changes: 4 additions & 0 deletions ui/pages/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
import type { FormEvent } from 'react';
import React from 'react';

import { SEARCH_RESULT_TYPES } from 'types/api/search';
import type { SearchResultItem } from 'types/client/search';

import config from 'configs/app';
Expand Down Expand Up @@ -95,6 +96,9 @@ const SearchResultsPageContent = () => {

const displayedItems: Array<SearchResultItem | SearchResultAppItem> = React.useMemo(() => {
const apiData = (data?.items || []).filter((item) => {
if (!SEARCH_RESULT_TYPES[item.type]) {
return false;
}
if (!config.features.userOps.isEnabled && item.type === 'user_operation') {
return false;
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 29 additions & 15 deletions ui/searchResults/SearchResultListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
import * as TxEntity from 'ui/shared/entities/tx/TxEntity';
import * as UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
import EntityTagIcon from 'ui/shared/EntityTags/EntityTagIcon';
import { ADDRESS_REGEXP } from 'ui/shared/forms/validators/address';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import IconSvg from 'ui/shared/IconSvg';
Expand Down Expand Up @@ -79,6 +80,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
);
}

case 'metadata_tag':
case 'contract':
case 'address': {
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
Expand Down Expand Up @@ -357,27 +359,39 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
</Text>
);
}
case 'metadata_tag':
case 'contract':
case 'address': {
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
const addressName = data.name || data.ens_info?.name;
const expiresText = data.ens_info?.expiry_date ? ` (expires ${ dayjs(data.ens_info.expiry_date).fromNow() })` : '';

return addressName ? (
<Flex alignItems="center">
<Text
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(addressName) : highlightText(addressName, searchTerm) }}/>
{ data.ens_info && (
data.ens_info.names_count > 1 ?
<chakra.span color="text_secondary"> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</chakra.span> :
<chakra.span color="text_secondary">{ expiresText }</chakra.span>
) }
</Text>
{ data.certified && <ContractCertifiedLabel iconSize={ 4 } boxSize={ 4 } ml={ 1 }/> }
return (addressName || data.type === 'metadata_tag') ? (
<Flex alignItems="center" gap={ 2 } justifyContent="space-between" flexWrap="wrap">
{ addressName && (
<Flex alignItems="center">
<Text
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(addressName) : highlightText(addressName, searchTerm) }}/>
{ data.ens_info && (
data.ens_info.names_count > 1 ?
<chakra.span color="text_secondary"> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</chakra.span> :
<chakra.span color="text_secondary">{ expiresText }</chakra.span>
) }
</Text>
{ data.certified && <ContractCertifiedLabel iconSize={ 4 } boxSize={ 4 } ml={ 1 }/> }
</Flex>
) }
{ data.type === 'metadata_tag' && (
// we show regular tag because we don't need all meta info here, but need to highlight search term
<Tag display="flex" alignItems="center">
<EntityTagIcon data={ data.metadata }/>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.metadata.name, searchTerm) }}/>
</Tag>
) }
</Flex>
) :
null;
Expand Down
17 changes: 15 additions & 2 deletions ui/searchResults/SearchResultTableItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
import * as TxEntity from 'ui/shared/entities/tx/TxEntity';
import * as UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
import EntityTagIcon from 'ui/shared/EntityTags/EntityTagIcon';
import { ADDRESS_REGEXP } from 'ui/shared/forms/validators/address';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import IconSvg from 'ui/shared/IconSvg';
Expand Down Expand Up @@ -99,6 +100,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
);
}

case 'metadata_tag':
case 'contract':
case 'address': {
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
Expand All @@ -119,7 +121,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P

return (
<>
<Td fontSize="sm" colSpan={ addressName ? 1 : 3 }>
<Td fontSize="sm" colSpan={ (addressName || data.type === 'metadata_tag') ? 1 : 3 } verticalAlign="middle">
<AddressEntity.Container>
<AddressEntity.Icon address={ address }/>
<AddressEntity.Link
Expand All @@ -138,7 +140,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
</AddressEntity.Container>
</Td>
{ addressName && (
<Td colSpan={ 2 } fontSize="sm" verticalAlign="middle">
<Td colSpan={ data.type === 'metadata_tag' ? 1 : 2 } fontSize="sm" verticalAlign="middle">
<Flex alignItems="center">
<Text
overflow="hidden"
Expand All @@ -159,6 +161,17 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
</Flex>
</Td>
) }
{ data.type === 'metadata_tag' && (
<Td colSpan={ addressName ? 1 : 2 } fontSize="sm" verticalAlign="middle">
<Flex justifyContent="flex-end">
{ /* we show regular tag because we don't need all meta info here, but need to highlight search term */ }
<Tag display="flex" alignItems="center">
<EntityTagIcon data={ data.metadata } iconColor="gray.400"/>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.metadata.name, searchTerm) }}/>
</Tag>
</Flex>
</Td>
) }
</>
);
}
Expand Down
23 changes: 3 additions & 20 deletions ui/shared/EntityTags/EntityTag.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { ResponsiveValue } from '@chakra-ui/react';
import { chakra, Image, Tag } from '@chakra-ui/react';
import { Tag } from '@chakra-ui/react';
import React from 'react';

import type { EntityTag as TEntityTag } from './types';

import Skeleton from 'ui/shared/chakra/Skeleton';
import IconSvg from 'ui/shared/IconSvg';
import TruncatedValue from 'ui/shared/TruncatedValue';

import EntityTagIcon from './EntityTagIcon';
import EntityTagLink from './EntityTagLink';
import EntityTagPopover from './EntityTagPopover';
import { getTagLinkParams } from './utils';
Expand All @@ -26,7 +26,6 @@ const EntityTag = ({ data, isLoading, maxW, noLink }: Props) => {
}

const hasLink = !noLink && Boolean(getTagLinkParams(data));
const iconColor = data.meta?.textColor ?? 'gray.400';

const name = (() => {
if (data.meta?.warpcastHandle) {
Expand All @@ -36,22 +35,6 @@ const EntityTag = ({ data, isLoading, maxW, noLink }: Props) => {
return data.name;
})();

const icon = (() => {
if (data.meta?.tagIcon) {
return <Image boxSize={ 3 } mr={ 1 } flexShrink={ 0 } src={ data.meta.tagIcon } alt={ `${ data.name } icon` }/>;
}

if (data.tagType === 'name') {
return <IconSvg name="publictags_slim" boxSize={ 3 } mr={ 1 } flexShrink={ 0 } color={ iconColor }/>;
}

if (data.tagType === 'protocol' || data.tagType === 'generic') {
return <chakra.span color={ iconColor } whiteSpace="pre"># </chakra.span>;
}

return null;
})();

return (
<EntityTagPopover data={ data }>
<Tag
Expand All @@ -66,7 +49,7 @@ const EntityTag = ({ data, isLoading, maxW, noLink }: Props) => {
_hover={ hasLink ? { opacity: 0.76 } : undefined }
>
<EntityTagLink data={ data } noLink={ noLink }>
{ icon }
<EntityTagIcon data={ data } iconColor={ data.meta?.textColor }/>
<TruncatedValue value={ name } tooltipPlacement="top"/>
</EntityTagLink>
</Tag>
Expand Down
Loading

0 comments on commit c8d6980

Please sign in to comment.