From 7b4db30efdc9bba70057844fd4b57709c4d2b66d Mon Sep 17 00:00:00 2001 From: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:04:03 +0530 Subject: [PATCH] fix: Slack integration pagination issue (#2733) --- .../integrations/slack/page.mdx | 3 +- .../components/AddChannelMappingModal.tsx | 2 +- packages/lib/slack/service.ts | 65 ++++++++++++------- packages/ui/DropdownSelector/index.tsx | 7 +- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/apps/docs/app/developer-docs/integrations/slack/page.mdx b/apps/docs/app/developer-docs/integrations/slack/page.mdx index 3535ca1544..1539561102 100644 --- a/apps/docs/app/developer-docs/integrations/slack/page.mdx +++ b/apps/docs/app/developer-docs/integrations/slack/page.mdx @@ -22,7 +22,8 @@ export const metadata = { The slack integration allows you to automatically send responses to a Slack channel of your choice. - If you are on a self-hosted instance, you will need to configure this integration separately. Please follow the guides [here](/self-hosting/integrations) to configure integrations on your self-hosted instance. + If you are on a self-hosted instance, you will need to configure this integration separately. Please follow + the guides [here](/self-hosting/integrations) to configure integrations on your self-hosted instance. ## Formbricks Cloud diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/slack/components/AddChannelMappingModal.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/slack/components/AddChannelMappingModal.tsx index 7cd5a19b35..4caf800da3 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/slack/components/AddChannelMappingModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/slack/components/AddChannelMappingModal.tsx @@ -171,7 +171,7 @@ export const AddChannelMappingModal = ({ ); return ( - +
diff --git a/packages/lib/slack/service.ts b/packages/lib/slack/service.ts index 0a985cfbe7..aff1f5598e 100644 --- a/packages/lib/slack/service.ts +++ b/packages/lib/slack/service.ts @@ -7,32 +7,51 @@ import { TIntegrationSlack, TIntegrationSlackCredential } from "@formbricks/type import { deleteIntegration, getIntegrationByType } from "../integration/service"; export const fetchChannels = async (slackIntegration: TIntegration): Promise => { - const response = await fetch("https://slack.com/api/conversations.list", { - method: "GET", - headers: { - Authorization: `Bearer ${slackIntegration.config.key.access_token}`, - "Content-Type": "application/x-www-form-urlencoded", - }, - }); + let channels: TIntegrationItem[] = []; + // `nextCursor` is a pagination token returned by the Slack API. It indicates the presence of additional pages of data. + // When `nextCursor` is not empty, it should be included in subsequent requests to fetch the next page of data. + let nextCursor: string | undefined = undefined; - if (!response.ok) { - throw new Error("Network response was not ok"); - } - - const data = await response.json(); - - if (!data.ok) { - if (data.error === "token_expired") { - // temporary fix to reset integration if token rotation is enabled - await deleteIntegration(slackIntegration.id); + do { + const url = new URL("https://slack.com/api/conversations.list"); + url.searchParams.append("limit", "200"); + if (nextCursor) { + url.searchParams.append("cursor", nextCursor); } - throw new Error(data.error); - } - return data.channels.map((channel: { name: string; id: string }) => ({ - name: channel.name, - id: channel.id, - })); + const response = await fetch(url.toString(), { + method: "GET", + headers: { + Authorization: `Bearer ${slackIntegration.config.key.access_token}`, + "Content-Type": "application/x-www-form-urlencoded", + }, + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + + if (!data.ok) { + if (data.error === "token_expired") { + // Temporary fix to reset integration if token rotation is enabled + await deleteIntegration(slackIntegration.id); + } + throw new Error(data.error); + } + + channels = channels.concat( + data.channels.map((channel: { name: string; id: string }) => ({ + name: channel.name, + id: channel.id, + })) + ); + + nextCursor = data.response_metadata?.next_cursor; + } while (nextCursor); + + return channels; }; export const getSlackChannels = async (environmentId: string): Promise => { diff --git a/packages/ui/DropdownSelector/index.tsx b/packages/ui/DropdownSelector/index.tsx index 4a55a2a4c4..04fdcafefd 100644 --- a/packages/ui/DropdownSelector/index.tsx +++ b/packages/ui/DropdownSelector/index.tsx @@ -51,10 +51,11 @@ export const DropdownSelector = ({ {!disabled && ( - {items && - items.map((item) => ( + {items + .sort((a, b) => a.name.localeCompare(b.name)) + .map((item) => (