diff --git a/apps/explorer/src/components/search/Search.tsx b/apps/explorer/src/components/search/Search.tsx index d607b9a0ae..1a963fb5cc 100644 --- a/apps/explorer/src/components/search/Search.tsx +++ b/apps/explorer/src/components/search/Search.tsx @@ -10,6 +10,28 @@ import { useDebouncedValue } from '~/hooks/useDebouncedValue'; import { useSearch } from '~/hooks/useSearch'; import { ampli } from '~/lib/utils'; +/** + * Extract an ID without a dash, if it is present as a suffix. + * + * CONTEXT: Search result `id` is used as React element key and must be unique, + * because of this, search result now comes with a suffix like `-notarization` or + * `-identity`, which must be ripped off as this is invalid network ID. + * + * @example + * extractOnlyTheId('0x123-audit-trail') + * // output: '0x123' + * @example + * extractOnlyTheId('0x123-identity') + * // output: '0x123' + * @example + * extractOnlyTheId('0x123') + * // output: '0x123' + */ +function extractOnlyTheId(possibleDashedId: string): string { + const dashAt = possibleDashedId.indexOf('-') || -1; + return dashAt > -1 ? possibleDashedId.slice(0, dashAt) : possibleDashedId; +} + export function Search(): JSX.Element { const [query, setQuery] = useState(''); const debouncedQuery = useDebouncedValue(query); @@ -22,7 +44,7 @@ export function Search(): JSX.Element { searchQuery: result.id, searchCategory: result.type, }); - navigate(`/${result?.type}/${encodeURIComponent(result?.id)}`, {}); + navigate(`/${result.type}/${encodeURIComponent(extractOnlyTheId(result.id))}`, {}); setQuery(''); } }, diff --git a/apps/explorer/src/hooks/useSearch.ts b/apps/explorer/src/hooks/useSearch.ts index 4554e1942b..a24db374a1 100644 --- a/apps/explorer/src/hooks/useSearch.ts +++ b/apps/explorer/src/hooks/useSearch.ts @@ -22,12 +22,33 @@ import { type IdentityClientReadOnly } from '@iota/identity-wasm/web'; import { useFeatureIsOn } from '@iota/apps-backend-client'; import { type NotarizationClientReadOnly } from '@iota/notarization/web'; import { tryDIDParse, tryEncodeDidToUrl } from '~/lib/utils/trust-framework/client'; -import { useIdentityClient, useNotarizationClient } from '~/contexts'; +import { useAuditTrailClient, useIdentityClient, useNotarizationClient } from '~/contexts'; +import { type AuditTrailClientReadOnly } from '@iota/audit-trails/web'; const isGenesisLibAddress = (value: string): boolean => /^(0x|0X)0{0,39}[12]$/.test(value); type Results = { id: string; label: string; type: string }[]; +const getResultsForAuditTrail = async ( + auditTrailClient: AuditTrailClientReadOnly | null, + isAuditTrailEnabled: boolean, + query: string, +): Promise => { + if (auditTrailClient == null) return null; // client not available + if (!isAuditTrailEnabled) return null; // feature flag disabled + + const auditTrailChain = await auditTrailClient.trail(query).get(); + if (!auditTrailChain) return null; + + return [ + { + id: `${auditTrailChain.id}-audittrail`, + label: auditTrailChain.id, + type: 'audit-trail', + }, + ]; +}; + const getResultsForNotarization = async ( notarizationClient: NotarizationClientReadOnly | null, isNotarizationEnabled: boolean, @@ -41,7 +62,7 @@ const getResultsForNotarization = async ( return [ { - id: notarizationChain.id, + id: `${notarizationChain.id}-notarization`, label: notarizationChain.id, type: 'notarization', }, @@ -278,10 +299,12 @@ export function useSearch(query: string): UseQueryResult { const client = useIotaClient(); const identityClient = useIdentityClient(); const notarizationClient = useNotarizationClient(); + const auditTrailClient = useAuditTrailClient(); const { data: systemStateSummary } = useIotaClientQuery('getLatestIotaSystemState'); const isTFIdentityEnabled = useFeatureIsOn(Feature.ExplorerTFIdentity as string); const isTFNotarizationEnabled = useFeatureIsOn(Feature.ExplorerTFNotarization as string); + const isTFAuditTrailEnabled = useFeatureIsOn(Feature.ExplorerTFAuditTrail as string); const { iotaNamesClient } = useIotaNamesClient(); return useQuery({ @@ -296,6 +319,7 @@ export function useSearch(query: string): UseQueryResult { getResultsForAddress(client, query, iotaNamesClient), getResultsForDid(identityClient, isTFIdentityEnabled, query), getResultsForNotarization(notarizationClient, isTFNotarizationEnabled, query), + getResultsForAuditTrail(auditTrailClient, isTFAuditTrailEnabled, query), getResultsForObject(client, query), getResultsForValidatorByPoolIdOrIotaAddress(systemStateSummary || null, query), ])