feat: add Composio Reddit component#8595
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughThis change removes Outlook and generic API components from the Composio backend integration and introduces a new Reddit API component. Corresponding frontend updates add Reddit icon support, remove Outlook and other unused icons, and update sidebar and bundle configurations to reflect the new Reddit integration. Comprehensive unit tests for the Reddit component are also included. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ComposioRedditAPIComponent
participant ComposioToolset
participant RedditAPI
User->>ComposioRedditAPIComponent: Selects Reddit action and provides input
ComposioRedditAPIComponent->>ComposioToolset: Maps action, prepares parameters, calls execute_action
ComposioToolset->>RedditAPI: Executes mapped Reddit API action
RedditAPI-->>ComposioToolset: Returns API response
ComposioToolset-->>ComposioRedditAPIComponent: Returns result or error
ComposioRedditAPIComponent-->>User: Returns normalized result or error message
Possibly related PRs
Suggested labels
✨ Finishing Touches🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (6)
src/frontend/src/icons/reddit/reddit.jsx (1)
2-2: Make icon size configurable instead of hard-coding 23 pxHard-coding
width="23px"/height="23px"limits reusability and breaks when an icon needs to scale with font size (e.g., inside buttons). Default to1emand allow callers to override via props.- <svg viewBox="0 0 16 16" width="23px" height="23px" {...props}> + <svg + viewBox="0 0 16 16" + width={props.width ?? "1em"} + height={props.height ?? "1em"} + {...props} + >src/frontend/src/icons/reddit/index.tsx (1)
4-9: Expose adisplayNameto improve DevTools readabilityAdding
displayNamekeeps this wrapper consistent with many existing icons and makes React-DevTools inspection easier.return <RedditIconSVG ref={ref} {...props} />; }); + +RedditIcon.displayName = "RedditIcon";src/frontend/src/icons/lazyIconImports.ts (1)
1-1: Remove unused eager import ofTwelveLabsIconThe symbol isn’t referenced anywhere in this file; the lazy mapping already covers
TwelveLabs. Keeping the eager import bloats the bundle.-import { TwelveLabsIcon } from "./TwelveLabs";src/backend/tests/unit/components/bundles/composio/test_reddit.py (1)
63-71: Reduce brittleness by avoiding direct_actions_dataoverridesManually recreating
_actions_dataduplicates component internals: a future change in field names will break the test even though functionality may still be correct. Prefer to:
- Use the component’s own
_actions_data(already built in__init__), or- Patch only the specific keys you need to control.
This keeps tests focused on behaviour rather than implementation details.
src/frontend/src/utils/styleUtils.ts (1)
8-9: Remove or wire up theRedditIconimport
RedditIconis imported but never referenced.
Either:
- Drop the import, or
- Add it to
eagerLoadedIconsMap(and/orlazyIconImports.ts) and map"Reddit"→RedditIcon.Keeping an unused import just bloats the bundle and may trigger lint warnings.
src/backend/base/langflow/components/composio/reddit_composio.py (1)
105-108:_bool_variablesis dead codeThe set is never referenced after declaration.
Remove it or integrate it into the parameter-building logic to avoid confusion.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/frontend/src/icons/reddit/reddit.svgis excluded by!**/*.svg
📒 Files selected for processing (7)
src/backend/base/langflow/components/composio/__init__.py(1 hunks)src/backend/base/langflow/components/composio/reddit_composio.py(1 hunks)src/backend/tests/unit/components/bundles/composio/test_reddit.py(1 hunks)src/frontend/src/icons/lazyIconImports.ts(2 hunks)src/frontend/src/icons/reddit/index.tsx(1 hunks)src/frontend/src/icons/reddit/reddit.jsx(1 hunks)src/frontend/src/utils/styleUtils.ts(6 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/frontend/src/icons/reddit/index.tsx (1)
Learnt from: dolfim-ibm
PR: langflow-ai/langflow#8394
File: src/frontend/src/icons/Docling/index.tsx:4-6
Timestamp: 2025-06-16T11:14:04.188Z
Learning: The Langflow codebase consistently uses `React.PropsWithChildren<{}>` as the prop type for all icon components using forwardRef, rather than `React.SVGProps<SVGSVGElement>`. This is an established pattern across hundreds of icon files in src/frontend/src/icons/.
🪛 Biome (1.9.4)
src/frontend/src/icons/reddit/index.tsx
[error] 6-6: Don't use '{}' as a type.
Prefer explicitly define the object shape. '{}' means "any non-nullable value".
(lint/complexity/noBannedTypes)
🪛 Pylint (3.3.7)
src/backend/base/langflow/components/composio/__init__.py
[error] 8-8: Undefined variable name 'ComposioAPIComponent' in all
(E0603)
src/backend/tests/unit/components/bundles/composio/test_reddit.py
[error] 5-5: No name 'components' in module 'langflow'
(E0611)
[error] 6-6: No name 'schema' in module 'langflow'
(E0611)
[refactor] 13-13: Too few public methods (0/2)
(R0903)
src/backend/base/langflow/components/composio/reddit_composio.py
[refactor] 286-286: Too many local variables (25/15)
(R0914)
[refactor] 286-286: Too many return statements (8/6)
(R0911)
[refactor] 286-286: Too many branches (19/12)
(R0912)
[refactor] 286-286: Too many statements (71/50)
(R0915)
🔇 Additional comments (3)
src/frontend/src/icons/lazyIconImports.ts (1)
59-60: Good addition of Reddit icon mappingMapping looks correct and follows existing pattern.
src/frontend/src/utils/styleUtils.ts (1)
233-236: Verify that theBoth
SIDEBAR_BUNDLESandnodeIconToDisplayIconMappoint to the string"Reddit".
Unless"Reddit"is present inlazyIconImports.tsoreagerLoadedIconsMap,getNodeIconwill fall through to an empty component and the icon will not render.Double-check that
lazyIconsMappingincludes an entry like:export const lazyIconsMapping = { // … Reddit: () => import("@/icons/reddit").then(m => m.RedditIcon), };Otherwise users will see a blank icon.
Also applies to: 335-336
src/backend/base/langflow/components/composio/reddit_composio.py (1)
354-356:isinstance(result_data, int | str | float | bool)requires Python 3.10+If the project still supports 3.9, use
isinstance(result_data, (int, str, float, bool)).
| __all__ = [ | ||
| "ComposioAPIComponent", | ||
| "ComposioGitHubAPIComponent", | ||
| "ComposioGmailAPIComponent", | ||
| "ComposioGoogleCalendarAPIComponent", | ||
| "ComposioOutlookAPIComponent", | ||
| "ComposioRedditAPIComponent", | ||
| "ComposioSlackAPIComponent", |
There was a problem hiding this comment.
__all__ still exports removed symbol ComposioAPIComponent
ComposioAPIComponent is no longer imported, so from composio import * will raise NameError.
Either import it again or, more plausibly, drop it from __all__.
- "ComposioAPIComponent",
"ComposioGitHubAPIComponent",
"ComposioGmailAPIComponent",
"ComposioGoogleCalendarAPIComponent",
"ComposioRedditAPIComponent",
"ComposioSlackAPIComponent",📝 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.
| __all__ = [ | |
| "ComposioAPIComponent", | |
| "ComposioGitHubAPIComponent", | |
| "ComposioGmailAPIComponent", | |
| "ComposioGoogleCalendarAPIComponent", | |
| "ComposioOutlookAPIComponent", | |
| "ComposioRedditAPIComponent", | |
| "ComposioSlackAPIComponent", | |
| __all__ = [ | |
| "ComposioGitHubAPIComponent", | |
| "ComposioGmailAPIComponent", | |
| "ComposioGoogleCalendarAPIComponent", | |
| "ComposioRedditAPIComponent", | |
| "ComposioSlackAPIComponent", | |
| ] |
🧰 Tools
🪛 Pylint (3.3.7)
[error] 8-8: Undefined variable name 'ComposioAPIComponent' in all
(E0603)
🤖 Prompt for AI Agents
In src/backend/base/langflow/components/composio/__init__.py between lines 7 and
13, the __all__ list includes "ComposioAPIComponent" which is no longer
imported, causing a NameError when using from composio import *. To fix this,
remove "ComposioAPIComponent" from the __all__ list to ensure only currently
imported symbols are exported.
| enum_name = getattr(Action, action_key) | ||
| params = {} |
There was a problem hiding this comment.
Guard against missing enum members
getattr(Action, action_key) will raise AttributeError if action_key isn’t defined in the Action enum.
Consider using getattr(Action, action_key, None) and failing gracefully with a clear error message.
🤖 Prompt for AI Agents
In src/backend/base/langflow/components/composio/reddit_composio.py around lines
298 to 299, the code uses getattr(Action, action_key) which raises
AttributeError if action_key is not a member of the Action enum. Modify this to
use getattr(Action, action_key, None) to safely get the enum member or None if
missing, then add a check to handle the None case by failing gracefully with a
clear error message indicating the invalid action_key.
| def execute_action(self): | ||
| """Execute action and return response as Message.""" | ||
| toolset = self._build_wrapper() | ||
|
|
||
| try: | ||
| self._build_action_maps() | ||
| display_name = self.action[0]["name"] if isinstance(self.action, list) and self.action else self.action | ||
| action_key = self._display_to_key_map.get(display_name) | ||
| if not action_key: | ||
| msg = f"Invalid action: {display_name}" | ||
| raise ValueError(msg) | ||
|
|
||
| enum_name = getattr(Action, action_key) | ||
| params = {} | ||
| if action_key in self._actions_data: | ||
| for field in self._actions_data[action_key]["action_fields"]: | ||
| value = getattr(self, field) | ||
|
|
||
| if value is None or value == "": | ||
| continue | ||
|
|
||
| param_name = field.replace(action_key + "_", "") | ||
| params[param_name] = value | ||
|
|
||
| result = toolset.execute_action( | ||
| action=enum_name, | ||
| params=params, | ||
| ) | ||
|
|
||
| # Ensure result is a dictionary | ||
| if not isinstance(result, dict): | ||
| logger.error(f"Unexpected result type: {type(result)}, value: {result}") | ||
| return {"error": f"Unexpected result type: {type(result)}"} | ||
|
|
||
| if not result.get("successful"): | ||
| message = result.get("data", {}) | ||
| message = message.get("message", {}) if isinstance(message, dict) else str(message) | ||
|
|
||
| error_info = {"error": result.get("error", "No response")} | ||
| if isinstance(message, str): | ||
| try: | ||
| parsed_message = json.loads(message) | ||
| if isinstance(parsed_message, dict) and "error" in parsed_message: | ||
| error_data = parsed_message["error"] | ||
| error_info = { | ||
| "error": { | ||
| "code": error_data.get("code", "Unknown"), | ||
| "message": error_data.get("message", "No error message"), | ||
| } | ||
| } | ||
| except (json.JSONDecodeError, KeyError) as e: | ||
| logger.error(f"Failed to parse error message as JSON: {e}") | ||
| error_info = {"error": str(message)} | ||
| elif isinstance(message, dict) and "error" in message: | ||
| error_data = message["error"] | ||
| error_info = { | ||
| "error": { | ||
| "code": error_data.get("code", "Unknown"), | ||
| "message": error_data.get("message", "No error message"), | ||
| } | ||
| } | ||
|
|
||
| return error_info | ||
|
|
||
| result_data = result.get("data", []) | ||
|
|
||
| if result_data is None: | ||
| result_data = [] | ||
| elif isinstance(result_data, int | str | float | bool): | ||
| logger.warning(f"Result data is a primitive type: {type(result_data)}, value: {result_data}") | ||
| result_data = [{"value": result_data}] | ||
|
|
||
| action_data = self._actions_data.get(action_key, {}) | ||
|
|
||
| def ensure_dict_list(data): | ||
| """Ensure data is a list of dictionaries.""" | ||
| if isinstance(data, dict): | ||
| return [data] | ||
| if isinstance(data, list): | ||
| dict_list = [] | ||
| for item in data: | ||
| if isinstance(item, dict): | ||
| dict_list.append(item) | ||
| else: | ||
| dict_list.append({"value": item}) | ||
| return dict_list | ||
| return [{"value": data}] | ||
|
|
||
| def convert_posts_list_to_indexed_dict(data): | ||
| """Convert posts_list array to indexed dictionary.""" | ||
| if isinstance(data, list): | ||
| indexed_dict = {} | ||
| for i, item in enumerate(data): | ||
| indexed_dict[str(i)] = item | ||
| return [indexed_dict] | ||
| return ensure_dict_list(data) | ||
|
|
||
| if action_data.get("get_result_field"): | ||
| result_field = action_data.get("result_field") | ||
| if result_field: | ||
| found = self._find_key_recursively(result_data, result_field) | ||
| if found is not None and found != []: | ||
| converted = self._convert_pandas_to_python(found) | ||
| if result_field in ["posts_list", "children"]: | ||
| return convert_posts_list_to_indexed_dict(converted) | ||
| return ensure_dict_list(converted) | ||
|
|
||
| converted_data = self._convert_pandas_to_python(result_data) | ||
| return ensure_dict_list(converted_data) | ||
|
|
||
| if result_data and isinstance(result_data, dict): | ||
| converted_data = self._convert_pandas_to_python(result_data) | ||
| if converted_data: | ||
| first_key = next(iter(converted_data)) | ||
| return ensure_dict_list(converted_data[first_key]) | ||
| return [] | ||
|
|
||
| converted_data = self._convert_pandas_to_python(result_data) | ||
| return ensure_dict_list(converted_data) | ||
| except Exception as e: | ||
| logger.error(f"Error executing action: {e}") | ||
| display_name = self.action[0]["name"] if isinstance(self.action, list) and self.action else str(self.action) | ||
| msg = f"Failed to execute {display_name}: {e!s}" | ||
| raise ValueError(msg) from e |
There was a problem hiding this comment.
🛠️ Refactor suggestion
execute_action exceeds reasonable complexity limits
Pylint flags: 25 locals, 8 returns, 19 branches, 71 statements.
Splitting the method into focused helpers (e.g. _build_params, _handle_error, _normalize_result) will:
• Improve readability & testability
• Eliminate the need for broad except Exception
• Satisfy static-analysis thresholds
Refactor suggested for maintainability.
🧰 Tools
🪛 Pylint (3.3.7)
[refactor] 286-286: Too many local variables (25/15)
(R0914)
[refactor] 286-286: Too many return statements (8/6)
(R0911)
[refactor] 286-286: Too many branches (19/12)
(R0912)
[refactor] 286-286: Too many statements (71/50)
(R0915)
🤖 Prompt for AI Agents
In src/backend/base/langflow/components/composio/reddit_composio.py from lines
286 to 409, the execute_action method is overly complex with too many local
variables, return points, branches, and statements. To fix this, refactor the
method by extracting distinct logical parts into separate helper methods such as
_build_params for constructing the params dictionary, _handle_error for
processing error responses, and _normalize_result for converting and normalizing
the result data. This will reduce complexity, improve readability and
testability, and allow removing the broad try-except block by handling errors
more granularly within these helpers.
erichare
left a comment
There was a problem hiding this comment.
Not sure if this is still in scope. but if we do want to include this, please fix the merge conflicts and i'll review, thanks so much!
This pull request introduces a new integration for Reddit, including backend support, frontend components, and tests. The changes ensure that Reddit can be used as a new component in the system, with appropriate backend logic, frontend icons, and sidebar integration.
Backend Changes:
ComposioRedditAPIComponentto thecomposiomodule, enabling backend support for Reddit API interactions. (src/backend/base/langflow/components/composio/__init__.py)ComposioRedditAPIComponent, covering initialization, action execution, data conversion to a DataFrame, and configuration updates. (src/backend/tests/unit/components/bundles/composio/test_Reddit.py)Frontend Changes:
Redditicon to the lazy icon imports for dynamic loading. (src/frontend/src/icons/lazyIconImports.ts)RedditIconcomponent and its corresponding SVG file for rendering the Reddit icon in the UI. (src/frontend/src/icons/reddit/index.tsx,src/frontend/src/icons/reddit/reddit.jsx)src/frontend/src/utils/styleUtils.ts)Summary by CodeRabbit
New Features
Bug Fixes
Style
Tests