Compare commits

...

9 Commits

Author SHA1 Message Date
Dhruwang
7616133a25 tweaks 2024-11-08 15:18:33 +05:30
Dhruwang
60139afd81 merged main 2024-11-08 15:14:05 +05:30
Dhruwang
19e5865d05 fix: backward compatibility 2024-10-17 12:08:53 +05:30
Dhruwang
6c5c27f571 Merge branch 'main' of https://github.com/merteroglu/formbricks into merteroglu/main 2024-10-16 17:48:30 +05:30
Dhruwang Jariwala
c12fb1a9f8 Merge branch 'main' into main 2024-10-16 17:46:00 +05:30
Dhruwang
526439def3 Merge branch 'main' of https://github.com/merteroglu/formbricks into merteroglu/main 2024-10-04 11:45:19 +05:30
Dhruwang
4e01ac211f Merge branch 'main' of https://github.com/Dhruwang/formbricks into merteroglu/main 2024-10-04 11:44:48 +05:30
Dhruwang Jariwala
f2f3ff6d46 Merge branch 'main' into main 2024-10-04 11:44:41 +05:30
Mert Eroğlu
b332cf12ca implement new slack ep 2024-09-29 23:21:30 +03:00
15 changed files with 88 additions and 34 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -8,7 +8,10 @@ import LinkWithQuestions from "./images/link-with-questions.webp";
import ListLinkedSurveys from "./images/list-linked-surveys.webp";
import SlackAuth from "./images/slack-auth.webp";
import SlackConnected from "./images/slack-connected.webp";
import AddSlackBot1 from "./images/add-slack-bot-1.webp";
import AddSlackBot2 from "./images/add-slack-bot-2.webp";
import AddSlackBot3 from "./images/add-slack-bot-3.webp";
import AddSlackBot4 from "./images/add-slack-bot-4.webp";
export const metadata = {
title: "Slack",
description:
@@ -69,7 +72,34 @@ The slack integration allows you to automatically send responses to a Slack chan
channel in the Slack workspace you integrated.
</Note>
5. Now click on the "Link channel" button to link a Slack channel with Formbricks and a modal will open up.
5. In order to make your channel available in channel dropdown, you need to add formbricks integration bot to the channel you want to link. You can do this by going to channel settings -> Integrations -> Add apps -> Search for "Formbricks" -> Select the bot -> Add.
<MdxImage
src={AddSlackBot1}
alt="Click on three dot at top right of the channel"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
<MdxImage
src={AddSlackBot2}
alt="Select Edit Settings"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
<MdxImage
src={AddSlackBot3}
alt="Navigate to Integrations"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
<MdxImage
src={AddSlackBot4}
alt="Add Formbricks Bot"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
6. Now click on the "Link channel" button to link a Slack channel with Formbricks and a modal will open up.
<MdxImage
src={LinkSurveyWithChannel}
@@ -78,7 +108,7 @@ The slack integration allows you to automatically send responses to a Slack chan
className="max-w-full rounded-lg sm:max-w-3xl"
/>
6. Select the channel you want to link with Formbricks and the Survey. On doing so, you will be asked to select the questions' responses you want to feed in the Slack channel. Select the questions and click on the "Link Channel" button.
7. Select the channel you want to link with Formbricks and the Survey. On doing so, you will be asked to select the questions' responses you want to feed in the Slack channel. Select the questions and click on the "Link Channel" button.
<MdxImage
src={LinkWithQuestions}
@@ -87,7 +117,7 @@ The slack integration allows you to automatically send responses to a Slack chan
className="max-w-full rounded-lg sm:max-w-3xl"
/>
7. On submitting, the modal will close and you will see the linked Slack channel in the list of linked Slack channels.
8. On submitting, the modal will close and you will see the linked Slack channel in the list of linked Slack channels.
<MdxImage
src={ListLinkedSurveys}
@@ -124,6 +154,7 @@ Enabling the Slack Integration in a self-hosted environment requires a setup usi
4. Go to the **OAuth & Permissions** tab on the sidebar and add the following **Bot Token Scopes**:
- `channels:read`
- `groups:read`
- `chat:write`
- `chat:write.public`
- `chat:write.customize`

View File

@@ -302,6 +302,7 @@ Enabling the Slack Integration in a self-hosted environment requires a setup usi
4. Go to the **OAuth & Permissions** tab on the sidebar and add the following **Bot Token Scopes**:
- `channels:read`
- `groups:read`
- `chat:write`
- `chat:write.public`
- `chat:write.customize`

View File

@@ -337,7 +337,7 @@ export const SurveyMenuBar = ({
/>
</div>
{responseCount > 0 && (
<div className="ju flex items-center rounded-lg border border-amber-200 bg-amber-100 p-1.5 text-amber-800 shadow-sm lg:mx-auto">
<div className="flex items-center rounded-lg border border-amber-200 bg-amber-100 p-1.5 text-amber-800 shadow-sm lg:mx-auto">
<TooltipProvider delayDuration={50}>
<Tooltip>
<TooltipTrigger>

View File

@@ -7,12 +7,12 @@ import { z } from "zod";
import { getSlackChannels } from "@formbricks/lib/slack/service";
import { ZId } from "@formbricks/types/common";
const ZRefreshChannelsAction = z.object({
const ZGetSlackChannelsAction = z.object({
environmentId: ZId,
});
export const refreshChannelsAction = authenticatedActionClient
.schema(ZRefreshChannelsAction)
export const getSlackChannelsAction = authenticatedActionClient
.schema(ZGetSlackChannelsAction)
.action(async ({ ctx, parsedInput }) => {
await checkAuthorizationUpdated({
userId: ctx.user.id,

View File

@@ -23,6 +23,8 @@ interface ManageIntegrationProps {
React.SetStateAction<(TIntegrationSlackConfigData & { index: number }) | null>
>;
refreshChannels: () => void;
showReconnectButton: boolean;
handleSlackAuthorization: () => void;
locale: TUserLocale;
}
@@ -33,6 +35,8 @@ export const ManageIntegration = ({
setIsConnected,
setSelectedIntegration,
refreshChannels,
showReconnectButton,
handleSlackAuthorization,
locale,
}: ManageIntegrationProps) => {
const t = useTranslations();
@@ -70,7 +74,18 @@ export const ManageIntegration = ({
return (
<div className="mt-6 flex w-full flex-col items-center justify-center p-6">
<div className="flex w-full justify-end">
{showReconnectButton && (
<div className="mb-4 flex w-full items-center justify-between space-x-4">
<p className="text-amber-700">
<strong>Note:</strong> We recently changed our Slack integration to also support private channels.
Please reconnect your Slack workspace.
</p>
<Button onClick={handleSlackAuthorization} variant="secondary">
Reconnect
</Button>
</div>
)}
<div className="flex w-full justify-end space-x-4">
<div className="mr-6 flex items-center">
<span className="mr-4 h-4 w-4 rounded-full bg-green-600"></span>
<span className="text-slate-500">

View File

@@ -1,11 +1,11 @@
"use client";
import { refreshChannelsAction } from "@/app/(app)/environments/[environmentId]/integrations/slack/actions";
import { getSlackChannelsAction } from "@/app/(app)/environments/[environmentId]/integrations/slack/actions";
import { AddChannelMappingModal } from "@/app/(app)/environments/[environmentId]/integrations/slack/components/AddChannelMappingModal";
import { ManageIntegration } from "@/app/(app)/environments/[environmentId]/integrations/slack/components/ManageIntegration";
import { authorize } from "@/app/(app)/environments/[environmentId]/integrations/slack/lib/slack";
import slackLogo from "@/images/slacklogo.png";
import { useState } from "react";
import { useEffect, useState } from "react";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TEnvironment } from "@formbricks/types/environment";
import { TIntegrationItem } from "@formbricks/types/integration";
@@ -18,7 +18,6 @@ interface SlackWrapperProps {
isEnabled: boolean;
environment: TEnvironment;
surveys: TSurvey[];
channelsArray: TIntegrationItem[];
slackIntegration?: TIntegrationSlack;
webAppUrl: string;
attributeClasses: TAttributeClass[];
@@ -29,27 +28,34 @@ export const SlackWrapper = ({
isEnabled,
environment,
surveys,
channelsArray,
slackIntegration,
webAppUrl,
attributeClasses,
locale,
}: SlackWrapperProps) => {
const [isConnected, setIsConnected] = useState(slackIntegration ? slackIntegration.config?.key : false);
const [slackChannels, setSlackChannels] = useState(channelsArray);
const [slackChannels, setSlackChannels] = useState<TIntegrationItem[]>([]);
const [isModalOpen, setModalOpen] = useState<boolean>(false);
const [showReconnectButton, setShowReconnectButton] = useState<boolean>(false);
const [selectedIntegration, setSelectedIntegration] = useState<
(TIntegrationSlackConfigData & { index: number }) | null
>(null);
const refreshChannels = async () => {
const refreshChannelsResponse = await refreshChannelsAction({ environmentId: environment.id });
const getSlackChannels = async () => {
const getSlackChannelsResponse = await getSlackChannelsAction({ environmentId: environment.id });
if (getSlackChannelsResponse?.serverError && getSlackChannelsResponse?.serverError === "missing_scope") {
setShowReconnectButton(true);
}
if (refreshChannelsResponse?.data) {
setSlackChannels(refreshChannelsResponse.data);
if (getSlackChannelsResponse?.data) {
setSlackChannels(getSlackChannelsResponse.data);
}
};
useEffect(() => {
getSlackChannels();
}, []);
const handleSlackAuthorization = async () => {
authorize(environment.id, webAppUrl).then((url: string) => {
if (url) {
@@ -76,7 +82,9 @@ export const SlackWrapper = ({
setOpenAddIntegrationModal={setModalOpen}
setIsConnected={setIsConnected}
setSelectedIntegration={setSelectedIntegration}
refreshChannels={refreshChannels}
refreshChannels={getSlackChannels}
showReconnectButton={showReconnectButton}
handleSlackAuthorization={handleSlackAuthorization}
locale={locale}
/>
</>

View File

@@ -12,10 +12,8 @@ import { getIntegrationByType } from "@formbricks/lib/integration/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getSlackChannels } from "@formbricks/lib/slack/service";
import { getSurveys } from "@formbricks/lib/survey/service";
import { findMatchingLocale } from "@formbricks/lib/utils/locale";
import { TIntegrationItem } from "@formbricks/types/integration";
import { TIntegrationSlack } from "@formbricks/types/integration/slack";
import { GoBackButton } from "@formbricks/ui/components/GoBackButton";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
@@ -45,10 +43,6 @@ const Page = async ({ params }) => {
throw new Error(t("common.product_not_found"));
}
let channelsArray: TIntegrationItem[] = [];
if (slackIntegration && slackIntegration.config.key) {
channelsArray = await getSlackChannels(params.environmentId);
}
const locale = await findMatchingLocale();
const currentUserMembership = await getMembershipByUserIdOrganizationId(
@@ -75,7 +69,6 @@ const Page = async ({ params }) => {
<SlackWrapper
isEnabled={isEnabled}
environment={environment}
channelsArray={channelsArray}
surveys={surveys}
slackIntegration={slackIntegration as TIntegrationSlack}
webAppUrl={WEBAPP_URL}

View File

@@ -1,8 +1,12 @@
import { responses } from "@/app/lib/api/response";
import { NextRequest } from "next/server";
import { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
import { createOrUpdateIntegration } from "@formbricks/lib/integration/service";
import { TIntegrationSlackConfig, TIntegrationSlackCredential } from "@formbricks/types/integration/slack";
import { createOrUpdateIntegration, getIntegrationByType } from "@formbricks/lib/integration/service";
import {
TIntegrationSlackConfig,
TIntegrationSlackConfigData,
TIntegrationSlackCredential,
} from "@formbricks/types/integration/slack";
export const GET = async (req: NextRequest) => {
const url = req.url;
@@ -44,18 +48,20 @@ export const GET = async (req: NextRequest) => {
team: data.team,
};
const slackIntegration = await getIntegrationByType(environmentId, "slack");
const slackConfiguration: TIntegrationSlackConfig = {
data: [],
data: (slackIntegration?.config.data as TIntegrationSlackConfigData[]) ?? [],
key: slackCredentials,
};
const slackIntegration = {
const integration = {
type: "slack" as "slack",
environment: environmentId,
config: slackConfiguration,
};
const result = await createOrUpdateIntegration(environmentId, slackIntegration);
const result = await createOrUpdateIntegration(environmentId, integration);
if (result) {
return Response.redirect(`${WEBAPP_URL}/environments/${environmentId}/integrations/slack`);

View File

@@ -22,7 +22,6 @@ export const actionClient = createSafeActionClient({
}
console.error("SERVER ERROR: ", e);
return DEFAULT_SERVER_ERROR_MESSAGE;
},
});

View File

@@ -53,7 +53,7 @@ export const INVITE_DISABLED = env.INVITE_DISABLED === "1";
export const SLACK_CLIENT_SECRET = env.SLACK_CLIENT_SECRET;
export const SLACK_CLIENT_ID = env.SLACK_CLIENT_ID;
export const SLACK_AUTH_URL = `https://slack.com/oauth/v2/authorize?client_id=${env.SLACK_CLIENT_ID}&scope=channels:read,chat:write,chat:write.public,chat:write.customize`;
export const SLACK_AUTH_URL = `https://slack.com/oauth/v2/authorize?client_id=${env.SLACK_CLIENT_ID}&scope=channels:read,chat:write,chat:write.public,chat:write.customize,groups:read`;
export const GOOGLE_SHEETS_CLIENT_ID = env.GOOGLE_SHEETS_CLIENT_ID;
export const GOOGLE_SHEETS_CLIENT_SECRET = env.GOOGLE_SHEETS_CLIENT_SECRET;

View File

@@ -11,8 +11,9 @@ export const fetchChannels = async (slackIntegration: TIntegration): Promise<TIn
let nextCursor: string | undefined = undefined;
do {
const url = new URL("https://slack.com/api/conversations.list");
const url = new URL("https://slack.com/api/users.conversations");
url.searchParams.append("limit", "200");
url.searchParams.append("types", "private_channel,public_channel");
if (nextCursor) {
url.searchParams.append("cursor", nextCursor);
}