From 4a14c3efd2d2abbd27a278b07596a32550333efd Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 3 Sep 2024 04:16:18 +0400 Subject: [PATCH] feat(integrations): visibility support for github integration --- .../src/hooks/api/integrations/queries.tsx | 2 + frontend/src/hooks/api/integrations/types.ts | 3 + .../src/pages/integrations/github/create.tsx | 203 +++++++++++++++--- 3 files changed, 181 insertions(+), 27 deletions(-) diff --git a/frontend/src/hooks/api/integrations/queries.tsx b/frontend/src/hooks/api/integrations/queries.tsx index de4c1a914..ebb0e8b1e 100644 --- a/frontend/src/hooks/api/integrations/queries.tsx +++ b/frontend/src/hooks/api/integrations/queries.tsx @@ -71,6 +71,8 @@ export const useCreateIntegration = () => { key: string; value: string; }[]; + githubVisibility?: string; + githubVisibilityRepoIds?: string[]; kmsKeyId?: string; shouldDisableDelete?: boolean; shouldMaskSecrets?: boolean; diff --git a/frontend/src/hooks/api/integrations/types.ts b/frontend/src/hooks/api/integrations/types.ts index 21e6bff26..f8c7ce244 100644 --- a/frontend/src/hooks/api/integrations/types.ts +++ b/frontend/src/hooks/api/integrations/types.ts @@ -34,6 +34,9 @@ export type TIntegration = { syncMessage?: string; __v: number; metadata?: { + githubVisibility?: string; + githubVisibilityRepoIds?: string[]; + secretSuffix?: string; syncBehavior?: IntegrationSyncBehavior; mappingBehavior?: IntegrationMappingBehavior; diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index 2015ac8f9..c002ea103 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import Head from "next/head"; import Image from "next/image"; @@ -53,6 +53,21 @@ enum TabSections { Options = "options" } +const secretsVisibility = [ + { + value: "selected", + label: "Select repositories" + }, + { + value: "all", + label: "All public repositories" + }, + { + value: "private", + label: "All private repositories" + } +] as const; + const targetEnv = ["github-repo", "github-org", "github-env"] as const; type TargetEnv = (typeof targetEnv)[number]; @@ -63,9 +78,12 @@ const schema = yup.object({ shouldEnableDelete: yup.boolean().optional(), scope: yup.mixed().oneOf(targetEnv.slice()).required(), - repoIds: yup.mixed().when("scope", { - is: "github-repo", - then: yup.array(yup.string().required()).min(1, "Select at least one repositories") + // Explanation: If scope is (github-repo) OR (github-org AND visibility is set to selected), then repoIds is required + repoIds: yup.mixed().when(["scope", "visibility"], { + is: (scope: string, visibility: string) => + scope === "github-repo" || (scope === "github-org" && visibility === "selected"), + then: yup.array(yup.string().required()).min(1, "Select at least one repository"), + otherwise: yup.mixed().notRequired() }), repoId: yup.mixed().when("scope", { @@ -91,6 +109,10 @@ const schema = yup.object({ orgId: yup.mixed().when("scope", { is: "github-org", then: yup.string().required("Organization is required") + }), + visibility: yup.mixed().when("scope", { + is: "github-org", + then: yup.string().required("Visibility is required") }) }); @@ -121,6 +143,7 @@ export default function GitHubCreateIntegrationPage() { secretPath: "/", scope: "github-repo", repoIds: [], + visibility: "all", shouldEnableDelete: false } }); @@ -130,6 +153,7 @@ export default function GitHubCreateIntegrationPage() { const repoIds = watch("repoIds"); const repoName = watch("repoName"); const repoOwner = watch("repoOwner"); + const selectedOrgId = watch("orgId"); const { data: integrationAuthGithubEnvs } = useGetIntegrationAuthGithubEnvs( integrationAuthId as string, @@ -196,6 +220,8 @@ export default function GitHubCreateIntegrationPage() { scope: data.scope, owner: integrationAuthOrgs?.find((e) => e.orgId === data.orgId)?.name, metadata: { + githubVisibility: data.visibility, + githubVisibilityRepoIds: data.repoIds, secretSuffix: data.secretSuffix, shouldEnableDelete: data.shouldEnableDelete } @@ -242,6 +268,15 @@ export default function GitHubCreateIntegrationPage() { } }; + const selectedOrganization = useMemo(() => { + if (!integrationAuthApps) return null; + + return integrationAuthApps.filter( + (authApp) => + integrationAuthOrgs?.find((e) => e.orgId === selectedOrgId)?.name === authApp.owner + ); + }, [selectedOrgId, integrationAuthApps]); + return integrationAuth && workspace && integrationAuthApps ? (
@@ -339,7 +374,10 @@ export default function GitHubCreateIntegrationPage() { + ( + - {integrationAuthOrgs && - integrationAuthOrgs.map(({ name, orgId }) => ( - - {name} + + + )} + /> + + ( + + - + + + )} + /> + + {watch("visibility") === "selected" && ( + ( + + + + {integrationAuthApps.length > 0 ? ( +
+ {repoIds.length === 1 + ? integrationAuthApps?.reduce( + (acc, { appId, name, owner }) => + repoIds[0] === appId ? `${owner}/${name}` : acc, + "" + ) + : `${repoIds.length} repositories selected`} + +
+ ) : ( +
+ No repositories found +
+ )} +
+ + {selectedOrganization ? ( + selectedOrganization.map((integrationAuthApp) => { + const isSelected = repoIds.includes( + String(integrationAuthApp.appId) + ); + + return ( + { + if (repoIds.includes(String(integrationAuthApp.appId))) { + onChange( + repoIds.filter( + (appId: string) => + appId !== String(integrationAuthApp.appId) + ) + ); + } else { + onChange([ + ...repoIds, + String(integrationAuthApp.appId) + ]); + } + }} + key={`repos-id-${integrationAuthApp.appId}`} + icon={ + isSelected ? ( + + ) : ( +
+ ) + } + iconPos="left" + className="w-[28.4rem] text-sm" + > + {integrationAuthApp.owner}/{integrationAuthApp.name} + + ); + }) + ) : ( +
+ )} + + + + )} + /> )} - /> + )} {scope === "github-env" && (