From 62ad82f7b1292c52d0af5ebd9a135f46a9ed0470 Mon Sep 17 00:00:00 2001 From: x032205 Date: Wed, 2 Jul 2025 17:56:48 -0400 Subject: [PATCH] feat(app-connection): BitBucket app connection --- backend/src/lib/api-docs/constants.ts | 4 + .../app-connection-router.ts | 10 +- .../bitbucket-connection-router.ts | 18 +++ .../routes/v1/app-connection-routers/index.ts | 4 +- .../app-connection/app-connection-enums.ts | 3 +- .../app-connection/app-connection-fns.ts | 15 +- .../app-connection/app-connection-maps.ts | 6 +- .../app-connection/app-connection-service.ts | 8 +- .../app-connection/app-connection-types.ts | 14 +- .../bitbucket/bitbucket-connection-enums.ts | 3 + .../bitbucket/bitbucket-connection-fns.ts | 40 +++++ .../bitbucket/bitbucket-connection-schemas.ts | 61 ++++++++ .../bitbucket/bitbucket-connection-service.ts | 15 ++ .../bitbucket/bitbucket-connection-types.ts | 38 +++++ .../app-connection/bitbucket/index.ts | 4 + frontend/src/helpers/appConnections.ts | 5 +- .../api/appConnections/bitbucket/index.ts | 2 + .../api/appConnections/bitbucket/queries.tsx | 0 .../api/appConnections/bitbucket/types.ts | 0 .../src/hooks/api/appConnections/enums.ts | 3 +- .../api/appConnections/types/app-options.ts | 8 +- .../types/bitbucket-connection.ts | 14 ++ .../hooks/api/appConnections/types/index.ts | 6 +- .../AppConnectionForm/AppConnectionForm.tsx | 5 + .../BitBucketConnectionForm.tsx | 144 ++++++++++++++++++ 25 files changed, 413 insertions(+), 17 deletions(-) create mode 100644 backend/src/server/routes/v1/app-connection-routers/bitbucket-connection-router.ts create mode 100644 backend/src/services/app-connection/bitbucket/bitbucket-connection-enums.ts create mode 100644 backend/src/services/app-connection/bitbucket/bitbucket-connection-fns.ts create mode 100644 backend/src/services/app-connection/bitbucket/bitbucket-connection-schemas.ts create mode 100644 backend/src/services/app-connection/bitbucket/bitbucket-connection-service.ts create mode 100644 backend/src/services/app-connection/bitbucket/bitbucket-connection-types.ts create mode 100644 backend/src/services/app-connection/bitbucket/index.ts create mode 100644 frontend/src/hooks/api/appConnections/bitbucket/index.ts create mode 100644 frontend/src/hooks/api/appConnections/bitbucket/queries.tsx create mode 100644 frontend/src/hooks/api/appConnections/bitbucket/types.ts create mode 100644 frontend/src/hooks/api/appConnections/types/bitbucket-connection.ts create mode 100644 frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/BitBucketConnectionForm.tsx diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 4405728b2..b457eafb3 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2268,6 +2268,10 @@ export const AppConnections = { accessToken: "The Access Token used to access GitLab.", code: "The OAuth code to use to connect with GitLab.", accessTokenType: "The type of token used to connect with GitLab." + }, + BITBUCKET: { + email: "The email used to access BitBucket.", + apiToken: "The API token used to access BitBucket." } } }; diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index 6160828f4..be991703c 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -31,6 +31,10 @@ import { AzureKeyVaultConnectionListItemSchema, SanitizedAzureKeyVaultConnectionSchema } from "@app/services/app-connection/azure-key-vault"; +import { + BitBucketConnectionListItemSchema, + SanitizedBitBucketConnectionSchema +} from "@app/services/app-connection/bitbucket"; import { CamundaConnectionListItemSchema, SanitizedCamundaConnectionSchema @@ -116,7 +120,8 @@ const SanitizedAppConnectionSchema = z.union([ ...SanitizedRenderConnectionSchema.options, ...SanitizedFlyioConnectionSchema.options, ...SanitizedGitLabConnectionSchema.options, - ...SanitizedCloudflareConnectionSchema.options + ...SanitizedCloudflareConnectionSchema.options, + ...SanitizedBitBucketConnectionSchema.options ]); const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ @@ -148,7 +153,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ RenderConnectionListItemSchema, FlyioConnectionListItemSchema, GitLabConnectionListItemSchema, - CloudflareConnectionListItemSchema + CloudflareConnectionListItemSchema, + BitBucketConnectionListItemSchema ]); export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/server/routes/v1/app-connection-routers/bitbucket-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/bitbucket-connection-router.ts new file mode 100644 index 000000000..bcef795c1 --- /dev/null +++ b/backend/src/server/routes/v1/app-connection-routers/bitbucket-connection-router.ts @@ -0,0 +1,18 @@ +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + CreateBitBucketConnectionSchema, + SanitizedBitBucketConnectionSchema, + UpdateBitBucketConnectionSchema +} from "@app/services/app-connection/bitbucket"; + +import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; + +export const registerBitBucketConnectionRouter = async (server: FastifyZodProvider) => { + registerAppConnectionEndpoints({ + app: AppConnection.BitBucket, + server, + sanitizedResponseSchema: SanitizedBitBucketConnectionSchema, + createSchema: CreateBitBucketConnectionSchema, + updateSchema: UpdateBitBucketConnectionSchema + }); +}; diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index cd4ccd728..e164a9088 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -9,6 +9,7 @@ import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-confi import { registerAzureClientSecretsConnectionRouter } from "./azure-client-secrets-connection-router"; import { registerAzureDevOpsConnectionRouter } from "./azure-devops-connection-router"; import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router"; +import { registerBitBucketConnectionRouter } from "./bitbucket-connection-router"; import { registerCamundaConnectionRouter } from "./camunda-connection-router"; import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router"; import { registerDatabricksConnectionRouter } from "./databricks-connection-router"; @@ -62,5 +63,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record { getRenderConnectionListItem(), getFlyioConnectionListItem(), getGitLabConnectionListItem(), - getCloudflareConnectionListItem() + getCloudflareConnectionListItem(), + getBitBucketConnectionListItem() ].sort((a, b) => a.name.localeCompare(b.name)); }; @@ -216,7 +222,8 @@ export const validateAppConnectionCredentials = async ( [AppConnection.Render]: validateRenderConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.GitLab]: validateGitLabConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator + [AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.BitBucket]: validateBitBucketConnectionCredentials as TAppConnectionCredentialsValidator }; return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection); @@ -253,6 +260,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) => case VercelConnectionMethod.ApiToken: case OnePassConnectionMethod.ApiToken: case CloudflareConnectionMethod.APIToken: + case BitBucketConnectionMethod.ApiToken: return "API Token"; case PostgresConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword: @@ -332,7 +340,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.Render]: platformManagedCredentialsNotSupported, [AppConnection.Flyio]: platformManagedCredentialsNotSupported, [AppConnection.GitLab]: platformManagedCredentialsNotSupported, - [AppConnection.Cloudflare]: platformManagedCredentialsNotSupported + [AppConnection.Cloudflare]: platformManagedCredentialsNotSupported, + [AppConnection.BitBucket]: platformManagedCredentialsNotSupported }; export const enterpriseAppCheck = async ( diff --git a/backend/src/services/app-connection/app-connection-maps.ts b/backend/src/services/app-connection/app-connection-maps.ts index 9c0a3b5b8..a2d953d07 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -29,7 +29,8 @@ export const APP_CONNECTION_NAME_MAP: Record = { [AppConnection.Render]: "Render", [AppConnection.Flyio]: "Fly.io", [AppConnection.GitLab]: "GitLab", - [AppConnection.Cloudflare]: "Cloudflare" + [AppConnection.Cloudflare]: "Cloudflare", + [AppConnection.BitBucket]: "BitBucket" }; export const APP_CONNECTION_PLAN_MAP: Record = { @@ -61,5 +62,6 @@ export const APP_CONNECTION_PLAN_MAP: Record>>; @@ -232,6 +239,7 @@ export type TAppConnectionInput = { id: string } & ( | TFlyioConnectionInput | TGitLabConnectionInput | TCloudflareConnectionInput + | TBitBucketConnectionInput ); export type TSqlConnectionInput = @@ -275,7 +283,8 @@ export type TAppConnectionConfig = | TRenderConnectionConfig | TFlyioConnectionConfig | TGitLabConnectionConfig - | TCloudflareConnectionConfig; + | TCloudflareConnectionConfig + | TBitBucketConnectionConfig; export type TValidateAppConnectionCredentialsSchema = | TValidateAwsConnectionCredentialsSchema @@ -306,7 +315,8 @@ export type TValidateAppConnectionCredentialsSchema = | TValidateRenderConnectionCredentialsSchema | TValidateFlyioConnectionCredentialsSchema | TValidateGitLabConnectionCredentialsSchema - | TValidateCloudflareConnectionCredentialsSchema; + | TValidateCloudflareConnectionCredentialsSchema + | TValidateBitBucketConnectionCredentialsSchema; export type TListAwsConnectionKmsKeys = { connectionId: string; diff --git a/backend/src/services/app-connection/bitbucket/bitbucket-connection-enums.ts b/backend/src/services/app-connection/bitbucket/bitbucket-connection-enums.ts new file mode 100644 index 000000000..629c5b6d7 --- /dev/null +++ b/backend/src/services/app-connection/bitbucket/bitbucket-connection-enums.ts @@ -0,0 +1,3 @@ +export enum BitBucketConnectionMethod { + ApiToken = "api-token" +} diff --git a/backend/src/services/app-connection/bitbucket/bitbucket-connection-fns.ts b/backend/src/services/app-connection/bitbucket/bitbucket-connection-fns.ts new file mode 100644 index 000000000..45ce2c0b5 --- /dev/null +++ b/backend/src/services/app-connection/bitbucket/bitbucket-connection-fns.ts @@ -0,0 +1,40 @@ +import { AxiosError } from "axios"; + +import { request } from "@app/lib/config/request"; +import { BadRequestError } from "@app/lib/errors"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; + +import { BitBucketConnectionMethod } from "./bitbucket-connection-enums"; +import { TBitBucketConnectionConfig } from "./bitbucket-connection-types"; + +export const getBitBucketConnectionListItem = () => { + return { + name: "BitBucket" as const, + app: AppConnection.BitBucket as const, + methods: Object.values(BitBucketConnectionMethod) as [BitBucketConnectionMethod.ApiToken] + }; +}; + +export const validateBitBucketConnectionCredentials = async (config: TBitBucketConnectionConfig) => { + const { email, apiToken } = config.credentials; + + try { + await request.get("https://api.bitbucket.org/2.0/user", { + headers: { + Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, + Accept: "application/json" + } + }); + } catch (error: unknown) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to validate credentials: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to validate connection: verify credentials" + }); + } + + return config.credentials; +}; diff --git a/backend/src/services/app-connection/bitbucket/bitbucket-connection-schemas.ts b/backend/src/services/app-connection/bitbucket/bitbucket-connection-schemas.ts new file mode 100644 index 000000000..a331db8cc --- /dev/null +++ b/backend/src/services/app-connection/bitbucket/bitbucket-connection-schemas.ts @@ -0,0 +1,61 @@ +import z from "zod"; + +import { AppConnections } from "@app/lib/api-docs"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + BaseAppConnectionSchema, + GenericCreateAppConnectionFieldsSchema, + GenericUpdateAppConnectionFieldsSchema +} from "@app/services/app-connection/app-connection-schemas"; + +import { BitBucketConnectionMethod } from "./bitbucket-connection-enums"; + +export const BitBucketConnectionAccessTokenCredentialsSchema = z.object({ + apiToken: z.string().trim().min(1, "API Token required").describe(AppConnections.CREDENTIALS.BITBUCKET.apiToken), + email: z.string().email().trim().min(1, "Email required").describe(AppConnections.CREDENTIALS.BITBUCKET.email) +}); + +const BaseBitBucketConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.BitBucket) }); + +export const BitBucketConnectionSchema = BaseBitBucketConnectionSchema.extend({ + method: z.literal(BitBucketConnectionMethod.ApiToken), + credentials: BitBucketConnectionAccessTokenCredentialsSchema +}); + +export const SanitizedBitBucketConnectionSchema = z.discriminatedUnion("method", [ + BaseBitBucketConnectionSchema.extend({ + method: z.literal(BitBucketConnectionMethod.ApiToken), + credentials: BitBucketConnectionAccessTokenCredentialsSchema.pick({ + email: true + }) + }) +]); + +export const ValidateBitBucketConnectionCredentialsSchema = z.discriminatedUnion("method", [ + z.object({ + method: z + .literal(BitBucketConnectionMethod.ApiToken) + .describe(AppConnections.CREATE(AppConnection.BitBucket).method), + credentials: BitBucketConnectionAccessTokenCredentialsSchema.describe( + AppConnections.CREATE(AppConnection.BitBucket).credentials + ) + }) +]); + +export const CreateBitBucketConnectionSchema = ValidateBitBucketConnectionCredentialsSchema.and( + GenericCreateAppConnectionFieldsSchema(AppConnection.BitBucket) +); + +export const UpdateBitBucketConnectionSchema = z + .object({ + credentials: BitBucketConnectionAccessTokenCredentialsSchema.optional().describe( + AppConnections.UPDATE(AppConnection.BitBucket).credentials + ) + }) + .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.BitBucket)); + +export const BitBucketConnectionListItemSchema = z.object({ + name: z.literal("BitBucket"), + app: z.literal(AppConnection.BitBucket), + methods: z.nativeEnum(BitBucketConnectionMethod).array() +}); diff --git a/backend/src/services/app-connection/bitbucket/bitbucket-connection-service.ts b/backend/src/services/app-connection/bitbucket/bitbucket-connection-service.ts new file mode 100644 index 000000000..16d3edea4 --- /dev/null +++ b/backend/src/services/app-connection/bitbucket/bitbucket-connection-service.ts @@ -0,0 +1,15 @@ +import { OrgServiceActor } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +// import { listBitBucketVaults } from "./bitbucket-connection-fns"; +import { TBitBucketConnection } from "./bitbucket-connection-types"; + +type TGetAppConnectionFunc = ( + app: AppConnection, + connectionId: string, + actor: OrgServiceActor +) => Promise; + +export const bitBucketConnectionService = (_getAppConnection: TGetAppConnectionFunc) => { + return {}; +}; diff --git a/backend/src/services/app-connection/bitbucket/bitbucket-connection-types.ts b/backend/src/services/app-connection/bitbucket/bitbucket-connection-types.ts new file mode 100644 index 000000000..a59fdb397 --- /dev/null +++ b/backend/src/services/app-connection/bitbucket/bitbucket-connection-types.ts @@ -0,0 +1,38 @@ +import z from "zod"; + +import { DiscriminativePick } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +import { + BitBucketConnectionSchema, + CreateBitBucketConnectionSchema, + ValidateBitBucketConnectionCredentialsSchema +} from "./bitbucket-connection-schemas"; + +export type TBitBucketConnection = z.infer; + +export type TBitBucketConnectionInput = z.infer & { + app: AppConnection.BitBucket; +}; + +export type TValidateBitBucketConnectionCredentialsSchema = typeof ValidateBitBucketConnectionCredentialsSchema; + +export type TBitBucketConnectionConfig = DiscriminativePick< + TBitBucketConnectionInput, + "method" | "app" | "credentials" +> & { + orgId: string; +}; + +export type TBitBucketVault = { + id: string; + name: string; + type: string; + items: number; + + attributeVersion: number; + contentVersion: number; + + createdAt: string; + updatedAt: string; +}; diff --git a/backend/src/services/app-connection/bitbucket/index.ts b/backend/src/services/app-connection/bitbucket/index.ts new file mode 100644 index 000000000..634342ba7 --- /dev/null +++ b/backend/src/services/app-connection/bitbucket/index.ts @@ -0,0 +1,4 @@ +export * from "./bitbucket-connection-enums"; +export * from "./bitbucket-connection-fns"; +export * from "./bitbucket-connection-schemas"; +export * from "./bitbucket-connection-types"; diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index e1fd9e365..972247bf4 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -42,6 +42,7 @@ import { import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection"; import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection"; import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection"; +import { BitBucketConnectionMethod } from "@app/hooks/api/appConnections/types/bitbucket-connection"; export const APP_CONNECTION_MAP: Record< AppConnection, @@ -88,7 +89,8 @@ export const APP_CONNECTION_MAP: Record< [AppConnection.Render]: { name: "Render", image: "Render.png" }, [AppConnection.Flyio]: { name: "Fly.io", image: "Flyio.svg" }, [AppConnection.Gitlab]: { name: "GitLab", image: "GitLab.png" }, - [AppConnection.Cloudflare]: { name: "Cloudflare", image: "Cloudflare.png" } + [AppConnection.Cloudflare]: { name: "Cloudflare", image: "Cloudflare.png" }, + [AppConnection.BitBucket]: { name: "BitBucket", image: "BitBucket.png" } }; export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => { @@ -120,6 +122,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) case VercelConnectionMethod.ApiToken: case OnePassConnectionMethod.ApiToken: case CloudflareConnectionMethod.ApiToken: + case BitBucketConnectionMethod.ApiToken: return { name: "API Token", icon: faKey }; case PostgresConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword: diff --git a/frontend/src/hooks/api/appConnections/bitbucket/index.ts b/frontend/src/hooks/api/appConnections/bitbucket/index.ts new file mode 100644 index 000000000..2c1906d36 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/bitbucket/index.ts @@ -0,0 +1,2 @@ +export * from "./queries"; +export * from "./types"; diff --git a/frontend/src/hooks/api/appConnections/bitbucket/queries.tsx b/frontend/src/hooks/api/appConnections/bitbucket/queries.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/hooks/api/appConnections/bitbucket/types.ts b/frontend/src/hooks/api/appConnections/bitbucket/types.ts new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/hooks/api/appConnections/enums.ts b/frontend/src/hooks/api/appConnections/enums.ts index 8097720a7..87e08ae36 100644 --- a/frontend/src/hooks/api/appConnections/enums.ts +++ b/frontend/src/hooks/api/appConnections/enums.ts @@ -27,5 +27,6 @@ export enum AppConnection { Render = "render", Flyio = "flyio", Gitlab = "gitlab", - Cloudflare = "cloudflare" + Cloudflare = "cloudflare", + BitBucket = "bitbucket" } diff --git a/frontend/src/hooks/api/appConnections/types/app-options.ts b/frontend/src/hooks/api/appConnections/types/app-options.ts index b71370da7..84af3dbe6 100644 --- a/frontend/src/hooks/api/appConnections/types/app-options.ts +++ b/frontend/src/hooks/api/appConnections/types/app-options.ts @@ -132,6 +132,10 @@ export type TCloudflareConnectionOption = TAppConnectionOptionBase & { app: AppConnection.Cloudflare; }; +export type TBitBucketConnectionOption = TAppConnectionOptionBase & { + app: AppConnection.BitBucket; +}; + export type TAppConnectionOption = | TAwsConnectionOption | TGitHubConnectionOption @@ -159,7 +163,8 @@ export type TAppConnectionOption = | TRenderConnectionOption | TFlyioConnectionOption | TGitlabConnectionOption - | TCloudflareConnectionOption; + | TCloudflareConnectionOption + | TBitBucketConnectionOption; export type TAppConnectionOptionMap = { [AppConnection.AWS]: TAwsConnectionOption; @@ -191,4 +196,5 @@ export type TAppConnectionOptionMap = { [AppConnection.Flyio]: TFlyioConnectionOption; [AppConnection.Gitlab]: TGitlabConnectionOption; [AppConnection.Cloudflare]: TCloudflareConnectionOption; + [AppConnection.BitBucket]: TBitBucketConnectionOption; }; diff --git a/frontend/src/hooks/api/appConnections/types/bitbucket-connection.ts b/frontend/src/hooks/api/appConnections/types/bitbucket-connection.ts new file mode 100644 index 000000000..5a4740105 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/types/bitbucket-connection.ts @@ -0,0 +1,14 @@ +import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection"; + +export enum BitBucketConnectionMethod { + ApiToken = "api-token" +} + +export type TBitBucketConnection = TRootAppConnection & { app: AppConnection.BitBucket } & { + method: BitBucketConnectionMethod.ApiToken; + credentials: { + email: string; + apiToken: string; + }; +}; diff --git a/frontend/src/hooks/api/appConnections/types/index.ts b/frontend/src/hooks/api/appConnections/types/index.ts index 2eaebb45a..6c0da9b69 100644 --- a/frontend/src/hooks/api/appConnections/types/index.ts +++ b/frontend/src/hooks/api/appConnections/types/index.ts @@ -7,6 +7,7 @@ import { TAzureAppConfigurationConnection } from "./azure-app-configuration-conn import { TAzureClientSecretsConnection } from "./azure-client-secrets-connection"; import { TAzureDevOpsConnection } from "./azure-devops-connection"; import { TAzureKeyVaultConnection } from "./azure-key-vault-connection"; +import { TBitBucketConnection } from "./bitbucket-connection"; import { TCamundaConnection } from "./camunda-connection"; import { TCloudflareConnection } from "./cloudflare-connection"; import { TDatabricksConnection } from "./databricks-connection"; @@ -59,6 +60,7 @@ export * from "./teamcity-connection"; export * from "./terraform-cloud-connection"; export * from "./vercel-connection"; export * from "./windmill-connection"; +export * from "./bitbucket-connection"; export type TAppConnection = | TAwsConnection @@ -89,7 +91,8 @@ export type TAppConnection = | TRenderConnection | TFlyioConnection | TGitLabConnection - | TCloudflareConnection; + | TCloudflareConnection + | TBitBucketConnection; export type TAvailableAppConnection = Pick; @@ -146,4 +149,5 @@ export type TAppConnectionMap = { [AppConnection.Flyio]: TFlyioConnection; [AppConnection.Gitlab]: TGitLabConnection; [AppConnection.Cloudflare]: TCloudflareConnection; + [AppConnection.BitBucket]: TBitBucketConnection; }; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx index 52abfee5d..92b23ce3e 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -38,6 +38,7 @@ import { TeamCityConnectionForm } from "./TeamCityConnectionForm"; import { TerraformCloudConnectionForm } from "./TerraformCloudConnectionForm"; import { VercelConnectionForm } from "./VercelConnectionForm"; import { WindmillConnectionForm } from "./WindmillConnectionForm"; +import { BitBucketConnectionForm } from "./BitBucketConnectionForm"; type FormProps = { onComplete: (appConnection: TAppConnection) => void; @@ -134,6 +135,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => { return ; case AppConnection.Cloudflare: return ; + case AppConnection.BitBucket: + return ; default: throw new Error(`Unhandled App ${app}`); } @@ -228,6 +231,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { return ; case AppConnection.Cloudflare: return ; + case AppConnection.BitBucket: + return ; default: throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`); } diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/BitBucketConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/BitBucketConnectionForm.tsx new file mode 100644 index 000000000..e700d7c08 --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/BitBucketConnectionForm.tsx @@ -0,0 +1,144 @@ +import { Controller, FormProvider, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { + Button, + FormControl, + Input, + ModalClose, + SecretInput, + Select, + SelectItem +} from "@app/components/v2"; +import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { BitBucketConnectionMethod, TBitBucketConnection } from "@app/hooks/api/appConnections"; +import { AppConnection } from "@app/hooks/api/appConnections/enums"; + +import { + genericAppConnectionFieldsSchema, + GenericAppConnectionsFields +} from "./GenericAppConnectionFields"; + +type Props = { + appConnection?: TBitBucketConnection; + onSubmit: (formData: FormData) => void; +}; + +const rootSchema = genericAppConnectionFieldsSchema.extend({ + app: z.literal(AppConnection.BitBucket) +}); + +const formSchema = z.discriminatedUnion("method", [ + rootSchema.extend({ + method: z.literal(BitBucketConnectionMethod.ApiToken), + credentials: z.object({ + email: z.string().email().trim().min(1, "Email required"), + apiToken: z.string().trim().min(1, "API Token required") + }) + }) +]); + +type FormData = z.infer; + +export const BitBucketConnectionForm = ({ appConnection, onSubmit }: Props) => { + const isUpdate = Boolean(appConnection); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: appConnection ?? { + app: AppConnection.BitBucket, + method: BitBucketConnectionMethod.ApiToken + } + }); + + const { + handleSubmit, + control, + formState: { isSubmitting, isDirty } + } = form; + + return ( + +
+ {!isUpdate && } + ( + + + + )} + /> + ( + + + + )} + /> + ( + + onChange(e.target.value)} + /> + + )} + /> +
+ + + + +
+ +
+ ); +};