Skip to content

Commit f848f71

Browse files
authored
Create Graphql Fetcher code component (#5555)
* feat(plasmic): create graphql-fetcher code component * fix(eslint): hoist eslint packages to root for vscode plugin to work
1 parent bc11643 commit f848f71

File tree

5 files changed

+203
-12
lines changed

5 files changed

+203
-12
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import {
2+
CommonDataProviderProps,
3+
CommonDataProviderRegistration,
4+
DataProviderView,
5+
} from "@/components/dataprovider/provider-view";
6+
import { CodeComponentMeta } from "@plasmicapp/loader-nextjs";
7+
import React from "react";
8+
import useSWR from "swr";
9+
10+
const DEFAULT_PLASMIC_KEY = "osoData";
11+
12+
type CustomError = Error & {
13+
info: Record<string, any>;
14+
status: number;
15+
};
16+
17+
interface FetchProps {
18+
url?: string;
19+
method?: string;
20+
body?: string | object;
21+
headers?: Record<string, string>;
22+
}
23+
24+
type GraphqlFetcherProps = CommonDataProviderProps &
25+
FetchProps & {
26+
query?: { query?: string; variables?: object };
27+
queryKey?: string;
28+
varOverrides?: object;
29+
};
30+
31+
/**
32+
* Tries to return the JSON response, or else returns an object with a text key containing the response body text.
33+
*/
34+
async function performFetch({ url, method, body, headers }: FetchProps) {
35+
if (!url) {
36+
throw new Error("Please specify a URL to fetch");
37+
}
38+
39+
// Add default headers unless specified
40+
if (!headers) {
41+
headers = {};
42+
}
43+
const headerNamesLowercase = new Set(
44+
Object.keys(headers).map((headerName) => headerName.toLowerCase()),
45+
);
46+
if (!headerNamesLowercase.has("accept")) {
47+
headers["Accept"] = "application/json";
48+
}
49+
if (body && !headerNamesLowercase.has("content-type")) {
50+
headers["Content-Type"] = "application/json";
51+
}
52+
53+
const response = await fetch(url, {
54+
method,
55+
headers,
56+
body:
57+
body === undefined
58+
? body
59+
: typeof body === "string"
60+
? body
61+
: JSON.stringify(body),
62+
});
63+
64+
const text = await response.text();
65+
let json: Record<string, any> = { text };
66+
67+
try {
68+
json = JSON.parse(text);
69+
} catch {
70+
json = { text };
71+
}
72+
73+
// @see https://swr.vercel.app/docs/error-handling
74+
// If the status code is not in the range 200-299,
75+
// we still try to parse and throw it.
76+
if (!response.ok) {
77+
const error = new Error(response.statusText) as CustomError;
78+
// Attach extra info to the error object.
79+
error.info = json;
80+
error.status = response.status;
81+
throw error;
82+
}
83+
84+
return json;
85+
}
86+
87+
function GraphqlFetcher(props: GraphqlFetcherProps) {
88+
const {
89+
query,
90+
url,
91+
method,
92+
headers,
93+
queryKey,
94+
varOverrides,
95+
variableName,
96+
...rest
97+
} = props;
98+
99+
let fetchProps: FetchProps;
100+
if (method === "GET") {
101+
// https://graphql.org/learn/serving-over-http/#get-request-and-parameters
102+
const urlWithQueryParams = new URL(url ?? "");
103+
urlWithQueryParams.searchParams.set("query", query?.query ?? "{}");
104+
urlWithQueryParams.searchParams.set(
105+
"variables",
106+
JSON.stringify({ ...query?.variables, ...varOverrides }),
107+
);
108+
fetchProps = {
109+
url: urlWithQueryParams.toString(),
110+
method,
111+
headers,
112+
};
113+
} else {
114+
fetchProps = {
115+
body: { ...query, variables: { ...query?.variables, ...varOverrides } },
116+
url,
117+
method,
118+
headers,
119+
};
120+
}
121+
122+
const { data, mutate, error, isLoading } = useSWR(
123+
queryKey || JSON.stringify({ type: "GraphqlFetcher", ...fetchProps }),
124+
() => {
125+
if (!query || !url) {
126+
return;
127+
}
128+
return performFetch(fetchProps);
129+
},
130+
);
131+
return (
132+
<DataProviderView
133+
{...rest}
134+
variableName={variableName ?? DEFAULT_PLASMIC_KEY}
135+
formattedData={{ ...data, revalidate: mutate }}
136+
loading={isLoading}
137+
error={error}
138+
/>
139+
);
140+
}
141+
142+
const graphqlFetcherMeta: CodeComponentMeta<GraphqlFetcherProps> = {
143+
name: "graphql-fetcher",
144+
displayName: "GraphQL Fetcher",
145+
providesData: true,
146+
props: {
147+
query: {
148+
type: "code",
149+
lang: "graphql",
150+
headers: (props: GraphqlFetcherProps) => props.headers,
151+
endpoint: (props: GraphqlFetcherProps) => props.url ?? "",
152+
},
153+
varOverrides: {
154+
type: "object",
155+
displayName: "Override variables",
156+
description:
157+
"Pass in dynamic values for your query variables, as an object of key-values",
158+
},
159+
160+
url: {
161+
type: "string",
162+
defaultValue: "https://www.oso.xyz/api/v1/osograph",
163+
description: "Where to fetch the data from",
164+
},
165+
method: {
166+
type: "choice",
167+
options: [
168+
"GET",
169+
"DELETE",
170+
"CONNECT",
171+
"HEAD",
172+
"OPTIONS",
173+
"POST",
174+
"PUT",
175+
"TRACE",
176+
],
177+
defaultValue: "POST",
178+
description: "Method to use when fetching",
179+
},
180+
headers: {
181+
type: "object",
182+
description: "Request headers (as JSON object) to send",
183+
},
184+
queryKey: {
185+
type: "string",
186+
description:
187+
"A globally unique ID for this query, used for invalidating queries",
188+
invariantable: true,
189+
},
190+
...CommonDataProviderRegistration,
191+
},
192+
};
193+
194+
export { GraphqlFetcher, graphqlFetcherMeta };

apps/frontend/components/dataprovider/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import {
2323
OsoChatProvider,
2424
OsoChatProviderMeta,
2525
} from "@/components/dataprovider/oso-chat-provider";
26+
import {
27+
GraphqlFetcher,
28+
graphqlFetcherMeta,
29+
} from "@/components/dataprovider/graphql-fetcher";
2630

2731
export function registerAllDataProvider(PLASMIC: NextJsPlasmicComponentLoader) {
2832
// Global Context
@@ -34,4 +38,5 @@ export function registerAllDataProvider(PLASMIC: NextJsPlasmicComponentLoader) {
3438
PLASMIC.registerComponent(OsoDataProvider, OsoDataProviderMeta);
3539
PLASMIC.registerComponent(AuthRouter, AuthRouterMeta);
3640
PLASMIC.registerComponent(OsoChatProvider, OsoChatProviderMeta);
41+
PLASMIC.registerComponent(GraphqlFetcher, graphqlFetcherMeta);
3742
}

apps/frontend/components/dataprovider/oso-data-provider.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ function OsoDataProvider(props: OsoDataProviderProps) {
7676
}
7777
result[key] = await (client as any)[method](args);
7878
}
79-
console.log("OsoDataProvider:", props, data);
8079
return result;
8180
},
8281
{

pnpm-lock.yaml

Lines changed: 1 addition & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ onlyBuiltDependencies:
1414
- '@apollo/rover'
1515
- '@tailwindcss/oxide'
1616
- supabase
17+
18+
publicHoistPattern:
19+
- '*eslint*'

0 commit comments

Comments
 (0)