Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Git integration more stuff #21

Merged
merged 24 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useCopyToClipboard } from "react-use";
import { z } from "zod";

import { cn } from "@ctrlplane/ui";
import { Alert, AlertDescription, AlertTitle } from "@ctrlplane/ui/alert";
import { Button } from "@ctrlplane/ui/button";
import {
Dialog,
Expand Down Expand Up @@ -91,25 +92,25 @@ export const GoogleDialog: React.FC<{ children: React.ReactNode }> = ({
from google.
</DialogDescription>

<div
className="relative mb-4 flex w-fit items-center gap-2 rounded-md bg-neutral-800/50 px-4 py-3 text-muted-foreground"
role="alert"
>
<TbBulb className="h-6 w-6 flex-shrink-0" />
<span className="text-sm">
To use the Google provider, you will need to invite our
service account to your project and configure the necessary
permissions. Read more{" "}
<Link
href="https://docs.ctrlplane.dev/integrations/google-cloud/compute-scanner"
target="_blank"
className="underline"
>
here
</Link>
.
</span>
</div>
<Alert variant="secondary">
<TbBulb className="h-5 w-5" />
<AlertTitle>Google Provider</AlertTitle>
<AlertDescription>
<span>
To use the Google provider, you will need to invite our
service account to your project and configure the necessary
permissions. Read more{" "}
<Link
href="https://docs.ctrlplane.dev/integrations/google-cloud/compute-scanner"
target="_blank"
className="underline"
>
here
</Link>
.
</span>
</AlertDescription>
</Alert>
</DialogHeader>

<div className="space-y-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TbBulb, TbCheck, TbCopy, TbX } from "react-icons/tb";
import { useCopyToClipboard } from "react-use";

import { cn } from "@ctrlplane/ui";
import { Alert, AlertDescription, AlertTitle } from "@ctrlplane/ui/alert";
import { Button } from "@ctrlplane/ui/button";
import {
Dialog,
Expand Down Expand Up @@ -101,12 +102,10 @@ export const UpdateGoogleProviderDialog: React.FC<{
from google.
</DialogDescription>

<div
className="relative mb-4 flex w-fit items-center gap-2 rounded-md bg-neutral-800/50 px-4 py-3 text-muted-foreground"
role="alert"
>
<TbBulb className="h-6 w-6 flex-shrink-0" />
<span className="text-sm">
<Alert variant="secondary">
<TbBulb className="h-5 w-5" />
<AlertTitle>Google Provider</AlertTitle>
<AlertDescription>
To use the Google provider, you will need to invite our
service account to your project and configure the necessary
permissions. Read more{" "}
Expand All @@ -118,8 +117,8 @@ export const UpdateGoogleProviderDialog: React.FC<{
here
</Link>
.
</span>
</div>
</AlertDescription>
</Alert>
</DialogHeader>

<div className="space-y-2">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

import { useRouter } from "next/navigation";

import { Button } from "@ctrlplane/ui/button";

import { api } from "~/trpc/react";

export const DeleteGithubUserButton: React.FC<{ githubUserId: string }> = ({
githubUserId,
}) => {
const deleteGithubUser = api.github.user.delete.useMutation();
const router = useRouter();

const handleDelete = () =>
deleteGithubUser.mutateAsync(githubUserId).then(() => router.refresh());

return (
<Button variant="secondary" onClick={handleDelete}>
Disconnect
</Button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client";

import React from "react";

import { DropdownMenuItem } from "@ctrlplane/ui/dropdown-menu";

export const DisconnectDropdownActionButton = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<typeof DropdownMenuItem>
>((props, ref) => {
return (
<DropdownMenuItem {...props} ref={ref} onSelect={(e) => e.preventDefault()}>
Disconnect
</DropdownMenuItem>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"use client";

import type { GithubUser } from "@ctrlplane/db/schema";
import { useState } from "react";
import Link from "next/link";
import { TbBulb } from "react-icons/tb";

import { Alert, AlertDescription, AlertTitle } from "@ctrlplane/ui/alert";
import { Button } from "@ctrlplane/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@ctrlplane/ui/dialog";

import type { GithubOrg } from "./SelectPreconnectedOrgDialogContent";
import { SelectPreconnectedOrgDialogContent } from "./SelectPreconnectedOrgDialogContent";

type GithubAddOrgDialogProps = {
githubUser: GithubUser;
children: React.ReactNode;
githubConfig: {
url: string;
botName: string;
clientId: string;
};
validOrgsToAdd: GithubOrg[];
workspaceId: string;
};

export const GithubAddOrgDialog: React.FC<GithubAddOrgDialogProps> = ({
githubUser,
children,
githubConfig,
validOrgsToAdd,
workspaceId,
}) => {
const [dialogStep, setDialogStep] = useState<"choose-org" | "pre-connected">(
"choose-org",
);
const [open, setOpen] = useState(false);

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="flex flex-col gap-4">
{dialogStep === "choose-org" && (
<>
<DialogHeader>
<DialogTitle>Connect a new Organization</DialogTitle>
{validOrgsToAdd.length === 0 && (
<DialogDescription>
Install the ctrlplane Github app on an organization to connect
it to your workspace.
</DialogDescription>
)}
</DialogHeader>

{validOrgsToAdd.length > 0 && (
<Alert variant="secondary">
<TbBulb className="h-5 w-5" />
<AlertTitle>Connect an organization</AlertTitle>
<AlertDescription>
You have two options for connecting an organization:
<ol className="list-decimal space-y-2 pl-5">
<li>
<strong>Connect a new organization:</strong> Install the
ctrlplane Github app on a new organization.
</li>
<li>
<strong>Select pre-connected:</strong> Select a
pre-connected organization where the ctrlplane app is
installed.
</li>
</ol>
<span>
Read more{" "}
<Link
href="https://docs.ctrlplane.dev/integrations/github/github-bot"
target="_blank"
className="underline"
>
here
</Link>
.
</span>
</AlertDescription>
</Alert>
)}

<DialogFooter className="flex">
<Link
href={`${githubConfig.url}/apps/${githubConfig.botName}/installations/select_target`}
>
<Button variant="outline">Connect new organization</Button>
</Link>

{validOrgsToAdd.length > 0 && (
<div className="flex flex-grow justify-end">
<Button
className="w-fit"
variant="outline"
onClick={() => setDialogStep("pre-connected")}
>
Select pre-connected
</Button>
</div>
)}
</DialogFooter>
</>
)}

{dialogStep === "pre-connected" && (
<SelectPreconnectedOrgDialogContent
githubOrgs={validOrgsToAdd}
githubUser={githubUser}
workspaceId={workspaceId}
onNavigateBack={() => setDialogStep("choose-org")}
onSave={() => {
setOpen(false);
setDialogStep("choose-org");
}}
/>
)}
</DialogContent>
</Dialog>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import type { GithubUser } from "@ctrlplane/db/schema";
import { SiGithub } from "react-icons/si";
import { TbPlus } from "react-icons/tb";

import { Avatar, AvatarFallback, AvatarImage } from "@ctrlplane/ui/avatar";
import { Button } from "@ctrlplane/ui/button";
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from "@ctrlplane/ui/card";
import { Separator } from "@ctrlplane/ui/separator";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@ctrlplane/ui/tooltip";

import { api } from "~/trpc/server";
import { GithubAddOrgDialog } from "./GithubAddOrgDialog";
import { OrgActionDropdown } from "./OrgActionDropdown";

type GithubConnectedOrgsProps = {
githubUser?: GithubUser | null;
workspaceId: string;
loading: boolean;
githubConfig: {
url: string;
botName: string;
clientId: string;
};
};

export const GithubConnectedOrgs: React.FC<GithubConnectedOrgsProps> = async ({
githubUser,
workspaceId,
githubConfig,
}) => {
const githubOrgsUserCanAccess =
githubUser != null
? await api.github.organizations.byGithubUserId({
workspaceId,
githubUserId: githubUser.githubUserId,
})
: [];
const githubOrgsInstalled = await api.github.organizations.list(workspaceId);
const validOrgsToAdd = githubOrgsUserCanAccess.filter(
(org) =>
!githubOrgsInstalled.some(
(installedOrg) => installedOrg.organizationName === org.login,
),
);

return (
<Card className="rounded-md">
<CardHeader className="flex flex-row items-center justify-between">
<div className="space-y-1">
<CardTitle className="flex-grow">
Connected Github organizations
</CardTitle>
<CardDescription>
You can configure job agents and sync config files for these
organizations
</CardDescription>
</div>
{githubUser != null ? (
<GithubAddOrgDialog
githubUser={githubUser}
githubConfig={githubConfig}
workspaceId={workspaceId}
validOrgsToAdd={validOrgsToAdd}
>
<Button size="icon" variant="secondary">
<TbPlus className="h-3 w-3" />
</Button>
</GithubAddOrgDialog>
) : (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
variant="secondary"
className="cursor-not-allowed hover:bg-secondary hover:text-secondary-foreground"
>
<TbPlus className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Connect your Github account to add organizations</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</CardHeader>

{githubOrgsInstalled.length > 0 && (
<>
<Separator />
<div className="flex flex-col gap-4 p-4">
{githubOrgsInstalled.map((org) => (
<div key={org.id} className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Avatar className="h-12 w-12">
<AvatarImage src={org.avatarUrl ?? ""} />
<AvatarFallback>
<SiGithub className="h-12 w-12" />
</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<p className="font-semibold text-neutral-200">
{org.organizationName}
</p>
{org.addedByUser != null && (
<p className="text-sm text-neutral-400">
Enabled by {org.addedByUser.githubUsername} on{" "}
{org.createdAt.toLocaleDateString()}
</p>
)}
</div>
</div>
<OrgActionDropdown githubConfig={githubConfig} org={org} />
</div>
))}
</div>
</>
)}
</Card>
);
};
Loading
Loading