Skip to content

Refactor/graphql batching and optimisations #1850

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
Jan 27, 2025
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
2 changes: 2 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
},
"dependencies": {
"@cyntler/react-doc-viewer": "^1.17.0",
"@graphql-tools/batch-execute": "^9.0.11",
"@graphql-tools/utils": "^10.7.2",
"@kleros/kleros-app": "workspace:^",
"@kleros/kleros-sdk": "workspace:^",
"@kleros/kleros-v2-contracts": "workspace:^",
Expand Down
3 changes: 2 additions & 1 deletion web/src/consts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { ArbitratorTypes };
export const ONE_BASIS_POINT = 10000n;

export const REFETCH_INTERVAL = 5000;
export const STALE_TIME = 1000;

export const IPFS_GATEWAY = import.meta.env.REACT_APP_IPFS_GATEWAY || "https://cdn.kleros.link";
export const HERMES_TELEGRAM_BOT_URL =
Expand All @@ -20,7 +21,7 @@ export const GIT_URL = `https://github.com/kleros/kleros-v2/tree/${gitCommitHash
export const RELEASE_VERSION = version;

// https://www.w3.org/TR/2012/WD-html-markup-20120329/input.email.html#input.email.attrs.value.single
// eslint-disable-next-line security/detect-unsafe-regex

export const EMAIL_REGEX =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
export const TELEGRAM_REGEX = /^@\w{5,32}$/;
Expand Down
43 changes: 30 additions & 13 deletions web/src/context/GraphqlBatcher.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { useMemo, createContext, useContext } from "react";

import { createBatchingExecutor } from "@graphql-tools/batch-execute";
import { AsyncExecutor, ExecutionResult } from "@graphql-tools/utils";
import { TypedDocumentNode } from "@graphql-typed-document-node/core";
import { create, windowedFiniteBatchScheduler, Batcher } from "@yornaath/batshit";
import { request } from "graphql-request";

import { debounceErrorToast } from "utils/debounceErrorToast";
import { getGraphqlUrl } from "utils/getGraphqlUrl";

interface IGraphqlBatcher {
graphqlBatcher: Batcher<any, IQuery>;
}
Expand All @@ -21,19 +22,35 @@ interface IQuery {

const Context = createContext<IGraphqlBatcher | undefined>(undefined);

const executor: AsyncExecutor = async ({ document, variables, extensions }) => {
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
const result = request(extensions.url, document, variables).then((res) => ({
data: res,
})) as Promise<ExecutionResult>;

return result;
} catch (error) {
console.error("Graph error: ", { error });
debounceErrorToast("Graph query error: failed to fetch data.");
return { data: {} };
}
};

const batchExec = createBatchingExecutor(executor);

const fetcher = async (queries: IQuery[]) => {
const promises = queries.map(async ({ id, document, variables, isDisputeTemplate, chainId }) => {
const url = getGraphqlUrl(isDisputeTemplate ?? false, chainId);
try {
return request(url, document, variables).then((result) => ({ id, result }));
} catch (error) {
console.error("Graph error: ", { error });
debounceErrorToast("Graph query error: failed to fetch data.");
return { id, result: {} };
}
});
const data = await Promise.all(promises);
return data;
const batchdata = await Promise.all(
queries.map(({ document, variables, isDisputeTemplate, chainId }) =>
batchExec({ document, variables, extensions: { url: getGraphqlUrl(isDisputeTemplate ?? false, chainId) } })
)
);

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
const processedData = batchdata.map((data, index) => ({ id: queries[index].id, result: data.data }));
return processedData;
};

const GraphqlBatcherProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
Expand Down
2 changes: 2 additions & 0 deletions web/src/hooks/queries/useAllCasesQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query";

import { useGraphqlBatcher } from "context/GraphqlBatcher";

import { STALE_TIME } from "src/consts";
import { graphql } from "src/graphql";
import { AllCasesQuery } from "src/graphql/graphql";

Expand All @@ -20,6 +21,7 @@ export const useAllCasesQuery = () => {
const { graphqlBatcher } = useGraphqlBatcher();
return useQuery({
queryKey: [`allCasesQuery`],
staleTime: STALE_TIME,
queryFn: async () =>
await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: allCasesQuery, variables: {} }),
});
Expand Down
3 changes: 2 additions & 1 deletion web/src/hooks/queries/useClassicAppealQuery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";

import { REFETCH_INTERVAL } from "consts/index";
import { REFETCH_INTERVAL, STALE_TIME } from "consts/index";
import { useGraphqlBatcher } from "context/GraphqlBatcher";

import { graphql } from "src/graphql";
Expand Down Expand Up @@ -44,6 +44,7 @@ export const useClassicAppealQuery = (id?: string | number) => {
queryKey: [`classicAppealQuery${id}`],
enabled: isEnabled,
refetchInterval: REFETCH_INTERVAL,
staleTime: STALE_TIME,
queryFn: async () =>
isEnabled
? await graphqlBatcher.fetch({
Expand Down
3 changes: 2 additions & 1 deletion web/src/hooks/queries/useCourtDetails.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";

import { REFETCH_INTERVAL } from "consts/index";
import { REFETCH_INTERVAL, STALE_TIME } from "consts/index";
import { useGraphqlBatcher } from "context/GraphqlBatcher";

import { graphql } from "src/graphql";
Expand Down Expand Up @@ -36,6 +36,7 @@ export const useCourtDetails = (id?: string) => {
queryKey: [`courtDetails${id}`],
enabled: isEnabled,
refetchInterval: REFETCH_INTERVAL,
staleTime: STALE_TIME,
queryFn: async () =>
await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: courtDetailsQuery, variables: { id } }),
});
Expand Down
2 changes: 2 additions & 0 deletions web/src/hooks/queries/useCourtTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query";

import { useGraphqlBatcher } from "context/GraphqlBatcher";

import { STALE_TIME } from "src/consts";
import { graphql } from "src/graphql";
import { CourtTreeQuery } from "src/graphql/graphql";
export type { CourtTreeQuery };
Expand Down Expand Up @@ -39,6 +40,7 @@ export const useCourtTree = () => {
const { graphqlBatcher } = useGraphqlBatcher();
return useQuery<CourtTreeQuery>({
queryKey: ["courtTreeQuery"],
staleTime: STALE_TIME,
queryFn: async () =>
await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: courtTreeQuery, variables: {} }),
});
Expand Down
3 changes: 2 additions & 1 deletion web/src/hooks/queries/useDisputeDetailsQuery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";

import { REFETCH_INTERVAL } from "consts/index";
import { REFETCH_INTERVAL, STALE_TIME } from "consts/index";
import { useGraphqlBatcher } from "context/GraphqlBatcher";

import { graphql } from "src/graphql";
Expand Down Expand Up @@ -48,6 +48,7 @@ export const useDisputeDetailsQuery = (id?: string | number) => {
queryKey: [`disputeDetailsQuery${id}`],
enabled: isEnabled,
refetchInterval: REFETCH_INTERVAL,
staleTime: STALE_TIME,
queryFn: async () =>
await graphqlBatcher.fetch({
id: crypto.randomUUID(),
Expand Down
2 changes: 2 additions & 0 deletions web/src/hooks/queries/useDisputeMaintenanceQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query";

import { useGraphqlBatcher } from "context/GraphqlBatcher";

import { STALE_TIME } from "src/consts";
import { graphql } from "src/graphql";
import { DisputeMaintenanceQuery } from "src/graphql/graphql";
import { isUndefined } from "src/utils";
Expand Down Expand Up @@ -40,6 +41,7 @@ const useDisputeMaintenanceQuery = (id?: string) => {
return useQuery<DisputeMaintenanceQuery>({
queryKey: [`disputeMaintenanceQuery-${id}`],
enabled: isEnabled,
staleTime: STALE_TIME,
queryFn: async () =>
await graphqlBatcher.fetch({
id: crypto.randomUUID(),
Expand Down
3 changes: 2 additions & 1 deletion web/src/hooks/queries/useJurorStakeDetailsQuery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";

import { REFETCH_INTERVAL } from "consts/index";
import { REFETCH_INTERVAL, STALE_TIME } from "consts/index";
import { useGraphqlBatcher } from "context/GraphqlBatcher";

import { graphql } from "src/graphql";
Expand Down Expand Up @@ -29,6 +29,7 @@ export const useJurorStakeDetailsQuery = (userId?: string) => {
queryKey: [`jurorStakeDetails${userId}`],
enabled: isEnabled,
refetchInterval: REFETCH_INTERVAL,
staleTime: STALE_TIME,
queryFn: async () =>
await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: jurorStakeDetailsQuery, variables: { userId } }),
});
Expand Down
2 changes: 2 additions & 0 deletions web/src/hooks/queries/useUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Address } from "viem";

import { useGraphqlBatcher } from "context/GraphqlBatcher";

import { STALE_TIME } from "src/consts";
import { graphql } from "src/graphql";
import { UserQuery, Dispute_Filter, UserDisputeFilterQuery, UserDetailsFragment } from "src/graphql/graphql";
export type { UserQuery, UserDetailsFragment };
Expand Down Expand Up @@ -58,6 +59,7 @@ export const useUserQuery = (address?: Address, where?: Dispute_Filter) => {
return useQuery<UserQuery | UserDisputeFilterQuery>({
queryKey: [`userQuery${address?.toLowerCase()}`],
enabled: isEnabled,
staleTime: STALE_TIME,
queryFn: async () =>
await graphqlBatcher.fetch({
id: crypto.randomUUID(),
Expand Down
3 changes: 2 additions & 1 deletion web/src/hooks/queries/useVotingHistory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";

import { REFETCH_INTERVAL } from "consts/index";
import { REFETCH_INTERVAL, STALE_TIME } from "consts/index";
import { useGraphqlBatcher } from "context/GraphqlBatcher";

import { graphql } from "src/graphql";
Expand Down Expand Up @@ -59,6 +59,7 @@ export const useVotingHistory = (disputeID?: string) => {
queryKey: [`VotingHistory${disputeID}`],
enabled: isEnabled,
refetchInterval: REFETCH_INTERVAL,
staleTime: STALE_TIME,
queryFn: async () =>
await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: votingHistoryQuery, variables: { disputeID } }),
});
Expand Down
54 changes: 53 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4510,6 +4510,19 @@ __metadata:
languageName: node
linkType: hard

"@graphql-tools/batch-execute@npm:^9.0.11":
version: 9.0.11
resolution: "@graphql-tools/batch-execute@npm:9.0.11"
dependencies:
"@graphql-tools/utils": "npm:^10.7.0"
dataloader: "npm:^2.2.3"
tslib: "npm:^2.8.1"
peerDependencies:
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
checksum: 10/6180424a5fa36a446baa665a92cff0332a566b1bd7481e2641c9d0aa2a7a47a24d21a9b90bb3d7f4c0d5a7331fc9e623fe43746f07e5eb0654419a29d860a940
languageName: node
linkType: hard

"@graphql-tools/code-file-loader@npm:^8.0.0":
version: 8.0.1
resolution: "@graphql-tools/code-file-loader@npm:8.0.1"
Expand Down Expand Up @@ -4837,6 +4850,20 @@ __metadata:
languageName: node
linkType: hard

"@graphql-tools/utils@npm:^10.7.0, @graphql-tools/utils@npm:^10.7.2":
version: 10.7.2
resolution: "@graphql-tools/utils@npm:10.7.2"
dependencies:
"@graphql-typed-document-node/core": "npm:^3.1.1"
cross-inspect: "npm:1.0.1"
dset: "npm:^3.1.4"
tslib: "npm:^2.4.0"
peerDependencies:
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
checksum: 10/b4725b081e5ff5c1441036db76ce907a6fe9b4c94aa9ceb070f75541b2297c3cccaa182f91d214f9abe6d89df33d8df51e055afbc4e382b01e8d8fb7c2f6edf6
languageName: node
linkType: hard

"@graphql-tools/wrap@npm:^10.0.0":
version: 10.0.0
resolution: "@graphql-tools/wrap@npm:10.0.0"
Expand Down Expand Up @@ -5609,6 +5636,8 @@ __metadata:
"@eslint/js": "npm:^9.15.0"
"@graphql-codegen/cli": "npm:^5.0.3"
"@graphql-codegen/client-preset": "npm:^4.5.1"
"@graphql-tools/batch-execute": "npm:^9.0.11"
"@graphql-tools/utils": "npm:^10.7.2"
"@kleros/kleros-app": "workspace:^"
"@kleros/kleros-sdk": "workspace:^"
"@kleros/kleros-v2-contracts": "workspace:^"
Expand Down Expand Up @@ -15796,6 +15825,15 @@ __metadata:
languageName: node
linkType: hard

"cross-inspect@npm:1.0.1":
version: 1.0.1
resolution: "cross-inspect@npm:1.0.1"
dependencies:
tslib: "npm:^2.4.0"
checksum: 10/7c1e02e0a9670b62416a3ea1df7ae880fdad3aa0a857de8932c4e5f8acd71298c7e3db9da8e9da603f5692cd1879938f5e72e34a9f5d1345987bef656d117fc1
languageName: node
linkType: hard

"cross-spawn@npm:7.0.3":
version: 7.0.3
resolution: "cross-spawn@npm:7.0.3"
Expand Down Expand Up @@ -16314,6 +16352,13 @@ __metadata:
languageName: node
linkType: hard

"dataloader@npm:^2.2.3":
version: 2.2.3
resolution: "dataloader@npm:2.2.3"
checksum: 10/83fe6259abe00ae64c5f48252ef59d8e5fcabda9fd4d26685f14a76eeca596bf6f9500d9f22a0094c50c3ea782a0977728f9367e232dfa0fdb5c9d646de279b2
languageName: node
linkType: hard

"date-fns@npm:^1.27.2":
version: 1.30.1
resolution: "date-fns@npm:1.30.1"
Expand Down Expand Up @@ -17170,6 +17215,13 @@ __metadata:
languageName: node
linkType: hard

"dset@npm:^3.1.4":
version: 3.1.4
resolution: "dset@npm:3.1.4"
checksum: 10/6268c9e2049c8effe6e5a1952f02826e8e32468b5ced781f15f8f3b1c290da37626246fec014fbdd1503413f981dff6abd8a4c718ec9952fd45fccb6ac9de43f
languageName: node
linkType: hard

"duplexer3@npm:^0.1.4":
version: 0.1.5
resolution: "duplexer3@npm:0.1.5"
Expand Down Expand Up @@ -34460,7 +34512,7 @@ __metadata:
languageName: node
linkType: hard

"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0, tslib@npm:^2.6.2, tslib@npm:^2.6.3":
"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0, tslib@npm:^2.6.2, tslib@npm:^2.6.3, tslib@npm:^2.8.1":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7
Expand Down
Loading