raycast / extensions

Everything you need to extend Raycast.
https://developers.raycast.com
MIT License
5.41k stars 3.1k forks source link

[Slack] Running into rate limit error for Org with high number of users. #15402

Closed jhasubhash closed 5 days ago

jhasubhash commented 6 days ago

Extension

https://www.raycast.com/mommertf/slack

Raycast Version

1.85.2

macOS Version

No response

Description

It looks like this extension is making the api requests to get the coomplete list of the contacts in segments. But there is seems to be no cool off period between making these requests leading to rate limit error. It can be fixed by adding a delay if there is a rate limit error thrown and retry it after that delay instead of throwin error to the user.

Steps To Reproduce

Note: My org has over users : 3k , channels: 19.5k

Current Behaviour

No response

Expected Behaviour

No response

raycastbot commented 6 days ago

Thank you for opening this issue!

🔔 @momme-rtf @Elliot67 @jfkisafk @thomaslombart @RobErskine you might want to have a look.

💡 Author and Contributors commands The author and contributors of `mommertf/slack` can trigger bot actions by commenting: - `@raycastbot close this issue` Closes the issue. - `@raycastbot close as not planned` Closes the issue as not planned. - `@raycastbot rename this issue to "Awesome new title"` Renames the issue. - `@raycastbot reopen this issue` Reopens the issue. - `@raycastbot assign me` Assigns yourself to the issue. - `@raycastbot good first issue` Adds the "Good first issue" label to the issue. - `@raycastbot keep this issue open` Make sure the issue won't go stale and will be kept open by the bot.
Elliot67 commented 5 days ago

Hi thanks for the report, looks like the same issue as #14113. There is currently no fix in development, feel free to contribute if you can.

@raycastbot close this issue

jhasubhash commented 4 days ago

Sure, I will raise a PR shortly

jhasubhash commented 4 days ago

This code base is huge. This is the solution. I hope someone with code setup locally can add it.

export const FetchUsers = async (
  access_token,
  maxRetries = 3,
  retryDelay = 1000,
) => {
  const params = { limit: 999999 };
  const users = await fetchDataWithRetry(
    access_token,
    "https://slack.com/api/users.list",
    params,
    maxRetries,
    retryDelay,
  );
  return users;
};

export const FetchChannels = async (
  access_token,
  maxRetries = 3,
  retryDelay = 1000,
) => {
  const params = {
    limit: 999999,
    types: "private_channel,public_channel",
    exclude_archived: true,
  };
  const channels = await fetchDataWithRetry(
    access_token,
    "https://slack.com/api/conversations.list",
    params,
    maxRetries,
    retryDelay,
  );
  return channels;
};

// Helper function to fetch data with retry and pagination
const fetchDataWithRetry = async (
  access_token,
  url,
  params,
  maxRetries,
  retryDelay,
  updateCb,
) => {
  let dataList = [];
  let cursor = "";
  let attempt = 0;

  while (true) {
    try {
      const response = await axios.get(url, {
        headers: { Authorization: `Bearer ${access_token}` },
        params: { ...params, cursor },
      });

      const data = response.data;
      if (!data.ok) {
        throw new Error(`Failed to fetch data: ${data.error}`);
      }

      attempt = 0;
      const newData = data.members || data.channels;
      dataList = dataList.concat(newData);

      // Optional: Update the list incrementally and not block the user till the complete list is loaded
     if (updateCb)  updateCb(dataList);

      if (data.response_metadata && data.response_metadata.next_cursor) {
        cursor = data.response_metadata.next_cursor;
      } else {
        break;
      }
    } catch (err) {
      if (err.response && err.response.status === 429) {
        // Rate limit error
        const retryAfter =
          err.response.headers["retry-after"] || retryDelay / 1000; // Use retry-after header if available
        error(`Rate limited. Retrying after ${retryAfter} seconds...`);

        if (attempt < maxRetries) {
          attempt++;
          await new Promise((resolve) =>
            setTimeout(resolve, retryAfter * 1000),
          );
        } else {
          throw new Error("Max retries exceeded.");
        }
      } else {
        // Other errors
        throw err;
      }
    }
  }

  return dataList;
};