feat: add test connection bitbucket and gitlab

This commit is contained in:
Mauricio Siu
2024-09-01 21:49:59 -06:00
parent d261fd4efe
commit 99f63597a8
32 changed files with 1120 additions and 596 deletions
@@ -58,7 +58,7 @@ interface Props {
export const SaveBitbucketProvider = ({ applicationId }: Props) => {
const { data: bitbucketProviders } =
api.gitProvider.bitbucketProviders.useQuery();
api.bitbucket.bitbucketProviders.useQuery();
const { data, refetch } = api.application.one.useQuery({ applicationId });
const { mutateAsync, isLoading: isSavingBitbucketProvider } =
@@ -85,15 +85,20 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
isLoading: isLoadingRepositories,
error,
isError,
} = api.gitProvider.getBitbucketRepositories.useQuery({
bitbucketId,
});
} = api.bitbucket.getBitbucketRepositories.useQuery(
{
bitbucketId,
},
{
enabled: !!bitbucketId,
},
);
const {
data: branches,
fetchStatus,
status,
} = api.gitProvider.getBitbucketBranches.useQuery(
} = api.bitbucket.getBitbucketBranches.useQuery(
{
owner: repository?.owner,
repo: repository?.repo,
@@ -56,7 +56,7 @@ interface Props {
}
export const SaveGithubProvider = ({ applicationId }: Props) => {
const { data: githubProviders } = api.gitProvider.githubProviders.useQuery();
const { data: githubProviders } = api.github.githubProviders.useQuery();
const { data, refetch } = api.application.one.useQuery({ applicationId });
const { mutateAsync, isLoading: isSavingGithubProvider } =
@@ -79,15 +79,20 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
const githubId = form.watch("githubId");
const { data: repositories, isLoading: isLoadingRepositories } =
api.gitProvider.getGithubRepositories.useQuery({
githubId,
});
api.github.getGithubRepositories.useQuery(
{
githubId,
},
{
enabled: !!githubId,
},
);
const {
data: branches,
fetchStatus,
status,
} = api.gitProvider.getGithubBranches.useQuery(
} = api.github.getGithubBranches.useQuery(
{
owner: repository?.owner,
repo: repository?.repo,
@@ -59,7 +59,7 @@ interface Props {
}
export const SaveGitlabProvider = ({ applicationId }: Props) => {
const { data: gitlabProviders } = api.gitProvider.gitlabProviders.useQuery();
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
const { data, refetch } = api.application.one.useQuery({ applicationId });
const { mutateAsync, isLoading: isSavingGitlabProvider } =
@@ -87,15 +87,20 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
data: repositories,
isLoading: isLoadingRepositories,
error,
} = api.gitProvider.getGitlabRepositories.useQuery({
gitlabId,
});
} = api.gitlab.getGitlabRepositories.useQuery(
{
gitlabId,
},
{
enabled: !!gitlabId,
},
);
const {
data: branches,
fetchStatus,
status,
} = api.gitProvider.getGitlabBranches.useQuery(
} = api.gitlab.getGitlabBranches.useQuery(
{
owner: repository?.owner,
repo: repository?.repo,
@@ -25,10 +25,10 @@ interface Props {
}
export const ShowProviderForm = ({ applicationId }: Props) => {
const { data: githubProviders } = api.gitProvider.githubProviders.useQuery();
const { data: gitlabProviders } = api.gitProvider.gitlabProviders.useQuery();
const { data: githubProviders } = api.github.githubProviders.useQuery();
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
const { data: bitbucketProviders } =
api.gitProvider.bitbucketProviders.useQuery();
api.bitbucket.bitbucketProviders.useQuery();
const { data: application } = api.application.one.useQuery({ applicationId });
const [tab, setSab] = useState<TabState>(application?.sourceType || "github");
@@ -58,7 +58,7 @@ interface Props {
export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
const { data: bitbucketProviders } =
api.gitProvider.bitbucketProviders.useQuery();
api.bitbucket.bitbucketProviders.useQuery();
const { data, refetch } = api.compose.one.useQuery({ composeId });
const { mutateAsync, isLoading: isSavingBitbucketProvider } =
@@ -85,15 +85,20 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
isLoading: isLoadingRepositories,
error,
isError,
} = api.gitProvider.getBitbucketRepositories.useQuery({
bitbucketId,
});
} = api.bitbucket.getBitbucketRepositories.useQuery(
{
bitbucketId,
},
{
enabled: !!bitbucketId,
},
);
const {
data: branches,
fetchStatus,
status,
} = api.gitProvider.getBitbucketBranches.useQuery(
} = api.bitbucket.getBitbucketBranches.useQuery(
{
owner: repository?.owner,
repo: repository?.repo,
@@ -56,7 +56,7 @@ interface Props {
}
export const SaveGithubProviderCompose = ({ composeId }: Props) => {
const { data: githubProviders } = api.gitProvider.githubProviders.useQuery();
const { data: githubProviders } = api.github.githubProviders.useQuery();
const { data, refetch } = api.compose.one.useQuery({ composeId });
const { mutateAsync, isLoading: isSavingGithubProvider } =
@@ -79,15 +79,20 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
const githubId = form.watch("githubId");
const { data: repositories, isLoading: isLoadingRepositories } =
api.gitProvider.getRepositories.useQuery({
githubId,
});
api.github.getGithubRepositories.useQuery(
{
githubId,
},
{
enabled: !!githubId,
},
);
const {
data: branches,
fetchStatus,
status,
} = api.gitProvider.getBranches.useQuery(
} = api.github.getGithubBranches.useQuery(
{
owner: repository?.owner,
repo: repository?.repo,
@@ -59,7 +59,7 @@ interface Props {
}
export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
const { data: gitlabProviders } = api.gitProvider.gitlabProviders.useQuery();
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
const { data, refetch } = api.compose.one.useQuery({ composeId });
const { mutateAsync, isLoading: isSavingGitlabProvider } =
@@ -87,15 +87,20 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
data: repositories,
isLoading: isLoadingRepositories,
error,
} = api.gitProvider.getGitlabRepositories.useQuery({
gitlabId,
});
} = api.gitlab.getGitlabRepositories.useQuery(
{
gitlabId,
},
{
enabled: !!gitlabId,
},
);
const {
data: branches,
fetchStatus,
status,
} = api.gitProvider.getGitlabBranches.useQuery(
} = api.gitlab.getGitlabBranches.useQuery(
{
owner: repository?.owner,
repo: repository?.repo,
@@ -23,10 +23,10 @@ interface Props {
}
export const ShowProviderFormCompose = ({ composeId }: Props) => {
const { data: githubProviders } = api.gitProvider.githubProviders.useQuery();
const { data: gitlabProviders } = api.gitProvider.gitlabProviders.useQuery();
const { data: githubProviders } = api.github.githubProviders.useQuery();
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
const { data: bitbucketProviders } =
api.gitProvider.bitbucketProviders.useQuery();
api.bitbucket.bitbucketProviders.useQuery();
const { data: compose } = api.compose.one.useQuery({ composeId });
const [tab, setSab] = useState<TabState>(compose?.sourceType || "github");
@@ -52,8 +52,7 @@ export const AddBitbucketProvider = () => {
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const url = useUrl();
const { mutateAsync, error, isError } =
api.gitProvider.createBitbucket.useMutation();
const { mutateAsync, error, isError } = api.bitbucket.create.useMutation();
const { data: auth } = api.auth.get.useQuery();
const router = useRouter();
const form = useForm<Schema>({
@@ -1,8 +1,4 @@
import {
BitbucketIcon,
GithubIcon,
GitlabIcon,
} from "@/components/icons/data-tools-icons";
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import { CardContent } from "@/components/ui/card";
@@ -23,11 +19,8 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { useUrl } from "@/utils/hooks/use-url";
import { zodResolver } from "@hookform/resolvers/zod";
import { ExternalLink } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { Edit } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -40,73 +33,77 @@ const Schema = z.object({
username: z.string().min(1, {
message: "Username is required",
}),
password: z.string().min(1, {
message: "App Password is required",
}),
workspaceName: z.string().optional(),
});
type Schema = z.infer<typeof Schema>;
export const AddBitbucketProvider = () => {
// const {data} = api.gitProvider.
interface Props {
bitbucketId: string;
}
export const EditBitbucketProvider = ({ bitbucketId }: Props) => {
const { data: bitbucket } = api.bitbucket.one.useQuery(
{
bitbucketId,
},
{
enabled: !!bitbucketId,
},
);
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const url = useUrl();
const { mutateAsync, error, isError } =
api.gitProvider.createBitbucket.useMutation();
const { data: auth } = api.auth.get.useQuery();
const router = useRouter();
const { mutateAsync, error, isError } = api.bitbucket.update.useMutation();
const { mutateAsync: testConnection, isLoading } =
api.bitbucket.testConnection.useMutation();
const form = useForm<Schema>({
defaultValues: {
username: "",
password: "",
workspaceName: "",
},
resolver: zodResolver(Schema),
});
const username = form.watch("username");
const workspaceName = form.watch("workspaceName");
useEffect(() => {
form.reset({
username: "",
password: "",
workspaceName: "",
username: bitbucket?.bitbucketUsername || "",
workspaceName: bitbucket?.bitbucketWorkspaceName || "",
name: bitbucket?.gitProvider.name || "",
});
}, [form, isOpen]);
const onSubmit = async (data: Schema) => {
await mutateAsync({
bitbucketId,
gitProviderId: bitbucket?.gitProviderId || "",
bitbucketUsername: data.username,
appPassword: data.password,
bitbucketWorkspaceName: data.workspaceName || "",
authId: auth?.id || "",
name: data.name || "",
})
.then(async () => {
await utils.gitProvider.getAll.invalidate();
toast.success("Bitbucket configured successfully");
toast.success("Bitbucket updated successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error configuring Bitbucket");
toast.error("Error to update Bitbucket");
});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button
variant="secondary"
className="flex items-center space-x-1 bg-blue-700 text-white hover:bg-blue-600"
>
<BitbucketIcon />
<span>Bitbucket</span>
<Button variant="ghost">
<Edit className="size-4" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
Bitbucket Provider <BitbucketIcon className="size-5" />
Update Bitbucket Provider <BitbucketIcon className="size-5" />
</DialogTitle>
</DialogHeader>
@@ -119,37 +116,6 @@ export const AddBitbucketProvider = () => {
>
<CardContent className="p-0">
<div className="flex flex-col gap-4">
<p className="text-muted-foreground text-sm">
To integrate your Bitbucket account, you need to create a new
App Password in your Bitbucket settings. Follow these steps:
</p>
<ol className="list-decimal list-inside text-sm text-muted-foreground">
<li className="flex flex-row gap-2 items-center">
Create new App Password{" "}
<Link
href="https://bitbucket.org/account/settings/app-passwords/new"
target="_blank"
>
<ExternalLink className="w-fit text-primary size-4" />
</Link>
</li>
<li>
When creating the App Password, ensure you grant the
following permissions:
<ul className="list-disc list-inside ml-4">
<li>Account: Read</li>
<li>Workspace membership: Read</li>
<li>Projects: Read</li>
<li>Repositories: Read</li>
<li>Pull requests: Read</li>
<li>Webhooks: Read and write</li>
</ul>
</li>
<li>
After creating, you'll receive an App Password. Copy it and
paste it below along with your Bitbucket username.
</li>
</ol>
<FormField
control={form.control}
name="name"
@@ -183,24 +149,6 @@ export const AddBitbucketProvider = () => {
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>App Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder="ATBBPDYUC94nR96Nj7Cqpp4pfwKk03573DD2"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="workspaceName"
@@ -218,9 +166,31 @@ export const AddBitbucketProvider = () => {
)}
/>
<Button isLoading={form.formState.isSubmitting}>
Configure Bitbucket
</Button>
<div className="flex w-full justify-end gap-4 mt-4">
<Button
type="button"
variant={"secondary"}
isLoading={isLoading}
onClick={async () => {
await testConnection({
bitbucketId,
bitbucketUsername: username,
workspaceName: workspaceName,
})
.then(async (message) => {
toast.info(`Message: ${message}`);
})
.catch((error) => {
toast.error(`Error: ${error.message}`);
});
}}
>
Test Connection
</Button>
<Button type="submit" isLoading={form.formState.isSubmitting}>
Update
</Button>
</div>
</div>
</CardContent>
</form>
@@ -53,8 +53,7 @@ export const AddGitlabProvider = () => {
const [isOpen, setIsOpen] = useState(false);
const url = useUrl();
const { data: auth } = api.auth.get.useQuery();
const { mutateAsync, error, isError } =
api.gitProvider.createGitlab.useMutation();
const { mutateAsync, error, isError } = api.gitlab.create.useMutation();
const webhookUrl = `${url}/api/providers/gitlab/callback`;
const form = useForm<Schema>({
@@ -0,0 +1,180 @@
import { GitlabIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import { CardContent } from "@/components/ui/card";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { useUrl } from "@/utils/hooks/use-url";
import { zodResolver } from "@hookform/resolvers/zod";
import { Edit } from "lucide-react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const Schema = z.object({
name: z.string().min(1, {
message: "Name is required",
}),
groupName: z.string().optional(),
});
type Schema = z.infer<typeof Schema>;
interface Props {
gitlabId: string;
}
export const EditGitlabProvider = ({ gitlabId }: Props) => {
const { data: gitlab } = api.gitlab.one.useQuery(
{
gitlabId,
},
{
enabled: !!gitlabId,
},
);
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, error, isError } = api.gitlab.update.useMutation();
const { mutateAsync: testConnection, isLoading } =
api.gitlab.testConnection.useMutation();
const form = useForm<Schema>({
defaultValues: {
groupName: "",
name: "",
},
resolver: zodResolver(Schema),
});
const groupName = form.watch("groupName");
useEffect(() => {
form.reset({
groupName: gitlab?.groupName || "",
name: gitlab?.gitProvider.name || "",
});
}, [form, isOpen]);
const onSubmit = async (data: Schema) => {
await mutateAsync({
gitlabId,
gitProviderId: gitlab?.gitProviderId || "",
groupName: data.groupName || "",
name: data.name || "",
})
.then(async () => {
await utils.gitProvider.getAll.invalidate();
toast.success("Gitlab updated successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error to update Gitlab");
});
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost">
<Edit className="size-4" />
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-2xl overflow-y-auto max-h-screen">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
Update GitLab Provider <GitlabIcon className="size-5" />
</DialogTitle>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
id="hook-form-add-bitbucket"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-1"
>
<CardContent className="p-0">
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input
placeholder="Random Name eg(my-personal-account)"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="groupName"
render={({ field }) => (
<FormItem>
<FormLabel>Group Name (Optional)</FormLabel>
<FormControl>
<Input
placeholder="For organization/group access"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex w-full justify-end gap-4 mt-4">
<Button
type="button"
variant={"secondary"}
isLoading={isLoading}
onClick={async () => {
await testConnection({
gitlabId,
groupName: groupName || "",
})
.then(async (message) => {
toast.info(`Message: ${message}`);
})
.catch((error) => {
toast.error(`Error: ${error.message}`);
});
}}
>
Test Connection
</Button>
<Button type="submit" isLoading={form.formState.isSubmitting}>
Update
</Button>
</div>
</div>
</CardContent>
</form>
</Form>
</DialogContent>
</Dialog>
);
};
@@ -12,6 +12,9 @@ import { api } from "@/utils/api";
import Link from "next/link";
import { RemoveGitProvider } from "./remove-git-provider";
import { useUrl } from "@/utils/hooks/use-url";
import { EditBitbucketProvider } from "./bitbucket/edit-bitbucket-provider";
import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider";
import { formatDate } from "date-fns";
export const ShowGitProviders = () => {
const { data } = api.gitProvider.getAll.useQuery();
@@ -48,6 +51,7 @@ export const ShowGitProviders = () => {
{data?.map((gitProvider, index) => {
const isGithub = gitProvider.providerType === "github";
const isGitlab = gitProvider.providerType === "gitlab";
const isBitbucket = gitProvider.providerType === "bitbucket";
const haveGithubRequirements =
gitProvider.providerType === "github" &&
gitProvider.github?.githubPrivateKey &&
@@ -61,7 +65,7 @@ export const ShowGitProviders = () => {
className="space-y-4"
key={`${gitProvider.gitProviderId}-${index}`}
>
<Card className="flex sm:flex-row max-sm:gap-2 flex-col justify-between items-center p-4 h-full">
<Card className="flex sm:flex-row max-sm:gap-2 flex-col justify-between items-center p-4 h-full bg-transparent">
<div className="flex items-center space-x-4 w-full">
{gitProvider.providerType === "github" && (
<GithubIcon className="w-6 h-6" />
@@ -72,7 +76,7 @@ export const ShowGitProviders = () => {
{gitProvider.providerType === "bitbucket" && (
<BitbucketIcon className="w-6 h-6" />
)}
<div>
<div className="flex flex-col gap-1">
<p className="font-medium">
{gitProvider.providerType === "github"
? "GitHub"
@@ -83,6 +87,15 @@ export const ShowGitProviders = () => {
<p className="text-sm text-muted-foreground">
{gitProvider.name}
</p>
<span>
<p className="text-sm text-muted-foreground">
Created{" "}
{formatDate(
gitProvider.createdAt,
"yyyy-MM-dd hh:mm:ss a",
)}
</p>
</span>
</div>
</div>
<div className="flex sm:gap-4 sm:flex-row flex-col">
@@ -92,7 +105,7 @@ export const ShowGitProviders = () => {
href={`${gitProvider?.github?.githubAppName}/installations/new?state=gh_setup:${gitProvider?.github.githubId}`}
className={buttonVariants({ className: "w-fit" })}
>
Install Github App
Install
</Link>
</div>
)}
@@ -107,7 +120,7 @@ export const ShowGitProviders = () => {
variant: "secondary",
})}
>
<span className="text-sm">Manage Github App</span>
<span className="text-sm">Manage</span>
</Link>
</div>
)}
@@ -125,15 +138,26 @@ export const ShowGitProviders = () => {
variant: "secondary",
})}
>
<span className="text-sm">Install Gitlab App</span>
<span className="text-sm">Install</span>
</Link>
</div>
)}
<RemoveGitProvider
gitProviderId={gitProvider.gitProviderId}
gitProviderType={gitProvider.providerType}
/>
<div className="flex flex-row gap-1">
{isBitbucket && (
<EditBitbucketProvider
bitbucketId={gitProvider.bitbucket.bitbucketId}
/>
)}
{isGitlab && haveGitlabRequirements && (
<EditGitlabProvider
gitlabId={gitProvider.gitlab.gitlabId}
/>
)}
<RemoveGitProvider
gitProviderId={gitProvider.gitProviderId}
gitProviderType={gitProvider.providerType}
/>
</div>
</div>
</Card>
</div>