Skip to content

[codex] Add member agreement contact selection#329

Merged
michaelmwu merged 4 commits into
mainfrom
michaelmwu/membership-agreement-contact-buttons
Jun 6, 2026
Merged

[codex] Add member agreement contact selection#329
michaelmwu merged 4 commits into
mainfrom
michaelmwu/membership-agreement-contact-buttons

Conversation

@michaelmwu

@michaelmwu michaelmwu commented Jun 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Add a member agreement contact selection view with per-contact Discord buttons.
  • Show the selection view when /send-member-agreement finds multiple CRM contacts instead of returning only a refine-search message.
  • Route selected contacts through the same single-contact send flow so already-signed, missing-email, DocuSeal, and audit behavior stays consistent.
  • Cover the ambiguous-search view and button callback with focused unit tests.

Validation

  • uv run pytest tests/unit/test_crm.py -k "send_member_agreement or member_agreement_selection" -q
  • uv run ruff check apps/discord_bot/src/five08/discord_bot/cogs/crm.py tests/unit/test_crm.py
  • git diff --check
  • pre-commit on commit: ruff, ruff format, mypy

Summary by CodeRabbit

  • New Features
    • Enhanced member agreement workflow with contact selection UI. When multiple CRM contacts match during a search, users now receive an interactive selection menu to choose the intended contact before sending the agreement. The menu includes a timeout that disables controls after a period of inactivity.

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Looking for one thing? Review this PR in Change Stack to search files, summaries, diffs, and code without losing your place.

Review Change Stack

Warning

Review limit reached

@michaelmwu, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 17 minutes and 36 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab5d99b6-7951-4f69-943e-2f45c00123d0

📥 Commits

Reviewing files that changed from the base of the PR and between 26a90f0 and 77cc5a9.

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

Walkthrough

This PR refactors the send-member-agreement slash command by extracting multi-contact selection into a dedicated Discord UI view with button-based contact selection and ephemeral deferrals, and extracting per-contact sending logic into helper methods. A new MemberAgreementSelectionView with MemberAgreementSelectionButton handles contact disambiguation with timeout-based view disabling. Tests validate the selection UI and per-contact triggering behavior.

Changes

Member Agreement Contact Selection

Layer / File(s) Summary
Contact selection UI and display
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
New MemberAgreementSelectionButton and MemberAgreementSelectionView UI components enable contact selection with requester validation, ephemeral deferrals, and timeout-based button disabling. Helper method _show_send_member_agreement_contact_choices builds and sends an ephemeral selection embed with up to five candidate contacts.
Per-contact agreement sending flow
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
New helper method _send_member_agreement_for_contact_flow extracts contact ID, name, and email from a selected contact and reads the existing "member agreement signed at" metadata before proceeding with the send flow.
Command refactoring and UI workflow tests
apps/discord_bot/src/five08/discord_bot/cogs/crm.py, tests/unit/test_crm.py
The send-member-agreement command is refactored to delegate multi-contact selection and per-contact sending to the new helpers while retaining error handling. Tests verify that multiple contacts trigger an ephemeral selection view with correct labels and that button clicks invoke the per-contact flow and disable the view's buttons.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 508-dev/508-workflows#209: Introduced the original /send-member-agreement CRM command; this PR refactors its contact selection and sending workflows with new UI components and helper methods.

Poem

🐰 A button to choose, a view to display,
Five contacts at once, pick one right away!
The workflow now flows in helpers so clean,
Where selection and sending are clearly seen.
Ephemeral messages that vanish with time,
The refactor's now finished—complete and sublime!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding a member agreement contact selection feature with UI components and refactored command logic.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/membership-agreement-contact-buttons

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds an interactive contact disambiguation flow to the Discord /send-member-agreement command so ambiguous CRM searches present a button-based selection UI, then routes the chosen contact through the existing single-contact send/audit logic.

Changes:

  • Introduces MemberAgreementSelectionView + per-contact buttons to select which CRM contact should receive the member agreement.
  • Refactors the member-agreement send logic into a shared _send_member_agreement_for_contact_flow used by both the slash command and the selection button callback.
  • Adds unit tests covering the ambiguous-search selection view and the button callback behavior.

Reviewed changes

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

File Description
tests/unit/test_crm.py Adds focused unit tests for the new multi-contact selection UI and callback flow.
apps/discord_bot/src/five08/discord_bot/cogs/crm.py Implements the member agreement selection view/buttons and updates the command flow to use it when multiple contacts match.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26a90f07d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +741 to +745
await self.view.crm_cog._send_member_agreement_for_contact_flow(
interaction=interaction,
contact=self.contact,
search_term=self.view.search_term,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Disable selection before sending the agreement

When the requester double-clicks a contact button or clicks another contact before the DocuSeal request returns, both component callbacks can reach this awaited send path because the view is only disabled after _send_member_agreement_for_contact_flow finishes. Each callback can create its own DocuSeal submission, so an ambiguous search can send duplicate agreements or agreements to multiple matches; guard the view with an in-progress flag or disable/edit the controls before awaiting the send.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
tests/unit/test_crm.py (1)

7669-7711: ⚡ Quick win

Add a non-requester callback test for the selection button.

You already cover the happy path. Please add one test where interaction.user.id != requester_id and assert _send_member_agreement_for_contact_flow is not called; this locks the requester-validation contract against regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_crm.py` around lines 7669 - 7711, Add a new async pytest that
mirrors test_member_agreement_selection_button_sends_selected_contact but sets
interaction.user.id to a value different from the view.requester_id (e.g.,
requester_id=123 and interaction.user.id=999), calls the same button.callback
obtained from MemberAgreementSelectionView after add_contact_button, and asserts
crm_cog._send_member_agreement_for_contact_flow.assert_not_awaited(); also
assert interaction.response.defer and interaction.message.edit were not awaited
and that the view buttons remain enabled (i.e., not all children disabled) to
lock requester-validation behavior in MemberAgreementSelectionView and
add_contact_button.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 733-738: The branches that reject non-requesters (checks against
self.requester_id that call interaction.response.send_message and return) must
also emit an audit record before returning; update the member-agreement button
callback handlers in crm.py to POST an audit event to the worker ingest endpoint
(use AUDIT_API_BASE_URL and API_SHARED_SECRET) whenever you send the "Only the
command requester..." or similar denial messages (the branches using
interaction.response.send_message and self.requester_id around those callbacks).
Ensure the audit payload includes actor id, action ("member-agreement_denied" or
similar), target/context (guild/message/request id), timestamp, and a reason,
sign or authorize the request with API_SHARED_SECRET, and only then return after
emitting the audit.

In `@tests/unit/test_crm.py`:
- Around line 7643-7650: Replace real-looking personal emails in the test
fixture entries that use the "emailAddress" field (e.g., the records with "id":
"crm-456" / name "Wilson Kao" and the other record around the same block) with
safe reserved example addresses (for example using example.com or clearly
synthetic domains) so tests avoid storing potential PII; update the two
occurrences noted (current values like "will.gutierrez@gmail.com" and
"lairwaves5888@gmail.com" and the similar entries around lines 7674-7678) to use
addresses such as "user@example.com" or "wilson.kao@example.com".

---

Nitpick comments:
In `@tests/unit/test_crm.py`:
- Around line 7669-7711: Add a new async pytest that mirrors
test_member_agreement_selection_button_sends_selected_contact but sets
interaction.user.id to a value different from the view.requester_id (e.g.,
requester_id=123 and interaction.user.id=999), calls the same button.callback
obtained from MemberAgreementSelectionView after add_contact_button, and asserts
crm_cog._send_member_agreement_for_contact_flow.assert_not_awaited(); also
assert interaction.response.defer and interaction.message.edit were not awaited
and that the view buttons remain enabled (i.e., not all children disabled) to
lock requester-validation behavior in MemberAgreementSelectionView and
add_contact_button.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 07880d37-577a-4702-b99c-ab7420af863f

📥 Commits

Reviewing files that changed from the base of the PR and between b03caf8 and 26a90f0.

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

Comment thread apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Comment thread tests/unit/test_crm.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a879d39aae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

search_term=search_term,
)

for i, contact in enumerate(contacts[:5], 1):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include unsigned choices past the first five matches

When an ambiguous search returns more than five contacts, this loop only inspects contacts[:5] for both display and buttons. If the first five matches are already signed but a later match is unsigned, has_send_options becomes false and the command sends an embed with no controls, even though there is a valid contact that could receive the agreement; users then cannot select that result from this flow and are not told that eligible matches were omitted.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

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.

Comment on lines +8811 to +8827
for i, contact in enumerate(contacts[:5], 1):
name = contact.get("name", "Unknown")
email = self._contact_preferred_email(contact) or "No email"
contact_id = contact.get("id", "")
signed_at = self._contact_text_value(
contact.get(MEMBER_AGREEMENT_SIGNED_AT_FIELD)
)
agreement_status = (
f"✅ Already signed: `{signed_at}`"
if signed_at
else "📝 Not signed; send/resend allowed"
)
contact_info = f"📧 {email}\n🆔 ID: `{contact_id}`\n{agreement_status}"
embed.add_field(name=f"{i}. {name}", value=contact_info, inline=True)
if not signed_at:
view.add_contact_button(contact)

Comment on lines +8828 to +8851
embed.add_field(
name="💡 Tip",
value=(
"Select an unsigned contact button to continue. "
"Prior send requests are not tracked, so unsigned contacts can be resent."
),
inline=False,
)

has_send_options = len(view.children) > 0
if has_send_options:
message = await interaction.followup.send(
embed=embed,
view=view,
ephemeral=True,
wait=True,
)
view.set_message(message)
return

await interaction.followup.send(
embed=embed,
ephemeral=True,
)
@michaelmwu michaelmwu merged commit 6c08c20 into main Jun 6, 2026
7 checks passed
@michaelmwu michaelmwu deleted the michaelmwu/membership-agreement-contact-buttons branch June 6, 2026 03:23
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