History

Keep source forms on local error messages

Rhys Sullivan committed 6 months ago

Commit 2256b0c1 parent dfda4fb2 files · +28 −36Browse files

Unified · Split

2 files
  1. packages/plugins/graphql/src/react/AddGraphqlSource.tsx +12 −8
  2. packages/plugins/mcp/src/react/AddMcpSource.tsx +16 −28
packages/plugins/graphql/src/react/AddGraphqlSource.tsx+12−8
@@ -1,8 +1,9 @@
1import { useCallback, useState } from "react";1import { useCallback, useState } from "react";
2import { useAtomSet } from "@effect/atom-react";2import { useAtomSet } from "@effect/atom-react";
3import * as Exit from "effect/Exit";3import * as Exit from "effect/Exit";
4import * as Option from "effect/Option";
5import * as Schema from "effect/Schema";
4 6
5import { useErrorMessageFromExit } from "@executor-js/react/api/error-reporting";
6import { useScope } from "@executor-js/react/api/scope-context";7import { useScope } from "@executor-js/react/api/scope-context";
7import { sourceWriteKeys } from "@executor-js/react/api/reactivity-keys";8import { sourceWriteKeys } from "@executor-js/react/api/reactivity-keys";
8import {9import {
@@ -44,6 +45,15 @@ import { addGraphqlSourceOptimistic } from "./atoms"; · 35 unmodified lines
44import { initialGraphqlCredentials } from "./defaults";45import { initialGraphqlCredentials } from "./defaults";
45import type { GraphqlCredentialInput } from "../sdk/types";46import type { GraphqlCredentialInput } from "../sdk/types";
46 47
48const ErrorMessage = Schema.Struct({ message: Schema.String });
49const decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);
50
51const errorMessageFromExit = (exit: Exit.Exit<unknown, unknown>, fallback: string): string =>
52 Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
53 onNone: () => fallback,
54 onSome: ({ message }) => message,
55 });
56
47type AuthMode = "none" | "oauth2";57type AuthMode = "none" | "oauth2";
48 58
49export default function AddGraphqlSource(props: {59export default function AddGraphqlSource(props: {
@@ -60,7 +70,6 @@ export default function AddGraphqlSource(props: { · 10 unmodified lines
60 const [addError, setAddError] = useState<string | null>(null);70 const [addError, setAddError] = useState<string | null>(null);
61 const [authMode, setAuthMode] = useState<AuthMode>("none");71 const [authMode, setAuthMode] = useState<AuthMode>("none");
62 const [tokens, setTokens] = useState<OAuthCompletionPayload | null>(null);72 const [tokens, setTokens] = useState<OAuthCompletionPayload | null>(null);
63 const errorMessageFromExit = useErrorMessageFromExit();
64 73
65 const scopeId = useScope();74 const scopeId = useScope();
66 const { credentialTargetScope: requestCredentialTargetScope } = useCredentialTargetScope();75 const { credentialTargetScope: requestCredentialTargetScope } = useCredentialTargetScope();
@@ -161,12 +170,7 @@ export default function AddGraphqlSource(props: { · 94 unmodified lines
161 reactivityKeys: sourceWriteKeys,170 reactivityKeys: sourceWriteKeys,
162 });171 });
163 if (Exit.isFailure(exit)) {172 if (Exit.isFailure(exit)) {
164 setAddError(173 setAddError(errorMessageFromExit(exit, "Failed to add source"));
165 errorMessageFromExit(exit, "Failed to add source", {
166 surface: "graphql_source",
167 action: "add_source",
168 }),
169 );
170 setAdding(false);174 setAdding(false);
171 return;175 return;
172 }176 }
packages/plugins/mcp/src/react/AddMcpSource.tsx+16−28
@@ -1,8 +1,9 @@
1import { useReducer, useCallback, useEffect, useRef, useState, type ReactNode } from "react";1import { useReducer, useCallback, useEffect, useRef, useState, type ReactNode } from "react";
2import { useAtomSet } from "@effect/atom-react";2import { useAtomSet } from "@effect/atom-react";
3import * as Exit from "effect/Exit";3import * as Exit from "effect/Exit";
4import * as Option from "effect/Option";
5import * as Schema from "effect/Schema";
4 6
5import { useErrorMessageFromExit } from "@executor-js/react/api/error-reporting";
6import { useScope } from "@executor-js/react/api/scope-context";7import { useScope } from "@executor-js/react/api/scope-context";
7import { Button } from "@executor-js/react/components/button";8import { Button } from "@executor-js/react/components/button";
8import {9import {
@@ -59,6 +60,15 @@ import { probeMcpEndpoint, addMcpSourceOptimistic } from "./atoms"; · 50 unmodified lines
59import { mcpPresets, type McpPreset } from "../sdk/presets";60import { mcpPresets, type McpPreset } from "../sdk/presets";
60import { MCP_OAUTH_CONNECTION_SLOT, type McpCredentialInput } from "../sdk/types";61import { MCP_OAUTH_CONNECTION_SLOT, type McpCredentialInput } from "../sdk/types";
61 62
63const ErrorMessage = Schema.Struct({ message: Schema.String });
64const decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);
65
66const errorMessageFromExit = (exit: Exit.Exit<unknown, unknown>, fallback: string): string =>
67 Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
68 onNone: () => fallback,
69 onSome: ({ message }) => message,
70 });
71
62// ---------------------------------------------------------------------------72// ---------------------------------------------------------------------------
63// Preset lookup73// Preset lookup
64// ---------------------------------------------------------------------------74// ---------------------------------------------------------------------------
@@ -291,7 +301,6 @@ export default function AddMcpSource(props: { · 226 unmodified lines
291 const doAdd = useAtomSet(addMcpSourceOptimistic(scopeId), {301 const doAdd = useAtomSet(addMcpSourceOptimistic(scopeId), {
292 mode: "promiseExit",302 mode: "promiseExit",
293 });303 });
294 const errorMessageFromExit = useErrorMessageFromExit();
295 const secretList = useSecretPickerSecrets();304 const secretList = useSecretPickerSecrets();
296 const oauth = useOAuthPopupFlow<OAuthCompletionPayload>({305 const oauth = useOAuthPopupFlow<OAuthCompletionPayload>({
297 popupName: "mcp-oauth",306 popupName: "mcp-oauth",
@@ -348,16 +357,13 @@ export default function AddMcpSource(props: { · 50 unmodified lines
348 if (Exit.isFailure(exit)) {357 if (Exit.isFailure(exit)) {
349 dispatch({358 dispatch({
350 type: "probe-fail",359 type: "probe-fail",
351 error: errorMessageFromExit(exit, "Failed to connect", {360 error: errorMessageFromExit(exit, "Failed to connect"),
352 surface: "mcp_source",
353 action: "probe_remote_source",
354 }),
355 });361 });
356 return;362 return;
357 }363 }
358 setRemoteAuthMode(exit.value.requiresOAuth ? "oauth2" : "none");364 setRemoteAuthMode(exit.value.requiresOAuth ? "oauth2" : "none");
359 dispatch({ type: "probe-ok", probe: exit.value });365 dispatch({ type: "probe-ok", probe: exit.value });
360 }, [state.url, scopeId, doProbe, remoteCredentials, errorMessageFromExit]);366 }, [state.url, scopeId, doProbe, remoteCredentials]);
361 367
362 // Keep the latest handleProbe in a ref so the debounced effect can call it368 // Keep the latest handleProbe in a ref so the debounced effect can call it
363 // without depending on its identity (which changes every render).369 // without depending on its identity (which changes every render).
@@ -477,10 +483,7 @@ export default function AddMcpSource(props: { · 113 unmodified lines
477 if (Exit.isFailure(exit)) {483 if (Exit.isFailure(exit)) {
478 dispatch({484 dispatch({
479 type: "add-fail",485 type: "add-fail",
480 error: errorMessageFromExit(exit, "Failed to add source", {486 error: errorMessageFromExit(exit, "Failed to add source"),
481 surface: "mcp_source",
482 action: "add_remote_source",
483 }),
484 });487 });
485 return;488 return;
486 }489 }
@@ -496,7 +499,6 @@ export default function AddMcpSource(props: { · 9 unmodified lines
496 doAdd,499 doAdd,
497 props,500 props,
498 scopeId,501 scopeId,
499 errorMessageFromExit,
500 requestCredentialTargetScope,502 requestCredentialTargetScope,
501 oauthCredentialTargetScope,503 oauthCredentialTargetScope,
502 ]);504 ]);
@@ -547,26 +549,12 @@ export default function AddMcpSource(props: { · 44 unmodified lines
547 reactivityKeys: sourceWriteKeys,549 reactivityKeys: sourceWriteKeys,
548 });550 });
549 if (Exit.isFailure(exit)) {551 if (Exit.isFailure(exit)) {
550 setStdioError(552 setStdioError(errorMessageFromExit(exit, "Failed to add source"));
551 errorMessageFromExit(exit, "Failed to add source", {
552 surface: "mcp_source",
553 action: "add_stdio_source",
554 }),
555 );
556 setStdioAdding(false);553 setStdioAdding(false);
557 return;554 return;
558 }555 }
559 props.onComplete();556 props.onComplete();
560 }, [557 }, [stdioCommand, stdioArgs, stdioEnv, stdioIdentity, doAdd, scopeId, props]);
561 stdioCommand,
562 stdioArgs,
563 stdioEnv,
564 stdioIdentity,
565 doAdd,
566 scopeId,
567 props,
568 errorMessageFromExit,
569 ]);
570 558
571 // ---- Render ----559 // ---- Render ----
572 560