Skip to content

Commit e01f2aa

Browse files
committed
Share HTTP source credential state
1 parent 6dec2e1 commit e01f2aa

9 files changed

Lines changed: 715 additions & 442 deletions

File tree

packages/plugins/graphql/src/react/AddGraphqlSource.tsx

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@ import * as Schema from "effect/Schema";
77
import { useScope } from "@executor-js/react/api/scope-context";
88
import { sourceWriteKeys } from "@executor-js/react/api/reactivity-keys";
99
import {
10-
HttpCredentials,
11-
httpCredentialsValid,
12-
serializeScopedHttpCredentials,
13-
serializeHttpCredentials,
14-
type HttpCredentialsState,
15-
} from "@executor-js/react/plugins/http-credentials";
10+
HttpCredentialEditor,
11+
useHttpCredentialEditorController,
12+
} from "@executor-js/react/plugins/http-credential-state";
1613
import {
1714
sourceDisplayNameFromUrl,
1815
slugifyNamespace,
@@ -31,7 +28,6 @@ import {
3128
} from "@executor-js/react/plugins/credential-target-scope";
3229
import { useSecretPickerSecrets } from "@executor-js/react/plugins/use-secret-picker-secrets";
3330
import { Button } from "@executor-js/react/components/button";
34-
import { FilterTabs } from "@executor-js/react/components/filter-tabs";
3531
import { FloatActions } from "@executor-js/react/components/float-actions";
3632
import { Spinner } from "@executor-js/react/components/spinner";
3733
import { addGraphqlSourceOptimistic } from "./atoms";
@@ -59,7 +55,6 @@ export default function AddGraphqlSource(props: {
5955
const identity = useSourceIdentity({
6056
fallbackName: sourceDisplayNameFromUrl(endpoint, "GraphQL") ?? "",
6157
});
62-
const [credentials, setCredentials] = useState<HttpCredentialsState>(initialGraphqlCredentials);
6358
const [adding, setAdding] = useState(false);
6459
const [addError, setAddError] = useState<string | null>(null);
6560
const [authMode, setAuthMode] = useState<AuthMode>("none");
@@ -76,14 +71,22 @@ export default function AddGraphqlSource(props: {
7671
mode: "promiseExit",
7772
});
7873
const secretList = useSecretPickerSecrets();
74+
const credentialEditor = useHttpCredentialEditorController({
75+
initialCredentials: initialGraphqlCredentials(),
76+
targetScope: requestCredentialTargetScope,
77+
existingSecrets: secretList,
78+
sourceName: identity.name,
79+
credentialScopeOptions,
80+
bindingScopeOptions: credentialScopeOptions,
81+
});
7982
const oauth = useOAuthPopupFlow({
8083
popupName: "graphql-oauth",
8184
startErrorMessage: "Failed to start OAuth",
8285
});
8386

8487
const canAdd =
8588
endpoint.trim().length > 0 &&
86-
httpCredentialsValid(credentials) &&
89+
credentialEditor.state.valid &&
8790
(authMode === "none" || tokens !== null) &&
8891
!oauth.busy;
8992

@@ -99,15 +102,13 @@ export default function AddGraphqlSource(props: {
99102
}, [endpoint, identity.name, identity.namespace]);
100103

101104
const handleOAuth = useCallback(async () => {
102-
if (!endpoint.trim() || !httpCredentialsValid(credentials)) return;
105+
if (!endpoint.trim() || !credentialEditor.state.valid) return;
103106
setAddError(null);
104107
const { trimmedEndpoint, namespace, displayName } = sourceIdentity();
105-
const { headers, queryParams } = serializeHttpCredentials(credentials);
106108
await oauth.start({
107109
payload: {
108110
endpoint: trimmedEndpoint,
109-
...(Object.keys(headers).length > 0 ? { headers } : {}),
110-
...(Object.keys(queryParams).length > 0 ? { queryParams } : {}),
111+
...credentialEditor.serialized.requestFields,
111112
redirectUrl: oauthCallbackUrl(),
112113
connectionId: oauthConnectionId({ pluginId: "graphql", namespace }),
113114
tokenScope: oauthCredentialTargetScope,
@@ -124,15 +125,12 @@ export default function AddGraphqlSource(props: {
124125
},
125126
onError: setAddError,
126127
});
127-
}, [endpoint, credentials, oauth, sourceIdentity, oauthCredentialTargetScope]);
128+
}, [endpoint, credentialEditor, oauth, sourceIdentity, oauthCredentialTargetScope]);
128129

129130
const handleAdd = async () => {
130131
setAdding(true);
131132
setAddError(null);
132-
const { headers: headerMap, queryParams } = serializeScopedHttpCredentials(
133-
credentials,
134-
requestCredentialTargetScope,
135-
);
133+
const requestCredentials = credentialEditor.serialized.scopedFields<GraphqlCredentialInput>();
136134

137135
const { trimmedEndpoint, namespace, displayName } = sourceIdentity();
138136
const exit = await doAdd({
@@ -142,12 +140,7 @@ export default function AddGraphqlSource(props: {
142140
endpoint: trimmedEndpoint,
143141
name: displayName,
144142
namespace,
145-
...(Object.keys(headerMap).length > 0 ? { headers: headerMap } : {}),
146-
...(Object.keys(queryParams).length > 0
147-
? {
148-
queryParams: queryParams as Record<string, GraphqlCredentialInput>,
149-
}
150-
: {}),
143+
...requestCredentials,
151144
credentialTargetScope:
152145
authMode === "oauth2" && tokens
153146
? oauthCredentialTargetScope
@@ -177,35 +170,26 @@ export default function AddGraphqlSource(props: {
177170

178171
<GraphqlSourceFields endpoint={endpoint} onEndpointChange={setEndpoint} identity={identity} />
179172

180-
<HttpCredentials.Root
181-
credentials={credentials}
182-
onChange={setCredentials}
183-
existingSecrets={secretList}
184-
sourceName={identity.name}
185-
targetScope={requestCredentialTargetScope}
186-
credentialScopeOptions={credentialScopeOptions}
187-
bindingScopeOptions={credentialScopeOptions}
188-
>
189-
<HttpCredentials.Headers />
190-
<HttpCredentials.QueryParams />
191-
</HttpCredentials.Root>
173+
<HttpCredentialEditor.Provider controller={credentialEditor}>
174+
<HttpCredentialEditor.Frame>
175+
<HttpCredentialEditor.Headers />
176+
<HttpCredentialEditor.QueryParams />
177+
</HttpCredentialEditor.Frame>
178+
</HttpCredentialEditor.Provider>
192179

193180
{/* Temporarily hidden while we revisit GraphQL OAuth discovery and UX. */}
194181
<section className="hidden space-y-2.5">
195-
<div className="flex items-center justify-between gap-3">
196-
<span className="text-sm font-medium text-foreground">Authentication</span>
197-
<FilterTabs<AuthMode>
198-
tabs={[
199-
{ value: "none", label: "None" },
200-
{ value: "oauth2", label: "OAuth" },
201-
]}
202-
value={authMode}
203-
onChange={(value) => {
204-
setAuthMode(value);
205-
setTokens(null);
206-
}}
207-
/>
208-
</div>
182+
<HttpCredentialEditor.Auth.Root
183+
label="Authentication"
184+
value={authMode}
185+
onValueChange={(value) => {
186+
setAuthMode(value === "oauth2" ? "oauth2" : "none");
187+
setTokens(null);
188+
}}
189+
>
190+
<HttpCredentialEditor.Auth.None />
191+
<HttpCredentialEditor.Auth.OAuth value="oauth2" label="OAuth" />
192+
</HttpCredentialEditor.Auth.Root>
209193

210194
{authMode === "oauth2" && (
211195
<CredentialUsageRow
@@ -233,7 +217,7 @@ export default function AddGraphqlSource(props: {
233217
size="sm"
234218
className="ml-auto h-7 px-2 text-xs"
235219
onClick={() => void handleOAuth()}
236-
disabled={!endpoint.trim() || !httpCredentialsValid(credentials) || oauth.busy}
220+
disabled={!endpoint.trim() || !credentialEditor.state.valid || oauth.busy}
237221
>
238222
{oauth.busy ? "Signing in..." : tokens ? "Reconnect" : "Sign in"}
239223
</Button>

0 commit comments

Comments
 (0)