Skip to content

feat(slack): credit Slack entries to the reporter's WordPress user - #196

Merged
leogermani merged 5 commits into
trunkfrom
feat/slack-handle-author
Oct 8, 2026
Merged

leogermani merged 5 commits into
trunkfrom
feat/slack-handle-author

Conversation

@leogermani

@leogermani leogermani commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Every rolling coverage entry posted from Slack is credited to a generic Slack bot user. Readers never see who reported it, and editors have to reassign entries by hand. With this change, reporters can link their Slack account to their WordPress user, and what they post from Slack is credited to them.

What changes

The profile and user edit screens get a Rolling Coverage section with a "Slack handle" field. It takes a Slack handle or a Slack member ID, and its help text explains how to find a member ID in Slack. When a message arrives, the entry is credited to the user whose member ID or handle matches the person who posted it. Messages from anyone else still go to the Slack bot user.

With this change:

  • A reporter's Slack posts show under their own byline, and images they upload with a message belong to them too.
  • A member ID is the reliable option. A handle can be matched by anyone who changes their Slack name to it; a member ID can't.
  • Each value belongs to one user at a time. The field is self-service, so an administrator settles any dispute by clearing it on the other profile.
  • Only users who can write entries see the section.

How to test

  1. Connect Slack and link a channel to a coverage.
  2. As an Author, open Profile. Expect a Rolling Coverage section with a "Slack handle" field.
  3. In Slack, open your profile, then More (⋮) → Copy member ID. Paste it into the field and save.
  4. Post in the linked channel. Expect the new entry credited to you, not the Slack bot user.
  5. Replace the value with your Slack display name, save, and post again. Expect the entry credited to you.
  6. Clear the field and post again. Expect the entry credited to the Slack bot user.
  7. As a second Author, save your member ID from step 3 on their profile. Expect "already assigned to another user", and nothing saved.
  8. In Slack, set another member's display name to your member ID, then post as them. Expect the entry credited to the Slack bot user, not you.
  9. As an administrator, open a Subscriber's profile. Expect no Rolling Coverage section.
Technical details

Matching. Slack_Author_Resolver::resolve_author() compares the message's member ID with stored member IDs, exactly. It then compares the author's Slack display name, full name (real_name) and username, in that order, with stored handles only, ignoring case through the database collation. Names are free text their owner can change, so a name shaped like a member ID never reaches a member-ID mapping. A stored value is a member ID when it starts with U or W and is uppercase letters with at least one digit. Only users who can edit_posts are credited, checked with user_can() on the matching rows rather than the get_users() capability argument, which misses filtered capabilities and network super admins.

Webhook budget. The member ID comes with the event, so it matches even when the 1s users.info lookup fails. Each message runs at most four indexed get_users() meta lookups.

Trust model. The field is self-service: anyone who can write entries can enter any value on their own profile, and nothing checks it against Slack. This is a deliberate choice. Restricting it to editors, or verifying member IDs through users.lookupByEmail (a new Slack scope), were the alternatives. src/admin/DEVELOPMENT.md ("Slack authors") documents the trust model.

Other changes. The ingest log's success entry gains an author field (member_id, display_name, real_name, name or bot), so entries credited through a name can be found if one turns out to be wrong. Entry_Ingestion_Service::ingest()'s $bot_user_id parameter is now $author_id.

Known gaps, left for follow-ups:

  • With Co-Authors Plus on, an ingested entry gets the WordPress user as co-author, not their linked guest author, unlike the entries list's Reassign.
  • On multisite, the duplicate check only sees users of the current site, while the meta is network-wide.
  • A member ID typed by hand in lowercase is stored as a handle, so a display name can match it. A save-time warning would catch this.

Tests. tests/test-slack-author-resolver.php (new) covers matching order, the member-ID impersonation case, capability gating and the profile save rules; tests/test-slack-webhook.php adds end-to-end cases for a mapped handle and member ID. The full PHPUnit suite passes (1009 tests), and PHPCS is clean on the changed files.

Self-review: three rounds (Opus 5.5), one blocker fixed.

🤖 Generated with Claude Code

leogermani and others added 3 commits October 7, 2026 16:20
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leogermani
leogermani requested a balanced review from Copilot October 7, 2026 20:00
@leogermani

Copy link
Copy Markdown
Contributor Author

Self-review summary — 3 rounds on this branch before handoff.

Accepted:

  • 25fa0ea: A Slack name set to someone's member ID no longer matches that person. Only the message's own member ID is compared with stored member IDs; display name, full name and username are compared with stored handles only. The field now shows only for users who can write entries, the full name is tried before the legacy username, member IDs must include a digit, the ingest log records how each author was matched, and the field copy and docs describe the trust model.
  • 60f11a5: Tests for refusing a duplicate member ID and for how an author was matched. The help text says member IDs are uppercase, and the profile nonce is checked only when the Slack field is submitted.
  • Round 3: no changes — confirmed the above holds.

Declined:

  • Restricting who can set the value — the field stays self-service by design. Anyone who can write entries can enter any value on their own profile, first come, first served; an administrator settles a dispute. The docs say so, and the field copy no longer claims a member ID can't be used by someone else.
  • Keeping the rest of the profile save when the Slack value is refused — a refused value returns the form with an error, as core does for any profile field.
Suggestions deferred (not blocking)
  • With Co-Authors Plus on, check whether an ingested entry should use the author's linked guest author, as the entries list's Reassign does.
  • On multisite, the duplicate check only sees users of the current site (documented).
  • A member ID typed by hand in lowercase is stored as a handle, so a display name can match it. A save-time warning would catch it.
  • A phpcs:ignore before the field check suppresses nothing and can go.

@leogermani
leogermani marked this pull request as ready for review October 7, 2026 20:01

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.

🟡 Changes recommended

Concurrent saves and double-unslashing can bypass mapping uniqueness and misattribute entries.

2 open findings
What changed in this PR

Credits Slack entries and uploaded images to mapped WordPress reporters, with the bot user as fallback.

Changes:

  • Adds profile fields and member-ID-first author matching.
  • Applies resolved ownership during ingestion and logs the match source.
  • Documents the trust model and adds resolver and webhook tests.
File Description
tests/​test-slack-webhook.php Tests mapped entry and image ownership.
tests/​test-slack-author-resolver.php Tests matching, permissions, and profile validation.
src/​admin/​DEVELOPMENT.md Documents author mapping and trust constraints.
includes/​sources/​class-entry-ingestion-service.php Renames the author parameter for broader use.
includes/​slack/​class-slack-webhook-controller.php Applies resolved authors and logs matching.
includes/​slack/​class-slack-author-resolver.php Adds profile mapping and author resolution.
includes/​class-slack.php Registers profile hooks.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread includes/slack/class-slack-author-resolver.php
Comment thread includes/slack/class-slack-author-resolver.php Outdated
leogermani and others added 2 commits October 7, 2026 17:43
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leogermani
leogermani merged commit 52254b6 into trunk Oct 8, 2026
5 checks passed
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Hey @leogermani, good job getting this PR merged! 🎉

Now, the needs-changelog label has been added to it.

Please check if this PR needs to be included in the "Upcoming Changes" and "Release Notes" doc. If it doesn't, simply remove the label.

If it does, please add an entry to our shared document, with screenshots and testing instructions if applicable, then remove the label.

Thank you! ❤️

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.

2 participants