From 15639ef122107781e8f6a7db3417e3687c49b50a Mon Sep 17 00:00:00 2001 From: Mayank Saini Date: Fri, 5 Jun 2026 23:23:48 +0530 Subject: [PATCH 01/20] some fixes --- prisma/schema/nodes_github.prisma | 4 + public/logos/github.svg | 2 +- src/app/api/auth/github/callback/route.ts | 115 ++++++++++++++++ src/app/api/auth/github/start/route.ts | 31 +++++ src/components/github-connect-button.tsx | 123 +++++++++++++++++ src/config/node-components.ts | 5 + .../credentials/components/credential.tsx | 117 +++++++++++++++- .../credentials/hooks/use-credentials.ts | 5 + src/features/credentials/server/routers.ts | 42 +++++- .../components/base-execution-node.tsx | 128 +++++++++--------- .../github/components/repo-fields.tsx | 35 +++-- .../executions/components/github/dialog.tsx | 56 +++++++- .../executions/components/github/executor.ts | 10 +- .../executions/components/github/types.ts | 2 + .../triggers/components/base-trigger-node.tsx | 5 +- src/lib/github-auth.ts | 87 ++++++++++++ 16 files changed, 678 insertions(+), 89 deletions(-) create mode 100644 src/app/api/auth/github/callback/route.ts create mode 100644 src/app/api/auth/github/start/route.ts create mode 100644 src/components/github-connect-button.tsx create mode 100644 src/lib/github-auth.ts diff --git a/prisma/schema/nodes_github.prisma b/prisma/schema/nodes_github.prisma index cdda700..81641b2 100644 --- a/prisma/schema/nodes_github.prisma +++ b/prisma/schema/nodes_github.prisma @@ -52,6 +52,10 @@ model GitHubNode { // Generic JSON options for all other specific flags and options options Json @default("{}") + // Output Configuration + variableName String @default("github") + continueOnFail Boolean @default(false) + createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt diff --git a/public/logos/github.svg b/public/logos/github.svg index ec2d7c8..707d85f 100644 --- a/public/logos/github.svg +++ b/public/logos/github.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/src/app/api/auth/github/callback/route.ts b/src/app/api/auth/github/callback/route.ts new file mode 100644 index 0000000..40afbca --- /dev/null +++ b/src/app/api/auth/github/callback/route.ts @@ -0,0 +1,115 @@ +import { NextRequest, NextResponse } from "next/server" +import { exchangeGithubCodeForToken, getGithubUserInfo } from "@/lib/github-auth" +import { encrypt } from "@/lib/encryption" +import { CredentialType } from "@/generated/prisma" +import prisma from "@/lib/db" +import { auth } from "@/lib/auth" +import { headers } from "next/headers" + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url) + const code = searchParams.get("code") + const stateParam = searchParams.get("state") + const error = searchParams.get("error") + + // GitHub denied access + if (error) { + return NextResponse.redirect( + new URL(`/credentials/new?github_error=${encodeURIComponent(error)}`, request.url) + ) + } + + if (!code || !stateParam) { + return NextResponse.redirect( + new URL("/credentials/new?github_error=missing_code", request.url) + ) + } + + // Parse state: base64url-encoded JSON { userId, credentialName, credentialType, returnUrl } + let stateData: { + userId: string + credentialName: string + credentialType: string + returnUrl: string + } + try { + stateData = JSON.parse(Buffer.from(stateParam, "base64url").toString("utf-8")) + } catch { + return NextResponse.redirect( + new URL("/credentials/new?github_error=invalid_state", request.url) + ) + } + + // CSRF verification + const session = await auth.api.getSession({ headers: await headers() }) + if (!session?.user?.id || session.user.id !== stateData.userId) { + return NextResponse.redirect( + new URL("/credentials/new?github_error=unauthorized", request.url) + ) + } + + try { + // Exchange code for token + const tokens = await exchangeGithubCodeForToken(code) + + // Get user's GitHub info for display + const userInfo = await getGithubUserInfo(tokens.access_token) + + // Validate returnUrl is relative + const safeReturnUrl = (stateData.returnUrl ?? "/credentials").startsWith("/") + ? stateData.returnUrl + : "/credentials" + + // Store credential value as encrypted JSON + const credentialValue = JSON.stringify({ + accessToken: tokens.access_token, + username: userInfo.login, + connectedAt: new Date().toISOString(), + }) + + // Upsert: update if credential with same name+type+userId exists, otherwise create + const existing = await prisma.credential.findFirst({ + where: { + userId: stateData.userId, + name: stateData.credentialName, + type: stateData.credentialType as CredentialType, + }, + }) + + let credentialId: string + if (existing) { + await prisma.credential.update({ + where: { id: existing.id }, + data: { value: encrypt(credentialValue) }, + }) + credentialId = existing.id + } else { + const created = await prisma.credential.create({ + data: { + userId: stateData.userId, + name: stateData.credentialName, + type: stateData.credentialType as CredentialType, + value: encrypt(credentialValue), + }, + }) + credentialId = created.id + } + + // Redirect back with success indicators + const baseUrl = process.env.NEXTAUTH_URL ?? "https://nodebase.mayanksaraswal.in" + const successUrl = new URL(safeReturnUrl, baseUrl) + successUrl.searchParams.set("github_success", userInfo.login) + successUrl.searchParams.set("credential_id", credentialId) + + return NextResponse.redirect(successUrl) + } catch (err) { + const errMsg = err instanceof Error ? err.message : "Unknown error" + const baseUrl = process.env.NEXTAUTH_URL ?? "https://nodebase.mayanksaraswal.in" + const safeErrorReturn = (stateData?.returnUrl ?? "/credentials/new").startsWith("/") + ? (stateData?.returnUrl ?? "/credentials/new") + : "/credentials/new" + return NextResponse.redirect( + new URL(`${safeErrorReturn}?github_error=${encodeURIComponent(errMsg)}`, baseUrl) + ) + } +} diff --git a/src/app/api/auth/github/start/route.ts b/src/app/api/auth/github/start/route.ts new file mode 100644 index 0000000..a555a25 --- /dev/null +++ b/src/app/api/auth/github/start/route.ts @@ -0,0 +1,31 @@ +import { NextRequest, NextResponse } from "next/server" +import { buildGithubAuthUrl } from "@/lib/github-auth" +import { auth } from "@/lib/auth" +import { headers } from "next/headers" + +export async function GET(request: NextRequest) { + const session = await auth.api.getSession({ headers: await headers() }) + + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + const { searchParams } = new URL(request.url) + const credentialName = searchParams.get("name") || "My GitHub Account" + const credentialType = searchParams.get("type") || "GITHUB_APP" + const rawReturnUrl = searchParams.get("returnUrl") || "/credentials" + // Only allow relative paths — block open redirect to external URLs + const returnUrl = rawReturnUrl.startsWith("/") ? rawReturnUrl : "/credentials" + + // Encode state as base64url JSON: { userId, credentialName, credentialType, returnUrl } + const stateData = { + userId: session.user.id, + credentialName, + credentialType, + returnUrl, + } + const state = Buffer.from(JSON.stringify(stateData)).toString("base64url") + + const authUrl = buildGithubAuthUrl(state) + return NextResponse.redirect(authUrl) +} diff --git a/src/components/github-connect-button.tsx b/src/components/github-connect-button.tsx new file mode 100644 index 0000000..49ecfe4 --- /dev/null +++ b/src/components/github-connect-button.tsx @@ -0,0 +1,123 @@ +"use client" + +import { Button } from "@/components/ui/button" +import { CheckCircle2Icon, ExternalLinkIcon, Loader2Icon } from "lucide-react" +import { useState } from "react" + +interface GithubConnectButtonProps { + credentialName: string + credentialType: "GITHUB_APP" + returnUrl?: string + isConnected?: boolean + connectedUsername?: string + onDisconnect?: () => void +} + +export function GithubConnectButton({ + credentialName, + credentialType, + returnUrl, + isConnected = false, + connectedUsername, + onDisconnect, +}: GithubConnectButtonProps) { + const [isLoading, setIsLoading] = useState(false) + + const handleConnect = () => { + if (!credentialName.trim()) { + alert("Please enter a credential name before connecting.") + return + } + setIsLoading(true) + const params = new URLSearchParams() + params.set("name", credentialName) + params.set("type", credentialType) + if (returnUrl) { + params.set("returnUrl", returnUrl) + } + window.location.href = `/api/auth/github/start?${params.toString()}` + } + + if (isConnected && connectedUsername) { + return ( +
+
+ +
+

+ Connected as {connectedUsername} +

+

+ GitHub account is linked and active +

+
+
+
+ + {onDisconnect && ( + + )} +
+
+ ) + } + + return ( +
+ + +

+ You will be redirected to GitHub to approve access. Nodebase will request permission to + manage your repositories, workflows, and profile on your behalf. You can revoke access at any + time from your{" "} + + GitHub Authorized OAuth Apps settings + + . +

+
+ ) +} diff --git a/src/config/node-components.ts b/src/config/node-components.ts index 436e983..23897d0 100644 --- a/src/config/node-components.ts +++ b/src/config/node-components.ts @@ -46,6 +46,8 @@ import { FilterNode } from "@/features/executions/components/filter/node"; import { CashfreeNode } from "@/features/executions/components/cashfree/node"; import { AggregateNode } from "@/features/executions/components/aggregate/node"; import { PostgresNode } from "@/features/executions/components/postgres/node"; +import { GitHubNode } from "@/features/executions/components/github/node"; +import { GitHubTriggerNode } from "@/features/triggers/components/github-trigger/node"; export const nodeComponents = { @@ -96,6 +98,9 @@ export const nodeComponents = { [NodeType.CASHFREE_TRIGGER]: CashfreeNode, [NodeType.AGGREGATE]: AggregateNode, [NodeType.POSTGRES]: PostgresNode, + [NodeType.GITHUB]: GitHubNode, + [NodeType.GITHUB_TRIGGER]: GitHubTriggerNode, + //change later diff --git a/src/features/credentials/components/credential.tsx b/src/features/credentials/components/credential.tsx index 2af4ead..d6f30ce 100644 --- a/src/features/credentials/components/credential.tsx +++ b/src/features/credentials/components/credential.tsx @@ -19,6 +19,7 @@ import { Button } from "@/components/ui/button"; import Image from "next/image"; import Link from "next/link"; import { GoogleConnectButton } from "@/components/google-connect-button"; +import { GithubConnectButton } from "@/components/github-connect-button"; const formSchema = z.object({ @@ -60,6 +61,8 @@ const formSchema = z.object({ postgresUser: z.string().optional(), postgresPassword: z.string().optional(), postgresSsl: z.enum(["disable", "require", "verify-full"]).optional(), + githubAccessToken: z.string().optional(), + githubBaseUrl: z.string().optional(), }).superRefine((data, ctx) => { if (data.type === CredentialType.GMAIL) { // Gmail now uses OAuth2 via GoogleConnectButton — no required form fields @@ -215,6 +218,15 @@ const formSchema = z.object({ if (!data.postgresUser) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "User is required", path: ["postgresUser"] }) if (!data.postgresPassword) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Password is required", path: ["postgresPassword"] }) } + if (data.type === CredentialType.GITHUB || data.type === CredentialType.GITHUB_APP) { + if (!data.githubAccessToken) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Access Token is required", + path: ["githubAccessToken"], + }) + } + } if (data.type === CredentialType.SLACK) { if (data.slackAuthType === "bot_token" && !data.slackBotToken) { ctx.addIssue({ @@ -349,6 +361,11 @@ const credentialTypeOptions = [ label: "PostgreSQL", logo: "/logos/postgres.svg" }, + { + value: CredentialType.GITHUB_APP, + label: "GitHub App", + logo: "/logos/github.svg" + }, ] @@ -360,6 +377,8 @@ interface CredentialsFormPage { value?: string; connectedEmail?: string; isGoogleOAuth?: boolean; + connectedGithubUsername?: string; + isGithubOAuth?: boolean; } }; @@ -379,6 +398,10 @@ export const CredentialForm = ({ initialData }: CredentialsFormPage) => { const [existingRefreshToken, setExistingRefreshToken] = useState() const [isJustConnected, setIsJustConnected] = useState(false) + // GitHub OAuth state + const [connectedGithubUsername, setConnectedGithubUsername] = useState() + const [isGithubOAuth, setIsGithubOAuth] = useState(false) + useEffect(() => { if (initialData?.connectedEmail) { setConnectedEmail(initialData.connectedEmail) @@ -386,6 +409,12 @@ export const CredentialForm = ({ initialData }: CredentialsFormPage) => { if (initialData?.isGoogleOAuth) { setExistingRefreshToken("oauth-connected") // truthy marker, not actual token } + if (initialData?.connectedGithubUsername) { + setConnectedGithubUsername(initialData.connectedGithubUsername) + } + if (initialData?.isGithubOAuth) { + setIsGithubOAuth(true) + } }, [initialData]) // Handle google_success / google_error URL params after OAuth callback @@ -403,6 +432,18 @@ export const CredentialForm = ({ initialData }: CredentialsFormPage) => { toast.error(`Google connection failed: ${decodeURIComponent(googleError)}`) window.history.replaceState({}, "", window.location.pathname) } + const githubSuccess = params.get("github_success") + if (githubSuccess) { + setConnectedGithubUsername(githubSuccess) + setIsGithubOAuth(true) + toast.success(`Successfully connected ${githubSuccess}`) + window.history.replaceState({}, "", window.location.pathname) + } + const githubError = params.get("github_error") + if (githubError) { + toast.error(`GitHub connection failed: ${decodeURIComponent(githubError)}`) + window.history.replaceState({}, "", window.location.pathname) + } }, []) // Parse WhatsApp JSON value into individual fields for editing @@ -610,10 +651,25 @@ export const CredentialForm = ({ initialData }: CredentialsFormPage) => { return { postgresHost: "", postgresPort: 5432, postgresDatabase: "", postgresUser: "", postgresPassword: "", postgresSsl: "disable" } }, [initialData]) + const githubDefaults = useMemo(() => { + if ((initialData?.type === CredentialType.GITHUB || initialData?.type === CredentialType.GITHUB_APP) && initialData.value) { + try { + const parsed = JSON.parse(initialData.value) + return { + githubAccessToken: parsed.accessToken ?? "", + githubBaseUrl: parsed.baseUrl ?? "", + } + } catch { + return { githubAccessToken: "", githubBaseUrl: "" } + } + } + return { githubAccessToken: "", githubBaseUrl: "" } + }, [initialData]) + const form = useForm({ resolver: zodResolver(formSchema) as any, defaultValues: initialData - ? { ...initialData, gmailEmail: "", gmailAppPassword: "", ...whatsappDefaults, ...notionDefaults, ...razorpayDefaults, ...msg91Defaults, ...shiprocketDefaults, ...slackDefaults, ...zohoDefaults, ...hubspotDefaults, ...freshdeskDefaults, ...cashfreeDefaults, ...postgresDefaults } + ? { ...initialData, gmailEmail: "", gmailAppPassword: "", ...whatsappDefaults, ...notionDefaults, ...razorpayDefaults, ...msg91Defaults, ...shiprocketDefaults, ...slackDefaults, ...zohoDefaults, ...hubspotDefaults, ...freshdeskDefaults, ...cashfreeDefaults, ...postgresDefaults, ...githubDefaults } : { name: "", type: CredentialType.OPENAI, @@ -653,6 +709,8 @@ export const CredentialForm = ({ initialData }: CredentialsFormPage) => { postgresUser: "", postgresPassword: "", postgresSsl: "disable", + githubAccessToken: "", + githubBaseUrl: "", } }) @@ -673,14 +731,16 @@ export const CredentialForm = ({ initialData }: CredentialsFormPage) => { const isFreshdesk = watchType === CredentialType.FRESHDESK const isCashfree = watchType === CredentialType.CASHFREE const isPostgres = watchType === CredentialType.POSTGRES + const isGithubPAT = watchType === CredentialType.GITHUB + const isGithubApp = watchType === CredentialType.GITHUB_APP const watchSlackAuthType = form.watch("slackAuthType") const onSubmit = async (values: FormValues) => { let submitValues = { ...values } - // Google OAuth services: credential already saved by /api/auth/google/callback. + // Google and GitHub OAuth services: credential already saved by callback. // Only update the name here. - if (isGoogleService) { + if (isGoogleService || isGithubApp) { if (isEdit && initialData?.id) { try { await updateCredentialName.mutateAsync({ @@ -791,6 +851,13 @@ export const CredentialForm = ({ initialData }: CredentialsFormPage) => { }) } + if (values.type === CredentialType.GITHUB) { + submitValues.value = JSON.stringify({ + accessToken: values.githubAccessToken, + baseUrl: values.githubBaseUrl, + }) + } + // For Slack, encode based on auth type if (values.type === CredentialType.SLACK) { if (values.slackAuthType === "bot_token") { @@ -806,7 +873,7 @@ export const CredentialForm = ({ initialData }: CredentialsFormPage) => { } } - const { gmailEmail, gmailAppPassword, whatsappAccessToken, whatsappPhoneNumberId, notionApiKey, razorpayKeyId, razorpayKeySecret, msg91AuthKey, shiprocketEmail, shiprocketPassword, zohoClientId, zohoClientSecret, zohoRefreshToken, zohoRegion, slackAuthType, slackBotToken, slackWebhookUrl, hubspotAccessToken, hubspotRefreshToken, hubspotExpiresAt, hubspotPortalId, hubspotHubId, freshdeskApiKey, freshdeskDomain, cashfreeClientId, cashfreeClientSecret, cashfreeEnvironment, cashfreePayoutClientId, cashfreePayoutClientSecret, postgresHost, postgresPort, postgresDatabase, postgresUser, postgresPassword, postgresSsl, ...payload } = submitValues + const { gmailEmail, gmailAppPassword, whatsappAccessToken, whatsappPhoneNumberId, notionApiKey, razorpayKeyId, razorpayKeySecret, msg91AuthKey, shiprocketEmail, shiprocketPassword, zohoClientId, zohoClientSecret, zohoRefreshToken, zohoRegion, slackAuthType, slackBotToken, slackWebhookUrl, hubspotAccessToken, hubspotRefreshToken, hubspotExpiresAt, hubspotPortalId, hubspotHubId, freshdeskApiKey, freshdeskDomain, cashfreeClientId, cashfreeClientSecret, cashfreeEnvironment, cashfreePayoutClientId, cashfreePayoutClientSecret, postgresHost, postgresPort, postgresDatabase, postgresUser, postgresPassword, postgresSsl, githubAccessToken, githubBaseUrl, ...payload } = submitValues if (isEdit && initialData?.id) { await updateCredential.mutate({ @@ -1687,6 +1754,48 @@ export const CredentialForm = ({ initialData }: CredentialsFormPage) => { )} /> + ) : isGithubPAT ? ( + <> + ( + + GitHub Personal Access Token (Classic or Fine-grained) * + + + + Token requires repo, workflow, read:org, and read:user scopes for full functionality. + + + )} + /> + ( + + GitHub API Base URL (Optional) + + + + Leave blank for github.com. Change only if using GitHub Enterprise Server. + + + )} + /> + + ) : isGithubApp ? ( + { + setConnectedGithubUsername(undefined) + setIsGithubOAuth(false) + }} + /> ) : ( { const trpc = useTRPC() return useQuery(trpc.credentials.getByType.queryOptions({type} )) } + +export const useCredentialsByTypes = (types: CredentialType[])=>{ + const trpc = useTRPC() + return useQuery(trpc.credentials.getByTypes.queryOptions({types} )) +} diff --git a/src/features/credentials/server/routers.ts b/src/features/credentials/server/routers.ts index 6f9a5db..a6b1a40 100644 --- a/src/features/credentials/server/routers.ts +++ b/src/features/credentials/server/routers.ts @@ -116,6 +116,8 @@ export const credentialsRouter = createTRPCRouter({ let connectedEmail: string | undefined let isGoogleOAuth = false + let connectedGithubUsername: string | undefined + let isGithubOAuth = false if (googleTypes.includes(credential.type)) { try { @@ -129,11 +131,25 @@ export const credentialsRouter = createTRPCRouter({ // Strip value for Google — UI uses connectedEmail/isGoogleOAuth instead const { value: _v, ...googleFields } = credential - return { ...googleFields, connectedEmail, isGoogleOAuth } + return { ...googleFields, connectedEmail, isGoogleOAuth, connectedGithubUsername: undefined, isGithubOAuth: false } } - // Non-Google: return value so form can pre-populate credential fields - return { ...credential, connectedEmail: undefined, isGoogleOAuth: false } + if (credential.type === CredentialType.GITHUB_APP) { + try { + const parsed = JSON.parse(decrypt(credential.value)) as { + username?: string + accessToken?: string + } + connectedGithubUsername = parsed.username + isGithubOAuth = !!parsed.accessToken + } catch { /* ignore */ } + + const { value: _v, ...githubFields } = credential + return { ...githubFields, connectedEmail: undefined, isGoogleOAuth: false, connectedGithubUsername, isGithubOAuth } + } + + // Non-Google and Non-GitHubApp: return value so form can pre-populate credential fields + return { ...credential, connectedEmail: undefined, isGoogleOAuth: false, connectedGithubUsername: undefined, isGithubOAuth: false } }), getMany: protectedProcedure @@ -208,5 +224,25 @@ export const credentialsRouter = createTRPCRouter({ updatedAt: "desc" }, }) + }), + getByTypes: protectedProcedure + .input( + z.object({ + types: z.array(z.nativeEnum(CredentialType)) + }) + ) + .query(({ ctx, input }) => { + const { types } = input; + return prisma.credential.findMany({ + where: { + userId: ctx.auth.user.id, + type: { + in: types + } + }, + orderBy: { + updatedAt: "desc" + }, + }) }) }); \ No newline at end of file diff --git a/src/features/executions/components/base-execution-node.tsx b/src/features/executions/components/base-execution-node.tsx index a5ea2a3..56977ae 100644 --- a/src/features/executions/components/base-execution-node.tsx +++ b/src/features/executions/components/base-execution-node.tsx @@ -1,30 +1,30 @@ "use client" -import {type NodeProps , Position, useReactFlow} from "@xyflow/react" -import type{LucideIcon} from "lucide-react" +import { type NodeProps, Position, useReactFlow } from "@xyflow/react" +import type { LucideIcon } from "lucide-react" import Image from "next/image" -import {memo , useCallback , type ReactNode} from "react" +import { memo, useCallback, type ReactNode } from "react" -import { BaseNode , BaseNodeContent } from "../../../components/react-flow/base-node" -import { BaseHandle} from "../../../components/react-flow/base-handle" +import { BaseNode, BaseNodeContent } from "../../../components/react-flow/base-node" +import { BaseHandle } from "../../../components/react-flow/base-handle" import { WrokflowNode } from "../../../components/workflow-node" -import { type NodeStatus , NodeStatusIndicator } from "@/components/react-flow/node-status-indicator" +import { type NodeStatus, NodeStatusIndicator } from "@/components/react-flow/node-status-indicator" -interface BaseExecutionProps extends NodeProps{ - icon:LucideIcon | string, - id:string - name:string, - description?:string, - status:NodeStatus, - children?:ReactNode - onSettings?() : void - onDoubleClick?() :void +interface BaseExecutionProps extends NodeProps { + icon: LucideIcon | string, + id: string + name: string, + description?: string, + status: NodeStatus, + children?: ReactNode + onSettings?(): void + onDoubleClick?(): void }; export const BaseExecutionNode = memo(function BaseExecutionNode({ - icon:Icon, + icon: Icon, id, name, description, @@ -32,66 +32,66 @@ export const BaseExecutionNode = memo(function BaseExecutionNode({ status = "initial", onSettings, onDoubleClick, - -}:BaseExecutionProps) { - const {setNodes , setEdges} = useReactFlow() - const handleDelete = ()=>{ - setNodes((currentNodes)=>{ - const updatedNodes = currentNodes.filter((node)=>node.id !== id) - return updatedNodes - }) - setEdges((currentEdges)=>{ - const updatedEdges = currentEdges.filter((edge)=>edge.source !== id && edge.target !== id) - return updatedEdges - }) - - } - + +}: BaseExecutionProps) { + const { setNodes, setEdges } = useReactFlow() + const handleDelete = () => { + setNodes((currentNodes) => { + const updatedNodes = currentNodes.filter((node) => node.id !== id) + return updatedNodes + }) + setEdges((currentEdges) => { + const updatedEdges = currentEdges.filter((edge) => edge.source !== id && edge.target !== id) + return updatedEdges + }) + + } + return ( - - - {typeof Icon ==="string"?( - {name} + {typeof Icon === "string" ? ( + {name} + ) : ( + + )} + {children} + - ):( - - )} - {children} - - - - + + + - + ) }) diff --git a/src/features/executions/components/github/components/repo-fields.tsx b/src/features/executions/components/github/components/repo-fields.tsx index 6a37183..d11f103 100644 --- a/src/features/executions/components/github/components/repo-fields.tsx +++ b/src/features/executions/components/github/components/repo-fields.tsx @@ -30,7 +30,10 @@ export function RepoFields({ values, setValues }: RepoFieldsProps) { value={values.owner || ""} onChange={(e) => setValues({ ...values, owner: e.target.value })} /> -

The account owner of the repository. Supports template variables.

+

+ The account owner or organization of the repository. + {op === "REPOSITORY_CREATE" && Leave blank to create a repository in your personal account.} +

@@ -148,14 +151,28 @@ export function RepoFields({ values, setValues }: RepoFieldsProps) { {/* Repo create description */} {op === "REPOSITORY_CREATE" && ( -
- -