-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Slack v2 components #18744
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Slack v2 components #18744
Conversation
- List users by real name in dropdowns - List users by real name in action summary - List files using bot token if available - Get files using bot token if available - Show configuration error if bot is not a member of the channel - Add `addToChannel` prop to Send Message actions - Check user's access to channel in message actions - Use RTS API to search messages in Find Message action - List only channels, not DMs, in 'file' actions - List only channels, not DMs, in 'message' triggers - Handle permission error fetching message in New Reaction trigger Context: In Slack's v2 OAuth flow, user access tokens and bot access tokens are issued with separate scopes. User tokens are used to perform actions on behalf of a user, while bot tokens are used for actions performed by the app itself. To send a message to a private channel or DM, the bot must be a member of that channel. Due to permission restrictions, some endpoints require bot tokens for access, such as viewing messages in DMs and listing files. As a result, access to messages and files in DMs is no longer supported.
This allows us to support both existing and new Slack connections created with Slack's v2 OAuth flow, with or without the deprecated perspectival chat:write:user and chat:write:bot scopes: | Slack auth | Token Types | Scopes | as_user Values | |------------|-------------|-----------------------|-----------------| | v1 | user | chat:write:bot, :user | true, false | | v2 | user, bot | chat:write:bot, :user | true, false | | v2 | user, bot | chat:write | true | In v2, when the chat:write scope is requested, it replaces chat:write:bot and chat:write:user. Without the chat:write:bot scope, as_user cannot be false; user tokens always post as the user, and bot tokens always post as the bot. In v2, with or without the chat:write:bot scope, we can use the bot token if as_user is false since it will have permission to post as itself. And we MAY pass through as_user if it's true since Slack allows it even when it's superfluous. However, we MUST pass through as_user if it's true AND the user token still has the chat:write:bot scope since otherwise the message will post as the bot user instead of the user. See: https://docs.slack.dev/reference/methods/chat.postMessage/#legacy_as_user
so it is shown in the component form by default so users aren't suprised by the new invite behavior
it's no longer supported for new auths since they will not have the im:history scope
as requested by Slack
Fallback to search.messages API method if missing scopese
Future-proofs Slack components to support proxying requests
add/update logged messages when unable to add app to channel
The following components have new minor versions: - delete-message - find-message - get-file - list-files - reply-to-a-message - send-block-kit-message - send-large-message - send-message - send-message-advanced - send-message-to-channel - send-message-to-user-or-group - update-message - new-keyword-mention - new-message-in-channels - new-user-mention All other Slack components have new patch versions.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
WalkthroughAdds a new Slack v2 component: app implementation, package, 40+ action modules (messages, CRUD, listings, membership, uploads), multiple event sources with caching/helpers, shared constants/utilities, and many test-event fixtures. All files are new additions under components/slack_v2 and minor newline fixes in unrelated apps. Changes
Sequence Diagram(s)sequenceDiagram
participant Workflow as Workflow
participant Action as Slack Action
participant App as slack_v2.app
participant Slack as Slack API
Workflow->>Action: invoke action with props
Action->>Action: normalize inputs, optionally build blocks
Action->>App: call method (e.g., postMessage, upload, list)
App->>App: select token & wrap request with retries/backoff
App->>Slack: HTTP request
Slack-->>App: response (ok / rate_limit / error)
alt retryable error
App->>App: backoff & retry
App->>Slack: retry request
else success
App-->>Action: return response
else fatal
App-->>Action: throw error
end
Action->>Workflow: export summary and return response
sequenceDiagram
participant Slack as Slack Events
participant Hook as slackApphook
participant Source as Event Source
participant Cache as Name Cache
participant Pipedream as Pipedream Emit
Slack->>Hook: deliver event
Hook->>Source: run(event)
Source->>Source: processEvent (filter by type/subtype/keyword/bot)
alt resolveNames enabled
Source->>Cache: maybeCached(key, fetchFn)
Cache-->>Source: cached value or fresh fetch
Source->>Source: enrich event with resolved names
end
Source->>Pipedream: emit standardized event (id, summary, ts, payload)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
components/slack/actions/send-large-message/send-large-message.mjs (1)
49-64: Fix metadata_event_payload handling to support object inputs.When
metadata_event_payloadis provided as an object (not a string),metadataEventPayloadremains undefined, resulting inevent_payload: undefinedin the metadata object. The code only assignsmetadataEventPayloadwhen parsing a string.Apply this diff to handle both string and object inputs:
- let metadataEventPayload; + let metadataEventPayload = this.metadata_event_payload; if (this.metadata_event_type) { if (typeof this.metadata_event_payload === "string") { try { metadataEventPayload = JSON.parse(this.metadata_event_payload); } catch (error) { throw new Error(`Invalid JSON in metadata_event_payload: ${error.message}`); } } this.metadata = { event_type: this.metadata_event_type, event_payload: metadataEventPayload, }; }components/slack/actions/common/send-message.mjs (1)
213-229: Fix metadata_event_payload handling to support object inputs.When
metadata_event_payloadis provided as an object (not a string),metadataEventPayloadremains undefined, resulting inevent_payload: undefinedin the metadata object. The code only assignsmetadataEventPayloadwhen parsing a string.Apply this diff to handle both string and object inputs:
- let metadataEventPayload; + let metadataEventPayload = this.metadata_event_payload; if (this.metadata_event_type) { if (typeof this.metadata_event_payload === "string") { try { metadataEventPayload = JSON.parse(this.metadata_event_payload); } catch (error) { throw new Error(`Invalid JSON in metadata_event_payload: ${error.message}`); } } this.metadata = { event_type: this.metadata_event_type, event_payload: metadataEventPayload, }; }
🧹 Nitpick comments (1)
components/slack/actions/update-group-members/update-group-members.mjs (1)
54-55: Good defensive fix!Adding default empty arrays prevents
TypeErrorwhen the optional arrays are undefined, since line 62 calls.includes()and line 63 spreads the array.Optionally, if a user appears in both the existing group members and
usersToAdd, they may be duplicated in the final array (line 63). Consider verifying whether the Slack API deduplicates on their end, or add explicit deduplication if needed:users = users.filter((user) => !usersToRemove.includes(user)); - users.push(...usersToAdd); + const allUsers = [...new Set([...users, ...usersToAdd])]; const response = await this.slack.updateGroupMembers({ usergroup: userGroup, - users, + users: allUsers, team_id: team, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (51)
components/azure_cosmos_db/azure_cosmos_db.app.mjs(1 hunks)components/binalyze_air/binalyze_air.app.mjs(1 hunks)components/brainbase_labs/brainbase_labs.app.mjs(1 hunks)components/google_perspective/google_perspective.app.mjs(1 hunks)components/n1n/n1n.app.mjs(1 hunks)components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs(1 hunks)components/slack/actions/approve-workflow/approve-workflow.mjs(1 hunks)components/slack/actions/archive-channel/archive-channel.mjs(1 hunks)components/slack/actions/common/send-message.mjs(4 hunks)components/slack/actions/create-channel/create-channel.mjs(1 hunks)components/slack/actions/create-reminder/create-reminder.mjs(1 hunks)components/slack/actions/delete-file/delete-file.mjs(1 hunks)components/slack/actions/delete-message/delete-message.mjs(1 hunks)components/slack/actions/find-message/find-message.mjs(3 hunks)components/slack/actions/find-user-by-email/find-user-by-email.mjs(1 hunks)components/slack/actions/get-file/get-file.mjs(3 hunks)components/slack/actions/invite-user-to-channel/invite-user-to-channel.mjs(1 hunks)components/slack/actions/kick-user/kick-user.mjs(1 hunks)components/slack/actions/list-channels/list-channels.mjs(1 hunks)components/slack/actions/list-files/list-files.mjs(3 hunks)components/slack/actions/list-group-members/list-group-members.mjs(1 hunks)components/slack/actions/list-members-in-channel/list-members-in-channel.mjs(1 hunks)components/slack/actions/list-replies/list-replies.mjs(2 hunks)components/slack/actions/list-users/list-users.mjs(1 hunks)components/slack/actions/reply-to-a-message/reply-to-a-message.mjs(1 hunks)components/slack/actions/send-block-kit-message/send-block-kit-message.mjs(1 hunks)components/slack/actions/send-large-message/send-large-message.mjs(3 hunks)components/slack/actions/send-message-advanced/send-message-advanced.mjs(1 hunks)components/slack/actions/send-message-to-channel/send-message-to-channel.mjs(1 hunks)components/slack/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs(2 hunks)components/slack/actions/send-message/send-message.mjs(1 hunks)components/slack/actions/set-channel-description/set-channel-description.mjs(1 hunks)components/slack/actions/set-channel-topic/set-channel-topic.mjs(1 hunks)components/slack/actions/set-status/set-status.mjs(1 hunks)components/slack/actions/update-group-members/update-group-members.mjs(2 hunks)components/slack/actions/update-message/update-message.mjs(1 hunks)components/slack/actions/update-profile/update-profile.mjs(1 hunks)components/slack/actions/upload-file/upload-file.mjs(1 hunks)components/slack/actions/verify-slack-signature/verify-slack-signature.mjs(0 hunks)components/slack/package.json(1 hunks)components/slack/slack.app.mjs(9 hunks)components/slack/sources/new-channel-created/new-channel-created.mjs(1 hunks)components/slack/sources/new-direct-message/new-direct-message.mjs(0 hunks)components/slack/sources/new-direct-message/test-event.mjs(0 hunks)components/slack/sources/new-interaction-event-received/new-interaction-event-received.mjs(1 hunks)components/slack/sources/new-keyword-mention/new-keyword-mention.mjs(2 hunks)components/slack/sources/new-message-in-channels/new-message-in-channels.mjs(2 hunks)components/slack/sources/new-reaction-added/new-reaction-added.mjs(2 hunks)components/slack/sources/new-saved-message/new-saved-message.mjs(1 hunks)components/slack/sources/new-user-added/new-user-added.mjs(1 hunks)components/slack/sources/new-user-mention/new-user-mention.mjs(2 hunks)
💤 Files with no reviewable changes (3)
- components/slack/sources/new-direct-message/new-direct-message.mjs
- components/slack/actions/verify-slack-signature/verify-slack-signature.mjs
- components/slack/sources/new-direct-message/test-event.mjs
🧰 Additional context used
🧬 Code graph analysis (3)
components/slack/actions/common/send-message.mjs (2)
components/slack/slack.app.mjs (3)
channelId(24-27)channel(505-507)channel(514-516)components/slack/actions/send-large-message/send-large-message.mjs (1)
channelName(91-91)
components/slack/actions/send-large-message/send-large-message.mjs (1)
components/slack/slack.app.mjs (2)
channel(505-507)channel(514-516)
components/slack/slack.app.mjs (2)
components/slack/actions/get-file/get-file.mjs (1)
response(53-55)components/slack/actions/find-message/find-message.mjs (3)
response(115-119)response(134-138)cursor(112-112)
🔇 Additional comments (29)
components/slack/sources/new-channel-created/new-channel-created.mjs (1)
8-8: Version bump looks good.No issues spotted with the update to version 0.0.11.
components/slack/actions/archive-channel/archive-channel.mjs (1)
8-8: Version bump looks good.Version increment to 0.0.25 is consistent and introduces no regressions.
components/slack/actions/set-channel-topic/set-channel-topic.mjs (1)
7-7: Version bump looks good.Matches surrounding Slack action updates; no further changes needed.
components/slack/actions/update-profile/update-profile.mjs (1)
8-8: Version bump looks good.Increment to 0.0.25 keeps this action aligned with the other Slack updates in the PR. No further action needed.
components/slack/actions/approve-workflow/approve-workflow.mjs (1)
8-8: Version bump looks good.Matches the broader Slack release versioning pattern with no functional impact.
components/slack/sources/new-user-added/new-user-added.mjs (1)
8-8: Version bump looks goodThe source metadata increment is consistent and introduces no risk.
components/slack/actions/create-reminder/create-reminder.mjs (1)
7-7: Version bump looks goodThe metadata-only bump to 0.0.26 keeps this action aligned with the broader Slack release. 👍
components/slack/actions/send-block-kit-message/send-block-kit-message.mjs (1)
10-10: Version bump looks good.Increment aligns with the broader Slack v2 rollout; no further action needed.
components/slack/actions/kick-user/kick-user.mjs (1)
8-8: Version bump looks consistent.Matches the broader component release cadence. No further issues spotted.
components/azure_cosmos_db/azure_cosmos_db.app.mjs (1)
11-11: LGTM — object terminator restored.Thanks for reintroducing the closing
};; keeps the module syntax tidy.components/slack/actions/reply-to-a-message/reply-to-a-message.mjs (1)
9-9: Version bump looks goodIncrement to
0.2.0is consistent with the ongoing Slack updates in this PR.components/slack/sources/new-saved-message/new-saved-message.mjs (1)
8-8: Version bump looks good.The patch version increment is appropriate for this maintenance update.
Note: Line 9 mentions this endpoint is deprecated by Slack and may be shut down in the future. Consider tracking this deprecation to ensure timely migration or removal when Slack discontinues support.
components/slack/actions/find-user-by-email/find-user-by-email.mjs (1)
7-7: LGTM! Version bump aligns with OAuth v2 compatibility.The version increment appropriately marks this action as compatible with the underlying Slack OAuth v2 changes, even though no logic changes were required.
components/slack/actions/list-channels/list-channels.mjs (1)
7-7: Verify that no functional changes are needed for v2 OAuth support.The version bump appears intentional for indicating v2 OAuth compatibility. Since this is a read-only action that only lists channels (as indicated by the
readOnlyHint: trueannotation), it likely doesn't require the additional props (likeaddToChannel) or pre-run logic that other Slack actions in this PR received.However, please confirm that the underlying
slack.app.mjscomponent handles all necessary v2 OAuth changes transparently, and that this action will function correctly without any code modifications.components/slack/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
7-7: LGTM: Version bump for coordinated release.The metadata-only version bump aligns with the coordinated release pattern across Slack actions in this PR.
components/slack/actions/update-group-members/update-group-members.mjs (1)
7-7: LGTM!Version bump is appropriate for this defensive code improvement.
components/slack/actions/list-replies/list-replies.mjs (3)
1-1: LGTM!The constants import is correctly added and used for channel type constraints.
8-8: LGTM!The version bump from 0.0.24 to 0.0.25 is appropriate for this change.
21-28: Confirm DM/group channel support isn't required: list-replies now limits the picker to CHANNEL_TYPE.PUBLIC and CHANNEL_TYPE.PRIVATE—verify no existing workflows depend on CHANNEL_TYPE.IM or CHANNEL_TYPE.MPIM.components/slack/sources/new-reaction-added/new-reaction-added.mjs (2)
8-8: LGTM: Version bump aligns with error handling changes.The minor version increment from
1.1.26to1.2.0appropriately reflects the defensive error handling added toprocessEvent.
100-107: Approve with observability enhancements
- Replace console.log with a structured logger or emit a metric on getMessage failure.
- Downstream Slack handlers do not reference event.message, so undefined messages won’t break processing.
- If message retrieval is critical, attach an error field to the returned event for better troubleshooting.
components/slack/actions/send-large-message/send-large-message.mjs (2)
38-42: LGTM! Pre-send channel invitation logic is correct.The conditional check for
addToChanneland subsequent call tomaybeAddAppToChannelsensures the app is properly invited to the channel before sending the message. TheaddToChannelprop is correctly inherited from the common send-message module.
91-91: LGTM! Channel name resolution refactored to use helper.Using
getChannelDisplayNamecentralizes the channel display logic and improves maintainability.components/slack/sources/new-user-mention/new-user-mention.mjs (1)
20-25: LGTM! Channel type constraints correctly applied.The dynamic type constraints limit conversation selection to public and private channels using shared constants, aligning with the pattern adopted across other Slack sources and actions in this PR.
components/slack/actions/common/send-message.mjs (2)
12-17: LGTM! addToChannel prop and pre-send logic implemented correctly.The new
addToChannelprop allows users to opt into automatic channel invitation, and the pre-send logic efficiently cacheschannelIdand invokesmaybeAddAppToChannelswhen needed.Also applies to: 190-196
258-258: LGTM! Channel name resolution refactored to use helper.Using
getChannelDisplayNamecentralizes the channel display logic and improves maintainability.components/slack/sources/new-keyword-mention/new-keyword-mention.mjs (1)
20-25: LGTM! Channel type constraints correctly applied.The dynamic type constraints limit conversation selection to public and private channels using shared constants, consistent with the pattern in new-user-mention.mjs and other Slack components.
components/slack/actions/get-file/get-file.mjs (1)
21-29: LGTM! All changes follow the established pattern.The channel type constraints,
addToChannelprop, and pre-run invitation logic are correctly implemented and consistent with the pattern adopted across other Slack actions in this PR.Also applies to: 30-35, 47-51
components/slack/actions/list-files/list-files.mjs (1)
21-29: LGTM! All changes follow the established pattern.The channel type constraints,
addToChannelprop, and pre-run invitation logic are correctly implemented and consistent with the pattern adopted across other Slack actions in this PR.Also applies to: 30-35, 64-68
components/slack/slack.app.mjs
Outdated
| if ([ | ||
| "not_in_channel", | ||
| "channel_not_found", | ||
| ].some((errorType) => error.includes(errorType)) && asBot) { | ||
| const followUp = method.startsWith("chat.") | ||
| ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user." | ||
| : "Ensure the bot is a member of the channel."; | ||
| throw new ConfigurationError(`${error}\n${followUp}`); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix error handling before it throws a TypeError
error is typically an Error object (e.g., WebAPIPlatformError), so calling error.includes(...) throws TypeError: error.includes is not a function, hiding the original Slack error instead of surfacing it. Coerce the error to a string (or read error.data?.error) before running .includes, and use that value in the ConfigurationError message.
- if ([
- "not_in_channel",
- "channel_not_found",
- ].some((errorType) => error.includes(errorType)) && asBot) {
- const followUp = method.startsWith("chat.")
- ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user."
- : "Ensure the bot is a member of the channel.";
- throw new ConfigurationError(`${error}\n${followUp}`);
- }
- throw error;
+ const errorMessage = typeof error === "string"
+ ? error
+ : error?.data?.error || error.message || `${error}`;
+ if ([
+ "not_in_channel",
+ "channel_not_found",
+ ].some((errorType) => errorMessage.includes(errorType)) && asBot) {
+ const followUp = method.startsWith("chat.")
+ ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user."
+ : "Ensure the bot is a member of the channel.";
+ throw new ConfigurationError(`${errorMessage}\n${followUp}`);
+ }
+ throw error;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ([ | |
| "not_in_channel", | |
| "channel_not_found", | |
| ].some((errorType) => error.includes(errorType)) && asBot) { | |
| const followUp = method.startsWith("chat.") | |
| ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user." | |
| : "Ensure the bot is a member of the channel."; | |
| throw new ConfigurationError(`${error}\n${followUp}`); | |
| } | |
| const errorMessage = typeof error === "string" | |
| ? error | |
| : error?.data?.error || error.message || `${error}`; | |
| if ([ | |
| "not_in_channel", | |
| "channel_not_found", | |
| ].some((errorType) => errorMessage.includes(errorType)) && asBot) { | |
| const followUp = method.startsWith("chat.") | |
| ? "Ensure the bot is a member of the channel, or set the **Send as User** option to true to act on behalf of the authenticated user." | |
| : "Ensure the bot is a member of the channel."; | |
| throw new ConfigurationError(`${errorMessage}\n${followUp}`); | |
| } | |
| throw error; |
🤖 Prompt for AI Agents
In components/slack/slack.app.mjs around lines 561 to 569, the code assumes
`error` is a string and calls `error.includes(...)`, which throws a TypeError
when `error` is an Error object; change the logic to extract a safe string first
(e.g., const errMsg = typeof error === "string" ? error : error?.data?.error ??
String(error)), use `errMsg.includes(...)` for the conditional, and use `errMsg`
when constructing the ConfigurationError so the original Slack error is
preserved and no TypeError is thrown.
guard against unexpected types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/slack/actions/find-message/find-message.mjs (1)
42-42: Fix stray backtick in description.Clean up the string.
- description: "Sort ascending (asc) or descending (desc)`", + description: "Sort ascending (asc) or descending (desc)",
♻️ Duplicate comments (1)
components/slack/slack.app.mjs (1)
561-569: Implement the error message extraction suggested in the previous review.While
${error}prevents the TypeError, the previous review correctly noted that extractingerror.data?.errorwould preserve the original Slack error message more accurately. The current string coercion might lose structured error details.Apply the diff suggested in the previous review to properly extract and preserve error details.
🧹 Nitpick comments (7)
components/slack/slack.app.mjs (1)
502-527: Consider logging caught errors for debugging.While the fallback strings provide good UX when name resolution fails, silently catching errors at lines 509 and 522 could hide legitimate issues (e.g., auth failures, rate limits). Consider adding
console.error(err)or similar logging in the catch blocks to aid troubleshooting.components/slack/actions/find-message/find-message.mjs (6)
80-90: Normalize merge precedence to avoid accidental overrides.Currently, ...baseMessage and ...rest can override canonical fields (user, ts, text, type, channel). If the canonical fields should win, flip the spread order.
- const normalized = { - type: "message", - user: authorUserId, - team: teamId, - ts: messageTs, - text: content, - permalink, - channel: channelInfo, - ...baseMessage, - ...rest, - }; + const normalized = { + ...baseMessage, + ...rest, + type: "message", + user: authorUserId, + team: teamId, + ts: messageTs, + text: content, + permalink, + channel: channelInfo, + };If overriding by baseMessage/rest is intentional, consider adding a brief comment to document the precedence.
115-119: Align with existing Slack client usage: pass throwRateLimitError.Other Slack app calls set throwRateLimitError: true. Consider adding it here for consistency and clearer retry behavior. Based on relevant code snippets.
- const response = await this.slack.assistantSearch({ + const response = await this.slack.assistantSearch({ ...baseParams, channel_types: "public_channel,private_channel", cursor, + throwRateLimitError: true, });
117-118: Channel types exclude DMs — intentional?assistantSearch is limited to public/private channels. If you also want to match IM/MPIM messages, add im,mpim or make it configurable.
- channel_types: "public_channel,private_channel", + channel_types: "public_channel,private_channel,im,mpim",
134-138: Also pass throwRateLimitError to searchMessages.Match the pattern used across the Slack app for consistent error semantics. Based on relevant code snippets.
- const response = await this.slack.searchMessages({ + const response = await this.slack.searchMessages({ ...baseParams, count, page, + throwRateLimitError: true, });
190-194: Avoid sending undefined params to the API.Only include sort/sort_dir when set to reduce risk of invalid query params.
- const baseParams = { - query: this.query, - sort: this.sort, - sort_dir: this.sortDirection, - }; + const baseParams = { + query: this.query, + ...(this.sort && { sort: this.sort }), + ...(this.sortDirection && { sort_dir: this.sortDirection }), + };
197-205: Optional: Fallback if assistantSearch is unavailable.If older app versions or tokens don’t expose assistantSearch, pre-check and use searchMessages directly.
- try { - matches = await this.searchWithAssistant(baseParams, maxResults); - } catch (error) { + try { + if (typeof this.slack.assistantSearch !== "function") { + matches = await this.searchWithSearchMessages(baseParams, maxResults); + } else { + matches = await this.searchWithAssistant(baseParams, maxResults); + } + } catch (error) { if (this.shouldFallbackToSearchMessages(error)) { matches = await this.searchWithSearchMessages(baseParams, maxResults); } else { throw error; } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
components/slack/actions/find-message/find-message.mjs(3 hunks)components/slack/slack.app.mjs(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
components/slack/actions/find-message/find-message.mjs (1)
components/slack/slack.app.mjs (8)
channel(505-507)channel(514-516)cursor(187-196)cursor(240-247)cursor(629-629)cursor(653-653)response(557-557)page(293-298)
components/slack/slack.app.mjs (2)
components/slack/actions/find-message/find-message.mjs (3)
response(115-119)response(134-138)cursor(112-112)components/slack/actions/get-file/get-file.mjs (1)
response(53-55)
🔇 Additional comments (9)
components/slack/slack.app.mjs (7)
5-5: LGTM: ConfigurationError import supports better error handling.The import enables throwing user-friendly configuration errors when bot permissions are insufficient, improving the developer experience.
31-32: LGTM: Name resolution improvements are well-implemented.The changes correctly:
- Filter out falsy user IDs before lookup
- Optimize by resolving IDs and usernames in parallel
- Extract usernames from MPIM purpose values using regex
The inline comment at lines 140-142 clearly explains the optimization rationale.
Also applies to: 124-143
468-473: LGTM: addToChannel prop provides good user control.The prop definition is clear, with a sensible default that automates the common case while allowing users to opt out when needed.
491-501: LGTM: Token selection logic is clear and correct.The methods properly implement the bot token precedence: use bot token when explicitly requested via
asBotand available, otherwise fall back to user token.
532-536: LGTM: SDK initialization properly supports token options.The method correctly passes options to
getTokenfor bot vs. user token selection and includesslackApiUrlconfiguration for custom Slack instances.
679-709: LGTM: Robust error handling for channel invitations.The method correctly:
- Checks for bot token availability
- Validates bot_id before proceeding
- Handles per-channel errors gracefully with logging
- Skips DM/group DM channels automatically (per the comment)
Note: Sequential channel processing (lines 694-705) could be slow for many channels, but this is likely necessary given Slack's API constraints.
948-973: LGTM: File methods correctly use bot token with clear documentation.The comments at lines 952-954 and 968-970 clearly explain that the bot token is required because the
files:readscope is only requested for bot tokens in the Pipedream app. This prevents auth errors when using file operations.components/slack/actions/find-message/find-message.mjs (2)
6-7: Verify the new docs endpoint and scope expectations.Confirm that assistant.search.context is the intended API reference for this action and that required scopes match the implementation and fallback logic.
163-170: Good fix: guarded includes on errorCode.The optional chaining prevents TypeError when errorCode is falsy. This resolves the earlier review concern.
components/slack/slack.app.mjs
Outdated
| async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) { | ||
| let cursor; | ||
| const realNames = {}; | ||
| do { | ||
| const { | ||
| members: users, | ||
| response_metadata: { next_cursor: nextCursor }, | ||
| } = await this.usersList({ | ||
| limit: constants.LIMIT, | ||
| cursor, | ||
| throwRateLimitError, | ||
| ...args, | ||
| }); | ||
|
|
||
| for (const user of users) { | ||
| if (ids.includes(user.id)) { | ||
| realNames[user.id] = user.profile.real_name; | ||
| } | ||
| if (usernames.includes(user.name)) { | ||
| realNames[user.name] = user.profile.real_name; | ||
| } | ||
| } | ||
|
|
||
| cursor = nextCursor; | ||
| } while (cursor && Object.keys(realNames).length < (ids.length + usernames.length)); | ||
| return realNames; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix potential infinite loop and performance issue.
Two issues in realNameLookup:
-
Infinite loop risk (line 676): If some IDs or usernames don't exist in the workspace,
realNameswill never reach the expected count, causing the loop to paginate indefinitely. -
O(n²) performance (lines 667, 670): Using
includes()on arrays inside a loop is inefficient for large lists.
Apply this diff to fix both issues:
- async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) {
+ async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) {
+ const idSet = new Set(ids);
+ const usernameSet = new Set(usernames);
let cursor;
const realNames = {};
+ const targetCount = ids.length + usernames.length;
do {
const {
members: users,
response_metadata: { next_cursor: nextCursor },
} = await this.usersList({
limit: constants.LIMIT,
cursor,
throwRateLimitError,
...args,
});
for (const user of users) {
- if (ids.includes(user.id)) {
+ if (idSet.has(user.id)) {
realNames[user.id] = user.profile.real_name;
}
- if (usernames.includes(user.name)) {
+ if (usernameSet.has(user.name)) {
realNames[user.name] = user.profile.real_name;
}
}
cursor = nextCursor;
- } while (cursor && Object.keys(realNames).length < (ids.length + usernames.length));
+ } while (cursor && Object.keys(realNames).length < targetCount);
return realNames;
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) { | |
| let cursor; | |
| const realNames = {}; | |
| do { | |
| const { | |
| members: users, | |
| response_metadata: { next_cursor: nextCursor }, | |
| } = await this.usersList({ | |
| limit: constants.LIMIT, | |
| cursor, | |
| throwRateLimitError, | |
| ...args, | |
| }); | |
| for (const user of users) { | |
| if (ids.includes(user.id)) { | |
| realNames[user.id] = user.profile.real_name; | |
| } | |
| if (usernames.includes(user.name)) { | |
| realNames[user.name] = user.profile.real_name; | |
| } | |
| } | |
| cursor = nextCursor; | |
| } while (cursor && Object.keys(realNames).length < (ids.length + usernames.length)); | |
| return realNames; | |
| }, | |
| async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) { | |
| const idSet = new Set(ids); | |
| const usernameSet = new Set(usernames); | |
| let cursor; | |
| const realNames = {}; | |
| const targetCount = ids.length + usernames.length; | |
| do { | |
| const { | |
| members: users, | |
| response_metadata: { next_cursor: nextCursor }, | |
| } = await this.usersList({ | |
| limit: constants.LIMIT, | |
| cursor, | |
| throwRateLimitError, | |
| ...args, | |
| }); | |
| for (const user of users) { | |
| if (idSet.has(user.id)) { | |
| realNames[user.id] = user.profile.real_name; | |
| } | |
| if (usernameSet.has(user.name)) { | |
| realNames[user.name] = user.profile.real_name; | |
| } | |
| } | |
| cursor = nextCursor; | |
| } while (cursor && Object.keys(realNames).length < targetCount); | |
| return realNames; | |
| }, |
🤖 Prompt for AI Agents
In components/slack/slack.app.mjs around lines 652 to 678, fix the potential
infinite loop and O(n²) performance by converting ids and usernames to Sets
before pagination, then on each page iterate members once and check membership
against those Sets (removing found entries from the Sets and mapping id/username
to real_name), and stop looping when either the Sets are empty (all requested
items resolved) or there is no next_cursor from usersList; also defensively
handle missing response_metadata/next_cursor to avoid relying on undefined and
thus prevent infinite pagination.
components/slack/slack.app.mjs
Outdated
| assistantSearch(args = {}) { | ||
| args.count ||= constants.LIMIT; | ||
| return this.sdk().apiCall("assistant.search.context", { | ||
| ...args, | ||
| }); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify whether assistantSearch should use makeRequest for consistency.
Unlike other methods, assistantSearch calls sdk().apiCall() directly, bypassing the retry logic and rate limit handling provided by makeRequest and _withRetries. This could lead to inconsistent behavior compared to other Slack API calls.
If the assistant.search.context API has different requirements that necessitate direct SDK usage, please document this with a comment. Otherwise, consider refactoring to use makeRequest for consistency.
🏁 Script executed:
#!/bin/bash
# Description: Check if other methods use direct apiCall instead of makeRequest
# Search for other instances of direct apiCall usage
rg -n "sdk\(\)\.apiCall" components/slack/Length of output: 96
🏁 Script executed:
#!/bin/bash
rg -n "this\\.makeRequest" components/slack/slack.app.mjs -C3Length of output: 7042
🏁 Script executed:
#!/bin/bash
# Search for makeRequest method definition
rg -n "makeRequest\s*\(" components/slack/slack.app.mjs -A10 | head -30Length of output: 1074
🏁 Script executed:
#!/bin/bash
# Search for _withRetries within makeRequest method
rg -n "_withRetries" components/slack/slack.app.mjs -C2 | head -40Length of output: 333
Refactor assistantSearch to use makeRequest for consistency with all other API methods.
The method uses direct sdk().apiCall() instead of makeRequest, bypassing the retry mechanism (3 retries) and rate-limit handling provided by _withRetries that every other API method in this file uses. This creates inconsistent error handling and resilience across the codebase.
At lines 895–900, change:
return this.sdk().apiCall("assistant.search.context", {
...args,
});To:
return this.makeRequest({
method: "assistant.search.context",
...args,
});🤖 Prompt for AI Agents
In components/slack/slack.app.mjs around lines 895 to 900, assistantSearch
currently calls this.sdk().apiCall directly (bypassing retries/rate-limit) and
should be changed to use this.makeRequest to get the same retry and rate-limit
behavior; set args.count default as before, then return this.makeRequest with
method: "assistant.search.context" and spread the rest of args so the call
signature and returned value remain identical but now uses the shared retry
wrapper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @slack/web-api npm package doesn't currently expose a method on WebClient for this API method, so we can't call it like the other methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@js07, thank you for the clarification! That makes perfect sense—if the @slack/web-api package doesn't expose assistant.search.context as a method on WebClient, then using apiCall() directly is the correct approach.
To help future maintainers understand why this method differs from the others, consider adding a brief comment above the method:
// Uses apiCall directly since assistant.search.context is not exposed as a method on WebClient
assistantSearch(args = {}) {
args.count ||= constants.LIMIT;
return this.sdk().apiCall("assistant.search.context", {
...args,
});
}This will make it clear that the direct apiCall() usage is intentional due to SDK limitations.
✏️ Learnings added
Learnt from: js07
PR: PipedreamHQ/pipedream#18744
File: components/slack/slack.app.mjs:0-0
Timestamp: 2025-10-20T00:28:38.702Z
Learning: In components/slack/slack.app.mjs, the assistantSearch method uses sdk().apiCall() directly instead of makeRequest because the slack/web-api npm package doesn't expose assistant.search.context as a method on WebClient. This direct usage is intentional and necessary when Slack API methods are not yet exposed in the SDK.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
Copies the action with the same name added to the Slack app in the master branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 34
🧹 Nitpick comments (36)
components/slack_v2/sources/new-interaction-event-received/README.md (1)
81-85: Vary sentence structure in troubleshooting steps to improve readability.Three consecutive list items all begin with "Make sure that", which creates repetitive reading. Vary the phrasing for better flow.
1. Make sure that your `action_id` or ****************channels**************** filters apply to that message, remove the filters to make sure that's not the case. -1. Make sure that the message comes from the same Slack account that this source is configured with. +1. Verify the message comes from the same Slack account that this source is configured with. -1. Make sure that the message was sent via Pipedream action (e.g. [Slack - Send Block Kit Message](https://pipedream.com/apps/slack-v2/actions/send-block-kit-message) Action) or via API call from the Pipedream app. +1. Confirm the message was sent via Pipedream action (e.g. [Slack - Send Block Kit Message](https://pipedream.com/apps/slack-v2/actions/send-block-kit-message) Action) or via API call from the Pipedream app.components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs (2)
24-41: Define label/description forslackApphook; remove eslint disable.Improve clarity and satisfy lint without disabling rules.
- // eslint-disable-next-line pipedream/props-description,pipedream/props-label slackApphook: { type: "$.interface.apphook", appProp: "slack", + label: "Slack Apphook", + description: "Internal apphook used to receive Slack events. Do not modify.", async eventNames() {
71-79: Simplify emoji parsing;iconEmojiis already a string[] prop.Current JSON/CSV parsing is unnecessary and fragile. Prefer normalized array handling.
- let iconEmojiParsed = []; - - try { - iconEmojiParsed = typeof this.iconEmoji === "string" ? - JSON.parse(this.iconEmoji) : - this.iconEmoji; - } catch (error) { - iconEmojiParsed = this.iconEmoji.replace(/\s+/g, "").split(","); - } + const iconEmojiParsed = Array.isArray(this.iconEmoji) + ? this.iconEmoji.map((e) => e.trim()).filter(Boolean) + : [];components/slack_v2/package.json (1)
1-22: Add license/engines and pin critical deps for reproducible builds.
- Include a license field and Node engines (Pipedream runs Node 18+).
- Consider pinning to exact versions for stability, especially
@pipedream/platform(recommended to pin 3.1.0). Based on learnings.{ "name": "@pipedream/slack_v2", "version": "0.0.1", "description": "Pipedream Slack_v2 Components", "main": "slack_v2.app.mjs", + "license": "MIT", + "engines": { "node": ">=18" }, "keywords": [ @@ "dependencies": { - "@pipedream/platform": "^3.1.0", - "@slack/web-api": "^7.9.0", + "@pipedream/platform": "3.1.0", + "@slack/web-api": "^7.9.0", "async-retry": "^1.3.3", "lodash": "^4.17.21" } }Optionally add
"repository"metadata.
To confirm latest safe@slack/web-apicompatible with Node 18, please verify current releases and changelog.components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs (1)
23-31: LGTM; consider clearer handling when user isn’t found.Slack returns
ok: falsewithusers_not_found. Optionally set a friendly summary or throw when not found.const response = await this.slack.lookupUserByEmail({ email: this.email, }); - if (response.ok) { + if (response.ok) { $.export("$summary", `Successfully found user with ID ${response.user.id}`); - } + } else if (response.error === "users_not_found") { + $.export("$summary", "No user found for the provided email"); + } return response;components/slack_v2/common/constants.mjs (1)
11-24: Inconsistent value mapping in CHANNEL_TYPE_OPTIONS.The first option uses
"Channels"as its value instead of aCHANNEL_TYPEconstant. This breaks the pattern established by the other two options (which useCHANNEL_TYPE.MPIMandCHANNEL_TYPE.IM). While this appears intentional (based on usage inapprove-workflow.mjswhere"Channels"maps to both PUBLIC and PRIVATE channel types), it creates an undocumented special case that could confuse maintainers.Consider either:
- Adding a comment explaining that
"Channels"is a special value representing both PUBLIC and PRIVATE channels, or- Refactoring to use a more explicit constant like
CHANNEL_TYPE.PUBLIC_AND_PRIVATEor an array value for consistency.components/slack_v2/actions/set-status/set-status.mjs (1)
37-48: Consider omitting optional properties when not provided.Lines 41 and 42-43 use the
&&operator to conditionally setstatus_emojiandstatus_expiration. When these optional props are not provided, the properties will be set to falsy values (e.g.,undefined,false,0) rather than being omitted from the object. Some APIs may not handle falsy values gracefully.Consider using the spread operator to omit properties entirely when not provided:
const response = await this.slack.updateProfile({ profile: { status_text: this.statusText, - status_emoji: this.statusEmoji && `:${this.statusEmoji}:`, - status_expiration: this.statusExpiration - && Math.floor(new Date(this.statusExpiration).getTime() / 1000), + ...(this.statusEmoji && { status_emoji: `:${this.statusEmoji}:` }), + ...(this.statusExpiration && { + status_expiration: Math.floor(new Date(this.statusExpiration).getTime() / 1000), + }), }, });components/slack_v2/actions/list-group-members/list-group-members.mjs (1)
61-65: Optional: Remove unnecessary optional chaining.Since
membersis initialized as an empty array on line 44, it can never benullorundefined, making the optional chaining on line 61 unnecessary.- if (members?.length) { + if (members.length) {components/slack_v2/actions/update-message/update-message.mjs (1)
48-58: LGTM! Consider enhancing the summary with context.The implementation correctly maps parameters and calls the Slack API. The generic success message is acceptable, though including the channel or timestamp could make debugging easier.
Optional enhancement:
- $.export("$summary", "Successfully updated message"); + $.export("$summary", `Successfully updated message in channel ${this.conversation}`);components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs (1)
24-34: Make channel multi-select via propDefinition options, not parallel type fieldsDeclare multi-select in the propDefinition to avoid schema drift between
type: "string[]"and the underlying definition.Apply:
- conversations: { - propDefinition: [ - common.props.slack, - "conversation", - ], - type: "string[]", - label: "Channels", - description: "Filter interaction events by one or more channels. If none selected, any interaction event in any channel will emit new events.", - optional: true, - default: [], - }, + conversations: { + propDefinition: [ + common.props.slack, + "conversation", + () => ({ multiple: true }), + ], + label: "Channels", + description: "Filter interaction events by one or more channels. If none selected, any interaction event in any channel will emit new events.", + optional: true, + },components/slack_v2/actions/upload-file/upload-file.mjs (2)
52-76: Use metadata-derived filename consistentlyUse
metadata.namefor both the presign request and the form field to avoid mismatches with path parsing.Apply:
- const filename = this.content.split("/").pop(); + const filename = metadata.name; ... - formData.append("filename", filename); + formData.append("filename", filename);
98-104: Consistent error type + more informative summaryUse the same error style as presign and include filename/channel in the summary.
Apply:
- if (!completeUploadResponse.ok) { - throw new Error(`Error completing upload: ${JSON.stringify(completeUploadResponse)}`); - } + if (!completeUploadResponse?.ok) { + throw new ConfigurationError(`Error completing upload: ${JSON.stringify(completeUploadResponse)}`); + } - - $.export("$summary", "Successfully uploaded file"); - return completeUploadResponse; + $.export("$summary", `Successfully uploaded ${metadata.name} to ${this.conversation}`); + return completeUploadResponse;components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
17-33: Consider removing redundant slack prop definition.Line 18 redefines
slack: common.props.slack, but line 31 already spreads...common.propswhich includes the slack prop. While this doesn't cause an error (the spread overwrites the earlier definition), it creates unnecessary redundancy.Apply this diff to remove the redundancy:
props: { - slack: common.props.slack, conversation: { propDefinition: [ common.props.slack, "conversation", ], }, text: { type: "string", label: "Notification Text", description: "Optionally provide a string for Slack to display as the new message notification (if you do not provide this, notification will be blank).", optional: true, }, ...common.props, ...buildBlocks.props, },components/slack_v2/actions/list-files/list-files.mjs (1)
80-85: Consider using a boolean for hasMore.Line 83 assigns
hasMore = files.length, making it a number rather than a boolean. While this works in the loop condition, it reduces code clarity. Consider usinghasMore = files.length > 0orhasMore = files.length === this.pageSizefor clearer intent.Apply this diff to improve clarity:
do { const { files } = await this.slack.listFiles(params); allFiles.push(...files); - hasMore = files.length; + hasMore = files.length > 0; params.page++; } while (hasMore && params.page <= this.numPages);components/slack_v2/sources/new-saved-message/new-saved-message.mjs (1)
25-30: Improve summary for observabilityConsider including user and channel in the summary for quick triage, e.g., “ saved a message in ”.
- getSummary() { - return "New saved message"; - }, + getSummary(event) { + const user = event?.user || "Someone"; + const channel = event?.item?.channel || "a channel"; + return `${user} saved a message in ${channel}`; + },components/slack_v2/actions/update-group-members/update-group-members.mjs (1)
69-70: Skip no-op updates, and align summary variableAvoid the API call when membership is unchanged; also use the local
userGroupfor consistency.- $.export("$summary", `Successfully updated members of group with ID ${this.userGroup}`); - return response; + if (response?.ok === false) return response; + $.export("$summary", `Updated members of group ${userGroup} (${users.length} users)`); + return response;components/slack_v2/actions/find-message/find-message.mjs (1)
51-66: Guard against cursor loops and overfetchAdd a seen‑cursor set to avoid infinite loops if an API bug returns the same cursor; stop when reaching maxResults.
async searchWithAssistant(baseParams, maxResults) { const matches = []; - let cursor; + let cursor; + const seen = new Set(); do { const response = await this.slack.assistantSearch({ ...baseParams, channel_types: "public_channel,private_channel", cursor, }); matches.push(...response.results?.messages || []); - cursor = response.response_metadata?.next_cursor; - } while (cursor && matches.length < maxResults); + const next = response.response_metadata?.next_cursor; + if (!next || seen.has(next) || matches.length >= maxResults) break; + seen.add(next); + cursor = next; + } while (true); return matches.slice(0, maxResults); },components/slack_v2/actions/list-users/list-users.mjs (1)
44-49: Defensive default for missing membersGuard against undefined
membersto avoid pushingundefined.- users.push(...members); + users.push(...(members ?? []));components/slack_v2/actions/send-large-message/send-large-message.mjs (3)
15-36: Props spread order causes duplication and overridesYou define
slack,conversation,text,mrkdwnthen spread...common.props, which overrides them. Either remove duplicates or spread first and override intentionally.- props: { - slack: common.props.slack, - conversation: { propDefinition: [ common.props.slack, "conversation" ] }, - text: { propDefinition: [ common.props.slack, "text" ] }, - mrkdwn: { propDefinition: [ common.props.slack, "mrkdwn" ] }, - ...common.props, - }, + props: { + ...common.props, + // override here only if you need different labels/descriptions/defaults: + // conversation: { ... }, + // text: { ... }, + // mrkdwn: { ... }, + },
44-47: Optional: safer “sent via Pipedream” text extractionGuard against unexpected block structure when appending the flag text.
- const sentViaPipedreamText = this._makeSentViaPipedreamBlock(); - this.text += `\n\n\n${sentViaPipedreamText.elements[0].text}`; + const block = this._makeSentViaPipedreamBlock(); + const text = block?.elements?.[0]?.text; + if (text) this.text += `\n\n\n${text}`;
66-87: Consider adding message chunking or file upload fallback for optimal Slack deliveryMessages sent via this action can exceed Slack's recommended text length for best results. Slack recommends keeping top-level text to approximately 4,000 characters for readability, and messages longer than 40,000 characters are truncated with a warning returned. Consider chunking
this.textinto multiple messages or implementing a fallback to file upload when the message size exceeds the ~4,000 character threshold.components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs (1)
79-86: Tighten comparisons and parallelize name resolution.Use strict equality and resolve names concurrently to reduce latency under load.
- if ((this.ignoreBot) && (event.subtype == "bot_message" || event.bot_id)) { + if ((this.ignoreBot) && (event.subtype === "bot_message" || event.bot_id)) { return; } @@ - if (this.resolveNames) { - if (event.user) { - event.user_id = event.user; - event.user = await this.getUserName(event.user); - } else if (event.bot_id) { - event.bot = await this.getBotName(event.bot_id); - } - event.channel_id = event.channel; - event.channel = await this.getConversationName(event.channel); - if (event.team) { - event.team_id = event.team; - event.team = await this.getTeamName(event.team); - } - } + if (this.resolveNames) { + const promises = []; + if (event.user) { + event.user_id = event.user; + promises.push(this.getUserName(event.user).then((name) => { event.user = name; })); + } else if (event.bot_id) { + promises.push(this.getBotName(event.bot_id).then((name) => { event.bot = name; })); + } + event.channel_id = event.channel; + promises.push(this.getConversationName(event.channel).then((name) => { event.channel = name; })); + if (event.team) { + event.team_id = event.team; + promises.push(this.getTeamName(event.team).then((name) => { event.team = name; })); + } + await Promise.all(promises); + }Also applies to: 88-101
components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (1)
18-19: Avoid duplicateslackprop; optionally expose rawblocksinput.
slackis already provided via...common.props; defining it again is redundant.- Expose the app’s
blocksprop so users can paste raw Block Kit JSON when not using the builder.props: { - slack: common.props.slack, + // rely on ...common.props for `slack` conversation: { propDefinition: [ common.props.slack, "conversation", ], }, @@ link_names: { propDefinition: [ common.props.slack, "link_names", ], }, + blocks: { + propDefinition: [ + common.props.slack, + "blocks", + ], + }, ...common.props, ...buildBlocks.props, },Also applies to: 56-58
components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (1)
49-56: Harden pagination and results (handle missing members, dedupe IDs).Prevents runtime errors if
membersis undefined and avoids duplicate IDs across pages; minor efficiency win before username lookup.do { const { - members, response_metadata: { next_cursor: nextCursor }, + members, response_metadata: { next_cursor: nextCursor }, } = await this.slack.listChannelMembers(params); - channelMembers.push(...members); + channelMembers.push(...(members ?? [])); params.cursor = nextCursor; page++; } while (params.cursor && page < this.numPages); + // Deduplicate before optional username lookup + channelMembers = [...new Set(channelMembers)];Also applies to: 58-64
components/slack_v2/slack_v2.app.mjs (5)
628-651: Speed up ID lookups with a Set (avoid O(n²)).Current
ids.includes(user.id)scales poorly on large teams.- let cursor; - const userNames = {}; + let cursor; + const userNames = {}; + const target = new Set(ids); do { @@ - for (const user of users) { - if (ids.includes(user.id)) { - userNames[user.id] = user.name; - } - } + for (const user of users) { + if (target.has(user.id)) userNames[user.id] = user.name; + }
652-678: Same Set optimization for realNameLookup.- let cursor; - const realNames = {}; + let cursor; + const realNames = {}; + const idSet = new Set(ids); + const unameSet = new Set(usernames); @@ - for (const user of users) { - if (ids.includes(user.id)) { - realNames[user.id] = user.profile.real_name; - } - if (usernames.includes(user.name)) { - realNames[user.name] = user.profile.real_name; - } - } + for (const user of users) { + if (idSet.has(user.id)) realNames[user.id] = user.profile.real_name; + if (unameSet.has(user.name)) realNames[user.name] = user.profile.real_name; + }
476-487: Potential label bug:resource.user.namelikely undefined.For DMs,
resource.useris usually a user ID string, not an object. This returnsDirect Messaging with: @undefined.getChannelLabel(resource) { - if (resource.user) { - return `Direct Messaging with: @${resource.user.name}`; - } + if (resource.user) return `Direct Messaging with: @${resource.user}`; const { is_private: isPrivate, name, } = resource.channel; return `${isPrivate && "Private" || "Public"} channel #${name}`; }
502-511: Simplify display name fallback for DMs.Avoid redundant
profile.real_name || profile?.real_nameand adddisplay_namefallback.- return `@${profile.real_name || profile?.real_name}`; + return `@${profile?.real_name || profile?.display_name || "user"}`;
684-705: Optionally tryconversations.joinbefore inviting the bot.For public channels, joining is simpler and avoids invite failures; fall back to invite on error.
- for (const channel of channelIds) { + for (const channel of channelIds) { try { - // Note: Trying to add the app to DM or group DM channels results in - // the error: method_not_supported_for_channel_type - await this.inviteToConversation({ - channel, - users: user_id, - }); + // Attempt join (public channels). If it fails, fall back to invite. + await this.sdk({ asBot: true }).conversations.join({ channel }); + } catch (errJoin) { + try { + await this.inviteToConversation({ channel, users: user_id }); + } catch (error) { + console.log(`Unable to add app to channel ${channel}: ${error}`); + } } catch (error) { console.log(`Unable to add app to channel ${channel}: ${error}`); } }components/slack_v2/actions/common/send-message.mjs (2)
205-206: ValidateblocksJSON and surface a clear error.- } else if (typeof blocks === "string") { - blocks = JSON.parse(blocks); + } else if (typeof blocks === "string") { + try { + blocks = JSON.parse(blocks); + } catch (e) { + throw new Error(`Invalid JSON in "Blocks": ${e.message}`); + } }
250-253: Validatepost_at(ISO-8601, not in the past) before scheduling.if (this.post_at) { - obj.post_at = Math.floor(new Date(this.post_at).getTime() / 1000); + const ts = Date.parse(this.post_at); + if (Number.isNaN(ts)) { + throw new Error('Invalid "Schedule message" datetime. Use ISO 8601 (e.g., 2014-01-01T00:00:00Z).'); + } + if (ts < Date.now()) { + throw new Error('"Schedule message" time cannot be in the past.'); + } + obj.post_at = Math.floor(ts / 1000); return await this.slack.scheduleMessage(obj); }components/slack_v2/actions/common/build-blocks.mjs (3)
160-164: Defensive: ensurethis.blocksis an array in “array” mode.Prevents runtime issues if upstream passes a non‑array.
- if (this.passArrayOrConfigure === "array") { - blocks = this.blocks; + if (this.passArrayOrConfigure === "array") { + blocks = Array.isArray(this.blocks) ? this.blocks : []; } else {
73-75: Avoid deprecatedsubstr; preferslice(orcrypto.randomUUID()for stronger IDs).Small DX fix; optional stronger ID generation shown below.
- action_id: `actionId-${Math.random().toString(36) - .substr(2, 9)}`, // Generates a random action_id + action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`, // Generates a random action_idOptional stronger IDs:
// add at top of file: import { randomUUID } from "crypto"; // and here: action_id: `actionId-${randomUUID()}`,
39-82: Handle unknown block types explicitly.Fail fast if an unsupported type is passed.
} else if (type === "link_button") { @@ return { type: "actions", elements: buttons, }; } + throw new Error(`Unsupported block type: ${type}`);components/slack_v2/sources/common/base.mjs (2)
1-1: Use stable ID generation; avoid deprecatedsubstr.Prefer
crypto.randomUUID()(Node 16+) for uniqueness and clarity; fallback tosliceif needed.+import { randomUUID } from "crypto"; @@ - event.pipedream_msg_id = `pd_${Date.now()}_${Math.random() - .toString(36) - .substr(2, 10)}`; + event.pipedream_msg_id = `pd_${Date.now()}_${randomUUID()}`;If
cryptoisn’t available, minimally:- .substr(2, 10)}`; + .slice(2, 12)}`;Also applies to: 172-175
177-181: Harden$emitid fallback chain.If
event.channelis a string,.idis undefined. Use safe fallbacks.- this.$emit(event, { - id: event.client_msg_id || event.pipedream_msg_id || event.channel.id, + this.$emit(event, { + id: event.client_msg_id + || event.pipedream_msg_id + || (event.channel && (event.channel.id || event.channel)) + || event.user + || String(event.event_ts || Date.now()), summary: this.getSummary(event), ts: event.event_ts || Date.now(), });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (57)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs(1 hunks)components/slack_v2/actions/approve-workflow/approve-workflow.mjs(1 hunks)components/slack_v2/actions/archive-channel/archive-channel.mjs(1 hunks)components/slack_v2/actions/common/build-blocks.mjs(1 hunks)components/slack_v2/actions/common/send-message.mjs(1 hunks)components/slack_v2/actions/create-channel/create-channel.mjs(1 hunks)components/slack_v2/actions/create-reminder/create-reminder.mjs(1 hunks)components/slack_v2/actions/delete-file/delete-file.mjs(1 hunks)components/slack_v2/actions/delete-message/delete-message.mjs(1 hunks)components/slack_v2/actions/find-message/find-message.mjs(1 hunks)components/slack_v2/actions/find-user-by-email/find-user-by-email.mjs(1 hunks)components/slack_v2/actions/get-current-user/get-current-user.mjs(1 hunks)components/slack_v2/actions/get-file/get-file.mjs(1 hunks)components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs(1 hunks)components/slack_v2/actions/kick-user/kick-user.mjs(1 hunks)components/slack_v2/actions/list-channels/list-channels.mjs(1 hunks)components/slack_v2/actions/list-files/list-files.mjs(1 hunks)components/slack_v2/actions/list-group-members/list-group-members.mjs(1 hunks)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs(1 hunks)components/slack_v2/actions/list-replies/list-replies.mjs(1 hunks)components/slack_v2/actions/list-users/list-users.mjs(1 hunks)components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs(1 hunks)components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs(1 hunks)components/slack_v2/actions/send-large-message/send-large-message.mjs(1 hunks)components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs(1 hunks)components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs(1 hunks)components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs(1 hunks)components/slack_v2/actions/send-message/send-message.mjs(1 hunks)components/slack_v2/actions/set-channel-description/set-channel-description.mjs(1 hunks)components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs(1 hunks)components/slack_v2/actions/set-status/set-status.mjs(1 hunks)components/slack_v2/actions/update-group-members/update-group-members.mjs(1 hunks)components/slack_v2/actions/update-message/update-message.mjs(1 hunks)components/slack_v2/actions/update-profile/update-profile.mjs(1 hunks)components/slack_v2/actions/upload-file/upload-file.mjs(1 hunks)components/slack_v2/common/constants.mjs(1 hunks)components/slack_v2/package.json(1 hunks)components/slack_v2/slack_v2.app.mjs(1 hunks)components/slack_v2/sources/common/base.mjs(1 hunks)components/slack_v2/sources/common/constants.mjs(1 hunks)components/slack_v2/sources/new-channel-created/new-channel-created.mjs(1 hunks)components/slack_v2/sources/new-channel-created/test-event.mjs(1 hunks)components/slack_v2/sources/new-interaction-event-received/README.md(1 hunks)components/slack_v2/sources/new-interaction-event-received/new-interaction-event-received.mjs(1 hunks)components/slack_v2/sources/new-interaction-event-received/test-event.mjs(1 hunks)components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs(1 hunks)components/slack_v2/sources/new-keyword-mention/test-event.mjs(1 hunks)components/slack_v2/sources/new-message-in-channels/new-message-in-channels.mjs(1 hunks)components/slack_v2/sources/new-message-in-channels/test-event.mjs(1 hunks)components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs(1 hunks)components/slack_v2/sources/new-reaction-added/test-event.mjs(1 hunks)components/slack_v2/sources/new-saved-message/new-saved-message.mjs(1 hunks)components/slack_v2/sources/new-saved-message/test-event.mjs(1 hunks)components/slack_v2/sources/new-user-added/new-user-added.mjs(1 hunks)components/slack_v2/sources/new-user-added/test-event.mjs(1 hunks)components/slack_v2/sources/new-user-mention/new-user-mention.mjs(1 hunks)components/slack_v2/sources/new-user-mention/test-event.mjs(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- components/slack_v2/sources/new-keyword-mention/test-event.mjs
- components/slack_v2/sources/new-user-mention/test-event.mjs
🧰 Additional context used
🧬 Code graph analysis (26)
components/slack_v2/actions/list-channels/list-channels.mjs (4)
components/slack_v2/actions/list-group-members/list-group-members.mjs (3)
params(45-49)page(50-50)nextCursor(53-55)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
params(43-46)page(47-47)nextCursor(50-52)components/slack_v2/actions/list-replies/list-replies.mjs (3)
params(51-55)page(56-56)nextCursor(59-61)components/slack_v2/actions/list-users/list-users.mjs (3)
params(38-41)page(42-42)nextCursor(45-47)
components/slack_v2/actions/set-channel-description/set-channel-description.mjs (3)
components/slack_v2/actions/archive-channel/archive-channel.mjs (1)
response(32-34)components/slack_v2/actions/create-channel/create-channel.mjs (1)
response(33-36)components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs (1)
response(30-33)
components/slack_v2/actions/list-files/list-files.mjs (5)
components/slack_v2/actions/list-channels/list-channels.mjs (1)
params(31-33)components/slack_v2/actions/list-group-members/list-group-members.mjs (1)
params(45-49)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (1)
params(43-46)components/slack_v2/actions/list-replies/list-replies.mjs (1)
params(51-55)components/slack_v2/actions/list-users/list-users.mjs (1)
params(38-41)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
components/slack_v2/actions/delete-message/delete-message.mjs (1)
response(37-41)
components/slack_v2/actions/list-replies/list-replies.mjs (4)
components/slack_v2/actions/list-channels/list-channels.mjs (3)
params(31-33)page(34-34)nextCursor(37-39)components/slack_v2/actions/list-group-members/list-group-members.mjs (3)
params(45-49)page(50-50)nextCursor(53-55)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
params(43-46)page(47-47)nextCursor(50-52)components/slack_v2/actions/list-users/list-users.mjs (3)
params(38-41)page(42-42)nextCursor(45-47)
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1)
components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs (2)
msgType(61-66)emitEvent(86-86)
components/slack_v2/actions/kick-user/kick-user.mjs (2)
components/slack_v2/actions/archive-channel/archive-channel.mjs (1)
response(32-34)components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
response(31-34)
components/slack_v2/actions/list-users/list-users.mjs (2)
components/slack_v2/slack_v2.app.mjs (4)
users(517-519)users(632-640)users(656-664)page(293-298)components/slack_v2/actions/list-channels/list-channels.mjs (3)
params(31-33)page(34-34)nextCursor(37-39)
components/slack_v2/actions/delete-message/delete-message.mjs (1)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
response(40-44)
components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
components/slack_v2/actions/common/send-message.mjs (2)
resp(254-254)resp(255-257)
components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs (1)
components/slack_v2/actions/set-channel-description/set-channel-description.mjs (1)
response(30-33)
components/slack_v2/slack_v2.app.mjs (13)
components/slack_v2/actions/common/send-message.mjs (3)
channelId(190-190)resp(254-254)resp(255-257)components/slack_v2/actions/list-group-members/list-group-members.mjs (3)
members(44-44)page(50-50)nextCursor(53-55)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
usernames(59-59)page(47-47)nextCursor(50-52)components/slack_v2/actions/list-channels/list-channels.mjs (2)
page(34-34)nextCursor(37-39)components/slack_v2/actions/list-replies/list-replies.mjs (2)
page(56-56)nextCursor(59-61)components/slack_v2/actions/list-users/list-users.mjs (3)
page(42-42)users(37-37)nextCursor(45-47)components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
response(40-44)components/slack_v2/actions/delete-file/delete-file.mjs (1)
response(33-35)components/slack_v2/actions/delete-message/delete-message.mjs (1)
response(37-41)components/slack_v2/actions/get-file/get-file.mjs (1)
response(53-55)components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
response(31-34)components/slack_v2/actions/kick-user/kick-user.mjs (1)
response(42-45)components/slack_v2/actions/send-large-message/send-large-message.mjs (2)
response(81-81)response(88-90)
components/slack_v2/sources/new-channel-created/new-channel-created.mjs (1)
components/slack_v2/actions/create-channel/create-channel.mjs (1)
name(31-31)
components/slack_v2/actions/list-group-members/list-group-members.mjs (4)
components/slack_v2/actions/list-channels/list-channels.mjs (3)
params(31-33)page(34-34)nextCursor(37-39)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
params(43-46)page(47-47)nextCursor(50-52)components/slack_v2/actions/list-users/list-users.mjs (4)
params(38-41)page(42-42)nextCursor(45-47)users(37-37)components/slack_v2/slack_v2.app.mjs (4)
page(293-298)users(517-519)users(632-640)users(656-664)
components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
components/slack_v2/actions/kick-user/kick-user.mjs (1)
response(42-45)
components/slack_v2/actions/delete-file/delete-file.mjs (1)
components/slack_v2/actions/get-file/get-file.mjs (1)
response(53-55)
components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (4)
components/slack_v2/actions/list-channels/list-channels.mjs (3)
params(31-33)page(34-34)nextCursor(37-39)components/slack_v2/actions/list-group-members/list-group-members.mjs (4)
params(45-49)page(50-50)nextCursor(53-55)members(44-44)components/slack_v2/actions/list-replies/list-replies.mjs (3)
params(51-55)page(56-56)nextCursor(59-61)components/slack_v2/actions/list-users/list-users.mjs (3)
params(38-41)page(42-42)nextCursor(45-47)
components/slack_v2/actions/common/send-message.mjs (4)
components/slack_v2/actions/common/build-blocks.mjs (2)
props(86-86)blocks(161-161)components/slack_v2/actions/send-large-message/send-large-message.mjs (4)
sentViaPipedreamText(45-45)metadataEventPayload(49-49)obj(66-79)channelName(91-91)components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
resp(43-45)components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (1)
resp(70-72)
components/slack_v2/actions/common/build-blocks.mjs (2)
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1)
blocks(75-81)components/slack_v2/actions/common/send-message.mjs (1)
blocks(198-198)
components/slack_v2/sources/common/base.mjs (1)
components/slack_v2/sources/common/constants.mjs (4)
NAME_CACHE_TIMEOUT(50-50)NAME_CACHE_TIMEOUT(50-50)NAME_CACHE_MAX_SIZE(49-49)NAME_CACHE_MAX_SIZE(49-49)
components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (2)
components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
resp(43-45)components/slack_v2/actions/common/send-message.mjs (2)
resp(254-254)resp(255-257)
components/slack_v2/actions/send-large-message/send-large-message.mjs (1)
components/slack_v2/actions/common/send-message.mjs (4)
sentViaPipedreamText(209-209)metadataEventPayload(213-213)obj(231-248)channelName(258-258)
components/slack_v2/actions/find-message/find-message.mjs (1)
components/slack_v2/slack_v2.app.mjs (4)
cursor(187-196)cursor(240-247)cursor(629-629)cursor(653-653)
components/slack_v2/actions/update-message/update-message.mjs (2)
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
response(40-44)components/slack_v2/actions/delete-message/delete-message.mjs (1)
response(37-41)
components/slack_v2/actions/get-file/get-file.mjs (1)
components/slack_v2/actions/delete-file/delete-file.mjs (1)
response(33-35)
components/slack_v2/sources/new-keyword-mention/new-keyword-mention.mjs (1)
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (2)
msgType(68-74)emitEvent(101-101)
🪛 Gitleaks (8.28.0)
components/slack_v2/sources/new-interaction-event-received/test-event.mjs
[high] 11-11: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 LanguageTool
components/slack_v2/sources/new-interaction-event-received/README.md
[style] ~11-~11: Consider using just “reply”.
Context: ...k with other APIs or services, and then reply back to the original message. # Getting Sta...
(RETURN_BACK)
[style] ~85-~85: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hat this source is configured with. 1. Make sure that the message was sent via Pipe...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Publish TypeScript components
- GitHub Check: Lint Code Base
- GitHub Check: pnpm publish
- GitHub Check: Verify TypeScript components
🔇 Additional comments (24)
components/slack_v2/sources/new-user-added/test-event.mjs (1)
1-48: Fixture looks sanitized and complete.No functional issues found. Safe defaults (empty strings) avoid PII exposure.
components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs (1)
15-23: Confirmconversationprop supports multiple selection.You set
type: "string[]"but reuse a singleconversationpropDefinition. Ensure the underlying definition returns an array and UI supports multi-select; otherwise switch to a multi-select propDefinition or wrap withoptions()to enable multiple values.components/slack_v2/actions/create-channel/create-channel.mjs (1)
29-39: LGTM! Clean channel name normalization and creation flow.The channel name normalization (replacing whitespace with hyphens and lowercasing) correctly aligns with Slack's channel naming requirements. The API call structure and success summary are appropriate.
components/slack_v2/actions/approve-workflow/approve-workflow.mjs (1)
47-91: LGTM! Well-structured workflow approval with proper flow suspension.The implementation correctly:
- Suspends the flow to obtain resume/cancel URLs
- Posts a formatted Slack message with interactive buttons
- Links the buttons to the appropriate flow control endpoints
- Handles the special "Channels" value to map to both PUBLIC and PRIVATE channel types
components/slack_v2/actions/set-channel-description/set-channel-description.mjs (1)
29-36: LGTM!The implementation correctly maps the UI concept of "channel description" to Slack's API terminology of "purpose". The parameter mapping and API call are consistent with the Slack documentation.
components/slack_v2/sources/new-channel-created/test-event.mjs (1)
1-44: LGTM!The test fixture provides a comprehensive static event payload that matches the Slack
channel_createdevent structure, including all relevant channel properties and metadata.components/slack_v2/sources/new-saved-message/test-event.mjs (1)
1-37: LGTM!The test fixture correctly represents a Slack
star_addedevent with appropriate nested structure for the item and message properties, including the blocks array for rich text formatting.components/slack_v2/actions/list-replies/list-replies.mjs (1)
58-65: The original code is correct. No changes needed.The Slack API
conversations.repliesendpoint returnsresponse_metadatacontainingnext_cursoras a standard part of the response structure for cursor-based pagination. The destructuring on line 60 is safe because the Slack API guarantees this field exists.The pattern used in list-replies is consistent across all list actions in the codebase (list-channels, list-group-members, list-members-in-channel, list-users), and none include defensive handling. This systematic consistency reflects the proper reliance on the Slack API contract.
Likely an incorrect or invalid review comment.
components/slack_v2/actions/list-group-members/list-group-members.mjs (1)
52-59: Verify response_metadata structure for usergroups.users.list API call.The web search results are conflicting on whether
usergroups.users.listincludesresponse_metadatain its response. One source indicates the response contains only{"ok": true, "users": [...]}withoutresponse_metadata, which would cause a destructuring error on line 54. However, pagination documentation referencesresponse_metadata.next_cursorfor this endpoint.The pattern is consistent across all list actions in the codebase, suggesting it may work in practice, but the API documentation ambiguity warrants verification—especially since
find-message.mjsuses defensive optional chaining forresponse_metadata, indicating some endpoints handle this differently.Recommended actions:
- Test this action against the actual Slack API to confirm the response structure
- If
response_metadatais missing, add defensive handling:response_metadata: { next_cursor: nextCursor } = {}- Check Slack API changelog or test against a real workspace to resolve documentation ambiguity
components/slack_v2/actions/get-current-user/get-current-user.mjs (1)
25-31: ****The
usersInfomethod does exist in the v2 wrapper (defined at line 865 ofslack_v2.app.mjs). The review comment's assumption that it may not exist is unfounded. The codebase shows that while some methods follow agetXxxpattern (e.g.,getUserProfile,getTeamInfo), others likeusersInfoandauthTestuse different naming conventions—all valid in the v2 wrapper.Likely an incorrect or invalid review comment.
components/slack_v2/actions/send-message-to-channel/send-message-to-channel.mjs (1)
16-44: Review comment is incorrect — props merge order concern is unfoundedThe common.props object does not contain
conversation,text, ormrkdwnproperties. It only exports:slack,as_user,addToChannel,post_at,include_sent_via_pipedream_flag,customizeBotSettings,username,icon_emoji,icon_url,replyToThread,thread_ts,thread_broadcast,addMessageMetadata,metadata_event_type,metadata_event_payload,configureUnfurlSettings,unfurl_links,unfurl_media.Since these keys don't exist in
common.props, spreading...common.propsat the end cannot override the customconversation,text, ormrkdwnprops with their PUBLIC/PRIVATE channel restrictions. The current code structure is correct and doesn't require refactoring.Likely an incorrect or invalid review comment.
components/slack_v2/actions/reply-to-a-message/reply-to-a-message.mjs (1)
16-53: Prop name is correct; no changes needed.The
thread_broadcastprop is correct. Insend-message.mjs, it's intentionally mapped to the Slack API fieldreply_broadcastin the run method (reply_broadcast: this.thread_broadcast). The review comment's concern about prop naming is factually incorrect.Regarding props override order: While moving
...common.propsto the beginning would be more conventional, the current structure works correctly because explicit props after the spread override the spread defaults via JavaScript object semantics. This is a style suggestion only, not a critical issue.Likely an incorrect or invalid review comment.
components/slack_v2/actions/set-channel-topic/set-channel-topic.mjs (1)
1-37: LGTM!The action follows the standard Slack v2 pattern with proper prop definitions and a clean implementation. The API call and summary export are correctly structured.
components/slack_v2/sources/new-channel-created/new-channel-created.mjs (1)
1-32: LGTM!The event source follows the standard Slack v2 pattern with proper extension of the common base, correct event name configuration, and a clear summary method.
components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs (1)
1-85: LGTM!The action properly handles both conversation selection and user list scenarios with appropriate validation. The ConfigurationError for missing inputs and the openConversation flow are well-implemented.
components/slack_v2/actions/list-channels/list-channels.mjs (1)
29-51: LGTM!The pagination logic correctly accumulates channels using cursor-based pagination and respects the page limit. The implementation is consistent with other list actions in the codebase.
components/slack_v2/actions/send-message/send-message.mjs (1)
1-56: LGTM!The action properly implements dynamic channel type filtering based on user selection. The conditional logic for constraining conversation types is clean and correct.
components/slack_v2/actions/find-message/find-message.mjs (1)
57-60: Verify sort_dir parameter support and consider exposing channel typesBased on Slack's documentation, the assistant.search.context API supports channel_types as a comma-separated list of public_channel, private_channel, mpim, and im. However, your code hardcodes only
"public_channel,private_channel", which excludes direct messages and multi-party IMs that the API supports.More critically, sort_dir is documented for Slack's classic search APIs but not explicitly documented for assistant.search.context. Your code passes
sort_dir: this.sortDirectionto the endpoint—verify that this parameter is actually supported by the assistant.search.context endpoint, as it may be silently ignored or cause unexpected behavior.Recommendations:
- Expose
channelTypesas a configurable prop (defaulting to the current value to preserve behavior).- Test or confirm with Slack that
sort_diris supported byassistant.search.contextbefore relying on it in production.components/slack_v2/actions/get-file/get-file.mjs (1)
46-58: LGTM — action is minimal and correct.Good use of
maybeAddAppToChannelsand clear summary. No blocking issues.components/slack_v2/actions/delete-message/delete-message.mjs (1)
28-34: Fix description text only;as_userparameter is valid and supported by Slack API.The
as_userparameter is supported by Slack's chat.delete method (as of October 20, 2025). It's an optional boolean that, when true, deletes the message as the authed user. The only issue is the description text incorrectly says "update" instead of "delete".Apply this fix:
as_user: { propDefinition: [ slack, "as_user", ], - description: "Pass true to update the message as the authed user. Bot users in this context are considered authed users.", + description: "Pass true to delete the message as the authed user. Bot users in this context are considered authed users.", },Also applies to: 37-41
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1)
33-41: No actionable issues found. The code is correct.The
conversationsprop contains an array of channel IDs (confirmed by the prop definition type"string[]"and description "Select one or more channels to monitor for new messages"). TheeventNames()method correctly returns either:
- The selected channel IDs when channels are chosen, or
["message"]as the fallback event type when no channels are selectedBoth are valid values per the Slack apphook contract. This pattern is consistently applied across related sources (new-message-in-channels, new-keyword-mention).
components/slack_v2/actions/archive-channel/archive-channel.mjs (1)
21-27: Review comment is incorrect — ignore this suggestion.Slack's
conversations.archiveAPI does support archiving IM and MPIM conversations when those conversation types are supported and the token has corresponding scopes (im:write and mpim:write). The current code allowing MPIM is correct and will not error at runtime with appropriate token permissions. Do not apply the suggested diff.Likely an incorrect or invalid review comment.
components/slack_v2/sources/common/base.mjs (1)
1-4: All required Slack app client methods are present.Verification confirms that
usersInfo,conversationsInfo,getConversationReplies,getBotInfo, andgetTeamInfoall exist in the slack_v2.app.mjs wrapper. No integration breakage risk from missing methods.components/slack_v2/actions/common/build-blocks.mjs (1)
99-102: Path verification successful; brittleness concern remains valid.The path
common.props.slack.propDefinitions.blocksexists and exposes the three accessed properties:
type→"string"label→"Blocks"description→ populated stringHowever, the underlying concern about brittleness stands: deep nested property access is inherently fragile. If the structure of
slack_v2.app.mjsorsend-message.mjschanges during refactoring, these references could break without warning. Consider extracting this to a named constant or utility function to isolate the dependency path.
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs
Outdated
Show resolved
Hide resolved
| const buttons = Object.keys(text).map((buttonText) => ({ | ||
| type: "button", | ||
| text: { | ||
| type: "plain_text", | ||
| text: buttonText, | ||
| emoji: true, | ||
| }, | ||
| url: text[buttonText], // Access the URL using buttonText as the key | ||
| action_id: `actionId-${Math.random().toString(36) | ||
| .substr(2, 9)}`, // Generates a random action_id | ||
| })); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate link_button input shape and guard keys.
Object.keys(text) will throw if text isn’t a plain object; also clarify error early.
- } else if (type === "link_button") {
- const buttons = Object.keys(text).map((buttonText) => ({
+ } else if (type === "link_button") {
+ if (!text || typeof text !== "object" || Array.isArray(text)) {
+ throw new Error("Link Button expects an object mapping button text → URL");
+ }
+ const buttons = Object.keys(text).map((buttonText) => ({
type: "button",
@@
- action_id: `actionId-${Math.random().toString(36)
- .substr(2, 9)}`, // Generates a random action_id
+ action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`, // Generates a random action_id
}));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const buttons = Object.keys(text).map((buttonText) => ({ | |
| type: "button", | |
| text: { | |
| type: "plain_text", | |
| text: buttonText, | |
| emoji: true, | |
| }, | |
| url: text[buttonText], // Access the URL using buttonText as the key | |
| action_id: `actionId-${Math.random().toString(36) | |
| .substr(2, 9)}`, // Generates a random action_id | |
| })); | |
| if (!text || typeof text !== "object" || Array.isArray(text)) { | |
| throw new Error("Link Button expects an object mapping button text → URL"); | |
| } | |
| const buttons = Object.keys(text).map((buttonText) => ({ | |
| type: "button", | |
| text: { | |
| type: "plain_text", | |
| text: buttonText, | |
| emoji: true, | |
| }, | |
| url: text[buttonText], // Access the URL using buttonText as the key | |
| action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`, // Generates a random action_id | |
| })); |
🤖 Prompt for AI Agents
In components/slack_v2/actions/common/build-blocks.mjs around lines 65 to 76,
the code uses Object.keys(text) without validating that text is a non-null plain
object which will throw for non-objects; update to first guard the input (if
typeof text !== "object" || text === null || Array.isArray(text) then throw a
clear TypeError or return an empty array), then iterate using
Object.entries(text) and filter to only own string key/value pairs (e.g. filter
([k,v]) => typeof k === "string" && typeof v === "string") before mapping to
buttons, and keep generating action_id safely (use slice instead of substr if
desired).
| async run({ $ }) { | ||
| const response = await this.slack.addReminders({ | ||
| text: this.text, | ||
| team_id: this.team_id, | ||
| time: this.timestamp, | ||
| user: this.user, | ||
| }); | ||
| $.export("$summary", `Successfully created reminder with ID ${response.reminder.id}`); | ||
| return response; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add defensive null check before accessing nested property.
The summary export directly accesses response.reminder.id without verifying that response or response.reminder exists. If the Slack API returns an unexpected structure or fails gracefully, this will throw a TypeError.
Consider adding a check or optional chaining:
- $.export("$summary", `Successfully created reminder with ID ${response.reminder.id}`);
+ $.export("$summary", `Successfully created reminder${response?.reminder?.id ? ` with ID ${response.reminder.id}` : ""}`);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async run({ $ }) { | |
| const response = await this.slack.addReminders({ | |
| text: this.text, | |
| team_id: this.team_id, | |
| time: this.timestamp, | |
| user: this.user, | |
| }); | |
| $.export("$summary", `Successfully created reminder with ID ${response.reminder.id}`); | |
| return response; | |
| }, | |
| async run({ $ }) { | |
| const response = await this.slack.addReminders({ | |
| text: this.text, | |
| team_id: this.team_id, | |
| time: this.timestamp, | |
| user: this.user, | |
| }); | |
| $.export("$summary", `Successfully created reminder${response?.reminder?.id ? ` with ID ${response.reminder.id}` : ""}`); | |
| return response; | |
| }, |
🤖 Prompt for AI Agents
In components/slack_v2/actions/create-reminder/create-reminder.mjs around lines
42 to 51, the code directly accesses response.reminder.id which can throw if
response or response.reminder is null/undefined; add a defensive null check or
use optional chaining to safely read the id (e.g., const id =
response?.reminder?.id) and export a safe summary using a fallback message when
id is missing, ensuring the function still returns the original response.
| const response = await this.slack.deleteFiles({ | ||
| file: this.file, | ||
| }); | ||
| $.export("$summary", `Successfully deleted file with ID ${this.file}`); | ||
| return response; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Add response.ok guard and confirm API method name
Check the Slack API response and fail fast on errors. Also confirm deleteFiles matches the v2 wrapper.
Apply:
const response = await this.slack.deleteFiles({
file: this.file,
});
- $.export("$summary", `Successfully deleted file with ID ${this.file}`);
- return response;
+ if (!response?.ok) {
+ throw new Error(`files.delete failed for ${this.file}: ${JSON.stringify(response)}`);
+ }
+ $.export("$summary", `Successfully deleted file with ID ${this.file}`);
+ return response;Verify method presence:
🏁 Script executed:
#!/bin/bash
fd -a 'slack_v2.app.mjs' components | xargs -I{} rg -n 'deleteFiles|files\.delete' {}Length of output: 70
🏁 Script executed:
#!/bin/bash
# Check how other methods/actions in the codebase handle response.ok or error handling
rg -n 'response\.ok|throw new Error' components/slack_v2/actions --type=mjs -C 2 | head -100Length of output: 27
🏁 Script executed:
#!/bin/bash
# Look at the complete delete-file.mjs file to understand current structure
cat components/slack_v2/actions/delete-file/delete-file.mjsLength of output: 837
🏁 Script executed:
#!/bin/bash
# Check the slack_v2 wrapper to see how the deleteFiles method implementation handles responses
rg -A 10 'deleteFiles\(args' components/slack_v2/slack_v2.app.mjsLength of output: 247
🏁 Script executed:
#!/bin/bash
# Find makeRequest implementation
rg -A 15 'makeRequest\(' components/slack_v2/slack_v2.app.mjs | head -50Length of output: 1497
🏁 Script executed:
#!/bin/bash
# Check how other destructive actions (create, delete, update) handle responses
find components/slack_v2/actions -name "*.mjs" | xargs grep -l "response\.ok\|throw new Error" | head -10Length of output: 313
🏁 Script executed:
#!/bin/bash
# Look at a similar destructive action to see pattern
find components/slack_v2/actions -name "*delete*" -o -name "*remove*" | head -5 | xargs catLength of output: 2021
🏁 Script executed:
#!/bin/bash
# Check response.ok pattern in upload-file action (similar destructive operation)
cat components/slack_v2/actions/upload-file/upload-file.mjsLength of output: 2575
🏁 Script executed:
#!/bin/bash
# Check response.ok pattern in send-message action
cat components/slack_v2/actions/common/send-message.mjsLength of output: 6738
Add response.ok guard and verify API method exists
The deleteFiles method correctly maps to Slack's files.delete API and is properly defined in the v2 wrapper. However, the action should verify the API response before exporting the summary. This aligns with error-handling patterns used in similar destructive operations like upload-file.mjs.
Apply:
const response = await this.slack.deleteFiles({
file: this.file,
});
- $.export("$summary", `Successfully deleted file with ID ${this.file}`);
- return response;
+ if (!response?.ok) {
+ throw new Error(`files.delete failed for ${this.file}: ${JSON.stringify(response)}`);
+ }
+ $.export("$summary", `Successfully deleted file with ID ${this.file}`);
+ return response;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const response = await this.slack.deleteFiles({ | |
| file: this.file, | |
| }); | |
| $.export("$summary", `Successfully deleted file with ID ${this.file}`); | |
| return response; | |
| }, | |
| const response = await this.slack.deleteFiles({ | |
| file: this.file, | |
| }); | |
| if (!response?.ok) { | |
| throw new Error(`files.delete failed for ${this.file}: ${JSON.stringify(response)}`); | |
| } | |
| $.export("$summary", `Successfully deleted file with ID ${this.file}`); | |
| return response; | |
| }, |
🤖 Prompt for AI Agents
In components/slack_v2/actions/delete-file/delete-file.mjs around lines 33 to
38, the action currently calls this.slack.deleteFiles and unconditionally
exports a success summary; add a guard to (1) verify the API method exists
(throw a clear error if typeof this.slack.deleteFiles !== 'function') before
calling, (2) after awaiting the call check response.ok and if false throw an
Error including response.error or the full response for debugging, and (3) only
export the success summary and return the response when response.ok is true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.slack.deleteFiles throws an error if the API response is not ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@js07, thank you for the clarification! I understand that the this.slack.deleteFiles method (and other wrapper methods in the slack_v2 app) already handles error responses internally by throwing an error when the API response is not ok. The suggested guard would indeed be redundant in this case.
✏️ Learnings added
Learnt from: js07
PR: PipedreamHQ/pipedream#18744
File: components/slack_v2/actions/delete-file/delete-file.mjs:33-38
Timestamp: 2025-10-20T00:50:44.233Z
Learning: In the slack_v2 component (components/slack_v2/), wrapper methods like deleteFiles, postChatMessage, etc. already throw errors internally when the Slack API response is not `ok`, so explicit `response.ok` checks are unnecessary in action files.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs
Outdated
Show resolved
Hide resolved
| const [ | ||
| { | ||
| elements: [ | ||
| { elements = [] } = {}, | ||
| ] = [], | ||
| } = {}, | ||
| ] = blocks; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Emit logic requires user AND keyword; should be OR. Also fragile block parsing and possible TypeError on undefined text.
Current code:
- Scans only the first block/section; misses mentions in later blocks.
- Requires
userMatch && (!this.keyword || text.indexOf(this.keyword) !== -1)which contradicts “username or specific keyword” and can throw iftextis undefined.
Fix: parse all blocks, add <@USER> text fallback, make keyword match safe and case-insensitive, and emit on (userMatch OR keywordMatch) OR PD history.
Apply:
@@
- const [
- {
- elements: [
- { elements = [] } = {},
- ] = [],
- } = {},
- ] = blocks;
+ // Collect inner elements from all blocks / sections
+ const blockElements = [];
+ for (const b of (blocks || [])) {
+ for (const el of (b.elements || [])) {
+ if (el && Array.isArray(el.elements)) {
+ blockElements.push(...el.elements);
+ }
+ }
+ }
@@
- let emitEvent = false;
- if (elements) {
- let userMatch = false;
- for (const item of elements) {
- if (item.user_id && item.user_id === this.user) {
- userMatch = true;
- break;
- }
- }
- if (userMatch && (!this.keyword || text.indexOf(this.keyword) !== -1)) {
- emitEvent = true;
- }
- }
- if (subtype === constants.SUBTYPE.PD_HISTORY_MESSAGE) {
- emitEvent = true;
- }
+ const t = typeof text === "string" ? text : "";
+ // Detect user mention via blocks and fallback to text markup
+ let userMatch = blockElements.some((item) => item && item.user_id === this.user);
+ if (!userMatch && this.user && t.includes(`<@${this.user}>`)) {
+ userMatch = true;
+ }
+ // Keyword match (case-insensitive), guarded against undefined text
+ const kw = this.keyword?.trim();
+ const keywordMatch = kw ? t.toLowerCase().includes(kw.toLowerCase()) : false;
+ // Emit if user OR keyword matches, or always for PD history
+ const emitEvent = userMatch || keywordMatch || (subtype === constants.SUBTYPE.PD_HISTORY_MESSAGE);
@@
- if (emitEvent) {
+ if (emitEvent) {
return event;
}Also applies to: 101-116
🤖 Prompt for AI Agents
In components/slack_v2/sources/new-user-mention/new-user-mention.mjs around
lines 75-82 (also apply same changes to 101-116), the current destructuring only
inspects the first block, can throw if text is undefined, and uses an AND when
it should be an OR for user vs keyword matches; change to iterate over all
blocks and their elements, safely extract text (fallback to the raw "<@USER>"
mention string if element text is missing), normalize text to lowercase before
matching to make keyword checks case-insensitive, compute userMatch =
Boolean(text.match(/<@(\w+)>/)) and keywordMatch = this.keyword &&
text.toLowerCase().includes(this.keyword.toLowerCase()), then emit when
userMatch OR keywordMatch is true (also include PD history emit as before), and
guard all accesses to avoid TypeErrors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (7)
components/slack_v2/sources/new-interaction-event-received/README.md (1)
11-11: Remove redundant "back" from "reply back".This issue was previously flagged and remains unresolved. Simplify to just "reply".
-With this trigger, you can build workflows that perform some work with other APIs or services, and then reply back to the original message. +With this trigger, you can build workflows that perform some work with other APIs or services, and then reply to the original message.components/slack_v2/actions/common/build-blocks.mjs (3)
65-76: Validatelink_buttoninput shape and guard against invalid types.The code calls
Object.keys(text)without validating thattextis a plain object, which will throw a TypeError for non-objects. Additionally,substr()is deprecated in favor ofslice().Apply this diff:
} else if (type === "link_button") { + if (!text || typeof text !== "object" || Array.isArray(text)) { + throw new Error("Link Button expects an object mapping button text → URL"); + } const buttons = Object.keys(text).map((buttonText) => ({ type: "button", text: { type: "plain_text", text: buttonText, emoji: true, }, url: text[buttonText], // Access the URL using buttonText as the key - action_id: `actionId-${Math.random().toString(36) - .substr(2, 9)}`, // Generates a random action_id + action_id: `actionId-${Math.random().toString(36).slice(2, 11)}`, // Generates a random action_id }));
97-103: Use strict equality for mode checks.Loose equality (
==) can cause unintended type coercion. Switch to===.- if (this.passArrayOrConfigure == "array") { + if (this.passArrayOrConfigure === "array") { props.blocks = {
160-164: Use strict equality in run method.- if (this.passArrayOrConfigure === "array") { + if (this.passArrayOrConfigure === "array") { blocks = this.blocks;components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs (2)
20-22: Update prop description to match source intent.The description incorrectly refers to "new messages" but should mention "new reactions".
- description: "Select one or more channels to monitor for new messages.", + description: "Select one or more channels to monitor for new reactions.",
81-86: Bot filter logic ineffective for reaction events; also use strict equality.Reaction events lack
subtypeandbot_idfields, so the bot filter will never match. Additionally, use===instead of==for type-safe comparisons. The bot check should run after fetching message context or by checking the reactor's identity.if ( - ((this.ignoreBot) && (event.subtype == "bot_message" || event.bot_id)) || - (iconEmojiParsed?.length > 0 && !iconEmojiParsed.includes(event.reaction)) + (iconEmojiParsed?.length > 0 && !iconEmojiParsed.includes(event.reaction)) ) { return; }Then after fetching the message (line 106-113), add:
+ // Filter bots after we have context + if (this.ignoreBot) { + // Check if message author is a bot + if (event.message?.bot_id || event.message?.subtype === "bot_message") { + return; + } + }components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (1)
9-9: Fix typo in description."setttings" should be "settings".
- description: "Customize advanced settings and send a message to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here", + description: "Customize advanced settings and send a message to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs.",
🧹 Nitpick comments (7)
components/slack_v2/sources/new-interaction-event-received/README.md (1)
77-85: Reduce repetitive sentence structure in troubleshooting section.All three steps in the troubleshooting list begin with "Make sure", which violates good writing practice. Vary the sentence openings for better readability.
-1. Make sure that your `action_id` or **channels** filters apply to that message, remove the filters to make sure that's not the case. +1. Verify that your `action_id` or **channels** filters apply to that message; remove the filters if you suspect they are blocking events. -1. Make sure that the message comes from the same Slack account that this source is configured with. +2. Confirm that the message originates from the same Slack account that this source is configured with. -1. Make sure that the message was sent via Pipedream action (e.g. [Slack - Send Block Kit Message](https://pipedream.com/apps/slack-v2/actions/send-block-kit-message) Action) or via API call from the Pipedream app. +3. Ensure that the message was sent via a Pipedream action (e.g. [Slack - Send Block Kit Message](https://pipedream.com/apps/slack-v2/actions/send-block-kit-message)) or via API call from the Pipedream app.components/slack/actions/update-profile/update-profile.mjs (2)
69-78: Validation logic is correct; consider refactoring for maintainability.The email field is correctly added to the "at least one value" validation check. With seven fields now, you might consider refactoring to an array-based check for better maintainability:
- if (!this.displayName - && !this.firstName - && !this.lastName - && !this.phone - && !this.pronouns - && !this.title - && !this.email - ) { + const fields = [ + this.displayName, + this.firstName, + this.lastName, + this.phone, + this.pronouns, + this.title, + this.email, + ]; + if (!fields.some(Boolean)) { throw new ConfigurationError("Please provide at least one value to update"); }
7-7: Consider updating the description to mention email.The description lists "name or title" as examples but doesn't mention email, which is now supported.
- description: "Update basic profile field such as name or title. [See the documentation](https://api.slack.com/methods/users.profile.set)", + description: "Update basic profile fields such as name, title, or email. [See the documentation](https://api.slack.com/methods/users.profile.set)",components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
37-43: Prefer error.message over string coercion for error checks.While
${error}works, usingerror.messageis more explicit and won't inadvertently match the error type in a stack trace.Apply this diff:
} catch (error) { - if (`${error}`.includes("already_in_channel")) { + if (error.message?.includes("already_in_channel")) { $.export("$summary", `The user ${this.user} is already in the channel`); return; } throw error; }components/slack_v2/actions/get-current-user/get-current-user.mjs (1)
26-33: Log errors in catch blocks for debugging.The empty catch block silences all errors, including network failures and bugs, not just scope issues. At minimum, log the error to aid debugging.
Apply this diff:
} catch (error) { - // Gracefully degrade if scope not available + // Gracefully degrade if scope not available + console.log("Unable to fetch user info:", error.message); }Apply similar changes to lines 36-42 and 45-49.
components/slack_v2/slack_v2.app.mjs (2)
486-486: Clarify conditional logic with ternary operator.The expression
isPrivate && "Private" || "Public"relies on operator precedence and short-circuit evaluation in a non-obvious way. Use a ternary for clarity.- return `${isPrivate && "Private" || "Public"} channel #${name}`; + return `${isPrivate ? "Private" : "Public"} channel #${name}`;
502-527: Log errors in getChannelDisplayName for debugging.The catch blocks silently return fallback values without logging errors, making it difficult to diagnose API failures versus permission issues.
Apply this diff:
} catch { + console.log("Unable to fetch user profile:", error.message); return "user"; } @@ } catch { + console.log("Unable to fetch group members:", error.message); return `Group Messaging with: ${channel.purpose.value}`; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
components/slack/actions/update-profile/update-profile.mjs(2 hunks)components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs(1 hunks)components/slack_v2/actions/common/build-blocks.mjs(1 hunks)components/slack_v2/actions/find-message/find-message.mjs(1 hunks)components/slack_v2/actions/get-current-user/get-current-user.mjs(1 hunks)components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs(1 hunks)components/slack_v2/actions/kick-user/kick-user.mjs(1 hunks)components/slack_v2/actions/list-users/list-users.mjs(1 hunks)components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs(1 hunks)components/slack_v2/actions/update-group-members/update-group-members.mjs(1 hunks)components/slack_v2/actions/upload-file/upload-file.mjs(1 hunks)components/slack_v2/slack_v2.app.mjs(1 hunks)components/slack_v2/sources/new-interaction-event-received/README.md(1 hunks)components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- components/slack_v2/actions/kick-user/kick-user.mjs
- components/slack_v2/actions/find-message/find-message.mjs
- components/slack_v2/actions/upload-file/upload-file.mjs
- components/slack_v2/actions/update-group-members/update-group-members.mjs
- components/slack_v2/actions/list-users/list-users.mjs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-20T00:28:38.702Z
Learnt from: js07
PR: PipedreamHQ/pipedream#18744
File: components/slack/slack.app.mjs:0-0
Timestamp: 2025-10-20T00:28:38.702Z
Learning: In components/slack/slack.app.mjs, the assistantSearch method uses sdk().apiCall() directly instead of makeRequest because the slack/web-api npm package doesn't expose assistant.search.context as a method on WebClient. This direct usage is intentional and necessary when Slack API methods are not yet exposed in the SDK.
Applied to files:
components/slack_v2/slack_v2.app.mjs
🧬 Code graph analysis (4)
components/slack_v2/actions/send-message-advanced/send-message-advanced.mjs (2)
components/slack_v2/actions/send-block-kit-message/send-block-kit-message.mjs (1)
resp(43-45)components/slack_v2/actions/common/send-message.mjs (2)
resp(254-254)resp(255-257)
components/slack_v2/slack_v2.app.mjs (5)
components/slack_v2/actions/common/send-message.mjs (3)
channelId(190-190)resp(254-254)resp(255-257)components/slack_v2/actions/list-group-members/list-group-members.mjs (3)
members(44-44)page(50-50)nextCursor(53-55)components/slack_v2/actions/list-members-in-channel/list-members-in-channel.mjs (3)
usernames(59-59)page(47-47)nextCursor(50-52)components/slack_v2/actions/delete-message/delete-message.mjs (1)
response(37-41)components/slack_v2/actions/get-file/get-file.mjs (1)
response(53-55)
components/slack_v2/actions/invite-user-to-channel/invite-user-to-channel.mjs (1)
components/slack_v2/actions/kick-user/kick-user.mjs (1)
response(42-45)
components/slack_v2/actions/common/build-blocks.mjs (2)
components/slack_v2/sources/new-user-mention/new-user-mention.mjs (1)
blocks(75-81)components/slack_v2/actions/common/send-message.mjs (1)
blocks(198-198)
🪛 LanguageTool
components/slack_v2/sources/new-interaction-event-received/README.md
[style] ~11-~11: Consider using just “reply”.
Context: ...k with other APIs or services, and then reply back to the original message. # Getting Sta...
(RETURN_BACK)
[style] ~85-~85: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hat this source is configured with. 1. Make sure that the message was sent via Pipe...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Verify TypeScript components
- GitHub Check: Publish TypeScript components
- GitHub Check: pnpm publish
- GitHub Check: Lint Code Base
🔇 Additional comments (6)
components/slack/actions/update-profile/update-profile.mjs (2)
53-58: LGTM! Clear documentation of email field constraints.The email prop is correctly defined with clear documentation about the admin-only restriction and self-update limitation. The API will enforce these constraints server-side.
79-90: LGTM! Email correctly added to profile payload.The email field is properly included in the profile payload sent to Slack's API, consistent with the pattern used for other profile fields.
components/slack_v2/actions/add-emoji-reaction/add-emoji-reaction.mjs (1)
1-47: LGTM!The import path is correct (
slack_v2.app.mjs), and the action properly integrates with the Slack v2 app. Props are well-defined, and the run method correctly invokesaddReactionswith appropriate parameters.components/slack_v2/sources/new-reaction-added/new-reaction-added.mjs (1)
88-104: LGTM!The user data fetching correctly distinguishes between the reactor (
event.user) and the message author (event.item_user), and assigns them to the appropriate properties.components/slack_v2/slack_v2.app.mjs (2)
578-599: LGTM!The retry logic properly uses
async-retrywith appropriate handling for rate limits, including thebailpattern to abort on unrecoverable errors.Based on learnings
898-905: LGTM!The direct use of
sdk().apiCall()is intentional and necessary because theassistant.search.contextmethod is not yet exposed in the Slack WebClient SDK.Based on learnings
| First, if you haven’t already - send yourself a message containing one or more interactive elements. Use the ******************Sending the message with an interactive element****************** guide below to send a message containing a button. | ||
|
|
||
| If you have already sent a message containing an element, skip to **********************************************Configuring the source.********************************************** | ||
|
|
||
| ## Sending the message with an interactive element | ||
|
|
||
| The easiest way is to send yourself a message using the ****************************Slack - Send Message Using Block Kit**************************** action: | ||
|
|
||
|  | ||
|
|
||
| Then select a **************Channel************** you’d like to send the message to, and use the **************[Block Kit Builder](https://app.slack.com/block-kit-builder/)************** to build a message, or just copy the example button blocks below: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix malformed markdown formatting with excessive asterisks.
Multiple lines contain corrupted markdown with excessive asterisks (30–46 asterisks on each side) instead of standard bold formatting (**text**). This breaks the documentation rendering. These lines should either use proper bold syntax or plain text.
-Use the ******************Sending the message with an interactive element****************** guide below to send a message containing a button.
+Use the **Sending the message with an interactive element** guide below to send a message containing a button.
-If you have already sent a message containing an element, skip to **********************************************Configuring the source.**********************************************
+If you have already sent a message containing an element, skip to the **Configuring the source** section below.
-The easiest way is to send yourself a message using the ****************************Slack - Send Message Using Block Kit**************************** action:
+The easiest way is to send yourself a message using the **Slack - Send Message Using Block Kit** action:
-Then select a **************Channel************** you'd like to send the message to, and use the **************[Block Kit Builder](https://app.slack.com/block-kit-builder/)************** to build a message, or just copy the example button blocks below:
+Then select a **Channel** you'd like to send the message to, and use the **[Block Kit Builder](https://app.slack.com/block-kit-builder/)** to build a message, or just copy the example button blocks below:
-Your ******************Slack - Send Message Using Block Kit****************** should look like this:
+Your **Slack - Send Message Using Block Kit** should look like this:
-You can filter these events by selecting a specific **************channel************** and/or a specific **********action_id.**********
+You can filter these events by selecting a specific **channel** and/or a specific **action_id**.
-Use the ****************Channels**************** dropdown to search for a specific channel for this source to subscribe to. ********Only******** button clicks, dropdown selects, etc. *in this selected channel* will trigger the source.
+Use the **Channels** dropdown to search for a specific channel for this source to subscribe to. Only button clicks, dropdown selects, etc. *in this selected channel* will trigger the source.
-1. Make sure that your `action_id` or ****************channels**************** filters apply to that message, remove the filters to make sure that's not the case.
+1. Make sure that your `action_id` or **channels** filters apply to that message, remove the filters to make sure that's not the case.Also applies to: 51-51, 59-59, 63-63, 81-81
🤖 Prompt for AI Agents
In components/slack_v2/sources/new-interaction-event-received/README.md around
lines 19 to 29 (and also at lines 51, 59, 63, 81), several markdown fragments
use excessive asterisks (30–46) instead of normal bold or plain text; replace
these corrupted sequences with proper markdown (e.g., **bold text**) or plain
text as appropriate, ensuring links and headings keep correct formatting and
removing any stray asterisks so the documentation renders correctly.
instead update slack v2 update profile action
Send Message actions can post to public channels as the bot without joining the channel The bot needs to be added to private channels before posting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
components/slack_v2/actions/common/send-message.mjs (2)
252-255: Add validation for scheduled message timestamp.The
post_atdate parsing (line 253) could fail silently or produce invalid timestamps ifthis.post_atis not a valid date string. Consider adding validation.Apply this diff to add validation:
if (this.post_at) { + const timestamp = Math.floor(new Date(this.post_at).getTime() / 1000); + if (isNaN(timestamp)) { + throw new Error(`Invalid date format for post_at: ${this.post_at}`); + } - obj.post_at = Math.floor(new Date(this.post_at).getTime() / 1000); + obj.post_at = timestamp; return await this.slack.scheduleMessage(obj); }
256-262: Add error handling for API response access.Lines 257-260 assume
resp.channelexists and thatconversationsInfosucceeds. If the API response is unexpected or the call fails, accessing these properties could throw.Consider adding defensive checks:
const resp = await this.slack.postChatMessage(obj); - const { channel } = await this.slack.conversationsInfo({ - channel: resp.channel, - }); - const channelName = await this.slack.getChannelDisplayName(channel); - $.export("$summary", `Successfully sent a message to ${channelName}`); + if (resp?.channel) { + const { channel } = await this.slack.conversationsInfo({ + channel: resp.channel, + }); + const channelName = await this.slack.getChannelDisplayName(channel); + $.export("$summary", `Successfully sent a message to ${channelName}`); + } else { + $.export("$summary", "Successfully sent a message"); + } return resp;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
components/slack_v2/actions/common/send-message.mjs(1 hunks)components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- components/slack_v2/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs
🧰 Additional context used
🧬 Code graph analysis (1)
components/slack_v2/actions/common/send-message.mjs (2)
components/slack_v2/slack_v2.app.mjs (2)
channel(505-507)channel(514-516)components/slack_v2/actions/send-large-message/send-large-message.mjs (4)
sentViaPipedreamText(45-45)metadataEventPayload(49-49)obj(66-79)channelName(91-91)
🔇 Additional comments (4)
components/slack_v2/actions/common/send-message.mjs (4)
169-186: Good defensive coding for text serialization.The type checking and JSON serialization for non-primitive text values (lines 174-176) is a good practice that prevents runtime errors when the Slack SDK expects a string.
217-231: Fix metadata payload assignment logic.When
metadata_event_payloadis already an object (not a string), it's never assigned tometadataEventPayload(declared on line 215), which means line 229 will useundefinedfor theevent_payload.Apply this diff to handle both string and object inputs:
let metadataEventPayload; if (this.metadata_event_type) { if (typeof this.metadata_event_payload === "string") { try { metadataEventPayload = JSON.parse(this.metadata_event_payload); } catch (error) { throw new Error(`Invalid JSON in metadata_event_payload: ${error.message}`); } + } else { + metadataEventPayload = this.metadata_event_payload; } this.metadata = { event_type: this.metadata_event_type, event_payload: metadataEventPayload, }; }⛔ Skipped due to learnings
Learnt from: js07 PR: PipedreamHQ/pipedream#18744 File: components/slack_v2/actions/send-large-message/send-large-message.mjs:49-64 Timestamp: 2025-10-20T01:01:02.946Z Learning: In components/slack_v2/actions/send-large-message/send-large-message.mjs, the metadata_event_payload prop is typed as string, so the code only needs to handle string-to-JSON parsing and does not need to handle object inputs.
233-250: Payload construction uses undefined properties.The object construction references several properties not defined in props:
text(line 234),attachments(line 236),parse(line 239),mrkdwn(line 244),link_names(line 246), andmetadata(line 249, though this one is assigned earlier in the method on line 227).These undefined values will be sent to the Slack API, potentially causing API errors or unexpected behavior.
⛔ Skipped due to learnings
Learnt from: js07 PR: PipedreamHQ/pipedream#18744 File: components/slack_v2/actions/send-large-message/send-large-message.mjs:49-64 Timestamp: 2025-10-20T01:01:02.946Z Learning: In components/slack_v2/actions/send-large-message/send-large-message.mjs, the metadata_event_payload prop is typed as string, so the code only needs to handle string-to-JSON parsing and does not need to handle object inputs.
3-126: The review comment is based on a misunderstanding of the architecture and is incorrect.The
common/send-message.mjsfile is an intentional base module designed to be extended by other actions. The properties flagged as missing (conversation,text,blocks,mrkdwn,attachments,parse,link_names,metadata) are intentionally not defined in the base module and are instead defined by each extending action.Every action that extends this module (send-message, send-large-message, send-message-advanced, send-message-to-user-or-group, send-message-to-channel, send-block-kit-message, reply-to-a-message, and build-blocks) properly defines these properties via
propDefinitionfrom the slack component. Each action uses the pattern:import common from "../common/send-message.mjs"; export default { ...common, props: { slack: common.props.slack, conversation: { propDefinition: [common.props.slack, "conversation"] }, text: { propDefinition: [common.props.slack, "text"] }, mrkdwn: { propDefinition: [common.props.slack, "mrkdwn"] }, ...common.props, } }This allows each action to customize these properties independently (different descriptions, filters, optional/required status). The base module is not meant to be used standalone—it provides shared business logic that all these actions inherit and extend.
Likely an incorrect or invalid review comment.
WHY
Summary by CodeRabbit