8000 Feature: NestBot block message template system by Li-ChangC · Pull Request #1301 · OWASP/Nest · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Feature: NestBot block message template system #1301

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

Merged
merged 20 commits into from
Apr 21, 2025

Conversation

Li-ChangC
Copy link
Contributor

Resolves #342

Changes:

  • Implemented a Jinja2-based template system for NestBot's Slack command responses.
  • Refactored hardcoded text messages into reusable .template files to improve maintainability and consistency.
  • Introduced a BaseCommand class to streamline the implementation of new Slack commands.
  • Updated unit tests to match the new template-based output, ensuring that all responses remain consistent with the previous behavior.

Reason:

  • Improve consistency and maintainability of Slack command outputs.
  • Enable future localization and customization with minimal code changes.
  • Align with best practices by separating logic and presentation.

@Li-ChangC Li-ChangC requested a review from arkid15r as a code owner April 7, 2025 07:15
Copy link
Contributor
coderabbitai bot commented Apr 7, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

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

Summary by CodeRabbit

  • New Features

    • Introduced a unified, class-based framework for Slack commands, replacing previous function-based handlers with dedicated command classes for improved modularity and maintainability.
    • Added new Jinja2-based templating for Slack command responses, enabling richer and more consistent message formatting.
    • Provided new Slack commands and help documentation, including commands for chapters, contribute, donate, events, GSoC, jobs, leaders, news, policies, sponsors, staff, and more.
    • Enhanced Slack command responses with dynamic templates for events, news, staff, sponsors, and other resources.
  • Refactor

    • Migrated all Slack command handlers from procedural functions to object-oriented class implementations.
    • Centralized command configuration and registration logic.
    • Updated internal logic to dynamically resolve command names and improve extensibility.
  • Bug Fixes

    • Improved error handling and user feedback for unsupported commands and missing data in Slack responses.
  • Documentation

    • Added and updated in-app help and usage instructions for all Slack commands via new template files.
  • Chores

    • Added Jinja2 as a project dependency for template rendering.
    • Up 8000 dated and refactored test suites to match the new class-based command structure.
      """

Summary by CodeRabbit

  • New Features

    • Introduced a unified, class-based architecture for all Slack commands, enhancing maintainability and scalability.
    • Added new and improved Slack command templates for chapters, contribute, donate, events, gsoc, jobs, leaders, news, owasp, policies, sponsors, and staff.
    • Slack commands now provide more structured, templated, and consistent responses with improved formatting and contextual information.
  • Refactor

    • Migrated all Slack command handlers from procedural functions to class-based implementations.
    • Centralized command registration and template rendering logic, streamlining command management.
  • Bug Fixes

    • Improved error handling and user feedback for unsupported commands and missing data in Slack responses.
  • Chores

    • Added Jinja2 as a new dependency for template rendering.
    • Updated and refactored test suites to align with the new class-based command structure.
  • Documentation

    • Enhanced in-app help and command documentation through new and updated Slack templates.

Walkthrough

This change introduces a comprehensive refactor of the Slack command handling system within the backend. The previous function-based, procedural command handlers are replaced with a class-based architecture, where each command is encapsulated in a subclass of a new CommandBase. A Jinja2-based template system is implemented, with templates created for each command to standardize and simplify message formatting. The command registration and dispatch logic are centralized, and all test suites are updated to use the new class-based handlers. The Jinja2 dependency is added to the project configuration, and a template loader module is introduced to manage template rendering.

Changes

Files / Paths Change Summary
backend/apps/slack/commands/* (board.py, chapters.py, committees.py, ...) Refactored all Slack command handlers from procedural functions to subclasses of CommandBase, encapsulating logic and template context generation. Removed direct Slack API interactions from these files.
backend/apps/slack/commands/command.py New file introducing CommandBase class, providing command registration, template rendering, and unified handler logic.
backend/apps/slack/commands/init.py Imports SlackConfig and CommandBase; adds logic to configure commands if Slack app is available.
backend/apps/slack/template_loader.py New module for Jinja2 environment and template loading configuration.
backend/apps/slack/templates/* (various .template files) Added Jinja2 template files for each Slack command, defining message structure and content for block rendering.
backend/pyproject.toml Added jinja2 as a dependency.
backend/apps/slack/models/event.py Updated to retrieve the OWASP command name dynamically from the new Owasp class.
backend/tests/apps/slack/commands/* (all command test files) Updated tests to use class-based handlers, replacing direct function calls with class instantiations and method calls.
backend/tests/apps/slack/models/event_test.py Updated to retrieve OWASP command name dynamically via Owasp().get_command_name().

Assessment against linked issues

Objective (Issue #) Addressed Explanation
Implement a template system for NestBot block message rendering (#342)
Replace procedural markdown formatting with template-based rendering (#342)
Ensure templates support formatting for \n, <@user_id>, <#...> etc. (#342)
Migrate all command handlers to use the new template system (#342)
"""

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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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 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.

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: 4

🧹 Nitpick comments (20)
backend/apps/slack/templates/policies.template (1)

1-5: New Policies Template Review
The template efficiently iterates over a collection of policies and renders each as a clickable link. A couple of points for consideration:

  • Ensure that the variables policies, NL, SECTION_BREAK, and DIVIDER are clearly defined in the context where this template is rendered.
  • The use of the |safe filter on {{ title }} is appropriate if the input is sanitized; please verify that no untrusted content is passed in.
    Overall, the template is well-structured for its intended purpose.
backend/apps/slack/templates/jobs.template (1)

1-5: New Jobs Template Review
This template is well-crafted for communicating job-related Slack channel information. The inclusion of a disclaimer and embedding of the feedback.template are good practices for modularity and transparency. One suggestion is to ensure that the variable jobs_channel is provided reliably; consider a fallback or error handling mechanism if it might ever be missing.

backend/apps/slack/template_system/loader.py (1)

8-8: Consider adding a check to ensure templates directory exists.

The code assumes that the templates directory exists, but if it doesn't, this would cause runtime errors when templates are loaded.

BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATES_DIR = BASE_DIR / "templates"
+
+if not TEMPLATES_DIR.exists():
+    raise FileNotFoundError(f"Templates directory not found: {TEMPLATES_DIR}")
backend/apps/slack/models/event.py (1)

45-45: Consider caching the Owasp instance to avoid repeated instantiation.

Creating a new Owasp instance on each comparison is inefficient. Consider caching the command string or the instance to improve performance.

- if command and command == Owasp().get_command():
+ # Cache the command string to avoid creating a new Owasp instance each time
+ OWASP_COMMAND = Owasp().get_command()  # This could be defined at module level
+ if command and command == OWASP_COMMAND:
backend/apps/slack/templates/leaders.template (1)

8-11: Consider verifying partial match logic.

If full or exact matches are intended, you might replace substring checks with an equality check or use word boundaries. Otherwise, this approach is fine.

backend/tests/apps/slack/commands/committees_test.py (1)

78-81: Good coverage of committees with mocked results.

This test correctly verifies that the returned blocks contain the committee details. Consider adding assertions for any additional relevant fields or edge cases, if necessary.

backend/apps/slack/commands/staff.py (1)

8-23: Add docstring details for the command parameter.

Currently, there's a simple docstring. Adding parameter details, expected structure, and examples will improve clarity and maintainability.

backend/apps/slack/templates/events.template (1)

1-10: Consider adding a fallback message for empty categories or no events.
I 8000 f categorized_events or a specific category is empty, users might see a blank list. Showing an explicit "No upcoming events found" message could improve user clarity and provide better UX.

backend/apps/slack/commands/projects.py (1)

11-25: Effectively implemented rendering method

The get_render_blocks method maintains the same functionality as the previous implementation while properly integrating with the new class-based structure. The method extracts the search query and passes it to the common handler with appropriate presentation settings.

One minor observation: While the implementation uses reasonable default values for the EntityPresentation parameters, these could potentially be moved to class constants or configuration settings for easier customization in the future.

backend/apps/slack/commands/users.py (1)

11-25: Implementation of required template rendering method

The get_render_blocks method cleanly extracts the search query and passes it to the existing get_blocks function with appropriate presentation parameters. This maintains existing functionality while enabling the template system described in the PR objectives.

Consider adding error handling for cases where command["text"] might not exist or could be None:

-        search_query = command["text"].strip()
+        search_query = command.get("text", "").strip()
backend/apps/slack/commands/chapters.py (2)

12-16: Consider validating the command["text"] field
When overriding get_render_blocks, you access command["text"] directly. If the Slack payload unexpectedly omits "text", this would raise a KeyError. Consider validating or defaulting to an empty string to ensure robustness.


18-29: Repeated block generation pattern
This block generation with the same pagination and feedback config appears repeated in other commands. If feasible, consider consolidating shared parameters or providing a helper method to reduce duplication and streamline maintenance.

backend/apps/slack/commands/contribute.py (1)

18-36: Similar block generation logic
As in other commands, you’re hard-coding the limit and reusing a similar EntityPresentation setup. If multiple commands share the same approach, consider factoring out common parameters into a utility function or config.

backend/apps/slack/commands/committees.py (1)

11-25: Recurring pattern for generating Slack blocks
Your use of get_blocks with EntityPresentation is consistent. This approach is similar to other commands. Consider whether you can factor this out into a reusable helper, as it’s repeated in multiple places.

backend/tests/apps/slack/commands/policies_test.py (1)

35-64: Consider reducing duplication of policy data
Replicating the entire policy list in tests could mean additional maintenance overhead when changes occur. Consider referencing a common fixture or using shared data to keep the test consistent with actual code.

backend/tests/apps/slack/commands/staff_test.py (1)

77-77: Flexible substring assertion.

Using assert expected_message in blocks[0]["text"]["text"] allows for slight variations in formatting. If exact matches are crucial, consider more precise matching; otherwise, this is suitable.

backend/tests/apps/slack/commands/sponsors_test.py (2)

67-69: Consider verifying ack call in the test.

After creating the Sponsors instance and calling handler, add an assertion that ack was invoked, ensuring proper Slack command acknowledgment is tested.

Example snippet:

 sponsors_instance.handler(ack=ack, command=mock_slack_command, client=mock_slack_client)
+ack.assert_called_once()

81-82: No issues with hardcoded mock channel.

Using the mock channel ID "C123456" is acceptable for testing, though consider documenting its test purpose if re-used.

backend/apps/slack/commands/contact.py (1)

10-12: Template naming consistency
Returning "navigate.template" under Contact may be confusing. Consider using a file name that matches the command's purpose for clarity.

backend/tests/apps/slack/commands/owasp_test.py (1)

6-9: Update the class name to match the pattern used in other test files.

While the import has been updated to use the Owasp class, the test class name hasn't been updated to match the pattern used in other test files (e.g., TestNewsCommand, TestSponsorCommand).

-class TestOwaspHandler:
+class TestOwaspCommand:
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 953d4b6 and 333de5c.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (58)
  • backend/apps/slack/commands/__init__.py (2 hunks)
  • backend/apps/slack/commands/board.py (1 hunks)
  • backend/apps/slack/commands/chapters.py (1 hunks)
  • backend/apps/slack/commands/command.py (1 hunks)
  • backend/apps/slack/commands/committees.py (1 hunks)
  • backend/apps/slack/commands/community.py (1 hunks)
  • backend/apps/slack/commands/contact.py (1 hunks)
  • backend/apps/slack/commands/contribute.py (1 hunks)
  • backend/apps/slack/commands/donate.py (1 hunks)
  • backend/apps/slack/commands/events.py (1 hunks)
  • backend/apps/slack/commands/gsoc.py (1 hunks)
  • backend/apps/slack/commands/jobs.py (1 hunks)
  • backend/apps/slack/commands/leaders.py (1 hunks)
  • backend/apps/slack/commands/news.py (1 hunks)
  • backend/apps/slack/commands/owasp.py (1 hunks)
  • backend/apps/slack/commands/policies.py (1 hunks)
  • backend/apps/slack/commands/projects.py (1 hunks)
  • backend/apps/slack/commands/sponsor.py (1 hunks)
  • backend/apps/slack/commands/sponsors.py (1 hunks)
  • backend/apps/slack/commands/staff.py (1 hunks)
  • backend/apps/slack/commands/users.py (1 hunks)
  • backend/apps/slack/models/event.py (2 hunks)
  • backend/apps/slack/template_system/loader.py (1 hunks)
  • backend/apps/slack/templates/chapters.template (1 hunks)
  • backend/apps/slack/templates/contribute.template (1 hunks)
  • backend/apps/slack/templates/donate.template (1 hunks)
  • backend/apps/slack/templates/events.template (1 hunks)
  • backend/apps/slack/templates/feedback.template (1 hunks)
  • backend/apps/slack/templates/gsoc.template (1 hunks)
  • backend/apps/slack/templates/jobs.template (1 hunks)
  • backend/apps/slack/templates/leaders.template (1 hunks)
  • backend/apps/slack/templates/navigate.template (1 hunks)
  • backend/apps/slack/templates/news.template (1 hunks)
  • backend/apps/slack/templates/owasp.template (1 hunks)
  • backend/apps/slack/templates/policies.template (1 hunks)
  • backend/apps/slack/templates/sponsors.template (1 hunks)
  • backend/apps/slack/templates/staff.template (1 hunks)
  • backend/pyproject.toml (1 hunks)
  • backend/tests/apps/slack/commands/board_test.py (3 hunks)
  • backend/tests/apps/slack/commands/chapters_test.py (3 hunks)
  • backend/tests/apps/slack/commands/committees_test.py (3 hunks)
  • backend/tests/apps/slack/commands/community_test.py (3 hunks)
  • backend/tests/apps/slack/commands/contact_test.py (3 hunks)
  • backend/tests/apps/slack/commands/contribute_test.py (4 hunks)
  • backend/tests/apps/slack/commands/donate_test.py (3 hunks)
  • backend/tests/apps/slack/commands/events_test.py (2 hunks)
  • backend/tests/apps/slack/commands/gsoc_test.py (4 hunks)
  • backend/tests/apps/slack/commands/jobs_test.py (3 hunks)
  • backend/tests/apps/slack/commands/leaders_test.py (4 hunks)
  • backend/tests/apps/slack/commands/news_test.py (5 hunks)
  • backend/tests/apps/slack/commands/owasp_test.py (3 hunks)
  • backend/tests/apps/slack/commands/policies_test.py (2 hunks)
  • backend/tests/apps/slack/commands/projects_test.py (4 hunks)
  • backend/tests/apps/slack/commands/sponsor_test.py (2 hunks)
  • backend/tests/apps/slack/commands/sponsors_test.py (3 hunks)
  • backend/tests/apps/slack/commands/staff_test.py (3 hunks)
  • backend/tests/apps/slack/commands/users_test.py (3 hunks)
  • backend/tests/apps/slack/models/event_test.py (3 hunks)
🧰 Additional context used
🧬 Code Definitions (38)
backend/tests/apps/slack/commands/jobs_test.py (2)
backend/apps/slack/commands/jobs.py (1)
  • Jobs (12-24)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/tests/apps/slack/commands/community_test.py (2)
backend/apps/slack/commands/community.py (1)
  • Community (7-20)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/apps/slack/models/event.py (2)
backend/apps/slack/commands/owasp.py (1)
  • Owasp (7-42)
backend/apps/slack/commands/command.py (1)
  • get_command (57-59)
backend/tests/apps/slack/commands/contact_test.py (2)
backend/apps/slack/commands/contact.py (1)
  • Contact (7-18)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/apps/slack/commands/__init__.py (2)
backend/apps/slack/apps.py (1)
  • SlackConfig (10-21)
backend/apps/slack/commands/command.py (2)
  • CommandBase (11-98)
  • config_all_commands (20-23)
backend/tests/apps/slack/commands/committees_test.py (2)
backend/apps/slack/commands/committees.py (1)
  • Committees (8-25)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/tests/apps/slack/commands/events_test.py (1)
backend/apps/slack/commands/events.py (1)
  • Events (8-38)
backend/tests/apps/slack/commands/donate_test.py (2)
backend/apps/slack/commands/donate.py (1)
  • Donate (7-17)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/apps/slack/commands/community.py (3)
backend/apps/slack/commands/command.py (4)
  • CommandBase (11-98)
  • get_template_file_name (70-72)
  • get_render_text (25-36)
  • get_template_file (61-68)
backend/apps/slack/commands/board.py (2)
  • get_template_file_name (10-12)
  • get_render_text (14-18)
backend/apps/slack/commands/contact.py (2)
  • get_template_file_name (10-12)
  • get_render_text (14-18)
backend/apps/slack/commands/sponsor.py (1)
backend/apps/slack/commands/command.py (2)
  • CommandBase (11-98)
  • get_render_text (25-36)
backend/apps/slack/commands/projects.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (11-98)
  • get_render_blocks (38-55)
backend/apps/slack/common/presentation.py (1)
  • EntityPresentation (7-25)
backend/apps/slack/commands/contribute.py (2)
backend/apps/slack/commands/command.py (4)
  • CommandBase (11-98)
  • get_render_text (25-36)
  • get_template_file (61-68)
  • get_render_blocks (38-55)
backend/apps/slack/common/handlers/contribute.py (1)
  • get_blocks (15-98)
backend/apps/slack/commands/jobs.py (1)
backend/apps/slack/commands/command.py (3)
  • CommandBase (11-98)
  • get_render_text (25-36)
  • get_template_file (61-68)
backend/tests/apps/slack/commands/chapters_test.py (2)
backend/apps/slack/commands/chapters.py (1)
  • Chapters (9-29)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/tests/apps/slack/models/event_test.py (2)
backend/apps/slack/commands/owasp.py (1)
  • Owasp (7-42)
backend/apps/slack/commands/command.py (1)
  • get_command (57-59)
backend/apps/slack/commands/sponsors.py (2)
backend/apps/slack/commands/command.py (3)
  • CommandBase (11-98)
  • get_render_text (25-36)
  • get_template_file (61-68)
backend/apps/slack/utils.py (1)
  • get_sponsors_data (136-152)
backend/tests/apps/slack/commands/policies_test.py (1)
backend/apps/slack/commands/policies.py (1)
  • Policies (7-42)
backend/tests/apps/slack/commands/staff_test.py (8)
backend/apps/slack/commands/staff.py (1)
  • Staff (8-23)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/tests/apps/slack/commands/events_test.py (2)
  • mock_slack_command (41-44)
  • mock_slack_client (47-50)
backend/tests/apps/slack/commands/gsoc_test.py (2)
  • mock_slack_command (13-17)
  • mock_slack_client (20-23)
backend/tests/apps/slack/commands/leaders_test.py (2)
  • mock_slack_command (12-16)
  • mock_slack_client (19-22)
backend/tests/apps/slack/commands/sponsors_test.py (2)
  • mock_slack_command (35-38)
  • mock_slack_client (41-44)
backend/tests/apps/slack/events/user_joined_channel/contribute_test.py (1)
  • mock_slack_client (20-23)
backend/tests/apps/slack/events/user_joined_channel/gsoc_test.py (1)
  • mock_slack_client (16-19)
backend/apps/slack/commands/gsoc.py (3)
backend/apps/common/utils.py (1)
  • get_absolute_url (13-23)
backend/apps/slack/commands/command.py (4)
  • CommandBase (11-98)
  • get_render_text (25-36)
  • get_template_file (61-68)
  • get_command (57-59)
backend/apps/slack/utils.py (1)
  • get_gsoc_projects (34-55)
backend/apps/slack/commands/events.py (2)
backend/apps/slack/commands/command.py (3)
  • CommandBase (11-98)
  • get_render_text (25-36)
  • get_template_file (61-68)
backend/apps/slack/utils.py (1)
  • get_events_data (120-133)
backend/apps/slack/commands/chapters.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (11-98)
  • get_render_blocks (38-55)
backend/apps/slack/commands/contribute.py (1)
  • get_render_blocks (18-35)
backend/tests/apps/slack/commands/contribute_test.py (3)
backend/apps/slack/commands/contribute.py (1)
  • Contribute (10-35)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/apps/slack/commands/owasp.py (1)
  • handler (20-28)
backend/apps/slack/commands/donate.py (15)
backend/apps/slack/commands/command.py (3)
  • CommandBase (11-98)
  • get_render_text (25-36)
  • get_template_file (61-68)
backend/apps/slack/commands/board.py (1)
  • get_render_text (14-18)
backend/apps/slack/commands/contact.py (1)
  • get_render_text (14-18)
backend/apps/slack/commands/community.py (1)
  • get_render_text (14-20)
backend/apps/slack/commands/contribute.py (1)
  • get_render_text (13-16)
backend/apps/slack/commands/events.py (1)
  • get_render_text (11-38)
backend/apps/slack/commands/jobs.py (1)
  • get_render_text (15-24)
backend/apps/slack/commands/news.py (1)
  • get_render_text (11-26)
backend/apps/slack/commands/gsoc.py (1)
  • get_render_text (31-70)
backend/apps/slack/commands/leaders.py (1)
  • get_render_text (12-64)
backend/apps/slack/commands/owasp.py (1)
  • get_render_text (30-42)
backend/apps/slack/commands/policies.py (1)
  • get_render_text (10-42)
backend/apps/slack/commands/sponsors.py (1)
  • get_render_text (12-26)
backend/apps/slack/commands/sponsor.py (1)
  • get_render_text (9-11)
backend/apps/slack/commands/staff.py (1)
  • get_render_text (11-23)
backend/tests/apps/slack/commands/projects_test.py (2)
backend/apps/slack/commands/projects.py (1)
  • Projects (8-25)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/tests/apps/slack/commands/news_test.py (2)
backend/apps/slack/commands/news.py (1)
  • News (8-26)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/tests/apps/slack/commands/sponsor_test.py (2)
backend/apps/slack/commands/sponsor.py (1)
  • Sponsor (6-11)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/apps/slack/commands/leaders.py (5)
backend/apps/slack/commands/command.py (4)
  • CommandBase (11-98)
  • get_render_text (25-36)
  • get_template_file (61-68)
  • get_command (57-59)
backend/apps/slack/utils.py (1)
  • escape (20-30)
backend/apps/owasp/api/search/chapter.py (1)
  • get_chapters (8-44)
backend/apps/owasp/api/search/project.py (1)
  • get_projects (8-48)
backend/apps/common/utils.py (1)
  • get_absolute_url (13-23)
backend/tests/apps/slack/commands/users_test.py (2)
backend/apps/slack/commands/users.py (1)
  • Users (8-25)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/apps/slack/commands/news.py (2)
backend/apps/slack/commands/command.py (3)
  • CommandBase (11-98)
  • get_render_text (25-36)
  • get_template_file (61-68)
backend/apps/slack/utils.py (1)
  • get_news_data (59-91)
backend/apps/slack/commands/policies.py (1)
backend/apps/slack/commands/command.py (3)
  • CommandBase (11-98)
  • get_render_text (25-36)
  • get_template_file (61-68)
backend/tests/apps/slack/commands/board_test.py (2)
backend/apps/slack/commands/board.py (1)
  • Board (7-18)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/apps/slack/commands/committees.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (11-98)
  • get_render_blocks (38-55)
backend/apps/slack/common/presentation.py (1)
  • EntityPresentation (7-25)
backend/tests/apps/slack/commands/owasp_test.py (1)
backend/apps/slack/commands/owasp.py (2)
  • Owasp (7-42)
  • handler (20-28)
backend/tests/apps/slack/commands/gsoc_test.py (1)
backend/apps/slack/commands/gsoc.py (1)
  • Gsoc (28-70)
backend/apps/slack/commands/contact.py (1)
backend/apps/slack/commands/command.py (4)
  • CommandBase (11-98)
  • get_template_file_name (70-72)
  • get_render_text (25-36)
  • get_template_file (61-68)
backend/apps/slack/commands/owasp.py (1)
backend/apps/slack/commands/command.py (6)
  • CommandBase (11-98)
  • get_all_commands (15-17)
  • handler (78-98)
  • get_render_text (25-36)
  • get_command (57-59)
  • get_template_file (61-68)
backend/tests/apps/slack/commands/sponsors_test.py (2)
backend/apps/slack/commands/sponsors.py (1)
  • Sponsors (9-26)
backend/apps/slack/commands/command.py (1)
  • handler (78-98)
backend/apps/slack/commands/command.py (3)
backend/apps/slack/blocks.py (1)
  • markdown (14-27)
backend/apps/slack/commands/donate.py (1)
  • get_render_text (10-17)
backend/apps/slack/commands/sponsor.py (1)
  • get_render_text (9-11)
🔇 Additional comments (162)
backend/apps/slack/templates/owasp.template (2)

1-18: Clear and Consistent Help Command Rendering

The template uses a straightforward conditional ({% if help %}) to display a well-formatted list of available OWASP commands when help is requested. The use of inline code formatting for placeholders (e.g. {{ command }}) is consistent across the list and enhances readability.


19-21: Effective Fallback for Unsupported Commands

The else block cleanly notifies the user that the given command is not supported by dynamically including {{ command }} and {{ handler }}. This provides clear user feedback and maintains consistency with the rest of the template.

backend/apps/slack/templates/gsoc.template (3)

1-7: Robust 'General' Mode Implementation

The 'general' mode section effectively outlines the starting steps for new GSoC participants. The use of dynamic placeholders (e.g. {{ gsoc_channel }}, {{ contribute_channel }}) and the inclusion of a newline token ({{ NL }}) ensures that the message adapts well to varying contexts.


8-11: Clear Presentation of Year-Specific Information

The 'year' mode correctly iterates over the provided projects with a concise for-loop, and the additional information segment leverages nested conditionals effectively. The formatting—with newlines and separators—improves the clarity of the output.


12-16: Comprehensive Handling of Edge Cases

Both the "unsupported_year" and "invalid" branches offer clear, user-friendly messages when inputs fall outside expectations. Using dynamic placeholders in these branches maintains consistency and provides precise guidance to the users.

backend/apps/slack/templates/donate.template (2)

1-2: Detailed Donation Information Provided

The template clearly explains the donation process, including organizational details, donation methods, and multi-currency support. The use of a newline placeholder ({{ NL }}) ensures proper formatting when rendered in Slack.


3-3: Effective Call-to-Action with Visual Breaks

The incorporation of section breaks ({{ SECTION_BREAK }}) and dividers ({{ DIVIDER }}) makes the call-to-action stand out, guiding users on where to donate. The dynamic URL placeholder further enhances maintainability.

backend/apps/slack/templates/feedback.template (1)

1-2: Concise and Engaging Feedback Prompt

This template succinctly invites users to share their feedback, dynamically referencing the bot's name and the appropriate feedback channel. The clear call-to-action supports user engagement and aligns with the overall design.

backend/apps/slack/templates/navigate.template (1)

1-2: Simple and Effective Navigation Message

The template clearly instructs users to visit a dynamic URL, using placeholders to insert both the URL and display name. The inclusion of a newline ({{ NL }}) ensures the message maintains proper formatting in Slack outputs.

backend/pyproject.toml (1)

49-49: Dependency Addition: Jinja2
The new dependency jinja2 = "^3.1.6" has been added correctly under the [tool.poetry.dependencies] section. This aligns with the PR objectives to introduce a Jinja2-based template system for Slack commands. Please ensure that using this version is compatible with the rest of the dependencies and that no conflicts occur during deployment.

backend/apps/slack/templates/contribute.template (1)

1-4: New Contribute Template
The template clearly lists the available commands for contributing (/contribute and /contribute <search term>). The formatting is clean and easy to read. This file effectively decouples command text from logic.

backend/apps/slack/templates/chapters.template (1)

1-5: New Chapters Template Review
The chapters template is succinct and clearly outlines the available commands for retrieving chapters. Its bullet list format improves readability and consistency with other command templates.

backend/apps/slack/commands/__init__.py (2)

1-3: Good addition of required imports for the new command system.

These imports provide access to the Slack app configuration and the new command base class, which are essential for the template-based command system implementation.


26-27: Good defensive programming with conditional configuration.

The conditional check ensures that commands are only configured when a valid Slack app instance exists. This prevents potential errors in environments where Slack credentials aren't available (like testing or development).

backend/apps/slack/templates/sponsors.template (1)

1-17: Well-structured template with proper conditional handling.

This template effectively formats sponsor information for Slack messages with:

  • Proper conditional checks for sponsor availability
  • Special handling for sponsors with/without URLs
  • Clear formatting with appropriate spacing and sections
  • Inclusion of a feedback template for reusability
  • Clean whitespace control using Jinja2's trimming syntax ({%- and -%})

These characteristics align well with the PR objective of improving message consistency and maintainability.

backend/apps/slack/templates/staff.template (1)

1-13: Well-implemented template with robust error handling.

This template effectively formats staff information for Slack messages with:

  • Proper conditional handling for staff data availability
  • Clear organization of staff member details
  • Good use of default filter for potentially missing descriptions
  • Appropriate formatting with section breaks and dividers
  • Clean whitespace control using Jinja2's trimming syntax

The template structure aligns well with the PR objective of improving message consistency and maintainability.

backend/apps/slack/templates/news.template (1)

1-9: Well-designed template with enhanced visual elements.

This template effectively formats news information for Slack messages with:

  • Clear conditional handling for news availability
  • Bullet-point formatting for easy readability
  • Good use of emojis (:newspaper: and :warning:) to enhance visual appeal
  • Proper hyperlink formatting for news titles
  • Appropriate error message when news can't be fetched

These improvements align with the PR objective of enhancing message consistency and user experience.

backend/apps/slack/template_system/loader.py (1)

1-10: Good implementation of Jinja2 template environment.

The implementation is clean, concise, and follows best practices:

  • Clear module docstring
  • Proper path resolution using Path(__file__).resolve()
  • Security-focused by enabling autoescape=True
  • Simple and focused responsibility
backend/tests/apps/slack/commands/jobs_test.py (3)

7-7: LGTM: Class-based import to support refactoring.

The import has been properly updated to use the new Jobs class instead of the function-based handler, which aligns with the refactoring to a class-based command system.


33-35: LGTM: Creating class instance instead of using function.

The test has been correctly updated to instantiate the Jobs class and invoke its handler method, which aligns with the new object-oriented approach to command handling.


60-60: LGTM: Updated assertion to handle whitespace differences.

The assertion now strips whitespace from FEEDBACK_CHANNEL_MESSAGE before checking if it's in the response text, which makes the test more robust against formatting changes in the templates.

backend/apps/slack/models/event.py (1)

6-6: LGTM: Class-based import to support refactoring.

The import has been properly updated to use the Owasp class, which aligns with the refactoring to a class-based command system.

backend/tests/apps/slack/commands/users_test.py (3)

6-6: LGTM: Class-based import to support refactoring.

The import has been properly updated to use the new Users class instead of the function-based handler, which aligns with the refactoring to a class-based command system.


45-46: LGTM: Creating class instance instead of using function.

The test has been correctly updated to instantiate the Users class and invoke its handler method, which aligns with the new object-oriented approach to command handling.


79-80: LGTM: Creating class instance instead of using function.

The second test has also been properly updated to use the class-based approach consistently.

backend/tests/apps/slack/commands/committees_test.py (2)

6-6: Import statement looks correct.


57-62: Test logic ensures commands are properly handled.

The approach of instantiating the Committees class and verifying ack and chat_postMessage calls is correct. This test effectively covers the scenario of SLACK_COMMANDS_ENABLED.

backend/apps/slack/commands/staff.py (1)

4-5: New imports align with the refactored architecture.

backend/tests/apps/slack/commands/chapters_test.py (4)

6-6: Good import change to reflect the new OOP structure.
No issues found; this aligns with the shift from procedural to class-based commands.


57-59: Tests correctly instantiate and invoke the Chapters class.
The approach is consistent with the new object-oriented refactor.


61-61: Ensuring the command is acknowledged.
The ack.assert_called_once() check is appropriate to verify that Slack commands are properly acknowledged.


81-83: Repeated pattern for chapter retrieval.
Again, this is consistent with the class-based command design and ensures the correct flow is tested.

backend/apps/slack/commands/policies.py (4)

4-5: Importing the base command class.
No issues here; referencing CommandBase aligns with the new command architecture.


7-9: Class definition aligns with the pattern for Slack commands.
Implementing policies as a subclass of CommandBase centralizes and standardizes command handling.


37-42: Efficient templating approach.
Rendering with self.get_template_file() centralizes layout and ensures consistency across commands.


10-35:

✅ Verification successful

Check that policy links are current and comprehensive.
Although the code is correct, policy links can break or become outdated over time. Periodic verification ensures users always see valid and up-to-date URLs.


🏁 Script executed:

#!/bin/bash
# Description: Check the validity of linked policy URLs by returning their HTTP status codes.

links=(
  "https://owasp.org/www-policy/operational/chapters"
  "https://owasp.org/www-policy/operational/code-of-conduct"
  "https://owasp.org/www-policy/operational/committees"
  "https://owasp.org/www-policy/operational/conflict-resolution"
  "https://owasp.org/www-policy/operational/conflict-of-interest"
  "https://owasp.org/www-policy/operational/donations"
  "https://owasp.org/www-policy/operational/election"
  "https://owasp.org/www-policy/operational/events"
  "https://owasp.org/www-policy/operational/expense-reimbursement"
  "https://owasp.org/www-policy/operational/grants"
  "https://owasp.org/www-policy/operational/membership"
  "https://owasp.org/www-policy/operational/projects"
  "https://owasp.org/www-policy/operational/whistleblower"
)

for link in "${links[@]}"; do
  echo "Checking: $link"
  curl -s -o /dev/null -w "%{http_code}\n" "$link"
done

Length of output: 4183


Verified: Policy Links are Updated and Valid

The policy URLs in backend/apps/slack/commands/policies.py (lines 10-35) have been verified and all returned HTTP status code 200, confirming that they are current and accessible. No changes are required at this time—please continue periodic checks to ensure ongoing link validity.

backend/tests/apps/slack/models/event_test.py (2)

3-3: Updated import to use the new Owasp class

The import has been updated to use the new class-based implementation, which is consistent with the PR's objective of refactoring command handlers into class-based implementations.


12-12: Correctly migrated to dynamic command retrieval

The previous hardcoded constants have been replaced with dynamic method calls to Owasp().get_command(), which ensures the command string is consistently derived from the class name. This is a good approach that reduces duplication and improves maintainability.

Also applies to: 57-57

backend/tests/apps/slack/commands/donate_test.py (3)

7-7: Updated import to use the new class-based implementation

The import has been correctly updated to use the new Donate class instead of the previous function-based implementation.


32-34: Successfully migrated to class-based handler

The test has been updated to instantiate the Donate class and call its handler method, which aligns with the new object-oriented design. This is consistent with the PR's objective of refactoring command handlers to use a template-based system.


62-64: Consistent application of the class-based pattern

The same pattern has been correctly applied here, using the class-based approach for the second test method as well. This maintains consistency throughout the test file.

backend/apps/slack/commands/projects.py (2)

3-3: Appropriate base class import

The import of CommandBase is correct and aligns with the PR's objective of creating a unified base for all command classes.


8-10: Well-structured class definition

The Projects class is properly defined as inheriting from CommandBase, with an appropriate docstring that clearly indicates its purpose.

backend/tests/apps/slack/commands/contact_test.py (3)

7-7: Updated import to use the new class-based implementation

The import has been correctly updated to use the new Contact class instead of the previous function-based implementation.


32-34: Successfully migrated to class-based handler

The test has been updated to instantiate the Contact class and call its handler method, which aligns with the new object-oriented design. This change maintains the same test behavior while adapting to the refactored command structure.


49-51: Consistent application of the class-based pattern

The same pattern has been correctly applied here, using the class-based approach for the second test method as well. This maintains consistency throughout the test file and properly validates the behavior of the command handler.

backend/tests/apps/slack/commands/community_test.py (3)

7-7: Change of import reflects new OOP structure

The import change from a function community_handler to a class Community indicates a shift from a procedural implementation to an object-oriented approach, which aligns with the PR objectives to make the codebase more maintainable.


34-35: Implementation follows OOP pattern consistently

The test now instantiates a Community class and calls its handler method instead of directly calling the function. This is consistent with the refactoring approach described in the PR objectives to implement a template-based system.


56-57: Consistent testing approach

The second test method follows the same pattern of instantiating the Community class and calling its handler method, maintaining consistency throughout the test file.

backend/tests/apps/slack/commands/events_test.py (2)

6-6: Import aligned with class-based architecture

The change from importing events_handler to importing the Events class aligns with the architectural shift to class-based commands in the PR.


72-74: OOP implementation with explicit ack mock

The test now creates an instance of the Events class and calls its handler method. The addition of an explicit MagicMock() for the ack parameter ensures proper testing of the acknowledgment functionality.

backend/tests/apps/slack/commands/board_test.py (3)

7-7: Updated import consistent with refactoring

The import change from board_handler function to the Board class reflects the consistent application of OOP principles across the codebase.


32-34: Refactored handler invocation

The test now creates a Board instance before the acknowledgment mock and calls the instance's handler method. This maintains the same test flow while adapting to the new class-based structure.


49-51: Consistent refactoring in block structure test

The second test method follows the same pattern of creating a Board instance and calling its handler method, ensuring consistency across all test methods.

backend/apps/slack/commands/users.py (2)

3-3: Updated imports for new class-based approach

The import statement now brings in the CommandBase class that serves as the foundation for the template-based system mentioned in the PR objectives.


8-10: New class structure consistent with PR objectives

The introduction of the Users class that inherits from CommandBase aligns with the PR's goal to introdu 8000 ce a base class for Slack commands. This allows for standardized handling of commands and template integration.

backend/tests/apps/slack/commands/contribute_test.py (5)

6-6: Looks good importing Contribute class.
The import of Contribute aligns with the new class-based command approach.


41-41: Mock return value usage approved.
Setting mock.return_value = 10 is appropriate and consistent with the test scenario for issue counts.


59-61: Successfully transitioned to class-based handler.
Instantiating Contribute and calling its handler method is a clean replacement for the old function-based approach.


63-63: Good use of ack assertion.
Confirming that ack is called exactly once ensures the handler logic aligns with Slack’s requirements.


83-85: Consistent pattern for class-based testing.
Reusing the instantiation and handler invocation confirms the class-based approach remains consistent in this test scenario.

backend/apps/slack/commands/board.py (4)

4-4: Appropriate import for the base command class.
CommandBase import affirms the shared structure for Slack commands.


7-8: Well-defined Board command class.
Creating a dedicated Board class with docstring clarifies its usage and improves maintainability.


10-12: Properly named template file.
Providing "navigate.template" ensures a distinct Jinja2 template for the /board command.


14-18: Jinja2 rendering approach is clear.
Using a template with placeholders for URL and name fosters maintainable, flexible responses.

backend/apps/slack/commands/jobs.py (4)

4-4: Consistent base command usage.
Importing CommandBase reflects the unified pattern for Slack commands.


5-9: Appropriate constants import.
Utilizing constants from apps.slack.constants promotes clarity and maintainability for Slack channel references.


12-13: Clear class documentation.
The Jobs class and docstring assist in understanding the purpose of this command.


15-24: Template-based text rendering is well-structured.
Populating placeholders for Slack channels and bot name aligns with the new Jinja2 rendering flow.

backend/apps/slack/commands/chapters.py (2)

3-3: New import aligns with the class-based architecture
No issues found. The import from CommandBase cleanly enables the new command structure.


9-10: Class-based refactor promotes clarity
Defining Chapters as a subclass of CommandBase is consistent with the new design. The docstring is succinct and helpful.

backend/apps/slack/commands/contribute.py (3)

4-4: Import for class-based command
This import is needed for the new approach. No concerns here.


10-11: Class creation aligns with new design
Instantiating Contribute from CommandBase promotes a consistent architecture for Slack commands.


13-16: Template rendering with NL
Passing NL=NL is a neat approach to handle newline needs within the template. Make sure templates requiring newlines are updated accordingly.

backend/apps/slack/commands/committees.py (2)

3-3: Import for class-based command
Using CommandBase lays the foundation for the revised Slack command structure. No issues found.


8-9: Committees class introduction
This direct subclass usage ensures each command is well-encapsulated. Class docstring is clear and descriptive.

backend/tests/apps/slack/commands/leaders_test.py (4)

7-7: Good update of import to align with class-based refactoring.

This change correctly imports the new Leaders class instead of the previous leaders_handler function to support the shift to an object-oriented approach.


67-70: Proper implementation of the new class-based handler structure.

This refactoring successfully transitions from directly calling the leaders_handler function to instantiating the Leaders class and invoking its handler method. This aligns with the new architecture while preserving the original test behavior.


107-108: Consistent implementation of the Leaders class pattern.

This section correctly follows the same pattern as in the previous test method, ensuring consistency throughout the test file.


146-149: Well-structured implementation of the Leaders class pattern.

This consistent implementation completes the refactoring of all test methods to use the new class-based approach, maintaining functionality while improving code organization.

backend/tests/apps/slack/commands/projects_test.py (3)

7-7: Good update of import to support class-based structure.

The import statement has been correctly updated to import the Projects class instead of the previous function-based handler.


54-61: Properly implemented class-based handler with improved assertions.

The refactoring correctly instantiates the Projects class and calls its handler method. The updated conditional structure with explicit assertions for both the enabled and disabled cases improves test clarity and coverage.


82-83: Consistent application of the Projects class pattern.

This section maintains the same pattern used in other test methods, ensuring consistency throughout the test suite.

backend/apps/slack/commands/events.py (3)

3-6: Streamlined imports for the new class-based implementation.

The imports have been properly simplified to include only what's necessary for the new class-based approach. This makes the code more maintainable and focused.


8-11: Well-structured class definition with clear docstrings.

The new Events class properly inherits from CommandBase and includes appropriate documentation, adhering to good object-oriented design principles.


11-38: Comprehensive implementation of template-based rendering.

This get_render_text method effectively processes event data and prepares it for templated rendering. The approach of categorizing events and constructing a structured data object enhances maintainability.

Note that the method includes proper handling of events with missing categories by assigning them to "Other", which prevents potential errors.

backend/apps/slack/commands/sponsor.py (2)

3-3: Simplified import for the new class-based implementation.

The import statement has been properly streamlined to import only the CommandBase class, which is all that's needed for this placeholder implementation.


6-11: Good placeholder implementation with minimal structure.

This is a clean implementation of a placeholder command. The class correctly inherits from CommandBase and the get_render_text method returns a simple "Coming soon..." message.

While minimal, this approach allows the command to be registered and function within the system while deferring the actual implementation details for later development.

backend/tests/apps/slack/commands/policies_test.py (6)

7-7: No issues with updated import
The new import aligns with the class-based approach for handling policies.


27-28: Instantiation of the Policies class
Creating an instance and calling handler shifts the functionality from a function-based to an object-oriented approach cleanly.


65-65: String building for policies
The use of a join operation and standard Slack formatting is clear and maintainable. No issues found.


67-67: Assertion with strip()
Excellent approach to handle extraneous whitespace in block text comparisons.


70-71: Updated footer text
The footer text now points to the central policy page. This helps users find more information easily.


73-73: Footer assertion
The assertion logic is straightforward and ensures correctness of the closing section.

backend/apps/slack/commands/sponsors.py (2)

4-5: Updated imports for class-based command
Switching to the CommandBase import matches the new design. No concerns.


9-26:

❓ Verification inconclusive

Refactored Sponsors command
This class-based approach centralizes the sponsor logic and uses Jinja2 templates. Ensure both the "no sponsors" and "some sponsors" scenarios are adequately tested.


🏁 Script executed:

#!/bin/bash
# Execute sponsor command tests
pytest backend/tests/apps/slack/commands/sponsors_test.py --maxfail=1 -q

Length of output: 171


Manual Verification Needed: Testing Environment Issue

The refactored Sponsors command using Jinja2 templates looks good. However, the testing command returned an error ("pytest: command not found"), which means we couldn't verify that both the "no sponsors" and "sponsors available" scenarios are adequately covered. Please ensure the test environment includes pytest (or run the tests manually/with an alternative command such as python -m pytest) and verify the functionality accordingly. Once the tests pass in a proper environment, you can merge confidently.

backend/apps/slack/commands/donate.py (3)

4-4: Base class import
The import from command is correct and aligns with the class-based design.


7-8: Introducing the Donate class
A clean transition from function-based to class-based command structure. The docstring is concise.


10-17: Template-based rendering for the donate command
The get_render_text method uses the inherited template:
• Incorporation of OWASP_WEBSITE_URL and placeholders is clear.

backend/tests/apps/slack/commands/staff_test.py (6)

1-1: Imports look appropriate.

No issues with adding ANY, MagicMock, patch to the imports. This aligns with the new mocking logic.


7-7: Confirmed import consistency.

Importing Staff is consistent with the usage in the tests below and adheres to the new class-based command structure.


65-67: New class-based testing approach is coherent.

Switching to instantiating Staff() and calling its handler method is consistent with the move to class-based commands. This test aligns well with the updated design.


82-82: Staff description check is valid.

Asserting staff["description"] in staff_block ensures that each staff member's description is displayed properly. No issues here.


85-87: Verifies correct user conversation.

Ensuring conversations_open is called with the Slack user ID confirms a proper Slack conversation is started. This is robust.


89-90: Blocks replaced with ANY.

Using ANY for the blocks parameter is appropriate when the test focuses on other response aspects (e.g., channel). This is acceptable for broader coverage.

backend/apps/slack/commands/community.py (4)

4-4: Import aligns with new command structure.

Importing CommandBase here conforms to the refactored class-based architecture for Slack commands.


7-8: New Community command class.

Defining Community as a subclass of CommandBase encapsulates command logic neatly, enhancing maintainability. Good approach.


10-12: Template file resolution.

get_template_file_name returning "navigate.template" passes a consistent template name downstream. Straightforward and clear.


14-20: Renders the community template.

get_render_text uses the Jinja2 template to embed the URL and name for the OWASP community. The code is succinct and leverages NL for formatting. Looks good.

backend/apps/slack/commands/gsoc.py (5)

3-7: Added timezone and utility imports.

Including django.utils.timezone, get_absolute_url, and CommandBase supports the new class-based Slack command approach. No issues found.


9-15: Relevant Slack constants.

Importing NEST_BOT_NAME and channel IDs centralizes Slack references for this command. This is consistent with the revised design.


22-25: Year-based logic setup.

Defining MARCH, now, and gsoc_year ensures the GSoC year is calculated correctly based on the current month. This is a sound approach to handle annual changes.


28-29: Gsoc class introduction.

Transitioning from a standalone function to the Gsoc class improves structure and maintainability.


31-70: Comprehensive text rendering for GSoC.

The get_render_text method:

  • Distinguishes between empty, numeric, and invalid command text cases.
  • Dynamically handles valid & invalid GSoC years, referencing SUPPORTED_YEARS.
  • Returns different template modes (general, year, unsupported_year, invalid).
  • Uses get_gsoc_projects for retrieving data, sorted for readability.

This neatly encapsulates logic and ensures clarity for Slack responses. Great use of templating.

backend/tests/apps/slack/commands/sponsors_test.py (3)

6-6: Good import alignment with new class structure.

Importing Sponsors aligns well with the refactoring into a class-based approach, matching the overall design of the PR.


77-79: LGTM.

Verifying conversations_open with the user ID is appropriate.


93-93: Assert partial match or exact match?

Checking for expected_header substring makes the test more flexible. If you want to ensure strict equality, revert to an equality check. Otherwise, this approach is suitable for handling possible formatting variations.

backend/apps/slack/commands/news.py (5)

4-5: Clear separation of command base and utility imports.

These imports are concise and reflect the new class-based design.


8-9: Class-based design improves maintainability.

Defining News as a subclass of CommandBase aligns with the PR's goal of modularizing Slack commands.


11-13: Simplified approach to news data retrieval.

Calling get_news_data() inside get_render_text is straightforward. Ensure any exceptions (e.g., network failures) are handled appropriately in upstream or the utility function.

Do we have a higher-level error handling strategy for get_news_data() calls? Please confirm coverage in related tests or usage logs.


14-22: Conditional template rendering looks good.

Using the template to handle both cases (has_news=True and has_news=False) is clean and maintains separation of logic from presentation.


23-26: Fallback path for no news items.

Returning a separate rendered template states “no news.” This elegantly handles empty data scenarios.

backend/apps/slack/commands/leaders.py (8)

5-6: Appropriate imports for the new command class.

Pulling in CommandBase and escape is consistent with the class-based Slack command pattern.


9-10: Descriptive class docstring.

The docstring for /leaders is concise and aligns with the Slack command naming conventions.


12-19: Correctly escaping user input to prevent injection.

Capturing and escaping command["text"] is a good step to avoid potential injection or formatting issues.


20-29: Selective attributes ensure minimal data retrieval.

Limiting returned fields (idx_key, idx_leaders, idx_name) can help performance and maintain focus in the Slack response.


31-37: Parallel retrieval logic for projects.

Consistent approach for get_projects ensures uniform handling of the user’s query.


39-47: Building chapter URLs enhances clarity.

Populating chapters_with_urls is understandable and helps later template rendering.


48-56: Consistent approach for project URLs.

Following the same pattern for projects_with_urls is maintainable and coherent.


57-64: Template rendering covers multiple use cases.

Passing has_results and the query details to the template ensures that UI can adapt to presence or absence of data.

backend/apps/slack/commands/owasp.py (5)

3-3: Migration to OOP structure
This updated import aligns with the new class-based design.


7-9: Class-based approach for /owasp command
Defining a dedicated Owasp class is consistent with the existing command architecture.


20-29: Chained command dispatch
Reusing command["text"] for sub-commands is reasonable. However, if the subsequent command expects multiple tokens, ensure they are preserved.


30-42: Template context ensures user input is escaped
User input is passed into the Jinja2 template. Please confirm autoescaping is enabled or that your templates sanitize/escape user input to prevent XSS.


10-19:

❓ Verification inconclusive

Check for potential edge cases in command lookup
The case-insensitive check is helpful, but confirm no unexpected partial matches or synonyms are required.

Run the following script to locate all find_command calls and confirm usage expectations:


🏁 Script executed:

#!/bin/bash
rg -A 5 'find_command'

Length of output: 1730


Edge Case Handling in Command Lookup

The current implementation in backend/apps/slack/commands/owasp.py does a case-insensitive equality check, which seems aligned with its usage (as observed in both production and test files). However, please verify whether the existing logic fully meets the requirements for handling edge cases (for example, unexpected partial matches, synonyms, extra whitespace, or alternate aliases). If these scenarios should be supported, we might need to adjust the lookup logic or supplement with additional tests.

backend/apps/slack/commands/contact.py (3)

4-4: Refactored import
Switching to CommandBase is consistent with the project's new class-based pattern.


7-8: Dedicated class for /contact command
This class-based structure neatly encapsulates the /contact logic.


14-18: Context injection for contact template
Providing url, name, and NL is appropriate. Ensure the template references them correctly.

backend/tests/apps/slack/commands/gsoc_test.py (6)

7-7: Class import for Gsoc
Replacing the old function import with the Gsoc class aligns with the new design.


11-11: Test class rename
Renaming to TestGsocCommand better reflects that this test targets the Gsoc command class.


33-33: Improved feedback message
Simplifying the invalid command message to "*/gsoc invalid is not supported" is clear and user-friendly.


48-50: Instantiation and handler call
Instantiating Gsoc and then invoking handler is consistent with the new OOP approach.


63-63: Refined assertion
Checking for "Getting Started with OWASP GSoC" corresponds to the updated template content.


78-83: Mock project retrieval workflow
Properly patches get_gsoc_projects and verifies the resulting Slack blocks. This test flow validates the new approach thoroughly.

backend/apps/slack/commands/command.py (5)

1-9: Well-structured imports and documentation.

The file has a clear module docstring and well-organized imports, separating standard library, Django, and application-specific imports.


11-24: Good use of OOP principles with static methods for command management.

The CommandBase class is well-designed with static methods to handle subclass discovery and configuration. This approach makes it easy to register all commands in a centralized way.


57-60: LGTM!

Good approach for command naming based on class name. This provides a consistent convention.


61-73: Good template file handling approach.

The methods for getting template files and their names follow a consistent naming convention based on the class name. This is a clean approach that will make template management straightforward.


74-77: LGTM!

Effective command registration with the Slack app.

backend/tests/apps/slack/commands/news_test.py (5)

6-10: Class rename aligns with the new command-based approach.

The import change and class rename from TestNewsHandler to TestNewsCommand appropriately reflect the shift to the command-based architecture.


21-27: Improved parameterization formatting.

The test parameterization has been reformatted for better readability while maintaining the same logic.


50-51: Object-oriented approach correctly implemented.

The test now properly instantiates a News object and calls its handler method instead of using the previous function-based approa 8000 ch.


60-61: Updated assertion logic for block content verification.

The assertion logic has been updated to use in for checking block content and modified the slicing to exclude the last two blocks. This accommodates changes in the response format from the template-based system.


82-84: Object-oriented approach consistently applied.

The test for the no-items case also properly instantiates a News object and calls its handler method.

backend/tests/apps/slack/commands/sponsor_test.py (3)

6-9: Class rename aligns with the new command-based approach.

The import change and class rename from TestSponsorHandler to TestSponsorCommand appropriately reflect the shift to the command-based architecture.


30-31: Object-oriented approach correctly implemented.

The test now properly instantiates a Sponsor object and calls its handler method instead of using the previous function-based approach.


39-40: Updated expected text to match template output.

The expected text has been simplified from f"Coming soon...{NL}" to just "Coming soon...", which matches the implementation in the Sponsor class's get_render_text method.

backend/tests/apps/slack/commands/owasp_test.py (5)

37-40: Object-oriented approach correctly implemented.

The test now properly instantiates an Owasp object and calls its handler method instead of using the previous function-based approach.


48-49: Good additional assertion for disabled commands.

Added an important assertion to verify that when commands are disabled, chat_postMessage is not called.


51-53: Simplified parameter format.

The parameterization has been simplified to a single list of subcommands, which is cleaner than the previous approach.


57-68: Improved patching approach.

The patching logic has been updated to use patch.object on the Owasp.find_command method, which is more direct and easier to understand than the previous module path patching approach.


70-75: Updated assertion logic for command output.

The test now checks the content of the blocks directly, which is more straightforward than the previous approach.

@Li-ChangC Li-ChangC requested a review from kasya as a code owner April 8, 2025 08:04
Copy link
Contributor
8000 @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: 2

🧹 Nitpick comments (10)
backend/apps/slack/commands/jobs.py (1)

11-24: Consider naming consistency for feedback_channel.

In get_template_context, feedback_channel is set to OWASP_PROJECT_NEST_CHANNEL_ID, which might lead to some confusion if it’s genuinely intended for feedback collection. Consider renaming the variable or adding a short comment clarifying its purpose. This also helps maintain clarity if used outside the scope of actual feedback.

backend/apps/slack/commands/contribute.py (1)

31-35: The get_template_context method is redundant

This method doesn't add any unique context beyond what's already provided by the parent class. Since it simply returns the parent's context without modification, you can safely remove this method.

-    def get_template_context(self, command):
-        """Get the template context."""
-        return {
-            **super().get_template_context(command),
-        }
backend/apps/slack/commands/leaders.py (2)

12-14: Consider moving imports to module level.

While importing functions inside a method follows good practice for import locality, consider moving these imports to the module level if they're always needed by this command. This would avoid repeated import overhead on each command execution.

from apps.common.utils import get_absolute_url
from apps.slack.commands.command import CommandBase
+from apps.owasp.api.search.chapter import get_chapters
+from apps.owasp.api.search.project import get_projects


class Leaders(CommandBase):
    """Slack bot /leaders command."""

    def get_template_context(self, command):
        """Get the template context."""
-        from apps.owasp.api.search.chapter import get_chapters
-        from apps.owasp.api.search.project import get_projects

54-63: Consider using a single dictionary creation for context.

For consistency with other command implementations (like Sponsors), consider using a single dictionary creation pattern that extends the parent context, rather than creating and then updating it separately.

-        context = super().get_template_context(command)
-        context.update(
-            {
-                "chapters": chapters_with_urls,
-                "projects": projects_with_urls,
-                "search_query": search_query,
-                "has_results": bool(chapters or projects),
-            }
-        )
-        return context
+        return {
+            **super().get_template_context(command),
+            "chapters": chapters_with_urls,
+            "projects": projects_with_urls,
+            "search_query": search_query,
+            "has_results": bool(chapters or projects),
+        }
backend/apps/slack/commands/gsoc.py (1)

33-75: Consider breaking down the template context creation into helper methods.

The current implementation uses a series of conditionals to update the template context. As this function grows, it may become harder to maintain. Consider breaking it down into smaller helper methods for each mode.

    def get_template_context(self, command):
        """Get the template context."""
+        now = timezone.now()
+        gsoc_year = now.year if now.month > MARCH else now.year - 1
        command_text = command["text"].strip()
        context = super().get_template_context(command)
        template_context = {"mode": "", "command": self.get_command()}

        if not command_text or command_text in COMMAND_START:
-            template_context.update(
-                {
-                    "mode": "general",
-                    "gsoc_channel": OWASP_GSOC_CHANNEL_ID,
-                    "contribute_channel": OWASP_CONTRIBUTE_CHANNEL_ID,
-                    "previous_year": gsoc_year,
-                    "projects_url": projects_url,
-                    "feedback_channel": OWASP_PROJECT_NEST_CHANNEL_ID,
-                    "nest_bot_name": NEST_BOT_NAME,
-                }
-            )
+            template_context.update(self._get_general_context(gsoc_year))
        elif command_text.isnumeric():
            year = int(command_text)
            if year in SUPPORTED_YEARS:
-                template_context.update(
-                    {
-                        "mode": "year",
-                        "year": year,
-                        "projects": sorted(get_gsoc_projects(year), key=lambda p: p["idx_name"]),
-                        "has_announcement": year in SUPPORTED_ANNOUNCEMENT_YEARS,
-                    }
-                )
+                template_context.update(self._get_year_context(year))
            else:
-                template_context.update(
-                    {
-                        "mode": "unsupported_year",
-                        "year": year,
-                        "supported_start": SUPPORTED_YEAR_START,
-                        "supported_end": SUPPORTED_YEAR_END,
-                    }
-                )
+                template_context.update(self._get_unsupported_year_context(year))
        else:
-            template_context.update(
-                {
-                    "mode": "invalid",
-                    "command_text": command_text,
-                }
-            )
+            template_context.update(self._get_invalid_context(command_text))

        context.update(template_context)
        return context
+
+    def _get_general_context(self, gsoc_year):
+        """Get the general context."""
+        return {
+            "mode": "general",
+            "gsoc_channel": OWASP_GSOC_CHANNEL_ID,
+            "contribute_channel": OWASP_CONTRIBUTE_CHANNEL_ID,
+            "previous_year": gsoc_year,
+            "projects_url": projects_url,
+            "feedback_channel": OWASP_PROJECT_NEST_CHANNEL_ID,
+            "nest_bot_name": NEST_BOT_NAME,
+        }
+
+    def _get_year_context(self, year):
+        """Get the year context."""
+        return {
+            "mode": "year",
+            "year": year,
+            "projects": sorted(get_gsoc_projects(year), key=lambda p: p["idx_name"]),
+            "has_announcement": year in SUPPORTED_ANNOUNCEMENT_YEARS,
+        }
+
+    def _get_unsupported_year_context(self, year):
+        """Get the unsupported year context."""
+        return {
+            "mode": "unsupported_year",
+            "year": year,
+            "supported_start": SUPPORTED_YEAR_START,
+            "supported_end": SUPPORTED_YEAR_END,
+        }
+
+    def _get_invalid_context(self, command_text):
+        """Get the invalid context."""
+        return {
+            "mode": "invalid",
+            "command_text": command_text,
+        }
backend/apps/slack/commands/owasp.py (2)

10-18: Consider using a more efficient command lookup mechanism.

The current implementation iterates through all command classes to find a matching one. As the number of commands grows, this could become inefficient. Consider using a dictionary to map command names to command classes.

class Owasp(CommandBase):
    """Slack bot /owasp command."""

+    def __init__(self):
+        """Initialize the command."""
+        super().__init__()
+        self._command_map = None
+
+    def _build_command_map(self):
+        """Build the command map."""
+        self._command_map = {}
+        for cmd_class in [cls for cls in CommandBase.get_all_commands() if cls is not Owasp]:
+            self._command_map[cmd_class.__name__.lower()] = cmd_class
+
    def find_command(self, command_name):
        """Find the command class by name."""
        if not command_name:
            return None

-        for cmd_class in [cls for cls in CommandBase.get_all_commands() if cls is not Owasp]:
-            if cmd_class.__name__.lower() == command_name.lower():
-                return cmd_class()
-        return None
+        if self._command_map is None:
+            self._build_command_map()
+        
+        cmd_class = self._command_map.get(command_name.lower())
+        return cmd_class() if cmd_class else None

22-26: Consider making a copy of the command object when delegating.

The current implementation modifies the command's text in-place before delegating, which could lead to confusion if the original command text is referenced elsewhere in the code. Consider creating a new command object or using a different approach to pass the command parameters.

    def handler(self, ack, command, client):
        """Handle the command."""
        command_tokens = command["text"].split()
        cmd = self.find_command(command_tokens[0].strip().lower() if command_tokens else "")
        if cmd:
-            command["text"] = " ".join(command_tokens[1:]).strip()
-            return cmd.handler(ack, command, client)
+            # Create a modified copy of the command object
+            delegated_command = command.copy()
+            delegated_command["text"] = " ".join(command_tokens[1:]).strip()
+            return cmd.handler(ack, delegated_command, client)
backend/apps/slack/commands/command.py (3)

1-2: Rename the file-level docstring to reflect the base class more accurately.

Currently, the docstring says "Slack bot board command," but this file implements a generic CommandBase. Renaming it to something like "Slack bot base command" maintains clarity and consistency.

-"""Slack bot board command."""
+"""Slack bot base command."""

35-47: Optional caching or preloading of Jinja2 templates.

If your deployment environment frequently loads and renders the same template, you could consider caching the template object. This would reduce the overhead of loading templates repeatedly, though this is typically minor for smaller projects.


101-135: Consider providing ephemeral responses or user context.

Currently, all messages are sent via direct message to the user’s Slack channel. As an enhancement, you could:

  1. Offer an option for ephemeral (in-channel) responses if needed.
  2. Log minimal user context (e.g., user ID) at debug level for troubleshooting, if privacy policies permit.

These steps can help refine the user experience and debugging workflow.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 333de5c and ccef73c.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • backend/apps/slack/commands/board.py (1 hunks)
  • < 8000 code class="notranslate">backend/apps/slack/commands/command.py (1 hunks)
  • backend/apps/slack/commands/community.py (1 hunks)
  • backend/apps/slack/commands/contact.py (1 hunks)
  • backend/apps/slack/commands/contribute.py (2 hunks)
  • backend/apps/slack/commands/donate.py (1 hunks)
  • backend/apps/slack/commands/events.py (1 hunks)
  • backend/apps/slack/commands/gsoc.py (1 hunks)
  • backend/apps/slack/commands/jobs.py (1 hunks)
  • backend/apps/slack/commands/leaders.py (1 hunks)
  • backend/apps/slack/commands/news.py (1 hunks)
  • backend/apps/slack/commands/owasp.py (1 hunks)
  • backend/apps/slack/commands/policies.py (1 hunks)
  • backend/apps/slack/commands/sponsors.py (1 hunks)
  • backend/apps/slack/commands/staff.py (1 hunks)
  • backend/apps/slack/templates/donate.template (1 hunks)
  • backend/apps/slack/templates/jobs.template (1 hunks)
  • backend/apps/slack/templates/leaders.template (1 hunks)
  • backend/apps/slack/templates/staff.template (1 hunks)
  • frontend/.dockerignore (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/.dockerignore
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/apps/slack/templates/jobs.template
  • backend/apps/slack/templates/donate.template
  • backend/apps/slack/templates/staff.template
🧰 Additional context used
🧬 Code Definitions (11)
backend/apps/slack/commands/news.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/utils.py (1)
  • get_news_data (59-91)
backend/apps/slack/commands/policies.py (14)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/commands/board.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/contact.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/community.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/donate.py (1)
  • get_template_context (10-15)
backend/apps/slack/commands/contribute.py (1)
  • get_template_context (31-35)
backend/apps/slack/commands/events.py (1)
  • get_template_context (11-36)
backend/apps/slack/commands/jobs.py (1)
  • get_template_context (14-24)
backend/apps/slack/commands/gsoc.py (1)
  • get_template_context (30-77)
backend/apps/slack/commands/leaders.py (1)
  • get_template_context (10-63)
backend/apps/slack/commands/owasp.py (1)
  • get_template_context (30-40)
backend/apps/slack/commands/sponsors.py (1)
  • get_template_context (12-22)
backend/apps/slack/commands/staff.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/news.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/board.py (3)
backend/apps/slack/commands/command.py (3)
  • CommandBase (16-134)
  • get_template_file_name (97-99)
  • get_template_context (48-63)
backend/apps/slack/commands/contact.py (2)
  • get_template_file_name (9-11)
  • get_template_context (13-19)
backend/apps/slack/commands/community.py (2)
  • get_template_file_name (9-11)
  • get_template_context (13-19)
backend/apps/slack/commands/events.py (4)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/utils.py (1)
  • get_events_data (120-133)
backend/apps/slack/commands/donate.py (1)
  • get_template_context (10-15)
backend/apps/slack/commands/staff.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/donate.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/commands/staff.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/jobs.py (1)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/commands/contribute.py (1)
backend/apps/slack/commands/command.py (3)
  • CommandBase (16-134)
  • get_render_blocks (65-82)
  • get_template_context (48-63)
backend/apps/slack/commands/leaders.py (1)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/commands/owasp.py (1)
backend/apps/slack/commands/command.py (4)
  • CommandBase (16-134)
  • get_all_commands (25-27)
  • handler (105-134)
  • get_template_context (48-63)
backend/apps/slack/commands/gsoc.py (3)
backend/apps/common/utils.py (1)
  • get_absolute_url (13-23)
backend/apps/slack/commands/command.py (3)
  • CommandBase (16-134)
  • get_template_context (48-63)
  • get_command (84-86)
backend/apps/slack/utils.py (1)
  • get_gsoc_projects (34-55)
backend/apps/slack/commands/command.py (2)
backend/apps/slack/apps.py (1)
  • SlackConfig (10-21)
backend/apps/slack/blocks.py (1)
  • markdown (14-27)
🔇 Additional comments (30)
backend/apps/slack/templates/leaders.template (1)

1-37: The template structure looks well organized and follows best practices for template design.

The template effectively handles different display scenarios:

  1. Shows chapters with their leaders when available
  2. Shows projects with their leaders when available
  3. Displays a "No results found" message when there are no matches
  4. Highlights matching leaders by formatting them as code when they match the search query

The use of Jinja2 conditionals and loops is clean and readable, making future maintenance easier. This template-based approach aligns well with the PR objectives of separating logic from presentation and enabling future localization.

backend/apps/slack/commands/policies.py (3)

3-4: Import correctly uses the new CommandBase class.

The import statement properly references the base class from which all command classes now inherit, following the new architecture.


6-8: Good class definition with descriptive docstring.

The class definition follows the established pattern of inheriting from CommandBase and includes a clear docstring explaining its purpose.


9-39: Well-structured method for template context preparation.

The get_template_context method properly:

  1. Defines a comprehensive list of policies with their titles and URLs
  2. Extends the parent context using the super() call
  3. Returns a merged context dictionary with the policies list

This implementation aligns well with the pattern used across other command files and provides a clean separation between data and presentation.

backend/apps/slack/commands/staff.py (3)

3-6: Imports are clear and appropriate.

The imports correctly include:

  1. Constants needed for URLs
  2. The CommandBase class for inheritance
  3. The utility function to retrieve staff data

8-10: Class definition follows the established pattern.

The Staff class properly inherits from CommandBase and includes a descriptive docstring.


11-22: Well-implemented template context method.

The get_template_context method:

  1. Properly calls the parent's context method first
  2. Retrieves staff data using the appropriate utility function
  3. Updates the context with staff-specific information including:
    • Flag indicating if staff data is available
    • The staff items themselves
    • Website URL for reference

The implementation is consistent with the approach used in other command files.

backend/apps/slack/commands/board.py (4)

3-3: Import correctly uses the new CommandBase class.

The import statement properly references the base class from which all command classes now inherit.


6-8: Class definition follows the established pattern.

The Board class properly inherits from CommandBase and includes a descriptive docstring.


9-11: Template file name is correctly overridden.

This method properly overrides the default implementation from CommandBase to specify a shared template ("navigate.template") rather than using a command-specific one. This promotes template reuse across commands with similar display patterns.


13-19: Well-implemented template context method.

The get_template_context method:

  1. Properly calls the parent's context method using super()
  2. Extends the context with command-specific data (URL and name)
  3. Uses the Python dictionary unpacking operator (**) correctly

This implementation is consistent with the pattern used in other similar commands that navigate to external resources.

backend/apps/slack/commands/jobs.py (1)

3-8: Imports are clear and aligned with new command structure.

These imports correctly bring in the base command class and relevant constants. Nothing problematic here.

backend/apps/slack/commands/news.py (3)

3-5: Imports for news retrieval and base command are appropriate.

All necessary references are succinctly included with no apparent issues.


8-9: Class definition aligns with the new Slack command convention.

The News class neatly inherits from CommandBase, enabling the codebase’s updated approach to Slack commands.


11-23:

❓ Verification inconclusive

Consider verifying handling of failed or empty news data.

While the code appears correct, ensure get_news_data() consistently returns a list—even on error. If there’s any possibility of returning None or raising an exception, handle or log it to avoid potential runtime errors.

Run this script to confirm unit tests cover scenarios where get_news_data() is empty or fails:


🏁 Script executed:

#!/bin/bash
# Searches for test cases covering get_news_data usage within the Slack news command tests.
fd -e py -I -t f | xargs rg -A 5 "test_.*news" 

Length of output: 1710


Ensure Consistent Return Type for News Data

The tests confirm that when there are no news items (see test_news_handler_no_items), get_news_data() correctly returns an empty list. However, please verify that the implementation of get_news_data() guarantees a list even under error conditions. If there’s any chance it could return None or raise an exception, consider adding error handling or logging to avoid potential runtime issues.

backend/apps/slack/commands/events.py (2)

3-5: Imports are concise and purposeful.

These references correctly link the base command, event retrieval, and constants. Nothing appears problematic.


8-9: Class definition is consistent with the command redesign.

Adhering to the inherited CommandBase pattern fosters maintainability and uniformity across Slack commands.

backend/apps/slack/commands/contribute.py (1)

12-17: Good implementation of conditional rendering

The approach to handling both help commands and search queries is well-implemented. For help commands, it delegates to the parent class's template-based rendering, while for search queries it uses the specialized get_blocks function.

backend/apps/slack/commands/contact.py (2)

9-11: Good reuse of shared templates

Using a shared navigate.template for multiple navigation-type commands is an efficient approach that promotes code reuse.


13-19: Clean implementation of template context

The context provision is clear and appropriately extends the parent class's context. The URL and name parameters will be correctly passed to the template.

backend/apps/slack/commands/donate.py (2)

10-15: Clean implementation of template context

The template context is properly extended with the website URL. The approach is consistent with other commands.


7-8:

✅ Verification successful

Note on template file name

Unlike some other commands, this class doesn't override get_template_file_name(), so it will use the default implementation which returns "donate.template". Make sure this template file exists.


🏁 Script executed:

#!/bin/bash
# Check if the donate.template file exists in the templates directory
echo "Checking for donate.template file..."
fd "donate.template" --type f

Length of output: 157


Donor Template File Confirmed

The donate.template file exists at backend/apps/slack/templates/donate.template as expected. No changes are required regarding the reference to the template in the Donate class.

backend/apps/slack/commands/community.py (2)

9-11: Good reuse of shared templates

Using a shared navigate.template for multiple navigation-type commands is an efficient approach that promotes code reuse, consistent with the approach in the Contact command.


13-19: Clean implementation of template context

The context provision is clear and appropriately extends the parent class's context. The URL and name parameters will be correctly passed to the template.

backend/apps/slack/commands/sponsors.py (1)

9-22: Great implementation of the new Sponsors command class!

This class cleanly implements the template context for the /sponsors command, inheriting from CommandBase. The implementation correctly sets up the template context with sponsor data and all necessary parameters for rendering.

backend/apps/slack/commands/owasp.py (1)

20-28:

❓ Verification inconclusive

Ensure the handler properly acknowledges commands.

When delegating to another command, it's important to ensure that the command is properly acknowledged. The current implementation passes the ack callback directly to the delegated command, which works if each command handler acknowledges the request, but could lead to missed acknowledgments if that behavior changes.


🌐 Web query:

What is the purpose of the "ack" function in the Slack Bolt framework?

💡 Result:

The ack() function in the Slack Bolt framework serves as a mandatory acknowledgment mechanism to confirm receipt of incoming events like actions, commands, and modal submissions. This ensures Slack’s UI remains responsive and avoids user-facing errors.

Key purposes of ack()

  1. Immediate confirmation to Slack

    • Required within 3 seconds for interactive components (buttons, menus, modals) and slash commands[7][9].
    • Prevents Slack from displaying a "failed to respond" message to users[7][9].
  2. UI state management

    • Removes interactive elements (like buttons) after acknowledgment to prevent duplicate interactions[7][9].
    • For modals, can update UI with validation errors:
      app.view('modal_callback', async ({ ack, view }) => {
        if (invalidInput) {
          await ack({
            response_action: 'errors',
            errors: { field_id: 'Invalid value' }
          });
        } else {
          await ack();
        }
      });
  3. Payload validation gateway

    • Lets apps validate inputs before proceeding with business logic[1][7].
    • Example workflow:
      1. Receive interaction → 2. Validate using ack() → 3. Process data if valid[3][7].
  4. Compatibility with async workflows

    • Returns a promise that resolves when Slack processes the acknowledgment[1][7].
    • Can be used with async/await for controlled flow:
      app.command('/weather', async ({ ack, respond }) => {
        await ack(); // Immediate confirmation
        const data = await fetchWeather();
        await respond(`Forecast: ${data}`);
      });

Special cases

  • Options requests: Requires returning dynamic dropdown options via ack()[5][7].
  • Socket Mode: Works seamlessly with WebSocket-based connections[6][8].
  • Workflow Builder: Enables synchronous handling for dynamic options with autoAcknowledge: false[5].

Failure to call ack() results in Slack retrying the request and users seeing timeout errors[7][9]. The function is distinct from say()/respond(), which handle message responses[1][7].

Citations:


ACK Acknowledgment Consistency

Please verify that all delegated command handlers invoked via cmd.handler(ack, command, client) (and ultimately the fallback via super().handler(...)) call the mandatory ack() within the required timefr 6D40 ame. In the Slack Bolt framework, failing to acknowledge an incoming command promptly results in Slack timing out the request. Consider adding either explicit fallback acknowledgment or logging to detect any delegated handlers that might miss calling ack(), especially as changes occur.

backend/apps/slack/commands/command.py (4)

16-23: Well-documented usage of special template delimiters.

Thank you for clarifying the usage of {{ SECTION_BREAK }} and {{ DIVIDER }} in the docstring. This helps future contributors understand how to structure templates properly.


48-64: Context generation appears flexible and maintainable.

The inclusion of placeholders (e.g., SECTION_BREAK, DIVIDER) plus NL in the context is well organized, enabling consistent references to special tokens. No issues found here.


65-100: Good implementation of block splitting logic.

Splitting on {{ SECTION_BREAK }} and checking for a standalone {{ DIVIDER }} aligns well with your documented approach. This design is straightforward and easy to extend for future Slack blocks if needed.


24-34:

❓ Verification inconclusive

Validate the discovery of all command subclasses.

The approach using __subclasses__() is sound, but ensure that any command subclasses are imported so they're recognized by this method at runtime. Unimported subclasses won't appear in CommandBase.__subclasses__().

Here is an optional script to verify the presence of your command subclasses in the codebase:


🏁 Script executed:

#!/bin/bash
# Description: Search for classes inheriting from CommandBase and compare results with usage in config_all_commands.

echo "Classes inheriting from CommandBase:"
ast-grep --pattern $'class $_ extends CommandBase {\n  $$$\n}' -A 5

echo "Occurrences of config_command calls:"
rg -A 3 "def config_command"

Length of output: 553


Ensure all command subclasses are imported and verify the AST search targets Python syntax.

The current implementation using CommandBase.__subclasses__() is appropriate—but it will only capture those subclasses that have already been imported at runtime. Please double-check that every module defining a command subclass (e.g., those declared as class MyCommand(CommandBase):) is imported before get_all_commands() is called.

Notably, the optional verification script used an AST pattern designed for a language with an extends keyword rather than Python’s inheritance syntax. For Python, you might want to update the pattern—for example, searching for a regular expression like class .*\\(CommandBase\\):—to effectively detect all subclasses.

• Verify that all files defining command subclasses are imported so that __subclasses__() returns the complete set.
• Update the verification script to use a Python-compatible pattern to avoid false negatives.

@github-actions github-actions bot removed the frontend label Apr 8, 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: 0

🧹 Nitpick comments (4)
backend/apps/slack/commands/events.py (1)

17-30: Categorization logic is clean and well-structured.

The event categorization into a dictionary with nested event details is well-implemented. This structure will be easy to iterate through in templates.

Consider a slight optimization for larger datasets using a defaultdict:

- categorized_events = {}
- for event in sorted_events:
-     category = event.category or "Other"
-     if category not in categorized_events:
-         categorized_events[category] = {"events": []}
+ from collections import defaultdict
+ categorized_events = defaultdict(lambda: {"events": []})
+ for event in sorted_events:
+     category = event.category or "Other"

This would eliminate the need for the conditional check on each iteration, but the current implementation is perfectly valid for typical event datasets.

backend/apps/slack/commands/gsoc.py (3)

21-22: Consider making SUPPORTED_YEAR_END dynamically calculated.

The hardcoded SUPPORTED_YEAR_END = 2025 will require periodic maintenance as time passes. Consider calculating this value dynamically based on the current year plus some reasonable buffer (e.g., current year + 2).

-SUPPORTED_YEAR_END = 2025
+SUPPORTED_YEAR_END = timezone.now().year + 2
SUPPORTED_YEARS = set(range(SUPPORTED_YEAR_START, SUPPORTED_YEAR_END + 1))

28-77: Consider breaking down the method into smaller, more focused methods.

The get_template_context method handles multiple different modes and scenarios. To improve readability and maintainability, consider breaking it down into smaller, more focused methods for each mode.

def get_template_context(self, command):
    """Get the template context."""
    now = timezone.now()
    gsoc_year = now.year if now.month > MARCH else now.year - 1
    command_text = command["text"].strip()
    context = super().get_template_context(command)
    template_context = {"mode": "", "command": self.get_command()}

    if not command_text or command_text in COMMAND_START:
-       template_context.update(
-           {
-               "mode": "general",
-               "gsoc_channel": OWASP_GSOC_CHANNEL_ID,
-               "contribute_channel": OWASP_CONTRIBUTE_CHANNEL_ID,
-               "previous_year": gsoc_year,
-               "projects_url": projects_url,
-               "feedback_channel": OWASP_PROJECT_NEST_CHANNEL_ID,
-               "nest_bot_name": NEST_BOT_NAME,
-           }
-       )
+       self._update_context_for_general(template_context, gsoc_year)
    elif command_text.isnumeric():
        year = int(command_text)
        if year in SUPPORTED_YEARS:
-           template_context.update(
-               {
-                   "mode": "year",
-                   "year": year,
-                   "projects": sorted(get_gsoc_projects(year), key=lambda p: p["idx_name"]),
-                   "has_announcement": year in SUPPORTED_ANNOUNCEMENT_YEARS,
-               }
-           )
+           self._update_context_for_supported_year(template_context, year)
        else:
-           template_context.update(
-               {
-                   "mode": "unsupported_year",
-                   "year": year,
-                   "supported_start": SUPPORTED_YEAR_START,
-                   "supported_end": SUPPORTED_YEAR_END,
-               }
-           )
+           self._update_context_for_unsupported_year(template_context, year)
    else:
-       template_context.update(
-           {
-               "mode": "invalid",
-               "command_text": command_text,
-           }
-       )
+       self._update_context_for_invalid(template_context, command_text)

    context.update(template_context)
    return context

+def _update_context_for_general(self, template_context, gsoc_year):
+    """Update template context for general information."""
+    template_context.update(
+        {
+            "mode": "general",
+            "gsoc_channel": OWASP_GSOC_CHANNEL_ID,
+            "contribute_channel": OWASP_CONTRIBUTE_CHANNEL_ID,
+            "previous_year": gsoc_year,
+            "projects_url": projects_url,
+            "feedback_channel": OWASP_PROJECT_NEST_CHANNEL_ID,
+            "nest_bot_name": NEST_BOT_NAME,
+        }
+    )
+
+def _update_context_for_supported_year(self, template_context, year):
+    """Update template context for a supported year."""
+    template_context.update(
+        {
+            "mode": "year",
+            "year": year,
+            "projects": sorted(get_gsoc_projects(year), key=lambda p: p["idx_name"]),
+            "has_announcement": year in SUPPORTED_ANNOUNCEMENT_YEARS,
+        }
+    )
+
+def _update_context_for_unsupported_year(self, template_context, year):
+    """Update template context for an unsupported year."""
+    template_context.update(
+        {
+            "mode": "unsupported_year",
+            "year": year,
+            "supported_start": SUPPORTED_YEAR_START,
+            "supported_end": SUPPORTED_YEAR_END,
+        }
+    )
+
+def _update_context_for_invalid(self, template_context, command_text):
+    """Update template context for an invalid command."""
+    template_context.update(
+        {
+            "mode": "invalid",
+            "command_text": command_text,
+        }
+    )

30-31: Consider using a constant for greater flexibility in GSoC year determination.

The logic for determining the GSoC year is currently based on the month of March. To make this more flexible in case the GSoC schedule changes in the future, consider using a named constant for the cutoff month.

-now = timezone.now()
-gsoc_year = now.year if now.month > MARCH else now.year - 1
+now = timezone.now()
+# Month after which the GSoC year is considered to be the current year
+gsoc_year = now.year if now.month > MARCH else now.year - 1
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ccef73c and b41924e.

📒 Files selected for processing (2)
  • backend/apps/slack/commands/events.py (1 hunks)
  • backend/apps/slack/commands/gsoc.py (1 hunks)
🧰 Additional context used
🧬 Code Definitions (2)
backend/apps/slack/commands/events.py (10)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/utils.py (1)
  • get_events_data (120-133)
backend/apps/slack/commands/board.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/contact.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/community.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/donate.py (1)
  • get_template_context (10-15)
backend/apps/slack/commands/news.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/policies.py (1)
  • get_template_context (9-39)
backend/apps/slack/commands/sponsors.py (1)
  • get_template_context (12-22)
backend/apps/slack/commands/staff.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/gsoc.py (3)
backend/apps/common/utils.py (1)
  • get_absolute_url (13-23)
backend/apps/slack/commands/command.py (3)
  • CommandBase (16-134)
  • get_template_context (48-63)
  • get_command (84-86)
backend/apps/slack/utils.py (1)
  • get_gsoc_projects (34-55)
🔇 Additional comments (4)
backend/apps/slack/commands/events.py (3)

13-15: Well-handled data preparation!

You've properly addressed the potential None return from get_events_data() with or [], and included filtering and sorting of events. This ensures the template will receive well-formatted data even in edge cases.


32-36: Clean context preparation with proper inheritance.

Good job using the superclass context as a base and extending it with the specific data needed for this command. This approach ensures consistency across all commands while allowing for command-specific customization.


8-9: Well-implemented class following the new template system.

The Events class follows the CommandBase pattern consistently with the other commands in the system. This implementation aligns perfectly with the PR objectives of refactoring hardcoded text messages into a template-based system.

backend/apps/slack/commands/gsoc.py (1)

28-77: The implementation properly addresses the previous review suggestion.

The dynamic variables now and gsoc_year are now calculated inside the method, ensuring they're always up-to-date when the command is executed, even for long-running server instances.

@Li-ChangC
Copy link
Contributor Author

Hi @arkid15r, would you please review this PR at your convenience?
Totally understand it’s a bit large — happy to make any adjustments if needed. Thanks so much!

@Li-ChangC Li-ChangC force-pushed the feature/template-system branch from ce0b46e to 9c39dd7 Compare April 11, 2025 20:33
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: 2

♻️ Duplicate comments (1)
backend/apps/slack/commands/events.py (1)

13-13: Properly handled potential None return from get_events_data().

The code correctly uses the or [] pattern to provide a default empty list if get_events_data() returns None, addressing the issue identified in a previous review.

🧹 Nitpick comments (14)
backend/tests/apps/slack/commands/gsoc_test.py (3)

48-50: Handler invocation updated to use class-based approach.

The code now properly instantiates the Gsoc class and calls its handler method instead of directly calling a function. This correctly implements the transition to the class-based command system.

Consider creating a fixture for the Gsoc instance to improve test efficiency and reduce duplication.

 @pytest.fixture
+def gsoc_command(self):
+    return Gsoc()
+
+@pytest.fixture
 def mock_slack_command(self):
     return {
         "text": "",

Then in the test method:

-    handler_instance = Gsoc()
+    # Use the fixture instead
     ack = MagicMock()
-    handler_instance.handler(ack=ack, command=command, client=mock_slack_client)
+    gsoc_command.handler(ack=ack, command=command, client=mock_slack_client)

81-83: Handler invocation updated for consistency.

This is similar to the change at lines 48-50, where the function call has been replaced with class instantiation and method call. The change is consistent with the refactoring approach.

Consider applying the same fixture recommendation suggested for lines 48-50 here as well to reduce code duplication.


1-88: Consider adding tests for exception handling.

The current tests validate the happy path scenarios and basic error responses, but there are no tests for exception handling in the handler method. From the relevant code snippet of handler in the command.py file, it appears there's exception handling logic that should be tested.

def test_handler_exception(self, mock_slack_client):
    """Test that exceptions in the handler are properly caught and an error message is sent."""
    command = {"text": "", "user_id": "U123456"}
    settings.SLACK_COMMANDS_ENABLED = True
    
    with patch.object(Gsoc, "get_render_blocks", side_effect=Exception("Test exception")):
        handler_instance = Gsoc()
        ack = MagicMock()
        handler_instance.handler(ack=ack, command=command, client=mock_slack_client)
        
        # Verify error message was sent
        mock_slack_client.chat_postMessage.assert_called_once()
        blocks = mock_slack_client.chat_postMessage.call_args[1]["blocks"]
        assert len(blocks) == 1
        assert ":warning: An error occurred. Please try again later." in str(blocks[0])
backend/apps/slack/commands/users.py (1)

11-25: Consider consistency with other commands

Unlike other commands that implement get_template_context to use the template system, this command directly overrides get_render_blocks. While this works correctly, it bypasses the template system introduced by this PR.

Consider whether this command should follow the same pattern as other commands to maintain consistency, unless there's a specific reason it needs special handling.

If using templates isn't suitable for this particular command, consider adding a comment explaining why this command needs a different approach.

backend/apps/slack/commands/staff.py (1)

11-22: Consider standardizing context creation pattern

The implementation is correct, but there's a minor inconsistency in how the context is constructed compared to other commands like Policies. This command uses context.update() while others use dictionary unpacking with **super().get_template_context(command).

Both approaches work, but consistency across the codebase is preferred. Consider standardizing on one approach across all commands:

- context = super().get_template_context(command)
- context.update(
-     {
-         "has_staff": bool(items),
-         "items": items,
-         "website_url": OWASP_WEBSITE_URL,
-     }
- )
- return context
+ return {
+     **super().get_template_context(command),
+     "has_staff": bool(items),
+     "items": items,
+     "website_url": OWASP_WEBSITE_URL,
+ }
backend/apps/slack/commands/sponsor.py (1)

6-11: Verify if template system is intentionally bypassed

Unlike other commands that use the template system, this implementation overrides get_render_text() to return a static string, bypassing the template rendering mechanism defined in CommandBase.

While this works for a "Coming soon..." message, it doesn't follow the same pattern as other commands that use templates. Consider whether this is intentional or if it would be better to use a simple template file for consistency.

If this is a temporary implementation until the sponsor feature is ready, consider adding a TODO comment to clarify this.

backend/apps/slack/commands/contribute.py (1)

31-35: Simplify or remove unnecessary method.

The get_template_context method doesn't add any new context and simply calls the parent method. Consider removing it entirely since it doesn't extend the functionality provided by the parent class.

-    def get_template_context(self, command):
-        """Get the template context."""
-        return {
-            **super().get_template_context(command),
-        }
backend/apps/slack/commands/leaders.py (3)

10-63: Refactor long method to improve readability and maintainability.

The get_template_context method is quite long and handles multiple responsibilities. Consider breaking it down into smaller, more focused methods.

     def get_template_context(self, command):
         """Get the template context."""
+        search_query = command["text"].strip()
+        chapters_with_urls = self._get_chapters_with_urls(search_query)
+        projects_with_urls = self._get_projects_with_urls(search_query)
+        
+        return {
+            **super().get_template_context(command),
+            "chapters": chapters_with_urls,
+            "projects": projects_with_urls,
+            "search_query": search_query,
+            "has_results": bool(chapters_with_urls or projects_with_urls),
+        }
+        
+    def _get_chapters_with_urls(self, search_query):
+        """Get chapters with URLs for the given search query."""
         from apps.owasp.api.search.chapter import get_chapters
-        from apps.owasp.api.search.project import get_projects
-
-        search_query = command["text"].strip()
-
         attributes = ["idx_key", "idx_leaders", "idx_name"]
         searchable_attributes = ["idx_leaders", "idx_name"]
         limit = 5
         chapters = get_chapters(
             query=search_query,
             attributes=attributes,
             limit=limit,
             page=1,
             searchable_attributes=searchable_attributes,
         )["hits"]
-
+        
+        return [
+            self._create_entity_with_url(chapter, "chapters")
+            for chapter in chapters
+        ]
+        
+    def _get_projects_with_urls(self, search_query):
+        """Get projects with URLs for the given search query."""
+        from apps.owasp.api.search.project import get_projects
+        
+        attributes = ["idx_key", "idx_leaders", "idx_name"]
+        searchable_attributes = ["idx_leaders", "idx_name"]
+        limit = 5
         projects = get_projects(
             query=search_query,
             attributes=attributes,
             limit=limit,
             page=1,
             searchable_attributes=searchable_attributes,
         )["hits"]
-
-        chapters_with_urls = [
-            {
-                "idx_key": chapter["idx_key"],
-                "idx_name": chapter["idx_name"],
-                "idx_leaders": chapter["idx_leaders"],
-                "url": get_absolute_url(f"chapters/{chapter['idx_key']}"),
-            }
-            for chapter in chapters
-        ]
-        projects_with_urls = [
-            {
-                "idx_key": project["idx_key"],
-                "idx_name": project["idx_name"],
-                "idx_leaders": project["idx_leaders"],
-                "url": get_absolute_url(f"projects/{project['idx_key']}"),
-            }
-            for project in projects
-        ]
-        context = super().get_template_context(command)
-        context.update(
-            {
-                "chapters": chapters_with_urls,
-                "projects": projects_with_urls,
-                "search_query": search_query,
-                "has_results": bool(chapters or projects),
-            }
-        )
-        return context
+        
10000

+        return [
+            self._create_entity_with_url(project, "projects")
+            for project in projects
+        ]
+        
+    def _create_entity_with_url(self, entity, entity_type):
+        """Create an entity dictionary with URL."""
+        return {
+            "idx_key": entity["idx_key"],
+            "idx_name": entity["idx_name"],
+            "idx_leaders": entity["idx_leaders"],
+            "url": get_absolute_url(f"{entity_type}/{entity['idx_key']}"),
+        }

12-14: Move imports to module level.

Consider moving these imports to the module level rather than inside the method for better readability and performance, unless there's a specific reason to avoid circular imports.

from apps.common.utils import get_absolute_url
from apps.slack.commands.command import CommandBase
+from apps.owasp.api.search.chapter import get_chapters
+from apps.owasp.api.search.project import get_projects


class Leaders(CommandBase):
    """Slack bot /leaders command."""

    def get_template_context(self, command):
        """Get the template context."""
-        from apps.owasp.api.search.chapter import get_chapters
-        from apps.owasp.api.search.project import get_projects

54-62: Simplify context creation with the unpacking operator.

Use the ** unpacking operator to simplify context creation, which is consistent with the pattern used in other command files.

-        context = super().get_template_context(command)
-        context.update(
-            {
-                "chapters": chapters_with_urls,
-                "projects": projects_with_urls,
-                "search_query": search_query,
-                "has_results": bool(chapters or projects),
-            }
-        )
-        return context
+        return {
+            **super().get_template_context(command),
+            "chapters": chapters_with_urls,
+            "projects": projects_with_urls,
+            "search_query": search_query,
+            "has_results": bool(chapters or projects),
+        }
backend/apps/slack/commands/owasp.py (1)

20-28: Add input validation for increased robustness.

The handler assumes command_tokens will be a valid list when checking for its first element, but this might not always be the case.

def handler(self, ack, command, client):
    """Handle the command."""
    command_tokens = command["text"].split()
-   cmd = self.find_command(command_tokens[0].strip().lower() if command_tokens else "")
+   first_token = command_tokens[0].strip().lower() if command_tokens else ""
+   cmd = self.find_command(first_token)
    if cmd:
        command["text"] = " ".join(command_tokens[1:]).strip()
        return cmd.handler(ack, command, client)

    return super().handler(ack, command, client)
backend/apps/slack/commands/command.py (3)

127-129: Improve exception logging

The current exception logging doesn't include the actual exception details in the log message, which would be helpful for debugging.

-        except Exception:
-            logger.exception("Failed to handle command '%s'", self.get_command())
+        except Exception as e:
+            logger.exception("Failed to handle command '%s': %s", self.get_command(), str(e))

84-86: Consider making command name derivation more flexible

The current implementation assumes the command name is always the lowercase class name. This might be too rigid if a command name needs to differ from its class name.

def get_command(self):
    """Get the command name."""
-    return f"/{self.__class__.__name__.lower()}"
+    # Allow subclasses to override command_name
+    command_name = getattr(self, 'command_name', self.__class__.__name__.lower())
+    return f"/{command_name}"

65-82: Consider using Jinja2 extensions for special tokens

The current approach of string replacement after rendering works but requires passing literal tokens to templates. Using Jinja2 extensions or custom tags might be more elegant.

Instead of post-processing rendered text with string replacements, you could create custom Jinja2 extensions that directly output the appropriate Slack block structures. This would make templates more readable and avoid the need to pass special tokens like {{ SECTION_BREAK }} through the context.

Example approach:

# In a Jinja2 extension file
from jinja2 import nodes
from jinja2.ext import Extension

class SlackBlocksExtension(Extension):
    tags = {'section_break', 'divider'}
    
    def parse(self, parser):
        token = parser.stream.current
        if token.value == 'section_break':
            return nodes.CallBlock(self.call_method('_section_break'), [], [], [])
        elif token.value == 'divider':
            return nodes.CallBlock(self.call_method('_divider'), [], [], [])
    
    def _section_break(self, caller):
        return "{{ SECTION_BREAK }}"
        
    def _divider(self, caller):
        return "{{ DIVIDER }}"

# Then in your template loader
env = Environment(
    loader=FileSystemLoader(TEMPLATE_DIR),
    extensions=[SlackBlocksExtension]
)

# In templates
{% section_break %}
{% divider %}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b41924e and 9c39dd7.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (58)
  • backend/apps/slack/commands/__init__.py (2 hunks)
  • backend/apps/slack/commands/board.py (1 hunks)
  • backend/apps/slack/commands/chapters.py (1 hunks)
  • backend/apps/slack/commands/command.py (1 hunks)
  • backend/apps/slack/commands/committees.py (1 hunks)
  • backend/apps/slack/commands/community.py (1 hunks)
  • backend/apps/slack/commands/contact.py (1 hunks)
  • backend/apps/slack/commands/contribute.py (2 hunks)
  • backend/apps/slack/commands/donate.py (1 hunks)
  • backend/apps/slack/commands/events.py (1 hunks)
  • backend/apps/slack/commands/gsoc.py (1 hunks)
  • backend/apps/slack/commands/jobs.py (1 hunks)
  • backend/apps/slack/commands/leaders.py (1 hunks)
  • backend/apps/slack/commands/news.py (1 hunks)
  • backend/apps/slack/commands/owasp.py (1 hunks)
  • backend/apps/slack/commands/policies.py (1 hunks)
  • backend/apps/slack/commands/projects.py (1 hunks)
  • backend/apps/slack/commands/sponsor.py (1 hunks)
  • backend/apps/slack/commands/sponsors.py (1 hunks)
  • backend/apps/slack/commands/staff.py (1 hunks)
  • backend/apps/slack/commands/users.py (1 hunks)
  • backend/apps/slack/models/event.py (2 hunks)
  • backend/apps/slack/template_system/loader.py (1 hunks)
  • backend/apps/slack/templates/chapters.template (1 hunks)
  • backend/apps/slack/templates/contribute.template (1 hunks)
  • backend/apps/slack/templates/donate.template (1 hunks)
  • backend/apps/slack/templates/events.template (1 hunks)
  • backend/apps/slack/templates/feedback.template (1 hunks)
  • backend/apps/slack/templates/gsoc.template (1 hunks)
  • backend/apps/slack/templates/jobs.template (1 hunks)
  • backend/apps/slack/templates/leaders.template (1 hunks)
  • backend/apps/slack/templates/navigate.template (1 hunks)
  • backend/apps/slack/templates/news.template (1 hunks)
  • backend/apps/slack/templates/owasp.template (1 hunks)
  • backend/apps/slack/templates/policies.template (1 hunks)
  • backend/apps/slack/templates/sponsors.template (1 hunks)
  • backend/apps/slack/templates/staff.template (1 hunks)
  • backend/pyproject.toml (1 hunks)
  • backend/tests/apps/slack/commands/board_test.py (3 hunks)
  • backend/tests/apps/slack/commands/chapters_test.py (3 hunks)
  • backend/tests/apps/slack/commands/committees_test.py (3 hunks)
  • backend/tests/apps/slack/commands/community_test.py (3 hunks)
  • backend/tests/apps/slack/commands/contact_test.py (3 hunks)
  • backend/tests/apps/slack/commands/contribute_test.py (4 hunks)
  • backend/tests/apps/slack/commands/donate_test.py (3 hunks)
  • backend/tests/apps/slack/commands/events_test.py (2 hunks)
  • backend/tests/apps/slack/commands/gsoc_test.py (4 hunks)
  • backend/tests/apps/slack/commands/jobs_test.py (3 hunks)
  • backend/tests/apps/slack/commands/leaders_test.py (4 hunks)
  • backend/tests/apps/slack/commands/news_test.py (5 hunks)
  • backend/tests/apps/slack/commands/owasp_test.py (3 hunks)
  • backend/tests/apps/slack/commands/policies_test.py (2 hunks)
  • backend/tests/apps/slack/commands/projects_test.py (4 hunks)
  • backend/tests/apps/slack/commands/sponsor_test.py (2 hunks)
  • backend/tests/apps/slack/commands/sponsors_test.py (3 hunks)
  • backend/tests/apps/slack/commands/staff_test.py (3 hunks)
  • backend/tests/apps/slack/commands/users_test.py (3 hunks)
  • backend/tests/apps/slack/models/event_test.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (36)
  • backend/apps/slack/templates/navigate.template
  • backend/apps/slack/templates/leaders.template
  • backend/tests/apps/slack/commands/donate_test.py
  • backend/apps/slack/templates/staff.template
  • backend/pyproject.toml
  • backend/apps/slack/templates/feedback.template
  • backend/apps/slack/templates/contribute.template
  • backend/apps/slack/templates/owasp.template
  • backend/apps/slack/templates/news.template
  • backend/apps/slack/templates/policies.template
  • backend/apps/slack/templates/events.template
  • backend/tests/apps/slack/commands/community_test.py
  • backend/apps/slack/models/event.py
  • backend/tests/apps/slack/commands/policies_test.py
  • backend/tests/apps/slack/models/event_test.py
  • backend/tests/apps/slack/commands/chapters_test.py
  • backend/tests/apps/slack/commands/events_test.py
  • backend/apps/slack/templates/chapters.template
  • backend/tests/apps/slack/commands/contribute_test.py
  • backend/apps/slack/templates/donate.template
  • backend/tests/apps/slack/commands/board_test.py
  • backend/tests/apps/slack/commands/users_test.py
  • backend/apps/slack/commands/init.py
  • backend/tests/apps/slack/commands/contact_test.py
  • backend/apps/slack/commands/projects.py
  • backend/tests/apps/slack/commands/sponsor_test.py
  • backend/apps/slack/templates/jobs.template
  • backend/apps/slack/template_system/loader.py
  • backend/apps/slack/templates/gsoc.template
  • backend/apps/slack/templates/sponsors.template
  • backend/tests/apps/slack/commands/jobs_test.py
  • backend/tests/apps/slack/commands/sponsors_test.py
  • backend/tests/apps/slack/commands/projects_test.py
  • backend/tests/apps/slack/commands/committees_test.py
  • backend/tests/apps/slack/commands/staff_test.py
  • backend/tests/apps/slack/commands/leaders_test.py
🧰 Additional context used
🧬 Code Graph Analysis (15)
backend/apps/slack/commands/board.py (1)
backend/apps/slack/commands/command.py (3)
  • CommandBase (16-134)
  • get_template_file_name (97-99)
  • get_template_context (48-63)
backend/apps/slack/commands/policies.py (14)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/commands/community.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/board.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/donate.py (1)
  • get_template_context (10-15)
backend/apps/slack/commands/gsoc.py (1)
  • get_template_context (28-77)
backend/apps/slack/commands/contact.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/contribute.py (1)
  • get_template_context (31-35)
backend/apps/slack/commands/events.py (1)
  • get_template_context (11-36)
backend/apps/slack/commands/jobs.py (1)
  • get_template_context (14-24)
backend/apps/slack/commands/news.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/leaders.py (1)
  • get_template_context (10-63)
backend/apps/slack/commands/owasp.py (1)
  • get_template_context (30-40)
backend/apps/slack/commands/sponsors.py (1)
  • get_template_context (12-22)
backend/apps/slack/commands/staff.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/events.py (3)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/utils.py (1)
  • get_events_data (120-133)
backend/apps/slack/commands/donate.py (1)
  • get_template_context (10-15)
backend/apps/slack/commands/staff.py (6)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/utils.py (1)
  • get_staff_data (95-117)
backend/apps/slack/commands/donate.py (1)
  • get_template_context (10-15)
backend/apps/slack/commands/contribute.py (1)
  • get_template_context (31-35)
backend/apps/slack/commands/events.py (1)
  • get_template_context (11-36)
backend/apps/slack/commands/news.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/jobs.py (1)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/commands/committees.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_render_blocks (65-82)
backend/apps/slack/common/handlers/committees.py (1)
  • get_blocks (17-113)
backend/apps/slack/commands/leaders.py (4)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/owasp/api/search/chapter.py (1)
  • get_chapters (8-44)
backend/apps/owasp/api/search/project.py (1)
  • get_projects (8-48)
backend/apps/common/utils.py (1)
  • get_absolute_url (13-23)
backend/apps/slack/commands/gsoc.py (3)
backend/apps/common/utils.py (1)
  • get_absolute_url (13-23)
backend/apps/slack/commands/command.py (3)
  • CommandBase (16-134)
  • get_template_context (48-63)
  • get_command (84-86)
backend/apps/slack/utils.py (1)
  • get_gsoc_projects (34-55)
backend/apps/slack/commands/sponsor.py (1)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_render_text (35-46)
backend/apps/slack/commands/contribute.py (1)
backend/apps/slack/commands/command.py (3)
  • CommandBase (16-134)
  • get_render_blocks (65-82)
  • get_template_context (48-63)
backend/tests/apps/slack/commands/news_test.py (2)
backend/apps/slack/commands/news.py (1)
  • News (8-22)
backend/apps/slack/commands/command.py (1)
  • handler (105-134)
backend/tests/apps/slack/commands/gsoc_test.py (2)
backend/apps/slack/commands/gsoc.py (1)
  • Gsoc (25-77)
backend/apps/slack/commands/command.py (1)
  • handler (105-134)
backend/apps/slack/commands/sponsors.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-134)
  • get_template_context (48-63)
backend/apps/slack/utils.py (1)
  • get_sponsors_data (136-152)
backend/apps/slack/commands/command.py (2)
backend/apps/slack/apps.py (1)
  • SlackConfig (10-21)
backend/apps/slack/blocks.py (1)
  • markdown (14-27)
backend/tests/apps/slack/commands/owasp_test.py (1)
backend/apps/slack/commands/owasp.py (2)
  • Owasp (7-40)
  • handler (20-28)
🔇 Additional comments (55)
backend/tests/apps/slack/commands/gsoc_test.py (5)

7-7: Import updated to reflect new class-based implementation.

The import has been properly updated to use the class-based implementation (Gsoc class) instead of the previous function-based approach, aligning with the PR's objective to transition to a more maintainable object-oriented design.


11-11: Class name updated to reflect command-based testing.

The test class name change from TestGsocHandler to TestGsocCommand properly reflects the shift from testing a handler function to testing a command class, maintaining consistency with the new architecture.


33-33: Simplified error message format.

The error message format has been simplified by removing extra formatting (likely a newline character), which should make the tests more readable and maintainable. This aligns with the PR's template-based approach.


63-63: Assertion updated to match new template-based response.

The assertion has been updated to check for the presence of "Getting Started with OWASP GSoC" instead of the static "GSOC_GENERAL_INFORMATION_BLOCKS", which aligns with the new template-based output system.

This change suggests the response text is now being generated from a template rather than hardcoded blocks, which is consistent with the PR's goal of implementing a template system.


78-78: Updated import path for mocked function.

The import path for the get_gsoc_projects function has been correctly updated to reflect its new location in the refactored code structure, ensuring the test mocks the function from the right module.

backend/apps/slack/commands/policies.py (3)

3-4: Implementation looks good!

The command has been successfully refactored to use the new CommandBase class which supports the Jinja2 template system.


6-8: LGTM!

The Policies class is properly defined inheriting from CommandBase with appropriate docstring.


9-39: Well-structured template context implementation!

The get_template_context method properly extends the base context with the policies list. The policies list is comprehensive and well-organized with clear names and valid URLs.

backend/apps/slack/commands/users.py (2)

3-6: LGTM!

Appropriate imports for the refactored command implementation.


8-10: Class definition looks good!

The Users class properly inherits from CommandBase with appropriate docstring.

backend/apps/slack/commands/staff.py (2)

3-6: LGTM!

Appropriate imports for the refactored command implementation.


8-10: Class definition looks good!

The Staff class properly inherits from CommandBase with appropriate docstring.

backend/apps/slack/commands/board.py (4)

3-4: LGTM!

Appropriate import for the refactored command implementation.


6-8: Class definition looks good!

The Board class properly inherits from CommandBase with appropriate docstring.


9-12: Good template reuse!

Overriding get_template_file_name to return "navigate.template" suggests that multiple navigation-type commands share the same template, which promotes code reuse.


13-19: LGTM!

The template context correctly extends the base context with the specific URL and name for the board command.

backend/apps/slack/commands/jobs.py (3)

1-9: Clean imports and class structure for the new template-based system.

The refactoring from a procedural approach to a class-based model is well-implemented. The imports are properly organized, including only what's necessary from the command base class and constants.


11-13: Well-named class following command structure.

The Jobs class appropriately inherits from CommandBase and includes a descriptive docstring that clarifies its purpose.


14-24: Context handling implementation follows best practices.

Good implementation of the get_template_context method, which properly extends the base class context with command-specific information. The use of super() ensures that all base context values are preserved while adding command-specific channel IDs and bot name.

backend/apps/slack/commands/committees.py (3)

3-6: Appropriate imports for the class-based structure.

The imports are correctly updated to include the CommandBase while maintaining the necessary functionality from the common handlers and presentation modules.


8-10: Well-defined class following the command pattern.

The Committees class properly inherits from CommandBase and includes a clear docstring.


11-25: Effective implementation of get_render_blocks.

The method correctly overrides the base implementation to provide committees-specific block rendering. It properly handles the search query from the command text and uses the existing get_blocks function with appropriate presentation parameters.

backend/apps/slack/commands/events.py (6)

3-6: Streamlined imports focused on the new template system.

The imports have been appropriately updated to support the class-based implementation, removing unnecessary imports while adding the required ones for the template context.


8-10: Well-structured class definition with clear docstring.

The Events class properly inherits from CommandBase and includes a descriptive docstring.


11-13: Good method signature and documentation.

The get_template_context method is properly defined with appropriate documentation.


14-16: Effective event filtering and sorting.

The code properly filters events to include only those with a valid start date and sorts them chronologically, ensuring a logical presentation order.


17-31: Well-structured event categorization logic.

The event categorization implementation is clean and efficient. It organizes events by category and extracts the necessary details into a structured format suitable for template rendering.


32-36: Properly constructed template context.

The method correctly combines the base context with the event-specific data using the spread operator, following best practices for extending dictionaries.

backend/apps/slack/commands/chapters.py (4)

3-7: Appropriate imports for the class-based structure.

The imports have been correctly updated to include the CommandBase while retaining the necessary constants and handlers.


9-11: Well-defined class with clear purpose.

The Chapters class properly inherits from CommandBase and includes a descriptive docstring.


12-17: Intelligent command text handling with fallback to parent implementation.

The method correctly checks if the command text is a help command and delegates to the parent implementation when appropriate. This maintains the functionality of help commands while allowing for custom rendering of search results.


18-29: Effective search query processing and block rendering.

The implementation properly constructs the search query based on the command text and uses the existing get_blocks function with appropriate presentation parameters.

backend/apps/slack/commands/news.py (1)

7-22: Implementation looks good!

The News class follows a clean architecture with proper inheritance from CommandBase. The get_template_context method correctly builds the template context by:

  1. Getting news data via the utility function
  2. Extending the base context from the parent class
  3. Adding news-specific parameters with appropriate boolean flag for empty results

This implementation aligns well with the PR objectives to refactor hardcoded text into reusable templates.

backend/apps/slack/commands/donate.py (1)

7-15: Clean implementation with proper context extension

The Donate class correctly extends the base context using the spread operator with the parent's context, adding the website URL parameter. This follows the pattern established for template-based command responses.

backend/apps/slack/commands/contact.py (2)

9-11: Good reuse of common template

The class correctly overrides get_template_file_name() to use a common "navigate.template" file rather than creating a command-specific template. This promotes template reuse for similar commands.


13-19: Implementation follows the template pattern well

The context building properly extends the base context and adds the URL and name specific to the contact command. This demonstrates the flexibility of the new template system to handle different command needs while maintaining a consistent structure.

backend/apps/slack/commands/community.py (1)

6-19: Implementation follows the established pattern well.

The Community class properly implements the template system by overriding the necessary methods and providing specific template context.

backend/apps/slack/commands/sponsors.py (1)

9-22: Good implementation with comprehensive context.

The Sponsors class correctly implements the template system with a detailed context that includes all necessary data for rendering.

backend/apps/slack/commands/gsoc.py (6)

21-22: Good practice: Using dedicated constants and utility functions.

The code defines MARCH as a constant and uses get_absolute_url("projects") for the projects URL, making the code more maintainable and easier to understand.


28-32: LGTM: Calculating time values inside the method ensures freshness.

Moving the calculation of now and gsoc_year into the method ensures these values are always up-to-date when the method is called, even if the server runs for a long time.


36-47: Well-structured template context for general information.

The template context for the general mode is comprehensive, providing all necessary information for rendering a useful response to the user.


48-67: Good implementation of year-based logic.

The code properly handles valid and invalid year inputs, with clear separation between supported and unsupported years. The sorting of projects by idx_name improves user experience with a consistent ordering.


68-74: Good fallback for invalid commands.

The code properly handles invalid commands by setting an appropriate mode in the template context, which will likely result in a user-friendly error message.


76-77: Clean context update approach.

The code updates the base context with the template-specific context, ensuring that both general command information and template-specific data are available for rendering.

backend/apps/slack/commands/owasp.py (2)

10-18: Well-implemented command lookup functionality.

The find_command method efficiently searches for commands by name while properly excluding the Owasp class itself to prevent recursion issues.


30-40: Clear context preparation for help and command execution.

The get_template_context method returns a well-structured context based on the command, distinguishing between help requests and specific command invocations.

backend/tests/apps/slack/commands/owasp_test.py (4)

37-39: Code correctly updated to use the new class-based approach.

The test has been properly updated to instantiate the Owasp class and call its handler method, reflecting the refactored implementation.


48-49: Good addition: Testing disabled commands behavior.

The test now verifies that when commands are disabled, no messages are posted to Slack, which is an important validation.


57-72: Well-structured mocking for subcommands.

The patch to find_command is a clean approach to testing the command delegation functionality, creating a mock handler that verifies the proper command flow.


74-75: Precise assertion for subcommand response.

The assertion properly verifies that the message sent to Slack contains the expected text from the delegated command.

backend/tests/apps/slack/commands/news_test.py (4)

10-10: Class name updated to reflect the new testing approach.

The class name has been appropriately changed from TestNewsHandler to TestNewsCommand to reflect the shift from testing a function to testing a class.


50-51: Test properly updated to use the class-based approach.

The test now instantiates the News class and calls its handler method, correctly reflecting the refactored implementation.


60-61: Flexible assertion using in operator.

The assertion has been updated to use in instead of exact equality, which is more resilient to minor text changes while still verifying the essential content.


82-84: Test case properly updated for no items scenario.

The test for when no news items are available has been correctly updated to use the class-based approach, maintaining test coverage for this edge case.

backend/apps/slack/commands/command.py (1)

1-135: Well-designed CommandBase class with good template handling!

The implementation of CommandBase provides a solid foundation for the Slack command system with template-based responses. The class follows a clean object-oriented design and addresses previous review suggestions including error handling and template context support.

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: 0

🧹 Nitpick comments (2)
backend/apps/slack/commands/command.py (2)

1-2: Use a more appropriate module-level docstring.

The current docstring states "Slack bot board command," but this file defines a shared base class for Slack commands rather than a board-specific command. Updating the docstring to clarify its broader purpose would help future maintainers quickly understand the file's scope.

-"""Slack bot board command."""
+"""Base class and common functionality for Slack commands."""

65-83: Consider supporting multiple placeholders in the same section.

Right now, each text section is processed as a single block unless it exactly matches "{{ DIVIDER }}". If developers want multiple dividers in the same section (e.g., "Intro {{ DIVIDER }} More text"), this code won't handle them as separate dividers. Consider splitting the section further or switching to a more flexible parsing approach.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c39dd7 and 80f4929.

📒 Files selected for processing (1)
  • backend/apps/slack/commands/command.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/apps/slack/commands/command.py (14)
backend/apps/slack/apps.py (1)
  • SlackConfig (10-21)
backend/apps/slack/blocks.py (1)
  • markdown (14-27)
backend/apps/slack/commands/sponsor.py (1)
  • get_render_text (9-11)
backend/apps/slack/commands/community.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/events.py (1)
  • get_template_context (11-36)
backend/apps/slack/commands/board.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/contact.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/gsoc.py (1)
  • get_template_context (28-77)
backend/apps/slack/commands/jobs.py (1)
  • get_template_context (14-24)
backend/apps/slack/commands/news.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/leaders.py (1)
  • get_template_context (10-63)
backend/apps/slack/commands/policies.py (1)
  • get_template_context (9-39)
backend/apps/slack/commands/staff.py (1)
  • get_template_context (11-22)
backend/apps/slack/commands/sponsors.py (1)
  • get_template_context (12-22)
🔇 Additional comments (1)
backend/apps/slack/commands/command.py (1)

3-14: No issues found.

Also applies to: 16-23, 24-34, 35-47, 48-64, 84-107, 108-116, 117-147

Copy link
Collaborator
@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

Hey @Li-ChangC
I'll look into it soon, just busy w/ other PRs now. Don't worry about the size, I can handle it.

Thanks 👍

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: 0

🧹 Nitpick comments (4)
backend/apps/slack/commands/leaders.py (1)

12-13: Consider moving imports to the module level.

The imports for the search modules are located inside the method rather than at the module level. While this works, it's generally better practice to have imports at the top of the file unless there's a specific reason for delayed importing (like circular dependencies or performance).

-    def get_template_context(self, command):
-        """Get the template context."""
-        from apps.owasp.api.search.chapter import get_chapters
-        from apps.owasp.api.search.project import get_projects
+from apps.owasp.api.search.chapter import get_chapters
+from apps.owasp.api.search.project import get_projects
+
+    def get_template_context(self, command):
+        """Get the template context."""
backend/apps/slack/commands/command.py (3)

24-33: Consider using class methods instead of static methods for command retrieval and configuration.

The methods get_all_commands() and config_all_commands() are defined as static methods. Converting these to class methods would allow subclasses to extend or override this behavior if needed in the future.

-    @staticmethod
-    def get_all_commands():
+    @classmethod
+    def get_all_commands(cls):
         """Get all commands."""
-        return list(CommandBase.__subclasses__())
+        return list(cls.__subclasses__())

-    @staticmethod
-    def config_all_commands():
+    @classmethod
+    def config_all_commands(cls):
         """Configure all commands."""
-        for cmd_class in CommandBase.get_all_commands():
+        for cmd_class in cls.get_all_commands():
             cmd_class().config_command()

84-106: Make command and template name methods overridable by adding a default implementation.

The current implementation assumes command names and template file names follow a specific convention (lowercase class name). This might be limiting for commands that need custom naming schemes.

     def get_command(self):
-        """Get the command name."""
+        """Get the command name.
+        
+        Returns:
+            str: The command name, defaulting to the lowercase class name with a '/' prefix.
+            
+        Note:
+            Subclasses can override this method to provide custom command names.
+        """
         return f"/{self.__class__.__name__.lower()}"

     def get_template_file_name(self):
-        """Get the template file name."""
+        """Get the template file name.
+        
+        Returns:
+            str: The template file name, defaulting to the lowercase class name with '.template' suffix.
+            
+        Note:
+            Subclasses can override this method to provide custom template file names.
+        """
         return f"{self.__class__.__name__.lower()}.template"

131-146: Consider providing more specific error messages for different exception types.

While the current error handling is good, you could provide more specific error messages to users based on the type of exception that occurred. This would help users understand what went wrong without exposing sensitive details.

         try:
             blocks = self.get_render_blocks(command)
             conversation = client.conversations_open(users=command["user_id"])
             client.chat_postMessage(
                 blocks=blocks,
                 channel=conversation["channel"]["id"],
                 text=get_text(blocks),
             )
-        except Exception:
-            logger.exception("Failed to handle command '%s'", self.get_command())
+        except FileNotFoundError as e:
+            logger.exception("Template not found for command '%s'", self.get_command())
+            error_message = ":warning: Configuration error. Please contact support."
+        except ValueError as e:
+            logger.exception("Value error in command '%s'", self.get_command())
+            error_message = ":warning: Invalid command parameters. Please check your input."
+        except Exception as e:
+            logger.exception("Failed to handle command '%s'", self.get_command())
+            error_message = ":warning: An error occurred. Please try again later."
+            
+        # Handle error response
+        if 'error_message' in locals():
             conversation = client.conversations_open(users=command["user_id"])
             client.chat_postMessage(
-                blocks=[markdown(":warning: An error occurred. Please try again later.")],
+                blocks=[markdown(error_message)],
                 channel=conversation["channel"]["id"],
                 text="An error occurred while processing your command.",
             )
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80f4929 and 644f856.

📒 Files selected for processing (7)
  • backend/apps/slack/commands/command.py (1 hunks)
  • backend/apps/slack/commands/jobs.py (1 hunks)
  • backend/apps/slack/commands/leaders.py (1 hunks)
  • backend/apps/slack/commands/news.py (1 hunks)
  • backend/apps/slack/commands/staff.py (1 hunks)
  • backend/apps/slack/templates/leaders.template (1 hunks)
  • backend/apps/slack/templates/news.template (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/apps/slack/templates/leaders.template
  • backend/apps/slack/templates/news.template
🧰 Additional context used
🧬 Code Graph Analysis (5)
backend/apps/slack/commands/staff.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-146)
  • get_template_context (48-63)
backend/apps/slack/utils.py (1)
  • get_staff_data (95-117)
backend/apps/slack/commands/leaders.py (4)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-146)
  • get_template_context (48-63)
backend/apps/owasp/api/search/chapter.py (1)
  • get_chapters (8-44)
backend/apps/owasp/api/search/project.py (1)
  • get_projects (8-48)
backend/apps/common/utils.py (1)
  • get_absolute_url (13-23)
backend/apps/slack/commands/news.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-146)
  • get_template_context (48-63)
backend/apps/slack/utils.py (1)
  • get_news_data (59-91)
backend/apps/slack/commands/jobs.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-146)
  • get_template_context (48-63)
backend/apps/slack/commands/sponsors.py (1)
  • get_template_context (12-22)
backend/apps/slack/commands/command.py (15)
backend/apps/slack/apps.py (1)
  • SlackConfig (10-21)
backend/apps/slack/blocks.py (1)
  • markdown (14-27)
backend/apps/slack/commands/sponsor.py (1)
  • get_render_text (9-11)
backend/apps/slack/commands/jobs.py (1)
  • get_template_context (14-21)
backend/apps/slack/commands/leaders.py (1)
  • get_template_context (10-60)
backend/apps/slack/commands/community.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/news.py (1)
  • get_template_context (11-19)
backend/apps/slack/commands/staff.py (1)
  • get_template_context (11-19)
backend/apps/slack/commands/board.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/events.py (1)
  • get_template_context (11-36)
backend/apps/slack/commands/contact.py (1)
  • get_template_context (13-19)
backend/apps/slack/commands/gsoc.py (1)
  • get_template_context (28-77)
backend/apps/slack/commands/donate.py (1)
  • get_template_context (10-15)
backend/apps/slack/commands/policies.py (1)
  • get_template_context (9-39)
backend/apps/slack/commands/sponsors.py (1)
  • get_template_context (12-22)
🔇 Additional comments (8)
backend/apps/slack/commands/staff.py (1)

8-19: Clean implementation of CommandBase pattern.

The Staff class follows a clean implementation of the CommandBase pattern. It correctly gets staff data and builds a template context with necessary information. The structure separates data preparation from rendering, which aligns with the PR objective of implementing a template system.

backend/apps/slack/commands/jobs.py (1)

11-21: LGTM - Appropriate context for template rendering.

The Jobs class implementation correctly provides channel IDs and bot name for the template context. This implementation aligns with the template-based messaging approach and maintains consistency with other command implementations.

backend/apps/slack/commands/news.py (1)

8-19: Good implementation with proper error handling.

The News command implementation properly handles the case where no news items are found by including the has_news boolean flag in the context. This ensures the template can display appropriate messaging when news data is unavailable.

backend/apps/slack/commands/leaders.py (1)

7-60: Well-structured implementation with good data processing.

The Leaders command implementation is robust and properly processes search results for both chapters and projects. The code correctly:

  1. Extracts search query from the command
  2. Performs targeted searches with appropriate attributes
  3. Builds enhanced data structures with URLs for template rendering
  4. Handles the case of no results through the has_results flag

This implementation shows good separation between data retrieval, processing, and presentation preparation.

backend/apps/slack/commands/command.py (4)

47-63: The base template context is well-structured and extendable.

The implementation provides a solid foundation for template contexts and includes the necessary special delimiters. The design makes it easy for subclasses to extend the context with additional variables.


65-83: Good implementation of block rendering with special delimiters.

The get_render_blocks method effectively processes the template output and handles the special delimiters to create properly formatted Slack blocks.


108-116: Effective null check for SlackConfig.app.

The null check for SlackConfig.app prevents potential runtime errors when the Slack configuration isn't properly set up.


95-102: Excellent error handling for template loading.

The error handling in get_template_file is well-implemented with detailed logging that includes both the template name and command name. Using logger.exception ensures that the full traceback is included in the logs.

@Li-ChangC Li-ChangC requested a review from arkid15r April 18, 2025 21:53
Copy link
Collaborator
@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

@Li-ChangC thanks for a great PR!
I've pushed some updates directly instead of commenting/suggesting changes.

Please review/verify if that works as expected and looks good to you.

Thank you!

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: 2

🧹 Nitpick comments (6)
backend/apps/slack/commands/contribute.py (1)

31-35: Consider removing unnecessary method override

The get_template_context method doesn't add any additional context data beyond what the parent class provides. If there's no specialized context needed for this command, consider removing this method override to simplify the code.

-    def get_template_context(self, command):
-        """Get the template context."""
-        return {
-            **super().get_template_context(command),
-        }
backend/apps/slack/commands/gsoc.py (1)

35-35: COMMAND_START check is case‑sensitive
command_text in COMMAND_START will fail for users typing /gsoc Start or /gsoc START. A simple .lower() comparison avoids surprise errors.

-        if not command_text or command_text in COMMAND_START:
+        if not command_text or command_text.lower() in (c.lower() for c in COMMAND_START):
backend/apps/slack/commands/owasp.py (1)

15-18: Sub‑command lookup can be O(N²) and misses cached instances
You instantiate a new sub‑command object for every call. Consider caching instances or building a lookup dict once in __init__ to avoid repeated reflection.

backend/apps/slack/commands/command.py (2)

24-34: get_commands() only returns direct subclasses
CommandBase.__subclasses__() is shallow, so nested subclasses are missed (e.g., if a command subclasses another command). A simple recursive generator future‑proofs this.

-        yield from CommandBase.__subclasses__()
+        for cls in CommandBase.__subclasses__():
+            yield cls
+            yield from cls.__subclasses__()

111-116: Log level nitpick
Using warning when the Slack app is unavailable is fine, but it might spam logs during local dev. Consider downgrading to info or guarding behind DEBUG.

backend/apps/slack/commands/staff.py (1)

1-19: Consider adding test information in docstring.

The implementation is good, but since this is a significant refactoring, it might be helpful to add a note in the class docstring referencing where the tests for this class can be found, to help future developers.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 644f856 and 4ed0105.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (44)
  • backend/apps/slack/commands/__init__.py (2 hunks)
  • backend/apps/slack/commands/board.py (1 hunks)
  • backend/apps/slack/commands/command.py (1 hunks)
  • backend/apps/slack/commands/committees.py (1 hunks)
  • backend/apps/slack/commands/community.py (1 hunks)
  • backend/apps/slack/commands/contact.py (1 hunks)
  • backend/apps/slack/commands/contribute.py (2 hunks)
  • backend/apps/slack/commands/gsoc.py (1 hunks)
  • backend/apps/slack/commands/jobs.py (1 hunks)
  • backend/apps/slack/commands/leaders.py (1 hunks)
  • backend/apps/slack/commands/news.py (1 hunks)
  • backend/apps/slack/commands/owasp.py (1 hunks)
  • backend/apps/slack/commands/policies.py (1 hunks)
  • backend/apps/slack/commands/projects.py (1 hunks)
  • backend/apps/slack/commands/sponsors.py (1 hunks)
  • backend/apps/slack/commands/staff.py (1 hunks)
  • backend/apps/slack/commands/users.py (1 hunks)
  • backend/apps/slack/models/event.py (1 hunks)
  • backend/apps/slack/template_loader.py (1 hunks)
  • backend/apps/slack/templates/leaders.template (1 hunks)
  • backend/apps/slack/templates/news.template (1 hunks)
  • backend/apps/slack/templates/sponsors.template (1 hunks)
  • backend/apps/slack/templates/staff.template (1 hunks)
  • backend/pyproject.toml (1 hunks)
  • backend/tests/apps/slack/commands/board_test.py (3 hunks)
  • backend/tests/apps/slack/commands/chapters_test.py (3 hunks)
  • backend/tests/apps/slack/commands/committees_test.py (3 hunks)
  • backend/tests/apps/slack/commands/community_test.py (4 hunks)
  • backend/tests/apps/slack/commands/contact_test.py (3 hunks)
  • backend/tests/apps/slack/commands/contribute_test.py (4 hunks)
  • backend/tests/apps/slack/commands/donate_test.py (3 hunks)
  • backend/tests/apps/slack/commands/events_test.py (2 hunks)
  • backend/tests/apps/slack/commands/gsoc_test.py (4 hunks)
  • backend/tests/apps/slack/commands/jobs_test.py (3 hunks)
  • backend/tests/apps/slack/commands/leaders_test.py (4 hunks)
  • backend/tests/apps/slack/commands/news_test.py (4 hunks)
  • backend/tests/apps/slack/commands/owasp_test.py (3 hunks)
  • backend/tests/apps/slack/commands/policies_test.py (2 hunks)
  • backend/tests/apps/slack/commands/projects_test.py (4 hunks)
  • backend/tests/apps/slack/commands/sponsor_test.py (2 hunks)
  • backend/tests/apps/slack/commands/sponsors_test.py (3 hunks)
  • backend/tests/apps/slack/commands/staff_test.py (3 hunks)
  • backend/tests/apps/slack/commands/users_test.py (3 hunks)
  • backend/tests/apps/slack/models/event_test.py (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • backend/apps/slack/template_loader.py
🚧 Files skipped from review as they are similar to previous changes (32)
  • backend/apps/slack/models/event.py
  • backend/pyproject.toml
  • backend/apps/slack/commands/init.py
  • backend/tests/apps/slack/commands/community_test.py
  • backend/tests/apps/slack/commands/contact_test.py
  • backend/tests/apps/slack/commands/donate_test.py
  • backend/apps/slack/commands/committees.py
  • backend/apps/slack/templates/leaders.template
  • backend/tests/apps/slack/commands/contribute_test.py
  • backend/tests/apps/slack/commands/leaders_test.py
  • backend/tests/apps/slack/commands/board_test.py
  • backend/tests/apps/slack/commands/users_test.py
  • backend/tests/apps/slack/commands/sponsor_test.py
  • backend/tests/apps/slack/models/event_test.py
  • backend/tests/apps/slack/commands/committees_test.py
  • backend/tests/apps/slack/commands/staff_test.py
  • backend/apps/slack/commands/users.py
  • backend/apps/slack/templates/staff.template
  • backend/tests/apps/slack/commands/owasp_test.py
  • backend/apps/slack/commands/projects.py
  • backend/apps/slack/templates/sponsors.template
  • backend/tests/apps/slack/commands/events_test.py
  • backend/tests/apps/slack/commands/news_test.py
  • backend/tests/apps/slack/commands/chapters_test.py
  • backend/tests/apps/slack/commands/gsoc_test.py
  • backend/tests/apps/slack/commands/policies_test.py
  • backend/tests/apps/slack/commands/sponsors_test.py
  • backend/tests/apps/slack/commands/projects_test.py
  • backend/apps/slack/commands/board.py
  • backend/tests/apps/slack/commands/jobs_test.py
  • backend/apps/slack/templates/news.template
  • backend/apps/slack/commands/community.py
🧰 Additional context used
🧬 Code Graph Analysis (4)
backend/apps/slack/commands/sponsors.py (2)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-147)
  • get_template_context (47-62)
backend/apps/slack/utils.py (1)
  • get_sponsors_data (136-152)
backend/apps/slack/commands/jobs.py (1)
backend/apps/slack/commands/command.py (2)
  • CommandBase (16-147)
  • get_template_context (47-62)
backend/apps/slack/commands/gsoc.py (3)
backend/apps/common/utils.py (1)
  • get_absolute_url (13-23)
backend/apps/slack/commands/command.py (3)
  • CommandBase (16-147)
  • get_template_context (47-62)
  • get_command_name (83-85)
backend/apps/slack/utils.py (1)
  • get_gsoc_projects (34-55)
backend/apps/slack/commands/command.py (2)
backend/apps/slack/apps.py (1)
  • SlackConfig (10-21)
backend/apps/slack/blocks.py (1)
  • markdown (14-27)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Run frontend unit tests
🔇 Additional comments (17)
backend/apps/slack/commands/sponsors.py (1)

9-21: Well implemented class-based command structure

The Sponsors class correctly inherits from CommandBase and properly overrides the get_template_context method. It extends the base context with sponsor-specific data and uses constants for configuration values, which is good practice.

I appreciate how you've maintained a clean separation of concerns: data retrieval in this class and presentation in the template file.

backend/apps/slack/commands/leaders.py (1)

7-59: Good implementation with clear data transformation

The Leaders class implements a more complex command with search functionality and data transformation. The code is well-structured with:

  1. Clear separation between data retrieval and preparation
  2. Appropriate use of list comprehensions for data transformation
  3. Good variable naming that communicates intent
  4. Proper use of inheritance with super().get_template_context(command)

The approach to enriching search results with URLs is clean and maintainable.

backend/apps/slack/commands/news.py (1)

8-18: Clean, concise implementation

This implementation is straightforward and follows the established command pattern. It properly inherits from CommandBase, extends the context dictionary with news-specific data, and maintains separation of concerns.

backend/apps/slack/commands/jobs.py (1)

11-21: Simple and effective implementation

The Jobs class implementation is minimalist but effective. It provides just the necessary context variables to the template without any additional logic, which is appropriate for this command's requirements.

backend/apps/slack/commands/contribute.py (1)

12-29: The implementation appears to bypass the template system for non-help commands

The get_render_blocks method returns help blocks via the parent class method (which presumably uses templates) when help is requested, but directly returns blocks from get_blocks() for search queries. This appears to diverge from the template-based approach described in the PR and implemented in other commands.

Consider whether this command should consistently use templates like the other commands or if there's a specific reason for this hybrid approach.

backend/apps/slack/commands/gsoc.py (1)

29-31: Potential off‑by‑one for GSoC “current year” calculation
gsoc_year = now.year if now.month > MARCH else now.year - 1 excludes March itself (now.month == 3). If the intention is “from March onwards we are in the new GSoC season”, the comparison should be >= MARCH. Please double‑check the business rule.

Would you like a quick patch to adjust the comparison?

backend/apps/slack/commands/command.py (1)

30-34: Duplicate registration on Django code‑reload
configure_commands() instantiates and registers every call. When Django’s autoreload runs, commands will accumulate multiple identical handlers. Track a “configured” flag or register once at import time.

backend/apps/slack/commands/policies.py (3)

3-4: Clean import structure.

The import has been simplified to just include the CommandBase which is good for maintainability and reduces dependencies.


6-8: Good use of class structure and documentation.

The class implementation with proper docstring follows good Python practices and clearly communicates the purpose of this class.


9-39: Well-structured data and context preparation.

The get_template_context method cleanly organizes the policies data as tuples and properly leverages inheritance by calling the parent method via super(). This approach separates the data preparation from presentation logic, which aligns with the PR's goal of improving maintainability.

backend/apps/slack/commands/contact.py (4)

3-3: Clean import structure.

The import has been simplified to just include the CommandBase which follows the principle of only importing what's needed.


6-8: Proper class definition with documentation.

The class is well-defined with appropriate documentation, making the code's purpose clear.


9-12: Template customization.

Good implementation of get_template_file_name to specify a custom template. This demonstrates how the new architecture allows for template reuse across different commands.


13-19: Clean context preparation.

The get_template_context method properly extends the base context and adds specific data needed for this command. This separation of concerns makes the code more maintainable.

backend/apps/slack/commands/staff.py (3)

3-6: Appropriate imports.

The imports are focused and import only what's needed for this command class. Including utilities like get_staff_data shows good separation of concerns.


8-10: Proper class definition with documentation.

The class follows the established pattern with clear documentation.


11-18: Effective data retrieval and context preparation.

The method retrieves staff data through a utility function and properly constructs the context by extending the base context. This approach keeps the command class focused on its specific responsibilities.

@Li-ChangC
Copy link
Contributor Author

@Li-ChangC thanks for a great PR! I've pushed some updates directly instead of commenting/suggesting changes.

Please review/verify if that works as expected and looks good to you.

Thank you!

Thanks! I’ve reviewed the changes and tested them in my Slack workspace, and everything works as expected. Appreciate your help!

@arkid15r arkid15r enabled auto-merge April 21, 2025 15:25
Copy link
Collaborator
@arkid15r arkid15r F9C3 left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@arkid15r arkid15r added this pull request to the merge queue Apr 21, 2025
Merged via the queue into OWASP:main with commit aafa78a Apr 21, 2025
22 checks passed
@Li-ChangC Li-ChangC deleted the feature/template-system branch April 21, 2025 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement NestBot block message template system
2 participants
0