Skip to content

feat(web): add-toast-for-graph-and-rpc-errors #1466

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions web/src/components/DisputeCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import DisputeInfo from "./DisputeInfo";
import PeriodBanner from "./PeriodBanner";
import { isUndefined } from "utils/index";
import { responsiveSize } from "styles/responsiveSize";
import { INVALID_DISPUTE_DATA_ERROR } from "consts/index";
import { INVALID_DISPUTE_DATA_ERROR, RPC_ERROR } from "consts/index";

const StyledCard = styled(Card)`
width: 100%;
Expand Down Expand Up @@ -104,12 +104,13 @@ const DisputeCard: React.FC<IDisputeCard> = ({
currentPeriodIndex === 4
? lastPeriodChange
: getPeriodEndTimestamp(lastPeriodChange, currentPeriodIndex, court.timesPerPeriod);
const { data: disputeDetails } = usePopulatedDisputeData(id, arbitrated.id as `0x${string}`);
const { data: disputeDetails, isError } = usePopulatedDisputeData(id, arbitrated.id as `0x${string}`);

const { data: courtPolicy } = useCourtPolicy(court.id);
const courtName = courtPolicy?.name;
const category = disputeDetails?.category;
const navigate = useNavigate();
const errMsg = isError ? RPC_ERROR : INVALID_DISPUTE_DATA_ERROR;
return (
<>
{!isList || overrideIsList ? (
Expand All @@ -119,7 +120,7 @@ const DisputeCard: React.FC<IDisputeCard> = ({
{isUndefined(disputeDetails) ? (
<StyledSkeleton />
) : (
<TruncatedTitle text={disputeDetails?.title ?? INVALID_DISPUTE_DATA_ERROR} maxLength={100} />
<TruncatedTitle text={disputeDetails?.title ?? errMsg} maxLength={100} />
)}
<DisputeInfo
disputeID={id}
Expand All @@ -137,7 +138,7 @@ const DisputeCard: React.FC<IDisputeCard> = ({
<PeriodBanner isCard={false} id={parseInt(id)} period={currentPeriodIndex} />
<ListContainer>
<ListTitle>
<TruncatedTitle text={disputeDetails?.title ?? INVALID_DISPUTE_DATA_ERROR} maxLength={50} />
<TruncatedTitle text={disputeDetails?.title ?? errMsg} maxLength={50} />
</ListTitle>
<DisputeInfo
courtId={court?.id}
Expand Down
10 changes: 5 additions & 5 deletions web/src/components/DisputePreview/DisputeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Answer as IAnswer } from "context/NewDisputeContext";
import AliasDisplay from "./Alias";
import { responsiveSize } from "styles/responsiveSize";
import { DisputeDetails } from "@kleros/kleros-sdk/src/dataMappings/utils/disputeDetailsTypes";
import { INVALID_DISPUTE_DATA_ERROR } from "consts/index";
import { INVALID_DISPUTE_DATA_ERROR, RPC_ERROR } from "consts/index";

const StyledH1 = styled.h1`
margin: 0;
Expand Down Expand Up @@ -60,14 +60,14 @@ const Divider = styled.hr`
`;
interface IDisputeContext {
disputeDetails?: DisputeDetails;
isRpcError?: boolean;
}

export const DisputeContext: React.FC<IDisputeContext> = ({ disputeDetails }) => {
export const DisputeContext: React.FC<IDisputeContext> = ({ disputeDetails, isRpcError = false }) => {
const errMsg = isRpcError ? RPC_ERROR : INVALID_DISPUTE_DATA_ERROR;
return (
<>
<StyledH1>
{isUndefined(disputeDetails) ? <StyledSkeleton /> : disputeDetails?.title ?? INVALID_DISPUTE_DATA_ERROR}
</StyledH1>
<StyledH1>{isUndefined(disputeDetails) ? <StyledSkeleton /> : disputeDetails?.title ?? errMsg}</StyledH1>
{!isUndefined(disputeDetails) && (
<QuestionAndDescription>
<StyledReactMarkDown>{disputeDetails?.question}</StyledReactMarkDown>
Expand Down
1 change: 1 addition & 0 deletions web/src/consts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export const GENESIS_BLOCK_ARBSEPOLIA = BigInt(process.env.REACT_APP_GENESIS_BLO
export const isProductionDeployment = () => process.env.REACT_APP_DEPLOYMENT !== "mainnet";

export const INVALID_DISPUTE_DATA_ERROR = `The dispute data is not valid, please vote "Refuse to arbitrate"`;
export const RPC_ERROR = `RPC Error: Unable to fetch dispute data. Please avoid voting.`;
14 changes: 10 additions & 4 deletions web/src/hooks/queries/usePopulatedDisputeData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { graphql } from "src/graphql";
import { PublicClient } from "viem";
import { HttpRequestError, PublicClient, RpcError } from "viem";
import { usePublicClient } from "wagmi";
import { getIArbitrableV2 } from "hooks/contracts/generated";
import { isUndefined } from "utils/index";
Expand All @@ -10,6 +10,7 @@ import { GENESIS_BLOCK_ARBSEPOLIA } from "consts/index";
import { populateTemplate } from "@kleros/kleros-sdk/src/dataMappings/utils/populateTemplate";
import { executeActions } from "@kleros/kleros-sdk/src/dataMappings/executeActions";
import { DisputeDetails } from "@kleros/kleros-sdk/src/dataMappings/utils/disputeDetailsTypes";
import { debounceErrorToast } from "utils/debounceErrorToast";

const disputeTemplateQuery = graphql(`
query DisputeTemplate($id: ID!) {
Expand All @@ -24,14 +25,14 @@ const disputeTemplateQuery = graphql(`

export const usePopulatedDisputeData = (disputeID?: string, arbitrableAddress?: `0x${string}`) => {
const publicClient = usePublicClient();
const { data: crossChainData } = useIsCrossChainDispute(disputeID, arbitrableAddress);
const { data: crossChainData, isError } = useIsCrossChainDispute(disputeID, arbitrableAddress);
const isEnabled = !isUndefined(disputeID) && !isUndefined(crossChainData) && !isUndefined(arbitrableAddress);
return useQuery<DisputeDetails>({
queryKey: [`DisputeTemplate${disputeID}${arbitrableAddress}`],
enabled: isEnabled,
staleTime: Infinity,
queryFn: async () => {
if (isEnabled) {
if (isEnabled && !isError) {
try {
const { isCrossChainDispute, crossChainTemplateId } = crossChainData;
const templateId = isCrossChainDispute
Expand All @@ -55,7 +56,12 @@ export const usePopulatedDisputeData = (disputeID?: string, arbitrableAddress?:
const disputeDetails = populateTemplate(templateData, data);

return disputeDetails;
} catch {
} catch (error) {
if (error instanceof HttpRequestError || error instanceof RpcError) {
debounceErrorToast("RPC failed!, Please avoid voting.");
throw Error;
}

return {} as DisputeDetails;
}
} else throw Error;
Expand Down
67 changes: 38 additions & 29 deletions web/src/hooks/useIsCrossChainDispute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { usePublicClient } from "wagmi";
import { getIHomeGateway } from "hooks/contracts/generated";
import { isUndefined } from "utils/index";
import { GENESIS_BLOCK_ARBSEPOLIA } from "src/consts";
import { debounceErrorToast } from "utils/debounceErrorToast";
import { HttpRequestError, RpcError } from "viem";

interface IIsCrossChainDispute {
isCrossChainDispute: boolean;
Expand All @@ -20,36 +22,43 @@ export const useIsCrossChainDispute = (disputeID?: string, arbitrableAddress?: `
staleTime: Infinity,
queryFn: async () => {
if (isEnabled) {
const arbitrable = getIHomeGateway({
address: arbitrableAddress,
});
const crossChainDisputeFilter = await arbitrable.createEventFilter.CrossChainDisputeIncoming(
{
_arbitratorDisputeID: BigInt(disputeID),
},
{
fromBlock: GENESIS_BLOCK_ARBSEPOLIA,
toBlock: "latest",
}
);
const crossChainDisputeEvents = await publicClient.getFilterLogs({
filter: crossChainDisputeFilter,
});
try {
const arbitrable = getIHomeGateway({
address: arbitrableAddress,
});
const crossChainDisputeFilter = await arbitrable.createEventFilter.CrossChainDisputeIncoming(
{
_arbitratorDisputeID: BigInt(disputeID),
},
{
fromBlock: GENESIS_BLOCK_ARBSEPOLIA,
toBlock: "latest",
}
);
const crossChainDisputeEvents = await publicClient.getFilterLogs({
filter: crossChainDisputeFilter,
});

if (crossChainDisputeEvents.length > 0) {
return {
isCrossChainDispute: true,
crossChainId: crossChainDisputeEvents[0].args._arbitrableChainId ?? 0n,
crossChainTemplateId: crossChainDisputeEvents[0].args._templateId ?? 0n,
crossChainArbitrableAddress: crossChainDisputeEvents[0].args._arbitrable ?? "0x",
};
} else {
return {
isCrossChainDispute: false,
crossChainId: 0n,
crossChainTemplateId: 0n,
crossChainArbitrableAddress: "0x",
};
if (crossChainDisputeEvents.length > 0) {
return {
isCrossChainDispute: true,
crossChainId: crossChainDisputeEvents[0].args._arbitrableChainId ?? 0n,
crossChainTemplateId: crossChainDisputeEvents[0].args._templateId ?? 0n,
crossChainArbitrableAddress: crossChainDisputeEvents[0].args._arbitrable ?? "0x",
};
} else {
return {
isCrossChainDispute: false,
crossChainId: 0n,
crossChainTemplateId: 0n,
crossChainArbitrableAddress: "0x",
};
}
} catch (error) {
if (error instanceof HttpRequestError || error instanceof RpcError) {
debounceErrorToast("RPC failed!, Please avoid voting.");
}
throw Error;
}
} else throw Error;
},
Expand Down
4 changes: 2 additions & 2 deletions web/src/pages/Cases/CaseDetails/Overview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ interface IOverview {

const Overview: React.FC<IOverview> = ({ arbitrable, courtID, currentPeriodIndex }) => {
const { id } = useParams();
const { data: disputeDetails } = usePopulatedDisputeData(id, arbitrable);
const { data: disputeDetails, isError } = usePopulatedDisputeData(id, arbitrable);
const { data: dispute } = useDisputeDetailsQuery(id);
const { data: courtPolicy } = useCourtPolicy(courtID);
const { data: votingHistory } = useVotingHistory(id);
Expand All @@ -52,7 +52,7 @@ const Overview: React.FC<IOverview> = ({ arbitrable, courtID, currentPeriodIndex
return (
<>
<Container>
<DisputeContext disputeDetails={disputeDetails} />
<DisputeContext disputeDetails={disputeDetails} isRpcError={isError} />
<Divider />

<Verdict arbitrable={arbitrable} />
Expand Down
5 changes: 3 additions & 2 deletions web/src/pages/Cases/CaseDetails/Voting/VotingHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getDrawnJurorsWithCount } from "utils/getDrawnJurorsWithCount";
import { useDisputeDetailsQuery } from "hooks/queries/useDisputeDetailsQuery";
import VotesAccordion from "./VotesDetails";
import Skeleton from "react-loading-skeleton";
import { INVALID_DISPUTE_DATA_ERROR, RPC_ERROR } from "consts/index";

const Container = styled.div``;

Expand All @@ -24,7 +25,7 @@ const VotingHistory: React.FC<{ arbitrable?: `0x${string}`; isQuestion: boolean
const { data: votingHistory } = useVotingHistory(id);
const { data: disputeData } = useDisputeDetailsQuery(id);
const [currentTab, setCurrentTab] = useState(0);
const { data: disputeDetails } = usePopulatedDisputeData(id, arbitrable);
const { data: disputeDetails, isError } = usePopulatedDisputeData(id, arbitrable);
const rounds = votingHistory?.dispute?.rounds;

const localRounds = getLocalRounds(votingHistory?.dispute?.disputeKitDispute);
Expand All @@ -45,7 +46,7 @@ const VotingHistory: React.FC<{ arbitrable?: `0x${string}`; isQuestion: boolean
{isQuestion && disputeDetails.question ? (
<ReactMarkdown>{disputeDetails.question}</ReactMarkdown>
) : (
<ReactMarkdown>{"The dispute's template is not correct please vote refuse to arbitrate"}</ReactMarkdown>
<ReactMarkdown>{isError ? RPC_ERROR : INVALID_DISPUTE_DATA_ERROR}</ReactMarkdown>
)}
<StyledTabs
currentValue={currentTab}
Expand Down
11 changes: 11 additions & 0 deletions web/src/utils/debounceErrorToast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { toast } from "react-toastify";
import { OPTIONS as toastOptions } from "utils/wrapWithToast";

let timeoutId: NodeJS.Timeout;
export const debounceErrorToast = (msg: string) => {
if (timeoutId) clearTimeout(timeoutId);

timeoutId = setTimeout(() => {
toast.error(msg, toastOptions);
}, 5000);
};
10 changes: 8 additions & 2 deletions web/src/utils/graphqlQueryFnHelper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import request from "graphql-request";
import { arbitrumSepolia } from "wagmi/chains";
import { TypedDocumentNode } from "@graphql-typed-document-node/core";
import { debounceErrorToast } from "./debounceErrorToast";

const CHAINID_TO_DISPUTE_TEMPLATE_SUBGRAPH = {
[arbitrumSepolia.id]:
Expand All @@ -18,6 +19,11 @@ export const graphqlQueryFnHelper = async (
isDisputeTemplate = false,
chainId = arbitrumSepolia.id
) => {
const url = graphqlUrl(isDisputeTemplate, chainId);
return request(url, query, parametersObject);
try {
const url = graphqlUrl(isDisputeTemplate, chainId);
return await request(url, query, parametersObject);
} catch (error) {
console.log("Graph error: ", { error });
debounceErrorToast("Graph query error: failed to fetch data.");
}
};