feat: finalized integration selection in project settings

This commit is contained in:
Sheen Capadngan
2024-09-04 00:34:03 +08:00
parent a2b555dd81
commit 604b0467f9
19 changed files with 383 additions and 220 deletions
@@ -6,9 +6,9 @@ import { useWorkspace } from "@app/context";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
import { EncryptionTab } from "./components/EncryptionTab";
import { NotificationTab } from "./components/NotificationSection";
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
import { WebhooksTab } from "./components/WebhooksTab";
import { WorkflowIntegrationTab } from "./components/WorkflowIntegrationSection";
export const ProjectSettingsPage = () => {
const { t } = useTranslation();
@@ -20,7 +20,7 @@ export const ProjectSettingsPage = () => {
key: "tab-project-encryption",
isHidden: currentWorkspace?.version !== ProjectVersion.V3
},
{ name: "Notification", key: "tab-project-notification" },
{ name: "Workflow Integrations", key: "tab-workflow-integrations" },
{ name: "Webhooks", key: "tab-project-webhooks" }
];
@@ -59,7 +59,7 @@ export const ProjectSettingsPage = () => {
</Tab.Panel>
)}
<Tab.Panel>
<NotificationTab />
<WorkflowIntegrationTab />
</Tab.Panel>
<Tab.Panel>
<WebhooksTab />
@@ -1 +0,0 @@
export * from "./NotificationTab";
@@ -1,45 +1,45 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { useRouter } from "next/router";
import Link from "next/link";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
ContentLoader,
DeleteActionModal,
EmptyState,
FormControl,
Input,
Select,
SelectItem,
Switch
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import {
fetchSlackInstallUrl,
useDeleteSlackIntegration,
useGetSlackIntegrationByProject,
useUpdateSlackIntegration
useGetSlackIntegrations,
useGetWorkspaceSlackConfig,
useUpdateProjectSlackConfig
} from "@app/hooks/api";
const formSchema = z.object({
slackIntegrationId: z.string(),
isSecretRequestNotificationEnabled: z.boolean(),
secretRequestChannels: z.string(),
secretRequestChannels: z.string().default(""),
isAccessRequestNotificationEnabled: z.boolean(),
accessRequestChannels: z.string()
accessRequestChannels: z.string().default("")
});
type TSlackIntegrationForm = z.infer<typeof formSchema>;
type TSlackConfigForm = z.infer<typeof formSchema>;
export const NotificationTab = () => {
export const WorkflowIntegrationTab = () => {
const { currentWorkspace } = useWorkspace();
const { data: slackIntegration, isLoading: isSlackIntegrationLoading } =
useGetSlackIntegrationByProject(currentWorkspace?.id);
const { mutateAsync: updateSlackIntegration } = useUpdateSlackIntegration();
const { mutateAsync: deleteSlackIntegration } = useDeleteSlackIntegration();
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([
"deleteSlackIntegration"
] as const);
const { data: slackConfig, isLoading: isSlackConfigLoading } = useGetWorkspaceSlackConfig({
workspaceId: currentWorkspace?.id ?? ""
});
const { data: slackIntegrations } = useGetSlackIntegrations(currentWorkspace?.orgId);
const { mutateAsync: updateProjectSlackConfig } = useUpdateProjectSlackConfig();
const {
control,
@@ -47,29 +47,27 @@ export const NotificationTab = () => {
handleSubmit,
setValue,
formState: { isDirty, isSubmitting }
} = useForm<TSlackIntegrationForm>({
} = useForm<TSlackConfigForm>({
resolver: zodResolver(formSchema),
defaultValues: {
isSecretRequestNotificationEnabled: slackIntegration?.isSecretRequestNotificationEnabled,
secretRequestChannels: slackIntegration?.secretRequestChannels || "",
isAccessRequestNotificationEnabled: slackIntegration?.isAccessRequestNotificationEnabled,
accessRequestChannels: slackIntegration?.accessRequestChannels || ""
isAccessRequestNotificationEnabled: false,
accessRequestChannels: "",
isSecretRequestNotificationEnabled: false,
secretRequestChannels: ""
}
});
const router = useRouter();
const [isConnectToSlackLoading, setIsConnectToSlackLoading] = useToggle(false);
const [isReinstallLoading, setIsReinstallLoading] = useToggle(false);
const secretRequestNotifState = watch("isSecretRequestNotificationEnabled");
const selectedSlackIntegrationId = watch("slackIntegrationId");
const accessRequestNotifState = watch("isAccessRequestNotificationEnabled");
const handleIntegrationSave = async (data: TSlackIntegrationForm) => {
if (!currentWorkspace || !slackIntegration) {
const handleIntegrationSave = async (data: TSlackConfigForm) => {
if (!currentWorkspace) {
return;
}
await updateSlackIntegration({
workspaceId: currentWorkspace?.id,
id: slackIntegration?.id,
await updateProjectSlackConfig({
workspaceId: currentWorkspace.id,
...data
});
@@ -79,96 +77,76 @@ export const NotificationTab = () => {
});
};
const handleIntegrationDelete = async () => {
if (!currentWorkspace || !slackIntegration) {
return;
}
await deleteSlackIntegration({
workspaceId: currentWorkspace.id,
id: slackIntegration.id
});
handlePopUpToggle("deleteSlackIntegration", false);
createNotification({
type: "success",
text: "Successfully deleted slack integration"
});
};
const triggerSlackInstall = async () => {
const slackInstallUrl = await fetchSlackInstallUrl(currentWorkspace?.id);
if (slackInstallUrl) {
router.push(slackInstallUrl);
}
};
useEffect(() => {
if (slackIntegration) {
if (slackConfig) {
setValue("slackIntegrationId", slackConfig.slackIntegrationId);
setValue(
"isSecretRequestNotificationEnabled",
slackIntegration.isSecretRequestNotificationEnabled
slackConfig.isSecretRequestNotificationEnabled
);
setValue("secretRequestChannels", slackIntegration.secretRequestChannels);
setValue("secretRequestChannels", slackConfig.secretRequestChannels);
setValue(
"isAccessRequestNotificationEnabled",
slackIntegration.isAccessRequestNotificationEnabled
slackConfig.isAccessRequestNotificationEnabled
);
setValue("accessRequestChannels", slackIntegration.accessRequestChannels);
setValue("accessRequestChannels", slackConfig.accessRequestChannels);
}
}, [slackIntegration]);
}, [slackConfig]);
if (isSlackIntegrationLoading) {
if (isSlackConfigLoading) {
return <ContentLoader />;
}
return (
<>
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex justify-between">
<h2 className="mb-2 flex-1 text-xl font-semibold text-mineshaft-100">
Slack Integration
</h2>
return !slackIntegrations?.length ? (
<EmptyState title="You do not have any integrations configured.">
<Link href={`/org/${currentWorkspace?.orgId}/settings?tab=workflowIntegrations`}>
<div className="mt-2 underline decoration-primary-800 underline-offset-4 duration-200 hover:cursor-pointer hover:text-mineshaft-100 hover:decoration-primary-600">
Create one now
</div>
<p className="mb-4 text-gray-400">
This integration allows you to send notifications to your Slack workspace in response to
events in your project.
</p>
{!slackIntegration && (
<Button
isLoading={isConnectToSlackLoading}
onClick={async () => {
setIsConnectToSlackLoading.on();
await triggerSlackInstall();
}}
>
Connect to Slack
</Button>
)}
{slackIntegration && (
<form onSubmit={handleSubmit(handleIntegrationSave)}>
<div>Connected Slack workspace: {slackIntegration.teamName}</div>
<div className="mt-2 mb-6">
<Button
colorSchema="secondary"
size="xs"
isLoading={isReinstallLoading}
onClick={async () => {
setIsReinstallLoading.on();
await triggerSlackInstall();
}}
>
Reinstall
</Button>
<Button
colorSchema="secondary"
size="xs"
className="ml-2"
onClick={() => handlePopUpOpen("deleteSlackIntegration")}
>
Delete
</Button>
</div>
</Link>
</EmptyState>
) : (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex justify-between">
<h2 className="mb-2 flex-1 text-xl font-semibold text-mineshaft-100">Slack Integration</h2>
</div>
<p className="mb-4 text-gray-400">
This integration allows you to send notifications to your Slack workspace in response to
events in your project.
</p>
<form onSubmit={handleSubmit(handleIntegrationSave)}>
<div className="max-w-md">
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => (
<Controller
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl errorText={error?.message} isError={Boolean(error)}>
<Select
{...field}
isDisabled={!isAllowed}
onValueChange={onChange}
defaultValue={slackConfig?.slackIntegrationId}
className="w-3/4 bg-mineshaft-600"
>
{slackIntegrations?.map((slackIntegration) => (
<SelectItem
value={slackIntegration.id}
key={`slack-integration-${slackIntegration.id}`}
>
{slackIntegration.slug}
</SelectItem>
))}
</Select>
</FormControl>
)}
control={control}
name="slackIntegrationId"
/>
)}
</ProjectPermissionCan>
</div>
{selectedSlackIntegrationId && (
<>
<h2 className="mb-2 flex-1 text-lg font-semibold text-mineshaft-100">Events</h2>
<Controller
control={control}
@@ -260,16 +238,9 @@ export const NotificationTab = () => {
>
Save
</Button>
</form>
</>
)}
</div>
<DeleteActionModal
isOpen={popUp.deleteSlackIntegration.isOpen}
title="Are you sure want to delete your Slack integration?"
onChange={(isOpen) => handlePopUpToggle("deleteSlackIntegration", isOpen)}
deleteKey="confirm"
onDeleteApproved={handleIntegrationDelete}
/>
</>
</form>
</div>
);
};
@@ -0,0 +1 @@
export * from "./WorkflowIntegrationTab";