Skip to content

Commit

Permalink
search result page added (MystenLabs#2434)
Browse files Browse the repository at this point in the history
* search result added

* remove unnecessary search filter code
  • Loading branch information
Jibz1 authored Jun 7, 2022
1 parent fae98f9 commit 45111db
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 95 deletions.
77 changes: 6 additions & 71 deletions explorer/client/src/components/search/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,16 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { isValidTransactionDigest, isValidSuiAddress } from '@mysten/sui.js';
import React, { useState, useCallback, useContext } from 'react';
import { useNavigate } from 'react-router-dom';

import { NetworkContext } from '../../context';
import { isGenesisLibAddress } from '../../utils/api/searchUtil';
import {
navigateWithUnknown,
overrideTypeChecks,
} from '../../utils/searchUtil';
import { navigateWithUnknown } from '../../utils/searchUtil';

import styles from './Search.module.css';

type SearchCategory = 'all' | 'transactions' | 'addresses' | 'objects';
function getPlaceholderText(category: SearchCategory) {
switch (category) {
case 'addresses':
return 'Search by address';
case 'transactions':
return 'Search by tx ID';
case 'objects':
case 'all':
return 'Search by ID';
}
}

function isInputValid(category: SearchCategory, input: string): boolean {
if (overrideTypeChecks) return true;

switch (category) {
case 'objects':
case 'addresses':
return isValidSuiAddress(input) || isGenesisLibAddress(input);
case 'transactions':
return isValidTransactionDigest(input);
case 'all':
return (
isValidSuiAddress(input) ||
isValidTransactionDigest(input) ||
isGenesisLibAddress(input)
);
}
}

function Search() {
const [input, setInput] = useState('');
const [category, setCategory] = useState('all' as SearchCategory);
const [network] = useContext(NetworkContext);
const navigate = useNavigate();

Expand All @@ -62,39 +25,20 @@ function Search() {

// remove empty char from input
let query = input.trim();
if (!isInputValid(category, query)) {
navigate(`../error/${category}/${query}`);
setInput('');
setPleaseWaitMode(false);
return;
}

if (category === 'all') {
// remove empty char from input
navigateWithUnknown(query, navigate, network).then(() => {
setInput('');
setPleaseWaitMode(false);
});
} else {
navigate(`../${category}/${query}`);
navigateWithUnknown(query, navigate, network).then(() => {
setInput('');
setPleaseWaitMode(false);
setCategory('all');
}
});
},
[input, navigate, category, setInput, network]
[input, navigate, setInput, network]
);

const handleTextChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) =>
setInput(e.currentTarget.value),
[setInput]
);
const handleCategoryChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) =>
setCategory(e.currentTarget.value as SearchCategory),
[setCategory]
);

return (
<form
Expand All @@ -105,21 +49,12 @@ function Search() {
<input
className={styles.searchtext}
id="searchText"
placeholder={getPlaceholderText(category)}
placeholder="Search by ID"
value={input}
onChange={handleTextChange}
type="text"
/>
<select
className={styles.categorydropdown}
onChange={handleCategoryChange}
value={category}
>
<option value="all">All</option>
<option value="transactions">Transactions</option>
<option value="objects">Objects</option>
<option value="addresses">Addresses</option>
</select>

<input
type="submit"
id="searchBtn"
Expand Down
2 changes: 2 additions & 0 deletions explorer/client/src/pages/config/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Navigate, Route, Routes } from 'react-router-dom';
import AddressResult from '../address-result/AddressResult';
import Home from '../home/Home';
import { ObjectResult } from '../object-result/ObjectResult';
import SearchResult from '../search-result/SearchResult';
import SearchError from '../searcherror/SearchError';
import TransactionResult from '../transaction-result/TransactionResult';

Expand All @@ -16,6 +17,7 @@ const AppRoutes = () => {
<Route path="/objects/:id" element={<ObjectResult />} />
<Route path="/transactions/:id" element={<TransactionResult />} />
<Route path="/addresses/:id" element={<AddressResult />} />
<Route path="/search-result/:id" element={<SearchResult />} />
<Route path="/error/:category/:id" element={<SearchError />} />
<Route path="*" element={<Navigate to="/" replace={true} />} />
</Routes>
Expand Down
24 changes: 24 additions & 0 deletions explorer/client/src/pages/search-result/SearchResult.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.searchresult {
@apply w-11/12 md:w-10/12 mx-auto mb-28 mt-10 m-auto;

max-width: 1200px;
margin-bottom: 2rem;
}

.searchresult h3 {
@apply font-normal leading-3 font-mono text-center break-words;

line-height: 150%;
}

.searchitem {
@apply font-mono md:flex grid items-center bg-center shadow-sm pt-5 pb-5 h-auto border-solid border border-gray-100 pl-5 pr-5 bg-white mt-5 mb-5 text-left md:justify-around;
}

strong {
@apply font-bold;
}

.textcenter {
@apply text-center;
}
152 changes: 152 additions & 0 deletions explorer/client/src/pages/search-result/SearchResult.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { isValidTransactionDigest, isValidSuiAddress } from '@mysten/sui.js';
import { useEffect, useState, useContext } from 'react';
import { useParams } from 'react-router-dom';

import ErrorResult from '../../components/error-result/ErrorResult';
import Longtext from '../../components/longtext/Longtext';
import { NetworkContext } from '../../context';
import theme from '../../styles/theme.module.css';
import {
DefaultRpcClient as rpc,
type Network,
} from '../../utils/api/DefaultRpcClient';
import { isGenesisLibAddress } from '../../utils/api/searchUtil';

import styles from './SearchResult.module.css';

type SearchDataType = {
resultdata: any[];
loadState?: 'loaded' | 'pending' | 'fail';
};

const initState: SearchDataType = {
loadState: 'pending',
resultdata: [],
};

const querySearchParams = async (input: string, network: Network | string) => {
let searchPromises = [];
if (isValidTransactionDigest(input)) {
searchPromises.push(
rpc(network)
.getTransactionWithEffects(input)
.then((data) => [
{
category: 'transactions',
data: data,
},
])
);
} else if (isValidSuiAddress(input) || isGenesisLibAddress(input)) {
const addrObjPromise = Promise.allSettled([
rpc(network)
.getObjectsOwnedByAddress(input)
.then((data) => {
if (data.length <= 0)
throw new Error('No objects for Address');

return {
category: 'addresses',
data: data,
};
}),
rpc(network)
.getObject(input)
.then((data) => {
if (data.status !== 'Exists') {
throw new Error('no object found');
}
return {
category: 'objects',
data: data,
};
}),
]).then((results) => {
// return only the successful results
const searchResult = results
.filter((result: any) => result.status === 'fulfilled')
.map((data: any) => data.value);
return searchResult;
});
searchPromises.push(addrObjPromise);
}
return Promise.any(searchPromises);
};

function SearchResult() {
const { id } = useParams();
const [network] = useContext(NetworkContext);
const [resultData, setResultData] = useState(initState);
useEffect(() => {
if (id == null) {
return;
}
querySearchParams(id, network)
.then((data: any) => {
setResultData({
resultdata: [...data],
loadState: 'loaded',
});
})
.catch((error) => {
setResultData({
loadState: 'fail',
resultdata: [],
});
});
}, [id, network]);

if (resultData.loadState === 'pending') {
return (
<div className={theme.textresults}>
<div className={styles.textcenter}>Loading...</div>
</div>
);
}

if (
resultData.loadState === 'fail' ||
resultData.resultdata.length === 0 ||
!id
) {
return (
<ErrorResult
id={id}
errorMsg={
id
? 'ID not a valid string'
: 'Data on the following query could not be found'
}
/>
);
}

return (
<div id="resultview" className={styles.searchresult}>
<div className={styles.result_header}>
<h3>
Search Result for <strong>{id}</strong>
</h3>
</div>
{resultData.resultdata.map((itm: any, index: number) => (
<div key={index} className={styles.searchitem}>
<div>
Query type: <strong>{itm.category}</strong>
</div>
<div>
<Longtext
text={id}
category={itm.category}
isLink={true}
/>
</div>
</div>
))}
</div>
);
}

export default SearchResult;
63 changes: 39 additions & 24 deletions explorer/client/src/utils/api/searchUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,40 @@ export const navigateWithUnknown = async (

// object IDs and addresses can't be distinguished just by the string, so search both.
// allow navigating to the standard Move packages at 0x1 & 0x2 as a convenience
// Get Search results for a given query from both the object and address index
else if (isValidSuiAddress(input) || isGenesisLibAddress(input)) {
const addrPromise = rpc(network)
.getObjectsOwnedByAddress(input)
.then((data) => {
if (data.length <= 0) throw new Error('No objects for Address');

return {
category: 'addresses',
data: data,
};
});
const objInfoPromise = rpc(network)
.getObject(input)
.then((data) => {
if (data.status !== 'Exists') {
throw new Error('no object found');
}

return {
category: 'objects',
data: data,
};
});
const addrObjPromise = Promise.allSettled([
rpc(network)
.getObjectsOwnedByAddress(input)
.then((data) => {
if (data.length <= 0)
throw new Error('No objects for Address');

searchPromises.push(addrPromise, objInfoPromise);
return {
category: 'addresses',
data: data,
};
}),
rpc(network)
.getObject(input)
.then((data) => {
if (data.status !== 'Exists') {
throw new Error('no object found');
}
return {
category: 'objects',
data: data,
};
}),
]).then((results) => {
// return only the successful results
const searchResult = results
.filter((result: any) => result.status === 'fulfilled')
.map((data: any) => data.value);
// return array of objects if results are found for both address and object, return just the data obj if only one is found
return searchResult.length > 1 ? searchResult : searchResult[0];
});
searchPromises.push(addrObjPromise);
}

if (searchPromises.length === 0) {
Expand All @@ -59,7 +68,13 @@ export const navigateWithUnknown = async (

return (
Promise.any(searchPromises)
.then((pac) => {
.then((pac: any) => {
// Redirect to search result page if there are multiple categories with the same query
if (Array.isArray(pac)) {
navigate(`../search-result/${encodeURIComponent(input)}`);
return;
}

if (
pac?.data &&
(pac?.category === 'objects' ||
Expand Down

0 comments on commit 45111db

Please sign in to comment.