fix: Slack integration pagination issue (#2733)

This commit is contained in:
Dhruwang Jariwala
2024-06-05 16:04:03 +05:30
committed by GitHub
parent c8aece8003
commit 7b4db30efd
4 changed files with 49 additions and 28 deletions

View File

@@ -22,7 +22,8 @@ export const metadata = {
The slack integration allows you to automatically send responses to a Slack channel of your choice.
<Note>
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.
</Note>
## Formbricks Cloud

View File

@@ -171,7 +171,7 @@ export const AddChannelMappingModal = ({
);
return (
<Modal open={open} setOpen={setOpenWithStates} noPadding closeOnOutsideClick={false}>
<Modal open={open} setOpen={setOpenWithStates} noPadding closeOnOutsideClick={true}>
<div className="flex h-full flex-col rounded-lg">
<div className="rounded-t-lg bg-slate-100">
<div className="flex w-full items-center justify-between p-6">

View File

@@ -7,32 +7,51 @@ import { TIntegrationSlack, TIntegrationSlackCredential } from "@formbricks/type
import { deleteIntegration, getIntegrationByType } from "../integration/service";
export const fetchChannels = async (slackIntegration: TIntegration): Promise<TIntegrationItem[]> => {
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<TIntegrationItem[]> => {

View File

@@ -51,10 +51,11 @@ export const DropdownSelector = ({
{!disabled && (
<DropdownMenuPortal>
<DropdownMenuContent
className="z-50 max-h-64 min-w-[220px] overflow-auto rounded-md bg-white text-sm text-slate-800 shadow-md"
className="z-50 max-h-64 min-w-[220px] max-w-[90%] overflow-auto rounded-md bg-white text-sm text-slate-800 shadow-md"
align="start">
{items &&
items.map((item) => (
{items
.sort((a, b) => a.name.localeCompare(b.name))
.map((item) => (
<DropdownMenuItem
key={item.id}
className="flex cursor-pointer items-center p-3 hover:bg-slate-100 hover:outline-none data-[disabled]:cursor-default data-[disabled]:opacity-50"