diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index eb8645530..d0fa2279a 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -1929,17 +1929,56 @@ const syncSecretsCircleCI = async ({ secrets: Record; accessToken: string; }) => { - let projectSlug: string | null = null; - const projectDetails = ( - await request.get<{ slug: string }>(`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${integration.appId}`, { + const getProjectSlug = async () => { + const requestConfig = { headers: { "Circle-Token": accessToken, "Accept-Encoding": "application/json" } - }) - ).data; + }; - projectSlug = `${projectDetails.slug}`; + try { + const projectDetails = ( + await request.get<{ slug: string }>( + `${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${integration.appId}`, + requestConfig + ) + ).data; + + return projectDetails.slug; + } catch (err) { + if (err instanceof AxiosError) { + if (err.response?.data?.message !== "Not Found") { + throw new Error("Failed to get project slug from CircleCI during first attempt."); + } + } + } + + // For backwards compatibility with old CircleCI integrations where we don't keep track of the organization name, so we can't filter by organization + try { + const circleCiOrganization = ( + await request.get<{ slug: string; name: string }[]>( + `${IntegrationUrls.CIRCLECI_API_URL}/v2/me/collaborations`, + requestConfig + ) + ).data; + + // Case 1: This is a new integration where the organization name is stored under `integration.owner` + if (integration.owner) { + const org = circleCiOrganization.find((o) => o.name === integration.owner); + if (org) { + return `${org.slug}/${integration.app}`; + } + } + + // Case 2: This is an old integration where the organization name is not stored, so we have to assume the first organization is the correct one + return `${circleCiOrganization[0].slug}/${integration.app}`; + } catch (err) { + throw new Error("Failed to get project slug from CircleCI during second attempt."); + } + }; + + const projectSlug = await getProjectSlug(); // sync secrets to CircleCI await Promise.all( diff --git a/frontend/src/pages/integrations/circleci/create.tsx b/frontend/src/pages/integrations/circleci/create.tsx index bb3541875..edd8dacfe 100644 --- a/frontend/src/pages/integrations/circleci/create.tsx +++ b/frontend/src/pages/integrations/circleci/create.tsx @@ -68,6 +68,7 @@ export default function CircleCICreateIntegrationPage() { type: "error", text: "Please select a project" }); + setIsLoading(false); return; } @@ -82,6 +83,7 @@ export default function CircleCICreateIntegrationPage() { type: "error", text: "Invalid project selected" }); + setIsLoading(false); return; } @@ -111,6 +113,19 @@ export default function CircleCICreateIntegrationPage() { }); }, [integrationAuthApps, targetOrganization]); + const filteredOrganizations = useMemo(() => { + const organizations = new Set(); + + if (integrationAuthApps) { + integrationAuthApps.forEach((integrationAuthApp) => { + if (!integrationAuthApp.owner) return; + organizations.add(integrationAuthApp.owner); + }); + } + + return Array.from(organizations); + }, [integrationAuthApps]); + return integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps ? (
@@ -179,15 +194,12 @@ export default function CircleCICreateIntegrationPage() { setTargetProjectId("none"); }} className="w-full border border-mineshaft-500" - isDisabled={integrationAuthApps.length === 0} + isDisabled={filteredOrganizations.length === 0} > - {integrationAuthApps.length > 0 ? ( - integrationAuthApps.map((integrationAuthApp) => ( - - {integrationAuthApp.owner} + {filteredOrganizations.length > 0 ? ( + filteredOrganizations.map((org) => ( + + {org} )) ) : (