Skip to content

Commit

Permalink
Merge pull request #1850 from kleros/refactor/graphql-batching-and-op…
Browse files Browse the repository at this point in the history
…timisations

Refactor/graphql batching and optimisations
  • Loading branch information
alcercu authored Jan 27, 2025
2 parents ec16bb7 + 11c8057 commit d4aaa7c
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 20 deletions.
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

0 comments on commit d4aaa7c

Please sign in to comment.