Skip to content

Fix discord role apply gating#201

Merged
michaelmwu merged 4 commits intomainfrom
michaelmwu/fix-disc-roles
Mar 9, 2026
Merged

Fix discord role apply gating#201
michaelmwu merged 4 commits intomainfrom
michaelmwu/fix-disc-roles

Conversation

@michaelmwu
Copy link
Member

@michaelmwu michaelmwu commented Mar 9, 2026

Description

This change fixes CRM resume role-apply behavior so the "Apply Discord Roles" button can be used whenever a Discord target user is known, even when can_apply_discord_roles is not explicitly passed.
It infers role-apply capability from discord_role_target_user_id and valid link_discord payloads, and preserves the existing explicit flag path.
It also keeps the UI and validation consistent by disabling the apply button when role application is not allowed and surfacing the existing warning flow.

Related Issue

#199

How Has This Been Tested?

Not run in this environment after this change; the upstream CI failure pinpointed this exact callback path, and the branch includes the patch to address that regression path.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added permission gating for Discord role application; users must now link a Discord account to apply roles.
    • Apply Discord Roles button now properly disables when insufficient permissions exist or no Discord account is linked.
    • UI displays "Link required" notices to guide users when account linking is necessary for role application.

Copilot AI review requested due to automatic review settings March 9, 2026 20:15
@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 18 minutes and 31 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: 2501cebc-810b-4b9f-93a3-b9183ec4335d

📥 Commits

Reviewing files that changed from the base of the PR and between 91728db and d462f40.

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

Walkthrough

A single-file update to the Discord bot CRM cog that introduces a can_apply_discord_roles flag to control whether Discord role application is permitted. The flag is computed based on linked user availability and propagated through UI components (button, view, embed) to gate role application and display contextual guidance.

Changes

Cohort / File(s) Summary
Discord CRM Role Application Permissions
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added can_apply_discord_roles parameter to ResumeUpdateConfirmationView, _build_role_suggestions_embed, and ResumeApplyDiscordRolesButton. Threaded flag through call sites to disable role application button and display "Link required" notice when Discord user linking is unavailable or insufficient permissions exist. Pre-check in callback denies role application if flag is false.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A button once bold, now gently controlled,
With Discord links missing, permissions withheld,
Through views and through embeds, the flag finds its way,
"Please link first," it whispers, in friendly display.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% 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 'Fix discord role apply gating' directly and concisely describes the main change—introducing gating logic for Discord role application based on user linking status.

✏️ 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/fix-disc-roles

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

Fixes Discord role-apply gating in the CRM resume flow so the “Apply Discord Roles” button is enabled/usable whenever a Discord target user is known, even if an explicit can_apply_discord_roles flag isn’t provided.

Changes:

  • Adds can_apply_discord_roles inference to ResumeUpdateConfirmationView and uses it to disable/guard the Apply button.
  • Extends the “Suggested Discord Roles” embed to show a “Link required” warning when role application isn’t allowed.
  • Threads can_apply_discord_roles through the resume extract/preview flow.

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

Comment on lines +1288 to +1293
if not view.can_apply_discord_roles:
await interaction.response.send_message(
"❌ Discord roles can only be applied after linking this contact to a Discord user.",
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.

The new early return when view.can_apply_discord_roles is false bypasses _audit_apply_roles_event, so denied apply attempts in this state will no longer be audit-logged (previously the missing-linked-user path was audited). Consider moving this check below the _audit_apply_roles_event helper (and recording a "denied" stage like missing_linked_user) or explicitly calling the audit helper before returning.

Copilot uses AI. Check for mistakes.
Comment on lines +1654 to +1656
self.add_item(
ResumeApplyDiscordRolesButton(disabled=not self.can_apply_discord_roles)
)
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 change disables the Apply button based on self.can_apply_discord_roles, which will flip existing unit-test expectations (e.g., tests that currently assert the Apply button is enabled even when no Discord link/target user ID is set). Please update/add unit tests to cover both states: disabled when there is no linked/target Discord user, and enabled when discord_role_target_user_id (or a valid link_discord.user_id) is present.

Copilot uses AI. Check for mistakes.
Comment on lines +3709 to +3718
if not can_apply_discord_roles:
embed.add_field(
name="🔒 Link required",
value=(
"This contact is not currently linked to a Discord user, so suggested roles "
"cannot be applied automatically. Link them first in CRM or use "
"`/link-discord-user`."
),
inline=False,
)
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.

_build_role_suggestions_embed now conditionally adds the "🔒 Link required" field. There are existing unit tests covering this embed builder, but none appear to assert this new field behavior. Add/adjust tests to verify the field is present when can_apply_discord_roles is false and absent when it’s true, so the UX/validation contract doesn’t regress.

Copilot uses AI. Check for mistakes.
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)
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

1616-1631: ⚠️ Potential issue | 🟠 Major

Preserve explicit False instead of inferring over it.

bool(can_apply_discord_roles or discord_role_target_user_id or ...) makes can_apply_discord_roles=False impossible once a target user or link payload exists. That changes the constructor contract and can re-enable Apply in flows that intentionally passed False. Use None as the “infer it” default instead.

Suggested fix
-        can_apply_discord_roles: bool = False,
+        can_apply_discord_roles: bool | None = None,
@@
-        self.can_apply_discord_roles = bool(
-            can_apply_discord_roles
-            or discord_role_target_user_id
-            or (isinstance(link_discord, dict) and bool(link_discord.get("user_id")))
-        )
+        inferred_can_apply_discord_roles = bool(
+            discord_role_target_user_id
+            or (isinstance(link_discord, dict) and bool(link_discord.get("user_id")))
+        )
+        self.can_apply_discord_roles = (
+            inferred_can_apply_discord_roles
+            if can_apply_discord_roles is None
+            else can_apply_discord_roles
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 1616 -
1631, The constructor currently forces can_apply_discord_roles to True whenever
discord_role_target_user_id or a link_discord payload exists by assigning
self.can_apply_discord_roles = bool(can_apply_discord_roles or
discord_role_target_user_id or ...); change the parameter type to Optional[bool]
(allow None) and preserve explicit False by using the passed value when not
None—i.e., if can_apply_discord_roles is not None assign it directly, otherwise
infer from discord_role_target_user_id or link_discord (convert that inference
to bool). Update the constructor signature and the assignment for
self.can_apply_discord_roles accordingly (refer to the __init__ parameter
can_apply_discord_roles, the assignment line, discord_role_target_user_id, and
link_discord).
🤖 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 1654-1656: The Apply button is being re-enabled in
ResumeEditDiscordRolesModal.on_submit regardless of the original gate; update
that handler to use the same predicate as the initial add_item call: when
resetting the ResumeApplyDiscordRolesButton, compute disabled using not
(self.can_apply_discord_roles and bool(normalized)) (or equivalent) instead of
not bool(normalized) so the button stays disabled when
self.can_apply_discord_roles is false; adjust the code in
ResumeEditDiscordRolesModal.on_submit where ResumeApplyDiscordRolesButton is
recreated/updated to use this combined predicate.
- Around line 1288-1293: The denied branch for view.can_apply_discord_roles
returns before emitting an audit record; call the existing auditing helper
before returning so the denial is recorded. Specifically, invoke the same audit
routine used elsewhere (e.g., call/await _audit_apply_roles_event(...) or the
project audit helper used by other paths) with a denial status/reason and the
same context (interaction, view/contact, actor) immediately before the
interaction.response.send_message and return, ensuring the audit call is awaited
and uses the worker audit ingest helper used elsewhere in this module.

---

Outside diff comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 1616-1631: The constructor currently forces
can_apply_discord_roles to True whenever discord_role_target_user_id or a
link_discord payload exists by assigning self.can_apply_discord_roles =
bool(can_apply_discord_roles or discord_role_target_user_id or ...); change the
parameter type to Optional[bool] (allow None) and preserve explicit False by
using the passed value when not None—i.e., if can_apply_discord_roles is not
None assign it directly, otherwise infer from discord_role_target_user_id or
link_discord (convert that inference to bool). Update the constructor signature
and the assignment for self.can_apply_discord_roles accordingly (refer to the
__init__ parameter can_apply_discord_roles, the assignment line,
discord_role_target_user_id, and link_discord).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8ed05279-fcbc-468e-ad8e-73f7d4da3f55

📥 Commits

Reviewing files that changed from the base of the PR and between 2b0dbdd and 91728db.

📒 Files selected for processing (1)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py

Copilot AI review requested due to automatic review settings March 9, 2026 20:25
@michaelmwu michaelmwu merged commit a1351ad into main Mar 9, 2026
7 checks passed
@michaelmwu michaelmwu deleted the michaelmwu/fix-disc-roles branch March 9, 2026 20:27
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 2 comments.


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

Comment on lines +1634 to +1638
self.can_apply_discord_roles = bool(
can_apply_discord_roles
or discord_role_target_user_id
or (isinstance(link_discord, dict) and bool(link_discord.get("user_id")))
)
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.

can_apply_discord_roles is computed with an or chain, so an explicitly provided False is overridden when discord_role_target_user_id or link_discord['user_id'] is present. If the intent is to preserve an explicit flag (True/False) and only infer when it’s not provided, consider making the parameter bool | None = None and computing self.can_apply_discord_roles = inferred if can_apply_discord_roles is None else can_apply_discord_roles.

Copilot uses AI. Check for mistakes.
Comment on lines +1312 to +1316
if not view.can_apply_discord_roles:
_audit_apply_roles_event(
"denied",
"missing_linked_user",
{"reason": "button_disabled_for_role_application"},
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 new early-return branch for unlinked contacts is important behavior (and was added specifically to guard a regression), but there’s no unit test covering the callback being triggered while view.can_apply_discord_roles is false (e.g., via a stale interaction/custom_id). Adding a test that asserts the ephemeral error message (and, if possible, that audit is called) would help prevent this from regressing again.

Copilot uses AI. Check for mistakes.
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