Skip to content

Commit 1622293

Browse files
committed
feat(web): add-subgraph-queries
1 parent 1e43686 commit 1622293

File tree

9 files changed

+234
-18
lines changed

9 files changed

+234
-18
lines changed

web/codegen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { graphqlUrl } from "utils/graphqlQueryFnHelper";
33

44
const config: CodegenConfig = {
55
overwrite: true,
6-
schema: [graphqlUrl(false), graphqlUrl(true)],
6+
schema: [graphqlUrl()],
77
documents: "./src/hooks/queries/*.ts",
88
generates: {
99
"./src/graphql/": {

web/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,22 @@
3838
"build-netlify": "scripts/runEnv.sh devnet 'node scripts/gitInfo.js && yarn generate && parcel build'",
3939
"check-style": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
4040
"check-types": "tsc --noEmit",
41-
"generateWhenSubgraphIsReadySubstituteGenerateWithThis": "yarn generate:gql && yarn generate:hooks",
42-
"generate": "yarn generate:hooks",
41+
"generate": "yarn generate:gql && yarn generate:hooks",
4342
"generate:gql": "graphql-codegen --require tsconfig-paths/register",
4443
"generate:hooks": "NODE_NO_WARNINGS=1 wagmi generate",
4544
"generate:supabase": "scripts/generateSupabaseTypes.sh"
4645
},
4746
"prettier": "@kleros/curate-v2-prettier-config",
4847
"devDependencies": {
4948
"@graphql-codegen/cli": "^4.0.1",
50-
"@graphql-codegen/client-preset": "^4.1.0",
49+
"@graphql-codegen/client-preset": "^4.2.0",
5150
"@netlify/functions": "^1.6.0",
5251
"@parcel/transformer-svg-react": "2.11.0",
5352
"@parcel/watcher": "~2.2.0",
5453
"@types/amqplib": "^0.10.4",
5554
"@types/busboy": "^1.5.3",
56-
"@types/react": "18.2.0",
57-
"@types/react-dom": "^18.2.18",
55+
"@types/react": "^18.2.59",
56+
"@types/react-dom": "^18.2.19",
5857
"@types/react-modal": "^3.16.3",
5958
"@types/styled-components": "^5.1.34",
6059
"@typescript-eslint/eslint-plugin": "^5.62.0",
@@ -80,6 +79,7 @@
8079
"@tanstack/react-query": "^4.28.0",
8180
"@web3modal/ethereum": "^2.7.1",
8281
"@web3modal/react": "^2.2.2",
82+
"@yornaath/batshit": "^0.9.0",
8383
"amqplib": "^0.10.3",
8484
"chart.js": "^3.9.1",
8585
"chartjs-adapter-moment": "^1.0.1",

web/src/app.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,26 @@ import RefetchOnBlock from "context/RefetchOnBlock";
1111
import Layout from "layout/index";
1212
import Home from "./pages/Home";
1313
import AllLists from "./pages/AllLists";
14+
import GraphqlBatcherProvider from "./context/GraphqlBatcher";
1415

1516
const App: React.FC = () => {
1617
return (
1718
<StyledComponentsProvider>
1819
<QueryClientProvider>
19-
<RefetchOnBlock />
20-
<Web3Provider>
21-
<IsListProvider>
22-
<SentryRoutes>
23-
<Route path="/" element={<Layout />}>
24-
<Route index element={<Home />} />
25-
<Route path="lists/*" element={<AllLists />} />
26-
<Route path="*" element={<h1>404 not found</h1>} />
27-
</Route>
28-
</SentryRoutes>
29-
</IsListProvider>
30-
</Web3Provider>
20+
<GraphqlBatcherProvider>
21+
<RefetchOnBlock />
22+
<Web3Provider>
23+
<IsListProvider>
24+
<SentryRoutes>
25+
<Route path="/" element={<Layout />}>
26+
<Route index element={<Home />} />
27+
<Route path="lists/*" element={<AllLists />} />
28+
<Route path="*" element={<h1>404 not found</h1>} />
29+
</Route>
30+
</SentryRoutes>
31+
</IsListProvider>
32+
</Web3Provider>
33+
</GraphqlBatcherProvider>
3134
</QueryClientProvider>
3235
</StyledComponentsProvider>
3336
);

web/src/context/GraphqlBatcher.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React, { useMemo, createContext, useContext } from "react";
2+
import { arbitrumSepolia } from "wagmi/chains";
3+
import { request } from "graphql-request";
4+
import { create, windowedFiniteBatchScheduler, Batcher } from "@yornaath/batshit";
5+
import { TypedDocumentNode } from "@graphql-typed-document-node/core";
6+
import { getGraphqlUrl } from "utils/getGraphqlUrl";
7+
import { debounceErrorToast } from "utils/debounceErrorToast";
8+
9+
interface IGraphqlBatcher {
10+
graphqlBatcher: Batcher<any, IQuery>;
11+
}
12+
13+
interface IQuery {
14+
id: string;
15+
document: TypedDocumentNode<any, any>;
16+
variables: Record<string, any>;
17+
isDisputeTemplate?: boolean;
18+
chainId?: number;
19+
}
20+
21+
const Context = createContext<IGraphqlBatcher | undefined>(undefined);
22+
23+
const fetcher = async (queries: IQuery[]) => {
24+
const promises = queries.map(async ({ id, document, variables, isDisputeTemplate, chainId }) => {
25+
const url = getGraphqlUrl(chainId ?? arbitrumSepolia.id);
26+
try {
27+
return request(url, document, variables).then((result) => ({ id, result }));
28+
} catch (error) {
29+
console.error("Graph error: ", { error });
30+
debounceErrorToast("Graph query error: failed to fetch data.");
31+
return { id, result: {} };
32+
}
33+
});
34+
const data = await Promise.all(promises);
35+
return data;
36+
};
37+
38+
const GraphqlBatcherProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
39+
const graphqlBatcher = create({
40+
fetcher,
41+
resolver: (results, query) => results.find((result) => result.id === query.id)!["result"],
42+
scheduler: windowedFiniteBatchScheduler({
43+
windowMs: 100,
44+
maxBatchSize: 5,
45+
}),
46+
});
47+
return <Context.Provider value={useMemo(() => ({ graphqlBatcher }), [graphqlBatcher])}>{children}</Context.Provider>;
48+
};
49+
50+
export const useGraphqlBatcher = () => {
51+
const context = useContext(Context);
52+
if (!context) {
53+
throw new Error("Context Provider not found.");
54+
}
55+
return context;
56+
};
57+
58+
export default GraphqlBatcherProvider;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { graphql } from "src/graphql";
2+
import { useQuery } from "@tanstack/react-query";
3+
import { useGraphqlBatcher } from "context/GraphqlBatcher";
4+
import { ItemDetailsQuery } from "src/graphql/graphql";
5+
export type { ItemDetailsQuery };
6+
7+
const itemDetailsQuery = graphql(`
8+
query ItemDetails($itemID: ID!) {
9+
item(id: $itemID) {
10+
status
11+
disputed
12+
latestChallenger
13+
latestRequester
14+
registryAddress
15+
props {
16+
type
17+
label
18+
description
19+
isIdentifier
20+
value
21+
}
22+
}
23+
}
24+
`);
25+
26+
export const useItemDetailsQuery = (id?: string | number) => {
27+
const isEnabled = id !== undefined;
28+
const { graphqlBatcher } = useGraphqlBatcher();
29+
30+
return useQuery({
31+
queryKey: ["refetchOnBlock", `itemDetailsQuery${id}`],
32+
enabled: isEnabled,
33+
queryFn: async () =>
34+
await graphqlBatcher.fetch({
35+
id: crypto.randomUUID(),
36+
document: itemDetailsQuery,
37+
variables: { itemID: id?.toString() },
38+
}),
39+
});
40+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { graphql } from "src/graphql";
2+
import { useQuery } from "@tanstack/react-query";
3+
import { OrderDirection, Item_Filter, ItemDetailsFragment } from "src/graphql/graphql";
4+
import { useGraphqlBatcher } from "context/GraphqlBatcher";
5+
import { isUndefined } from "utils/index";
6+
export type { ItemDetailsFragment };
7+
export const itemFragment = graphql(`
8+
fragment ItemDetails on Item {
9+
id
10+
status
11+
}
12+
`);
13+
14+
const itemsQueryWhere = graphql(`
15+
query ItemsPageWhere($skip: Int, $where: Item_filter, $orderDirection: OrderDirection, $first: Int) {
16+
items(
17+
first: $first
18+
skip: $skip
19+
orderBy: latestRequestSubmissionTime
20+
orderDirection: $orderDirection
21+
where: $where
22+
) {
23+
...ItemDetails
24+
}
25+
}
26+
`);
27+
28+
const itemsQuery = graphql(`
29+
query ItemsPage($skip: Int, $orderDirection: OrderDirection, $first: Int) {
30+
items(first: $first, skip: $skip, orderBy: latestRequestSubmissionTime, orderDirection: $orderDirection) {
31+
...ItemDetails
32+
}
33+
}
34+
`);
35+
36+
export const useItemsQuery = (skip = 0, first = 3, where?: Item_Filter, sortOrder?: OrderDirection) => {
37+
const { graphqlBatcher } = useGraphqlBatcher();
38+
39+
return useQuery({
40+
queryKey: [`useItemsQuery`, skip, where, sortOrder, first],
41+
queryFn: async () =>
42+
await graphqlBatcher.fetch({
43+
id: crypto.randomUUID(),
44+
document: isUndefined(where) ? itemsQuery : itemsQueryWhere,
45+
variables: {
46+
first,
47+
skip,
48+
where,
49+
orderDirection: sortOrder ?? "desc",
50+
},
51+
}),
52+
});
53+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { graphql } from "src/graphql";
2+
import { useQuery } from "@tanstack/react-query";
3+
import { OrderDirection, Registry_Filter, RegistryDetailsFragment } from "src/graphql/graphql";
4+
import { useGraphqlBatcher } from "context/GraphqlBatcher";
5+
import { isUndefined } from "utils/index";
6+
export type { RegistryDetailsFragment };
7+
8+
export const registryFragment = graphql(`
9+
fragment RegistryDetails on Registry {
10+
id
11+
}
12+
`);
13+
14+
const registriesQueryWhere = graphql(`
15+
query RegistriesPageWhere($skip: Int, $where: Registry_filter, $orderDirection: OrderDirection, $first: Int) {
16+
registries(first: $first, skip: $skip, orderDirection: $orderDirection, where: $where) {
17+
...RegistryDetails
18+
}
19+
}
20+
`);
21+
22+
const registriesQuery = graphql(`
23+
query RegistriesPage($skip: Int, $orderDirection: OrderDirection, $first: Int) {
24+
registries(first: $first, skip: $skip, orderDirection: $orderDirection) {
25+
...RegistryDetails
26+
}
27+
}
28+
`);
29+
30+
export const useRegistriesQuery = (skip = 0, first = 3, where?: Registry_Filter, sortOrder?: OrderDirection) => {
31+
const { graphqlBatcher } = useGraphqlBatcher();
32+
33+
return useQuery({
34+
queryKey: [`useRegistriesQuery`, skip, where, sortOrder, first],
35+
queryFn: async () =>
36+
await graphqlBatcher.fetch({
37+
id: crypto.randomUUID(),
38+
document: isUndefined(where) ? registriesQuery : registriesQueryWhere,
39+
variables: {
40+
first,
41+
skip,
42+
where,
43+
orderDirection: sortOrder ?? "desc",
44+
},
45+
}),
46+
});
47+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { toast } from "react-toastify";
2+
import { OPTIONS as toastOptions } from "utils/wrapWithToast";
3+
4+
let timeoutId: NodeJS.Timeout;
5+
export const debounceErrorToast = (msg: string) => {
6+
if (timeoutId) clearTimeout(timeoutId);
7+
8+
timeoutId = setTimeout(() => {
9+
toast.error(msg, toastOptions);
10+
}, 5000);
11+
};

web/src/utils/getGraphqlUrl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { arbitrumSepolia } from "wagmi/chains";
2+
3+
export const getGraphqlUrl = (chainId: number = arbitrumSepolia.id) =>
4+
process.env.REACT_APP_ARBSEPOLIA_SUBGRAPH ?? "Wrong";

0 commit comments

Comments
 (0)