feat(gitlab): track contributor seat on merge request opened#116576
Conversation
Adds a webhook processor that seeds OrganizationContributors when a GitLab merge request is opened, so seat-based Seer billing works once an org is moved onto organizations:seat-based-seer-enabled. Mirrors the GitHub side where track_contributor_seat fires from the pull_request opened webhook. Registered as a WebhookProcessor on MergeEventWebhook so errors are isolated per-processor by the existing _handle loop. Action filtered to "open" only — GitLab's merge_request event also delivers update / close / merge actions that should not seed a contributor. Gated by organizations:seer-code-review-gitlab — the same cohort flag the merge_request handler uses, so seat seeding only happens for orgs that are already opted in to GitLab code review. The downstream should_increment_contributor_seat additionally requires organizations:seat-based-seer-enabled before any row is actually written. Refs SCM-99
…butor_seat The webhook processor receives an RpcOrganization, but track_contributor_seat is typed to take an Organization. Mirror the pattern used by handle_merge_request_event: fetch the real Organization from cache via the rpc org's id, then pass that down.
Addresses two issues called out by Cursor Bugbot on PR #116576: 1. Seat processor runs too late. MergeEventWebhook.WEBHOOK_EVENT_PROCESSORS ran handle_merge_request_event before track_gitlab_contributor_seat_processor, so the code-review handler's preflight billing check hit ORG_CONTRIBUTOR_NOT_FOUND on the first MR open from a new contributor even though the same delivery seeded the row seconds later. Swap the tuple order so seeding runs first. 2. Open redelivery double-counts num_actions. GitLab redelivers merge_request hooks on response timeout, and the endpoint dispatches each payload once per installed organization. The action=="open" gate is not idempotent against either, so a re-delivered open event would call track_contributor_seat again and increment num_actions twice for a single MR. Mirror the existing dedup pattern from merge_request.py: short Redis TTL key per (org, repo, MR iid), separate namespace from the code-review handler's dedup key. Also rewrite the module docstring to describe what we actually do (action filter + Redis dedup), how it differs from GitHub's DB-driven `if created:` gate, and the malformed-payload follow-up tracked on SCM-99.
If Organization.objects.get_from_cache raised DoesNotExist, the Redis NX key was already set, so GitLab redeliveries within the 20s TTL would be treated as duplicates and skip seeding even though no contributor was ever written. Resolve the org first; only mark the delivery as seen after we know we have an org to track for. The track_contributor_seat reraise path (DB errors, etc.) still leaves the seen key in place — matches the pattern in merge_request.py and is a small acceptable trade-off vs. cleanup-on-failure complexity. Addresses Cursor Bugbot finding on PR #116576.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit cde14b9. Configure here.
|
Reviewed this — design and ordering rationale are solid, and the dedup/ordering docstrings are genuinely helpful. Two things worth addressing before merge (filing the other two findings as SCM follow-ups): 1. Module docstring contradicts the actual write behaviorIn
But This is almost certainly the intended behavior (seeding the row is exactly what makes preflight stop returning 4. No end-to-end test that a row is actually seededAll the unit tests mock The documented "known gap" (missing Two more minor findings (alias/ |

Seeds
OrganizationContributorswhen a GitLab merge request is opened, so seat-based Seer billing works once an org is moved ontoorganizations:seat-based-seer-enabled. Mirrors the GitHub side, wheretrack_contributor_seatfires from thepull_request openedwebhook (src/sentry/integrations/github/webhook.py).Implemented as a
WebhookProcessorregistered onMergeEventWebhookso errors are isolated per-processor by the existing_handleloop. Action filtered to"open"only — GitLab's merge_request event also delivers update / close / merge actions that should not seed a contributor.Gating:
organizations:seer-code-review-gitlab— the same cohort flaghandle_merge_request_eventuses, so seat seeding only happens for orgs that are already opted in to GitLab code review.organizations:seat-based-seer-enabled— checked inside the downstreamshould_increment_contributor_seat. Required before any row is actually written or a seat is assigned.This supersedes #116305, which was opened against the pre-#116317 webhook shape and is no longer rebaseable.
Once this lands, the gap documented in
src/sentry/seer/code_review/webhooks/merge_request.py's module docstring ("Code review does not fire in production yet: GitLab contributors are never seeded") closes, and the only remaining blocker before billing flips on is the per-org subscription option (SubscriptionOptions.SEER_USER_ENABLED) gatingseat-based-seer-enabled.Refs SCM-99