Skip to content

Add Discord role edit/preview flow for CRM resumes#195

Merged
michaelmwu merged 6 commits intomainfrom
michaelmwu/role-editor
Mar 9, 2026
Merged

Add Discord role edit/preview flow for CRM resumes#195
michaelmwu merged 6 commits intomainfrom
michaelmwu/role-editor

Conversation

@michaelmwu
Copy link
Member

@michaelmwu michaelmwu commented Mar 9, 2026

Summary

  • Added a CRM resume edit flow for Discord roles with a dedicated modal, preview action, and apply button.
  • Added safeguards so reserved roles (Member, Admin, Steering Committee) are filtered from role suggestions and excluded on apply.
  • Extended resume update confirmation view to carry role suggestions and target user context through the review and apply path.
  • Added unit tests for role suggestion filtering, modal submit updates, and protected-role validation on apply.

Summary by CodeRabbit

Release Notes

  • New Features
    • Resume processing now displays suggested Discord roles based on extracted profile data
    • Added interface to edit and review suggested Discord roles before applying
    • Implemented Discord role application with permission validation and role hierarchy checks
    • Protected critical roles (Member, Admin, Steering Committee) from accidental modification
    • Enhanced feedback with detailed success/failure messaging for role application outcomes

Copilot AI review requested due to automatic review settings March 9, 2026 19:44
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Warning

Rate limit exceeded

@michaelmwu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 30 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8235746e-841f-4ef3-aa80-19326e322f63

📥 Commits

Reviewing files that changed from the base of the PR and between 9509b1e and 04c3f1c.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • tests/unit/test_crm.py
📝 Walkthrough

Walkthrough

Introduces Discord role suggestion and application functionality to the CRM resume processing workflow. New UI components enable users to view, edit, and apply role suggestions extracted from resumes. Implements role protection, hierarchy validation, permission checks, and audit logging for role applications.

Changes

Cohort / File(s) Summary
Discord Role Suggestion & Application Components
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added three new UI classes: ResumeEditDiscordRolesModal for editing suggested roles, ResumeEditDiscordRolesButton to open editor, and ResumeApplyDiscordRolesButton to apply roles with permission/hierarchy validation. Added protected roles set, _build_role_suggestions_embed helper, and modified _build_discord_role_suggestions to return tuple. Extended ResumeUpdateConfirmationView with discord_role_suggestions and discord_role_target_user_id parameters. Integrated role suggestions into resume preview flow with audit logging and detailed error messaging.
Test Coverage
tests/unit/test_crm.py
Added comprehensive test coverage for Discord role features: role suggestion building logic, UI component presence/absence in confirmation views, modal prepopulation, form submission behavior, and apply button flow. Tests validate filtering of protected roles and existing member roles. Extended imports to include three new public entities.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Bot as Discord Bot
    participant Modal as Edit Modal
    participant CRM as CRM/Database
    participant AuditLog as Audit Log

    User->>Bot: Submit resume for processing
    activate Bot
    Bot->>CRM: Extract profile & suggest roles
    CRM-->>Bot: Suggested roles (tech + locality)
    deactivate Bot
    
    Bot->>User: Display role suggestions in embed
    User->>Bot: Click edit roles button
    
    activate Modal
    Bot->>Modal: Open modal with suggestions
    User->>Modal: Edit role list
    User->>Modal: Submit edited roles
    Modal-->>Bot: Updated suggestions
    deactivate Modal
    
    User->>Bot: Click apply roles button
    activate Bot
    Bot->>Bot: Validate permissions & hierarchy
    Bot->>CRM: Apply non-protected roles to user
    CRM-->>Bot: Roles applied (with status: applied/blocked/protected)
    Bot->>AuditLog: Log role application event
    Bot->>User: Display summary (applied/blocked/protected/not-found counts)
    deactivate Bot
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰✨ Hops gleefully

Discord roles now bloom with care,
Protected tiers beyond compare!
Edit, apply, audit trails so bright—
Resume magic takes its flight! 🎩✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.72% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Discord role edit/preview flow for CRM resumes' accurately and concisely describes the main change: introducing a Discord role editor/preview workflow for resume processing in the CRM cog.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch michaelmwu/role-editor

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a Discord role edit/preview/apply flow for CRM resume processing. It introduces a modal for editing suggested Discord roles, an apply button that grants roles to linked members, and safeguards to prevent assigning protected roles (Member, Admin, Steering Committee).

Changes:

  • Added ResumeEditDiscordRolesModal, ResumeEditDiscordRolesButton, and ResumeApplyDiscordRolesButton classes for the Discord role edit/apply UI flow.
  • Refactored _build_discord_role_suggestions into a static method with case-insensitive filtering and added protected-role exclusion.
  • Extended ResumeUpdateConfirmationView to carry role suggestions and target user context, and added comprehensive unit tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
apps/discord_bot/src/five08/discord_bot/cogs/crm.py Added Discord role modal, edit/apply buttons, protected-role constant, refactored suggestion builder, and wired suggestions into the confirmation view and preview flow.
tests/unit/test_crm.py Added tests for role suggestion filtering, modal submit behavior, and the apply button's role assignment and status reporting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1007 to +1012
for role in raw.splitlines():
role_name = role.strip()
if not role_name:
continue
key = role_name.casefold()
if key in {name.casefold() for name in _DISCORD_ROLES_PROTECTED_FROM_APPLY}:
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The set {name.casefold() for name in _DISCORD_ROLES_PROTECTED_FROM_APPLY} is reconstructed on every iteration of the loop. Since _DISCORD_ROLES_PROTECTED_FROM_APPLY is a module-level constant, compute the casefolded set once before the loop (or define it as a module-level constant alongside _DISCORD_ROLES_PROTECTED_FROM_APPLY).

Suggested change
for role in raw.splitlines():
role_name = role.strip()
if not role_name:
continue
key = role_name.casefold()
if key in {name.casefold() for name in _DISCORD_ROLES_PROTECTED_FROM_APPLY}:
protected_roles_casefolded = {
name.casefold() for name in _DISCORD_ROLES_PROTECTED_FROM_APPLY
}
for role in raw.splitlines():
role_name = role.strip()
if not role_name:
continue
key = role_name.casefold()
if key in protected_roles_casefolded:

Copilot uses AI. Check for mistakes.
Comment on lines +1363 to +1366
for role_name in suggested_roles:
if role_name.casefold() in {
name.casefold() for name in _DISCORD_ROLES_PROTECTED_FROM_APPLY
}:
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

Same issue as in the modal's on_submit — the casefolded protected-role set is rebuilt on every loop iteration. Compute it once before the loop to avoid redundant set construction. This pattern appears in three places across this PR (lines 1012, 1364, and 3533).

Suggested change
for role_name in suggested_roles:
if role_name.casefold() in {
name.casefold() for name in _DISCORD_ROLES_PROTECTED_FROM_APPLY
}:
protected_casefolded = {
name.casefold() for name in _DISCORD_ROLES_PROTECTED_FROM_APPLY
}
for role_name in suggested_roles:
if role_name.casefold() in protected_casefolded:

Copilot uses AI. Check for mistakes.
continue
role_add.append(role)

if not role_add and not already_assigned and not protected:
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

This guard returns early with "None of the suggested roles are assignable" when there are no roles to add, none already assigned, and no protected roles — but it does not check missing or blocked. If all suggested roles fall into the missing or blocked categories, the code falls through to defer() and sends a confusing follow-up saying "✅ No new roles to apply." with missing/blocked lines, rather than giving the user a clear upfront warning. Consider also including missing and blocked in this guard condition, or restructuring so the summary always goes through the follow-up path.

Suggested change
if not role_add and not already_assigned and not protected:
if (
not role_add
and not already_assigned
and not protected
and not missing
and not blocked
):

Copilot uses AI. Check for mistakes.
)
return

if not (bot_member.top_role > target_member.top_role):
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The _Role.__gt__ helper in the test stub returns False when other is not a _Role instance, but discord.Role uses position-based comparison. More importantly, the > operator on Discord roles compares by position and by ID for equal positions. If the bot's top role and target's top role are at the same position, this check will block the action even though it might be valid. This is a minor edge case, but worth noting that the comparison semantics here depend on Discord.py's Role.__gt__ implementation, which this mirrors correctly for the typical case.

Copilot uses AI. Check for mistakes.
current_discord_roles=["backend"],
)

assert technical == []
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The test asserts technical == [] but the expected filtering logic is: suggest_technical_discord_roles returns ["Backend", "Manager", "Member", "Steering Committee"], then Manager is removed by DISCORD_ROLES_NEVER_SUGGEST, Member and Steering Committee are removed by _DISCORD_ROLES_PROTECTED_FROM_APPLY, and Backend is removed because "backend" is in current_discord_roles (case-insensitive match). This is correct. No issue here — disregard.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 9, 2026 19:52
Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/unit/test_crm.py (1)

1253-1254: ⚠️ Potential issue | 🔴 Critical

Debug field in _build_resume_preview_embed must reference the debug filename.

The test expects the Debug field value to include both the fallback reason and a reference to "resume-extract-debug.json" so operators know to look for the debug attachment. Currently, the implementation at lines 3397-3399 only includes the fallback reason without mentioning the filename.

The Debug field should be updated to include a reference to the debug JSON filename in its value, e.g., f"See resume-extract-debug.json for details. Fallback: {llm_fallback_reason}".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_crm.py` around lines 1253 - 1254, The Debug field created in
_build_resume_preview_embed currently only contains the fallback reason; update
the value assigned to that Debug field (the field in embed.fields with name
"Debug") to include the debug filename reference, e.g. build the string like
"See resume-extract-debug.json for details. Fallback: {llm_fallback_reason}"
(use the existing llm_fallback_reason variable) so operators can find the debug
attachment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 1266-1441: Add best-effort audit writes throughout
ResumeApplyDiscordRolesButton.callback: on every early exit (no guild, no linked
user, invalid ID, user not in server, unable to resolve bot_member, missing
Manage Roles, bot role too low, no suggestions), on HTTPException failure to add
roles, and on final success. Use the existing audit helper used elsewhere in the
resume flows (call it the project’s audit helper function e.g.,
write_audit_event / audit_helper.* used in other cogs) and invoke it inside a
try/except that logs but does not alter control flow so audit failures are
non-fatal; include view.contact_id, interaction.user as actor,
target_user_id_int, action="apply_discord_roles", outcome details
(denied/reason, failed/error, or success with roles applied/summary) in the
audit payload to mirror surrounding resume action audits.
- Around line 1002-1044: The modal currently treats free-form role names as an
authorization source; fix by enforcing a server-computed immutable allowlist and
intersection both at submit and at apply time: in on_submit use a trusted set
(e.g., DISCORD_ROLES_ALLOWLIST) instead of accepting any normalized entries and
set confirmation_view.discord_role_suggestions to the intersection of normalized
and that allowlist (still update ResumeApplyDiscordRolesButton.disabled
appropriately), and ensure the code path that actually applies roles (the
resume/apply handler referenced by the ResumeApplyDiscordRolesButton and the
apply logic previously around lines 1266-1379) re-validates by intersecting
requested roles with the same server allowlist and/or verifying staff
permissions before performing any role grants.

---

Outside diff comments:
In `@tests/unit/test_crm.py`:
- Around line 1253-1254: The Debug field created in _build_resume_preview_embed
currently only contains the fallback reason; update the value assigned to that
Debug field (the field in embed.fields with name "Debug") to include the debug
filename reference, e.g. build the string like "See resume-extract-debug.json
for details. Fallback: {llm_fallback_reason}" (use the existing
llm_fallback_reason variable) so operators can find the debug attachment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2740904f-ebd0-43f6-89ab-7112c0ca574f

📥 Commits

Reviewing files that changed from the base of the PR and between 171bbda and 9509b1e.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • tests/unit/test_crm.py

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1028 to +1034
if normalized:
count = len(normalized)
await interaction.response.send_message(
f"✅ Discord roles updated to {count} role{'s' if count != 1 else ''}.",
ephemeral=True,
)
return
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

When both valid and protected roles are submitted, the confirmation message only reports the count of valid roles (e.g., "✅ Discord roles updated to 2 roles.") without mentioning that protected roles like "Steering Committee" and "Admin" were silently removed. This could confuse users who don't realize their entries were filtered out. Consider appending a note about blocked roles when blocked is non-empty, e.g., mentioning which protected roles were excluded.

Copilot uses AI. Check for mistakes.
@michaelmwu michaelmwu merged commit 24679e8 into main Mar 9, 2026
5 checks passed
@michaelmwu michaelmwu deleted the michaelmwu/role-editor branch March 9, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants