import { startTransition, useState, type ReactNode } from "react"; import type { Source, SourceInspectionToolDetail, } from "@executor/react"; import { SecretReferenceField, defineExecutorPluginHttpApiClient, Result, useAtomSet, useAtomValue, useExecutorMutation, useLocalInstallation, useSource, } from "@executor/react"; import { Alert, Badge, Button, Card, IconPencil, Input, Label, Select, SourceToolExplorer, Textarea, parseSourceToolExplorerSearch, useSourcePluginRouteParams, type SourceToolExplorerSearch, useSourcePluginNavigation, useSourcePluginSearch, } from "@executor/react/plugins"; import { graphqlHttpApiExtension, } from "@executor/plugin-graphql-http"; import { type GraphqlConnectInput, type GraphqlConnectionAuth, } from "@executor/plugin-graphql-shared"; const getGraphqlHttpClient = defineExecutorPluginHttpApiClient<"GraphqlReactHttpClient">()( "GraphqlReactHttpClient", [graphqlHttpApiExtension] as const, ); type GraphqlToolRouteParams = { sourceId: string; toolPath?: string; }; const defaultGraphqlInput = (): GraphqlConnectInput => ({ name: "My GraphQL Source", endpoint: "https://example.com/graphql", defaultHeaders: null, auth: { kind: "none", }, }); const DEFAULT_BEARER_HEADER_NAME = "Authorization"; const DEFAULT_BEARER_PREFIX = "Bearer "; const presetString = ( search: Record, key: string, ): string | null => { const value = search[key]; return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; }; const firstSearchString = ( search: Record, keys: ReadonlyArray, ): string | null => { for (const key of keys) { const value = presetString(search, key); if (value) { return value; } } return null; }; const graphqlInputFromSearch = ( search: Record, ): GraphqlConnectInput => { const defaults = defaultGraphqlInput(); return { ...defaults, name: presetString(search, "presetName") ?? defaults.name, endpoint: firstSearchString(search, [ "presetEndpoint", "endpoint", "inputUrl", "pastedUrl", "url", ]) ?? defaults.endpoint, }; }; const parseStringMap = (value: string): Record | null => { const trimmed = value.trim(); if (!trimmed) { return null; } const parsed = JSON.parse(trimmed) as unknown; if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("Headers must be a JSON object."); } const entries = Object.entries(parsed as Record); if (!entries.every(([, entry]) => typeof entry === "string")) { throw new Error("All header values must be strings."); } return Object.fromEntries(entries as Array<[string, string]>); }; const stringifyStringMap = ( value: Record | null | undefined, ): string => !value || Object.keys(value).length === 0 ? "" : JSON.stringify(value, null, 2); const secretValue = (input: GraphqlConnectionAuth): string => input.kind === "bearer" ? JSON.stringify(input.tokenSecretRef) : ""; const bearerHeaderNameValue = (input: GraphqlConnectionAuth): string => input.kind === "bearer" ? input.headerName?.trim() || DEFAULT_BEARER_HEADER_NAME : DEFAULT_BEARER_HEADER_NAME; const bearerPrefixValue = (input: GraphqlConnectionAuth): string => input.kind === "bearer" ? input.prefix ?? DEFAULT_BEARER_PREFIX : DEFAULT_BEARER_PREFIX; const trimToNull = (value: string): string | null => { const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; }; const authFromSecretValue = ( authKind: GraphqlConnectionAuth["kind"], value: string, headerName: string, prefix: string, ): GraphqlConnectionAuth => { if (authKind === "none") { return { kind: "none", }; } if (!value) { throw new Error("Select a secret for bearer auth."); } return { kind: "bearer", tokenSecretRef: JSON.parse(value) as GraphqlConnectionAuth & { tokenSecretRef: never }["tokenSecretRef"], headerName: trimToNull(headerName), prefix: prefix === DEFAULT_BEARER_PREFIX ? null : prefix, }; }; const graphqlToolRoutePath = (toolPath: string): string => `tool/${encodeURIComponent(toolPath)}`; const readGraphqlToolPath = (params: GraphqlToolRouteParams): string | null => { if (!params.toolPath || params.toolPath.length === 0) { return null; } try { return decodeURIComponent(params.toolPath); } catch { return params.toolPath; } }; function GraphqlSourceForm(props: { initialValue: GraphqlConnectInput; mode: "create" | "edit"; onSubmit: (input: GraphqlConnectInput) => Promise; }) { const submitMutation = useExecutorMutation(props.onSubmit); const [name, setName] = useState(props.initialValue.name); const [endpoint, setEndpoint] = useState(props.initialValue.endpoint); const [headersText, setHeadersText] = useState( stringifyStringMap(props.initialValue.defaultHeaders), ); const [authKind, setAuthKind] = useState( props.initialValue.auth.kind, ); const [secretRef, setSecretRef] = useState(secretValue(props.initialValue.auth)); const [authHeaderName, setAuthHeaderName] = useState( bearerHeaderNameValue(props.initialValue.auth), ); const [authPrefix, setAuthPrefix] = useState( bearerPrefixValue(props.initialValue.auth), ); const [error, setError] = useState(null); return (
setName(event.target.value)} />
setEndpoint(event.target.value)} className="font-mono text-xs" />