From 376813fcd5b233645b1b6961db443aef554c5407 Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 27 Mar 2024 11:13:28 +0100 Subject: [PATCH] Feat: Search by slot index or commitmentId (#1320) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Remove double network in path from Slot table links * fix: Slot page infinite loader * feat: Fix search by slot index * feat: Support slot search by commitment Id. Show commitmentId on slot page * fix: Add network in link paths of useGenerateSlotsTable --------- Co-authored-by: Begoña Álvarez de la Cruz --- api/src/models/api/nova/ISearchResponse.ts | 7 ++++- api/src/services/nova/novaApiService.ts | 15 ++++++++++ api/src/utils/nova/searchExecutor.ts | 30 ++++++++++++++++++- api/src/utils/nova/searchQueryBuilder.ts | 7 +++++ .../nova/slot/blocks/SlotBlocksSection.tsx | 10 ++++--- client/src/app/routes/nova/Search.tsx | 25 ++++++++++------ client/src/app/routes/nova/SlotPage.tsx | 10 ++++--- .../nova/hooks/useGenerateSlotsTable.ts | 4 +-- .../src/helpers/nova/hooks/useSlotBlocks.ts | 4 ++- .../src/helpers/nova/hooks/useSlotDetails.ts | 12 ++++++-- client/src/models/api/nova/ISearchResponse.ts | 5 ++++ 11 files changed, 104 insertions(+), 25 deletions(-) diff --git a/api/src/models/api/nova/ISearchResponse.ts b/api/src/models/api/nova/ISearchResponse.ts index 332c4eada..f6253f62d 100644 --- a/api/src/models/api/nova/ISearchResponse.ts +++ b/api/src/models/api/nova/ISearchResponse.ts @@ -2,8 +2,8 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ import { Block, OutputWithMetadataResponse } from "@iota/sdk-nova"; import { IAddressDetails } from "./IAddressDetails"; +import { IResponse } from "./IResponse"; import { ITaggedOutputsResponse } from "./ITaggedOutputsResponse"; -import { IResponse } from "../IResponse"; export interface ISearchResponse extends IResponse { /** @@ -46,6 +46,11 @@ export interface ISearchResponse extends IResponse { */ transactionBlock?: Block; + /** + * A slot index. + */ + slotIndex?: string; + /** * Basic and/or Nft tagged output ids. */ diff --git a/api/src/services/nova/novaApiService.ts b/api/src/services/nova/novaApiService.ts index 027352b43..50f167c05 100644 --- a/api/src/services/nova/novaApiService.ts +++ b/api/src/services/nova/novaApiService.ts @@ -590,6 +590,21 @@ export class NovaApiService { } } + /** + * Get the slot commitment by commitment id. + * @param slotCommitmentId The slot commitment id to get the commitment for. + * @returns The slot commitment. + */ + public async getCommitment(slotCommitmentId: string): Promise { + try { + const slot = await this.client.getCommitment(slotCommitmentId); + + return { slot }; + } catch (e) { + logger.error(`Failed fetching slot with commitment id ${slotCommitmentId}. Cause: ${e}`); + } + } + /** * Get the epoch committee. * @param epochIndex The epoch index to get the committee for. diff --git a/api/src/utils/nova/searchExecutor.ts b/api/src/utils/nova/searchExecutor.ts index a871a40fc..1f4748847 100644 --- a/api/src/utils/nova/searchExecutor.ts +++ b/api/src/utils/nova/searchExecutor.ts @@ -142,6 +142,34 @@ export class SearchExecutor { ); } + if (searchQuery.slotIndex) { + promises.push( + this.executeQuery( + this.apiService.getSlotCommitment(searchQuery.slotIndex), + (_) => { + promisesResult = { + slotIndex: String(searchQuery.slotIndex), + }; + }, + "Slot commitment fetch failed", + ), + ); + } + + if (searchQuery.slotCommitmentId) { + promises.push( + this.executeQuery( + this.apiService.getCommitment(searchQuery.slotCommitmentId), + (result) => { + promisesResult = { + slotIndex: String(result.slot.slot), + }; + }, + "Slot commitment fetch failed", + ), + ); + } + if (searchQuery.tag) { promises.push( this.executeQuery( @@ -170,7 +198,7 @@ export class SearchExecutor { }; } - return {}; + return { message: "Nothing found" }; } private async executeQuery(query: Promise, successHandler: (result: T) => void, failureMessage: string): Promise { diff --git a/api/src/utils/nova/searchQueryBuilder.ts b/api/src/utils/nova/searchQueryBuilder.ts index d977a8d8b..d0568d372 100644 --- a/api/src/utils/nova/searchQueryBuilder.ts +++ b/api/src/utils/nova/searchQueryBuilder.ts @@ -55,6 +55,10 @@ export interface SearchQuery { * The tag of an output. */ tag?: HexEncodedString; + /** + * A slot commitment id. + */ + slotCommitmentId?: string; } /** @@ -102,6 +106,7 @@ export class SearchQueryBuilder { let anchorId: string; let delegationId: string; let tag: string; + let slotCommitmentId: string; const queryDetails = AddressHelper.buildAddress(this.networkBechHrp, this.queryLower); const slotIndex = /^\d+$/.test(this.query) ? Number.parseInt(this.query, 10) : undefined; @@ -114,6 +119,7 @@ export class SearchQueryBuilder { if (queryDetails?.hex && queryDetails.hex.length === 74) { blockId = queryDetails.hex; transactionId = queryDetails.hex; + slotCommitmentId = queryDetails.hex; } else if (queryDetails?.hex && queryDetails.hex.length === 66) { // if the hex has 66 characters it might be a accoount id or a nft id accountId = queryDetails.hex; @@ -157,6 +163,7 @@ export class SearchQueryBuilder { anchorId, delegationId, tag, + slotCommitmentId, }; } } diff --git a/client/src/app/components/nova/slot/blocks/SlotBlocksSection.tsx b/client/src/app/components/nova/slot/blocks/SlotBlocksSection.tsx index a1667f848..7a8edf277 100644 --- a/client/src/app/components/nova/slot/blocks/SlotBlocksSection.tsx +++ b/client/src/app/components/nova/slot/blocks/SlotBlocksSection.tsx @@ -3,15 +3,17 @@ import SlotBlocksSectionRow from "./SlotBlocksSectionRow"; import Spinner from "~/app/components/Spinner"; import Pagination from "~/app/components/Pagination"; import { ISlotBlock } from "~/models/api/nova/ISlotBlocksResponse"; +import useSlotBlocks from "~/helpers/nova/hooks/useSlotBlocks"; import "./SlotBlocksSection.scss"; interface SlotBlocksSectionProps { - readonly blocks: ISlotBlock[] | null; + readonly slotIndex: string | null; } const PAGE_SIZE: number = 10; -const SlotBlocksSection: React.FC = ({ blocks }) => { +const SlotBlocksSection: React.FC = ({ slotIndex }) => { + const { blocks, isLoading } = useSlotBlocks(slotIndex); const [currentPage, setCurrentPage] = useState([]); const [pageNumber, setPageNumber] = useState(1); @@ -50,7 +52,7 @@ const SlotBlocksSection: React.FC = ({ blocks }) => { ) : ( - + isLoading && )} {showPagination && ( = ({ blocks }) => { }; SlotBlocksSection.defaultProps = { - blocks: undefined, + slotIndex: null, }; export default SlotBlocksSection; diff --git a/client/src/app/routes/nova/Search.tsx b/client/src/app/routes/nova/Search.tsx index 4176e9e69..8fe60117c 100644 --- a/client/src/app/routes/nova/Search.tsx +++ b/client/src/app/routes/nova/Search.tsx @@ -56,7 +56,15 @@ const Search: React.FC> = (props) => { network, query: queryTerm, }); - if (response && Object.keys(response).length > 0) { + if (!response || response?.error || response?.message) { + setState((prevState) => ({ + ...prevState, + completion: response?.error ? "invalid" : "notFound", + invalidError: response?.error ?? response?.message ?? "", + status: "", + statusBusy: false, + })); + } else if (Object.keys(response).length > 0) { const routeSearch = new Map(); let route = ""; let routeParam = query; @@ -102,6 +110,9 @@ const Search: React.FC> = (props) => { } else if (response.foundryId) { route = "foundry"; routeParam = response.foundryId; + } else if (response.slotIndex) { + route = "slot"; + routeParam = response.slotIndex.toString(); } else if (response.taggedOutputs) { route = "outputs"; redirectState = { @@ -131,14 +142,6 @@ const Search: React.FC> = (props) => { search: getEncodedSearch(), redirectState, })); - } else { - setState((prevState) => ({ - ...prevState, - completion: response?.error ? "invalid" : "notFound", - invalidError: response?.error ?? "", - status: "", - statusBusy: false, - })); } }, 0); } else { @@ -240,6 +243,10 @@ const Search: React.FC> = (props) => { NFT Id 66 Hex characters +
  • + Slot + Index or commitmentId of finalized slot +

  • Please perform another search with a valid hash.

    diff --git a/client/src/app/routes/nova/SlotPage.tsx b/client/src/app/routes/nova/SlotPage.tsx index 85befd173..6850d3b3f 100644 --- a/client/src/app/routes/nova/SlotPage.tsx +++ b/client/src/app/routes/nova/SlotPage.tsx @@ -9,7 +9,6 @@ import { RouteComponentProps } from "react-router-dom"; import StatusPill from "~/app/components/nova/StatusPill"; import { getSlotStatusFromLatestSlotCommitments, parseSlotIndexFromParams } from "~/app/lib/utils/slot.utils"; import { SLOT_STATUS_TO_PILL_STATUS } from "~/app/lib/constants/slot.constants"; -import useSlotBlocks from "~/helpers/nova/hooks/useSlotBlocks"; import SlotBlocksSection from "~/app/components/nova/slot/blocks/SlotBlocksSection"; import { useSlotManaBurned } from "~/helpers/nova/hooks/useSlotManaBurned"; import "./SlotPage.scss"; @@ -23,19 +22,22 @@ export default function SlotPage({ slotIndex: string; }>): React.JSX.Element { const { latestSlotCommitments = [] } = useSlotsFeed(); - const { slotCommitment: slotCommitmentDetails } = useSlotDetails(network, slotIndex); + const { slotCommitment: slotCommitmentDetails, slotCommitmentId } = useSlotDetails(network, slotIndex); const { slotManaBurned } = useSlotManaBurned(slotIndex); const parsedSlotIndex = parseSlotIndexFromParams(slotIndex); const slotStatus = getSlotStatusFromLatestSlotCommitments(parsedSlotIndex, latestSlotCommitments); const slotFromSlotCommitments = latestSlotCommitments.find((slot) => slot.slotCommitment.slot === parsedSlotIndex); - const { blocks } = useSlotBlocks(network, slotIndex); const dataRows: IPageDataRow[] = [ { label: "Slot Index", value: parsedSlotIndex ?? "-", }, + { + label: "Commitment Id", + value: slotCommitmentId ?? "-", + }, { label: "RMC", value: @@ -74,7 +76,7 @@ export default function SlotPage({ )} - + diff --git a/client/src/helpers/nova/hooks/useGenerateSlotsTable.ts b/client/src/helpers/nova/hooks/useGenerateSlotsTable.ts index 686c1ca68..a2528f860 100644 --- a/client/src/helpers/nova/hooks/useGenerateSlotsTable.ts +++ b/client/src/helpers/nova/hooks/useGenerateSlotsTable.ts @@ -4,9 +4,9 @@ import { SlotTableCellType, type TSlotTableData } from "~/app/components/nova/la import { SlotStatus } from "~app/lib/enums"; import { SlotTableHeadings } from "~/app/lib/ui/enums"; import { Utils } from "@iota/sdk-wasm-nova/web"; -import { useNetworkInfoNova } from "../networkInfo"; import useSlotsFeed from "./useSlotsFeed"; import { useNovaTimeConvert } from "./useNovaTimeConvert"; +import { useNetworkInfoNova } from "../networkInfo"; import moment from "moment"; type SlotTimeRange = { @@ -168,9 +168,9 @@ function getSlotCommitmentTableRow( } export function useGenerateSlotsTable(): ITableRow[] { + const { name: network } = useNetworkInfoNova((s) => s.networkInfo); const { slotIndexToUnixTimeRange } = useNovaTimeConvert(); const { currentSlotIndex, currentSlotTimeRange, latestSlotCommitments, latestSlotIndexes } = useSlotsFeed(); - const { name: network } = useNetworkInfoNova((s) => s.networkInfo); const rows: ITableRow[] = []; diff --git a/client/src/helpers/nova/hooks/useSlotBlocks.ts b/client/src/helpers/nova/hooks/useSlotBlocks.ts index c8c4b4227..bc1a6ef3d 100644 --- a/client/src/helpers/nova/hooks/useSlotBlocks.ts +++ b/client/src/helpers/nova/hooks/useSlotBlocks.ts @@ -4,6 +4,7 @@ import { useIsMounted } from "~/helpers/hooks/useIsMounted"; import { ISlotBlock } from "~/models/api/nova/ISlotBlocksResponse"; import { NOVA } from "~/models/config/protocolVersion"; import { NovaApiClient } from "~/services/nova/novaApiClient"; +import { useNetworkInfoNova } from "../networkInfo"; interface IUseSlotBlocks { blocks: ISlotBlock[] | null; @@ -11,7 +12,8 @@ interface IUseSlotBlocks { error: string | undefined; } -export default function useSlotBlocks(network: string, slotIndex: string): IUseSlotBlocks { +export default function useSlotBlocks(slotIndex: string | null): IUseSlotBlocks { + const { name: network } = useNetworkInfoNova((s) => s.networkInfo); const isMounted = useIsMounted(); const [apiClient] = useState(ServiceFactory.get(`api-client-${NOVA}`)); const [slotBlocks, setSlotBlocks] = useState(null); diff --git a/client/src/helpers/nova/hooks/useSlotDetails.ts b/client/src/helpers/nova/hooks/useSlotDetails.ts index 45da0620f..56779a9ee 100644 --- a/client/src/helpers/nova/hooks/useSlotDetails.ts +++ b/client/src/helpers/nova/hooks/useSlotDetails.ts @@ -1,4 +1,4 @@ -import { SlotCommitment } from "@iota/sdk-wasm-nova/web"; +import { SlotCommitment, Utils } from "@iota/sdk-wasm-nova/web"; import { plainToInstance } from "class-transformer"; import { useEffect, useState } from "react"; import { ServiceFactory } from "~/factories/serviceFactory"; @@ -8,6 +8,7 @@ import { NovaApiClient } from "~/services/nova/novaApiClient"; interface IUseSlotDetails { slotCommitment: SlotCommitment | null; + slotCommitmentId: string | null; error: string | undefined; isLoading: boolean; } @@ -16,12 +17,14 @@ export default function useSlotDetails(network: string, slotIndex: string): IUse const isMounted = useIsMounted(); const [apiClient] = useState(ServiceFactory.get(`api-client-${NOVA}`)); const [slotCommitment, setSlotCommitment] = useState(null); + const [slotCommitmentId, setSlotCommitmentId] = useState(null); const [error, setError] = useState(); const [isLoading, setIsLoading] = useState(true); useEffect(() => { setIsLoading(true); setSlotCommitment(null); + setSlotCommitmentId(null); if (!slotCommitment) { // eslint-disable-next-line no-void void (async () => { @@ -32,8 +35,10 @@ export default function useSlotDetails(network: string, slotIndex: string): IUse }) .then((response) => { if (isMounted) { - const slot = plainToInstance(SlotCommitment, response.slot) as unknown as SlotCommitment; - setSlotCommitment(slot); + const slotCommitment = plainToInstance(SlotCommitment, response.slot) as unknown as SlotCommitment; + const slotCommitmentId = Utils.computeSlotCommitmentId(slotCommitment); + setSlotCommitment(slotCommitment); + setSlotCommitmentId(slotCommitmentId); setError(response.error); } }) @@ -48,6 +53,7 @@ export default function useSlotDetails(network: string, slotIndex: string): IUse return { slotCommitment, + slotCommitmentId, error, isLoading, }; diff --git a/client/src/models/api/nova/ISearchResponse.ts b/client/src/models/api/nova/ISearchResponse.ts index 6c03195e4..b78e73e5c 100644 --- a/client/src/models/api/nova/ISearchResponse.ts +++ b/client/src/models/api/nova/ISearchResponse.ts @@ -64,6 +64,11 @@ export interface ISearchResponse extends IResponse { */ transactionBlock?: Block; + /** + * A slot index. + */ + slotIndex?: string; + /** * Basic and/or Nft tagged output ids. */