mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 22:28:15 +00:00
feat: finished slack integration update
This commit is contained in:
@@ -17,9 +17,9 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
tb.string("slackBotId").notNullable();
|
tb.string("slackBotId").notNullable();
|
||||||
tb.string("slackBotUserId").notNullable();
|
tb.string("slackBotUserId").notNullable();
|
||||||
tb.boolean("isAccessRequestNotificationEnabled").defaultTo(false);
|
tb.boolean("isAccessRequestNotificationEnabled").defaultTo(false);
|
||||||
tb.string("accessRequestChannels");
|
tb.string("accessRequestChannels").defaultTo("");
|
||||||
tb.boolean("isSecretRequestNotificationEnabled").defaultTo(false);
|
tb.boolean("isSecretRequestNotificationEnabled").defaultTo(false);
|
||||||
tb.string("secretRequestChannels");
|
tb.string("secretRequestChannels").defaultTo("");
|
||||||
tb.timestamps(true, true, true);
|
tb.timestamps(true, true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SlackIntegrationsSchema } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { validateSlackChannelsField } from "@app/services/slack/slack-auth-validators";
|
||||||
|
|
||||||
export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@@ -27,8 +29,9 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.string()
|
200: z.string()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
// TODO: add audit logs
|
||||||
return server.services.slack.getInstallUrl({
|
return server.services.slack.getInstallUrl({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -39,6 +42,90 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
querystring: z.object({
|
||||||
|
projectId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: SlackIntegrationsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
teamName: true,
|
||||||
|
isAccessRequestNotificationEnabled: true,
|
||||||
|
accessRequestChannels: true,
|
||||||
|
isSecretRequestNotificationEnabled: true,
|
||||||
|
secretRequestChannels: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
return server.services.slack.getSlackIntegrationByProjectId({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId: req.query.projectId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:slackIntegrationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
slackIntegrationId: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
isAccessRequestNotificationEnabled: z.boolean().optional(),
|
||||||
|
accessRequestChannels: validateSlackChannelsField.optional(),
|
||||||
|
isSecretRequestNotificationEnabled: z.boolean().optional(),
|
||||||
|
secretRequestChannels: validateSlackChannelsField.optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: SlackIntegrationsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
teamName: true,
|
||||||
|
isAccessRequestNotificationEnabled: true,
|
||||||
|
accessRequestChannels: true,
|
||||||
|
isSecretRequestNotificationEnabled: true,
|
||||||
|
secretRequestChannels: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
// TODO: add audit logs
|
||||||
|
return server.services.slack.updateSlackIntegration({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.slackIntegrationId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/oauth_redirect",
|
url: "/oauth_redirect",
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const validateSlackChannelsField = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.default("")
|
||||||
|
.transform((data) => {
|
||||||
|
if (data === "") return "";
|
||||||
|
return data
|
||||||
|
.split(",")
|
||||||
|
.map((id) => id.trim())
|
||||||
|
.join(", ");
|
||||||
|
});
|
||||||
@@ -34,12 +34,12 @@ export const triggerSlackNotification = async ({
|
|||||||
|
|
||||||
let targetChannels: string[] = [];
|
let targetChannels: string[] = [];
|
||||||
if (feature === SlackTriggerFeature.ACCESS_REQUEST) {
|
if (feature === SlackTriggerFeature.ACCESS_REQUEST) {
|
||||||
targetChannels = slackIntegration.accessRequestChannels?.split(",") || [];
|
targetChannels = slackIntegration.accessRequestChannels?.split(", ") || [];
|
||||||
if (!targetChannels.length || !slackIntegration.isAccessRequestNotificationEnabled) {
|
if (!targetChannels.length || !slackIntegration.isAccessRequestNotificationEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (feature === SlackTriggerFeature.SECRET_APPROVAL) {
|
} else if (feature === SlackTriggerFeature.SECRET_APPROVAL) {
|
||||||
targetChannels = slackIntegration.secretRequestChannels?.split(",") || [];
|
targetChannels = slackIntegration.secretRequestChannels?.split(", ") || [];
|
||||||
if (!targetChannels.length || !slackIntegration.isSecretRequestNotificationEnabled) {
|
if (!targetChannels.length || !slackIntegration.isSecretRequestNotificationEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,15 @@ import { TKmsServiceFactory } from "../kms/kms-service";
|
|||||||
import { KmsDataKey } from "../kms/kms-types";
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { TSlackIntegrationDALFactory } from "./slack-integration-dal";
|
import { TSlackIntegrationDALFactory } from "./slack-integration-dal";
|
||||||
import { TCompleteSlackIntegrationDTO, TGetSlackInstallUrlDTO } from "./slack-types";
|
import {
|
||||||
|
TCompleteSlackIntegrationDTO,
|
||||||
|
TGetSlackInstallUrlDTO,
|
||||||
|
TGetSlackIntegrationByProjectId,
|
||||||
|
TUpdateSlackIntegration
|
||||||
|
} from "./slack-types";
|
||||||
|
|
||||||
type TSlackServiceFactoryDep = {
|
type TSlackServiceFactoryDep = {
|
||||||
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "create">;
|
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "create" | "findOne" | "findById" | "updateById">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
@@ -142,14 +147,72 @@ export const slackServiceFactory = ({
|
|||||||
redirectUri: `${appCfg.SITE_URL}/api/v1/slack/oauth_redirect`
|
redirectUri: `${appCfg.SITE_URL}/api/v1/slack/oauth_redirect`
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: add audit log here
|
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSlackIntegrationByProjectId = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectId
|
||||||
|
}: TGetSlackIntegrationByProjectId) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
||||||
|
const slackIntegration = await slackIntegrationDAL.findOne({
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
return slackIntegration;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSlackIntegration = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
id,
|
||||||
|
isAccessRequestNotificationEnabled,
|
||||||
|
accessRequestChannels,
|
||||||
|
isSecretRequestNotificationEnabled,
|
||||||
|
secretRequestChannels
|
||||||
|
}: TUpdateSlackIntegration) => {
|
||||||
|
const slackIntegration = await slackIntegrationDAL.findById(id);
|
||||||
|
if (!slackIntegration) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Slack integration not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
slackIntegration.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
|
||||||
|
|
||||||
|
return slackIntegrationDAL.updateById(slackIntegration.id, {
|
||||||
|
isAccessRequestNotificationEnabled,
|
||||||
|
accessRequestChannels,
|
||||||
|
isSecretRequestNotificationEnabled,
|
||||||
|
secretRequestChannels
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getInstallUrl,
|
getInstallUrl,
|
||||||
|
getSlackIntegrationByProjectId,
|
||||||
completeSlackIntegration,
|
completeSlackIntegration,
|
||||||
getSlackInstaller
|
getSlackInstaller,
|
||||||
|
updateSlackIntegration
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,16 @@ import { TProjectPermission } from "@app/lib/types";
|
|||||||
|
|
||||||
export type TGetSlackInstallUrlDTO = TProjectPermission;
|
export type TGetSlackInstallUrlDTO = TProjectPermission;
|
||||||
|
|
||||||
|
export type TGetSlackIntegrationByProjectId = TProjectPermission;
|
||||||
|
|
||||||
|
export type TUpdateSlackIntegration = {
|
||||||
|
id: string;
|
||||||
|
isAccessRequestNotificationEnabled?: boolean;
|
||||||
|
accessRequestChannels?: string;
|
||||||
|
isSecretRequestNotificationEnabled?: boolean;
|
||||||
|
secretRequestChannels?: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCompleteSlackIntegrationDTO = {
|
export type TCompleteSlackIntegrationDTO = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export * from "./secretSharing";
|
|||||||
export * from "./secretSnapshots";
|
export * from "./secretSnapshots";
|
||||||
export * from "./serverDetails";
|
export * from "./serverDetails";
|
||||||
export * from "./serviceTokens";
|
export * from "./serviceTokens";
|
||||||
|
export * from "./slack";
|
||||||
export * from "./ssoConfig";
|
export * from "./ssoConfig";
|
||||||
export * from "./subscriptions";
|
export * from "./subscriptions";
|
||||||
export * from "./tags";
|
export * from "./tags";
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { useUpdateSlackIntegration } from "./mutation";
|
||||||
|
export { fetchSlackInstallUrl, useGetSlackIntegrationByProject } from "./queries";
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { slackKeys } from "./queries";
|
||||||
|
import { TUpdateSlackIntegrationDTO } from "./types";
|
||||||
|
|
||||||
|
export const useUpdateSlackIntegration = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, TUpdateSlackIntegrationDTO>({
|
||||||
|
mutationFn: async (dto) => {
|
||||||
|
const { data } = await apiRequest.patch(`/api/v1/slack/${dto.id}`, dto);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { workspaceId }) => {
|
||||||
|
queryClient.invalidateQueries(slackKeys.getSlackIntegrationByProject(workspaceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,5 +1,16 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { ProjectSlackIntegration } from "./types";
|
||||||
|
|
||||||
|
export const slackKeys = {
|
||||||
|
getSlackIntegrationByProject: (workspaceId?: string) => [
|
||||||
|
{ workspaceId },
|
||||||
|
"slack-integration-by-project"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchSlackInstallUrl = async (workspaceId?: string) => {
|
export const fetchSlackInstallUrl = async (workspaceId?: string) => {
|
||||||
const { data } = await apiRequest.get<string>("/api/v1/slack/install", {
|
const { data } = await apiRequest.get<string>("/api/v1/slack/install", {
|
||||||
params: {
|
params: {
|
||||||
@@ -9,3 +20,20 @@ export const fetchSlackInstallUrl = async (workspaceId?: string) => {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchSlackIntegrationByProject = async (workspaceId?: string) => {
|
||||||
|
const { data } = await apiRequest.get<ProjectSlackIntegration>("/api/v1/slack", {
|
||||||
|
params: {
|
||||||
|
projectId: workspaceId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSlackIntegrationByProject = (workspaceId?: string) =>
|
||||||
|
useQuery({
|
||||||
|
queryKey: slackKeys.getSlackIntegrationByProject(workspaceId),
|
||||||
|
queryFn: () => fetchSlackIntegrationByProject(workspaceId),
|
||||||
|
enabled: Boolean(workspaceId)
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export type ProjectSlackIntegration = {
|
||||||
|
id: string;
|
||||||
|
teamName: string;
|
||||||
|
isAccessRequestNotificationEnabled: boolean;
|
||||||
|
accessRequestChannels: string;
|
||||||
|
isSecretRequestNotificationEnabled: boolean;
|
||||||
|
secretRequestChannels: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUpdateSlackIntegrationDTO = {
|
||||||
|
id: string;
|
||||||
|
workspaceId: string;
|
||||||
|
isAccessRequestNotificationEnabled?: boolean;
|
||||||
|
accessRequestChannels?: string;
|
||||||
|
isSecretRequestNotificationEnabled?: boolean;
|
||||||
|
secretRequestChannels?: string;
|
||||||
|
};
|
||||||
+195
-17
@@ -1,37 +1,215 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Button } from "@app/components/v2";
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, ContentLoader, FormControl, Input, Switch } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { fetchSlackInstallUrl } from "@app/hooks/api/slack/queries";
|
import {
|
||||||
|
fetchSlackInstallUrl,
|
||||||
|
useGetSlackIntegrationByProject,
|
||||||
|
useUpdateSlackIntegration
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
isSecretRequestNotificationEnabled: z.boolean(),
|
||||||
|
secretRequestChannels: z.string(),
|
||||||
|
isAccessRequestNotificationEnabled: z.boolean(),
|
||||||
|
accessRequestChannels: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
type TSlackIntegrationForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
export const NotificationTab = () => {
|
export const NotificationTab = () => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const { data: slackIntegration, isLoading: isSlackIntegrationLoading } =
|
||||||
|
useGetSlackIntegrationByProject(currentWorkspace?.id);
|
||||||
|
const { mutateAsync: updateSlackIntegration } = useUpdateSlackIntegration();
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
watch,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { isDirty, isSubmitting }
|
||||||
|
} = useForm<TSlackIntegrationForm>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
isSecretRequestNotificationEnabled: slackIntegration?.isSecretRequestNotificationEnabled,
|
||||||
|
secretRequestChannels: slackIntegration?.secretRequestChannels || "",
|
||||||
|
isAccessRequestNotificationEnabled: slackIntegration?.isAccessRequestNotificationEnabled,
|
||||||
|
accessRequestChannels: slackIntegration?.accessRequestChannels || ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isConnectToSlackLoading, setIsConnectToSlackLoading] = useToggle(false);
|
const [isConnectToSlackLoading, setIsConnectToSlackLoading] = useToggle(false);
|
||||||
|
const secretRequestNotifState = watch("isSecretRequestNotificationEnabled");
|
||||||
|
const accessRequestNotifState = watch("isAccessRequestNotificationEnabled");
|
||||||
|
|
||||||
|
const handleIntegrationSave = async (data: TSlackIntegrationForm) => {
|
||||||
|
if (!currentWorkspace || !slackIntegration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await updateSlackIntegration({
|
||||||
|
workspaceId: currentWorkspace?.id,
|
||||||
|
id: slackIntegration?.id,
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully updated slack integration"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (slackIntegration) {
|
||||||
|
setValue(
|
||||||
|
"isSecretRequestNotificationEnabled",
|
||||||
|
slackIntegration.isSecretRequestNotificationEnabled
|
||||||
|
);
|
||||||
|
setValue("secretRequestChannels", slackIntegration.secretRequestChannels);
|
||||||
|
setValue(
|
||||||
|
"isAccessRequestNotificationEnabled",
|
||||||
|
slackIntegration.isAccessRequestNotificationEnabled
|
||||||
|
);
|
||||||
|
setValue("accessRequestChannels", slackIntegration.accessRequestChannels);
|
||||||
|
}
|
||||||
|
}, [slackIntegration]);
|
||||||
|
|
||||||
|
if (isSlackIntegrationLoading) {
|
||||||
|
return <ContentLoader />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<h2 className="mb-2 flex-1 text-xl font-semibold text-mineshaft-100">Slack Integration</h2>
|
<h2 className="mb-2 flex-1 text-xl font-semibold text-mineshaft-100">Slack Integration</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mb-4 text-gray-400">
|
<p className="mb-4 text-gray-400">
|
||||||
This integration allows you send notifications to your Slack workspace in response to events
|
This integration allows you to send notifications to your Slack workspace in response to
|
||||||
in your project.
|
events in your project.
|
||||||
</p>
|
</p>
|
||||||
<Button
|
{!slackIntegration && (
|
||||||
isLoading={isConnectToSlackLoading}
|
<Button
|
||||||
onClick={async () => {
|
isLoading={isConnectToSlackLoading}
|
||||||
setIsConnectToSlackLoading.on();
|
onClick={async () => {
|
||||||
const slackInstallUrl = await fetchSlackInstallUrl(currentWorkspace?.id);
|
setIsConnectToSlackLoading.on();
|
||||||
if (slackInstallUrl) {
|
const slackInstallUrl = await fetchSlackInstallUrl(currentWorkspace?.id);
|
||||||
router.push(slackInstallUrl);
|
if (slackInstallUrl) {
|
||||||
}
|
router.push(slackInstallUrl);
|
||||||
}}
|
}
|
||||||
>
|
}}
|
||||||
Connect to Slack
|
>
|
||||||
</Button>
|
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">
|
||||||
|
Reinstall integration
|
||||||
|
</Button>
|
||||||
|
<Button colorSchema="secondary" size="xs" className="ml-2">
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isSecretRequestNotificationEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
className="mt-3 mb-2"
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="secret-approval-notification"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-full">Secret Approval Requests</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{secretRequestNotifState && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="secretRequestChannels"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Slack channels"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
isRequired={false}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
autoCorrect="off"
|
||||||
|
spellCheck={false}
|
||||||
|
placeholder="general, bot"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isAccessRequestNotificationEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message} className="mb-2">
|
||||||
|
<Switch
|
||||||
|
id="access-request-notification"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-full">Access Requests</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{accessRequestNotifState && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="accessRequestChannels"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Slack channels"
|
||||||
|
className="mt-0"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
isRequired={false}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
autoCorrect="off"
|
||||||
|
spellCheck={false}
|
||||||
|
placeholder="general, bot"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="mt-4"
|
||||||
|
type="submit"
|
||||||
|
isDisabled={!isDirty}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user