Skip to content

feat: add Composio YouTube component #8227

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

abhishekpatil4
Copy link
Contributor

@abhishekpatil4 abhishekpatil4 commented May 27, 2025

This pull request introduces a new YouTube component to the langflow project, including backend support & unit tests. The most important changes are:

Backend support:

  • src/backend/base/langflow/components/composio/__init__.py: Added import and registration for ComposioYoutubeAPIComponent .

Unit tests:

  • src/backend/tests/unit/components/bundles/composio/test_youtube.py: Added comprehensive unit tests for ComposioYoutubeAPIComponent, including tests for initialization, action execution, data conversion, and configuration updates.

Summary by CodeRabbit

  • New Features

    • Introduced YouTube API integration, allowing users to perform actions such as retrieving channel IDs, listing videos, managing playlists, searching YouTube, subscribing to channels, and updating video details directly within the application.
  • Tests

    • Added comprehensive tests to ensure correct behavior of the new YouTube API integration, covering action execution, data handling, error management, and configuration updates.

@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label May 27, 2025
@dosubot dosubot bot added the enhancement New feature or request label May 27, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels May 27, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels May 27, 2025
Copy link
Contributor

autofix-ci bot commented May 27, 2025

Hi! I'm autofix logoautofix.ci, a bot that automatically fixes trivial issues such as code formatting in pull requests.

I would like to apply some automated changes to this pull request, but it looks like I don't have the necessary permissions to do so. To get this pull request into a mergeable state, please do one of the following two things:

  1. Allow edits by maintainers for your pull request, and then re-trigger CI (for example by pushing a new commit).
  2. Manually fix the issues identified for your pull request (see the GitHub Actions output for details on what I would like to change).

@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels May 27, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels May 27, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels May 28, 2025
Copy link
Collaborator

@edwinjosechittilappilly edwinjosechittilappilly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On testing the Search Function I ma getting the Error:
Screenshot 2025-05-28 at 1 33 01 PM

Copy link
Collaborator

@edwinjosechittilappilly edwinjosechittilappilly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On testing the Search Function I ma getting the Error:
Screenshot 2025-05-28 at 1 33 01 PM

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label May 28, 2025
Copy link
Contributor

coderabbitai bot commented Jun 6, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

A new YouTube API integration component, ComposioYoutubeAPIComponent, has been added to the composio package. This component encapsulates YouTube API actions, input handling, error processing, and response extraction. The package's public exports are updated accordingly. Comprehensive unit tests for the new component are also introduced.

Changes

File(s) Change Summary
src/backend/base/langflow/components/composio/init.py Added import and export of ComposioYoutubeAPIComponent in the __all__ list.
src/backend/base/langflow/components/composio/youtube_composio.py Introduced ComposioYoutubeAPIComponent class implementing YouTube API actions, input, and error logic.
src/backend/tests/unit/components/bundles/composio/test_youtube.py Added unit tests for initialization, action execution, error handling, and config updating of the new component.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ComposioYoutubeAPIComponent
    participant ComposioToolSet
    participant YouTubeAPI

    User->>ComposioYoutubeAPIComponent: Selects action and provides inputs
    ComposioYoutubeAPIComponent->>ComposioToolSet: Maps action and prepares parameters
    ComposioToolSet->>YouTubeAPI: Executes YouTube API action
    YouTubeAPI-->>ComposioToolSet: Returns API response
    ComposioToolSet-->>ComposioYoutubeAPIComponent: Delivers response
    ComposioYoutubeAPIComponent->>ComposioYoutubeAPIComponent: Processes response, handles errors, extracts data
    ComposioYoutubeAPIComponent-->>User: Returns result or error information
Loading

Suggested labels

fast-track

✨ Finishing Touches
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Post Copyable Unit Tests in Comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 6, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/backend/tests/unit/components/bundles/composio/test_youtube.py (1)

54-74: Consider adding assertion for API parameters.

The test verifies the result but doesn't check if the correct parameters were passed to the toolset. Consider adding an assertion to verify that channel_handle was correctly passed.

You could enhance the test by capturing and verifying the parameters:

# After line 72, add:
# Verify the correct parameters were passed
mock_toolset = component._build_wrapper()
mock_toolset.execute_action.assert_called_once()
call_kwargs = mock_toolset.execute_action.call_args.kwargs
assert call_kwargs['params'].get('channel_handle') == 'test_handle'
src/backend/base/langflow/components/composio/youtube_composio.py (1)

315-392: Consider refactoring the complex execute_action method.

The method has high cyclomatic complexity with deeply nested error handling logic. Consider extracting the error parsing logic into a separate method for better maintainability.

Extract the error handling logic into a separate method:

def _parse_error_response(self, result: dict) -> dict:
    """Parse error response from API result."""
    message = result.get("data", {}).get("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

Then simplify the main method by calling return self._parse_error_response(result) on line 374.

🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 315-315: Too many local variables (20/15)

(R0914)


[refactor] 315-315: Too many branches (15/12)

(R0912)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09a65f6 and e9808e2.

📒 Files selected for processing (3)
  • src/backend/base/langflow/components/composio/__init__.py (1 hunks)
  • src/backend/base/langflow/components/composio/youtube_composio.py (1 hunks)
  • src/backend/tests/unit/components/bundles/composio/test_youtube.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/backend/base/langflow/components/composio/__init__.py (1)
src/backend/base/langflow/components/composio/youtube_composio.py (1)
  • ComposioYoutubeAPIComponent (14-401)
🪛 Pylint (3.3.7)
src/backend/tests/unit/components/bundles/composio/test_youtube.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/youtube_composio.py

[refactor] 315-315: Too many local variables (20/15)

(R0914)


[refactor] 315-315: Too many branches (15/12)

(R0912)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Optimize new Python code in this PR
  • GitHub Check: Update Starter Projects
  • GitHub Check: Run Ruff Check and Format
  • GitHub Check: Ruff Style Check (3.13)
🔇 Additional comments (3)
src/backend/base/langflow/components/composio/__init__.py (1)

6-6: LGTM! Import and export follow established patterns.

The addition of ComposioYoutubeAPIComponent to both the import statement and __all__ list is consistent with the existing pattern for other Composio components.

Also applies to: 14-14

src/backend/tests/unit/components/bundles/composio/test_youtube.py (1)

13-16: Test mock class is appropriately minimal.

The static analysis warning about too few public methods can be safely ignored. This is a test mock class that only needs to define the action constants.

🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 13-13: Too few public methods (0/2)

(R0903)

src/backend/base/langflow/components/composio/youtube_composio.py (1)

299-313: Efficient recursive key search implementation.

The _find_key_recursively method is well-implemented with proper handling of both dictionaries and lists. Good use of early returns to optimize performance.

@abhishekpatil4
Copy link
Contributor Author

On testing the Search Function I ma getting the Error: Screenshot 2025-05-28 at 1 33 01 PM

Hey @edwinjosechittilappilly, this is due to YouTube API rate limits. We've requested an increase to our rate limits from YouTube. Alternatively, user can use their own OAuth app while creating the integration (this needs to be created on Composio dashboard) and it will work fine.

@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 6, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/backend/base/langflow/components/composio/youtube_composio.py (1)

260-274: Add type hints for better code clarity.

The recursive search logic is correct and useful for navigating YouTube API response structures. Consider adding type hints for better maintainability.

-    def _find_key_recursively(self, data, key):
+    def _find_key_recursively(self, data: Any, key: str) -> Any:
         """Recursively search for a key in nested dicts/lists and return its value if found."""
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9808e2 and 494a47e.

📒 Files selected for processing (2)
  • src/backend/base/langflow/components/composio/__init__.py (2 hunks)
  • src/backend/base/langflow/components/composio/youtube_composio.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/backend/base/langflow/components/composio/init.py
🧰 Additional context used
🪛 Pylint (3.3.7)
src/backend/base/langflow/components/composio/youtube_composio.py

[refactor] 276-276: Too many local variables (20/15)

(R0914)


[refactor] 276-276: Too many branches (14/12)

(R0912)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Update Starter Projects
🔇 Additional comments (3)
src/backend/base/langflow/components/composio/youtube_composio.py (3)

14-97: Well-structured component design.

The class follows established patterns with comprehensive actions data and dynamic field computation. The data-driven approach ensures consistency between actions and their associated fields.


99-258: Comprehensive and well-documented input definitions.

The input fields provide thorough coverage of YouTube API parameters with consistent naming, appropriate types, and helpful documentation. The default values are sensible and the field organization follows the established pattern.


352-359: Appropriate delegation and sensible defaults.

The configuration methods properly delegate to the parent class and set reasonable default tools for YouTube functionality.

Comment on lines +276 to +350
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,
)
if not result.get("successful"):
message = result.get("data", {}).get("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", [])
action_data = self._actions_data.get(action_key, {})
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:
return found
return result_data
if result_data and isinstance(result_data, dict):
return result_data[next(iter(result_data))]
return result_data # noqa: TRY300
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor to reduce complexity and address edge cases.

The method has excessive complexity (20+ variables, 14+ branches) and contains several potential edge cases:

  1. Line 282: If self.action is an empty list, self.action[0] will raise IndexError
  2. Line 344: Accessing next(iter(result_data)) without checking if dict is empty will raise StopIteration
  3. Line 297: Parameter name extraction assumes specific naming pattern

Consider refactoring into smaller methods:

+    def _extract_action_key(self, action) -> str:
+        """Extract and validate action key from action input."""
+        if isinstance(action, list):
+            if not action:
+                raise ValueError("Action list cannot be empty")
+            display_name = action[0]["name"]
+        else:
+            display_name = action
+        
+        action_key = self._display_to_key_map.get(display_name)
+        if not action_key:
+            raise ValueError(f"Invalid action: {display_name}")
+        return action_key
+
+    def _build_action_params(self, action_key: str) -> dict:
+        """Build parameters for the specified action."""
+        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 not None and value != "":
+                    param_name = field.replace(f"{action_key}_", "")
+                    params[param_name] = value
+        return params
+
+    def _process_error_response(self, result: dict) -> dict:
+        """Process and structure error responses."""
+        # Extract existing error handling logic here
+        pass

Then simplify the main method:

     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)
+            action_key = self._extract_action_key(self.action)
+            params = self._build_action_params(action_key)
             
             enum_name = getattr(Action, action_key)
-            # ... existing parameter building logic
             
             result = toolset.execute_action(action=enum_name, params=params)
             if not result.get("successful"):
-                # ... existing error handling
+                return self._process_error_response(result)
             
             # ... rest of success handling

Also fix the potential edge case:

-            if result_data and isinstance(result_data, dict):
-                return result_data[next(iter(result_data))]
+            if result_data and isinstance(result_data, dict) and result_data:
+                return result_data[next(iter(result_data))]
📝 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.

Suggested change
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,
)
if not result.get("successful"):
message = result.get("data", {}).get("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", [])
action_data = self._actions_data.get(action_key, {})
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:
return found
return result_data
if result_data and isinstance(result_data, dict):
return result_data[next(iter(result_data))]
return result_data # noqa: TRY300
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
def _extract_action_key(self, action) -> str:
"""Extract and validate action key from action input."""
if isinstance(action, list):
if not action:
raise ValueError("Action list cannot be empty")
display_name = action[0]["name"]
else:
display_name = action
action_key = self._display_to_key_map.get(display_name)
if not action_key:
raise ValueError(f"Invalid action: {display_name}")
return action_key
def _build_action_params(self, action_key: str) -> dict:
"""Build parameters for the specified action."""
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 not None and value != "":
param_name = field.replace(f"{action_key}_", "")
params[param_name] = value
return params
def _process_error_response(self, result: dict) -> dict:
"""Process and structure error responses."""
# TODO: move existing error-handling logic here
pass
def execute_action(self):
"""Execute action and return response as Message."""
toolset = self._build_wrapper()
try:
self._build_action_maps()
action_key = self._extract_action_key(self.action)
params = self._build_action_params(action_key)
enum_name = getattr(Action, action_key)
result = toolset.execute_action(action=enum_name, params=params)
if not result.get("successful"):
return self._process_error_response(result)
result_data = result.get("data", [])
action_data = self._actions_data.get(action_key, {})
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:
return found
return result_data
if result_data and isinstance(result_data, dict) and result_data:
return result_data[next(iter(result_data))]
return result_data # noqa: TRY300
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
🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 276-276: Too many local variables (20/15)

(R0914)


[refactor] 276-276: Too many branches (14/12)

(R0912)

@abhishekpatil4 abhishekpatil4 force-pushed the feat/youtube-component branch from 494a47e to 05ab666 Compare June 15, 2025 07:35
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 15, 2025
@abhishekpatil4 abhishekpatil4 force-pushed the feat/youtube-component branch from 05ab666 to f2cb2e9 Compare June 15, 2025 18:09
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 15, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request lgtm This PR has been approved by a maintainer size:XL This PR changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants