Add painted door request eligibility and callouts
What does this MR do and why?
This adds the shared backend pieces for a painted door test that measures whether Free-group members who aren't Owners would ask for a Premium or Duo upgrade, before we build a real request flow. Nothing here is user-visible; everything is behind a flag that's off by default.
Two new user group callout IDs are added, duo_panel_request_painted_door (43) and explore_premium_request_painted_door (44), along with the regenerated GraphQL docs and introspection files.
Everything is behind one gitlab_com_derisk feature flag, painted_door_request_ctas, with the root group as the actor.
Eligibility is centralized in one shared class, GitlabSubscriptions::PaintedDoorRequestEligibility: GitLab.com only, root namespace is a group on the Free plan (so active trials, OSS, and EDU are excluded, but expired-trial groups are included), the user is a member of the group or one of its subgroups (project-only members are excluded) but can't edit billing (i.e. not an Owner), and the user isn't a GitLab team member. The eligibility class also supports a chat-panel-only rule (no active GitLab Credits add-on on the group); that rule is used by the companion Duo chat panel MR, not by this surface.
EE::SidebarsHelper gains an explore_premium super sidebar context (group_id and requested). Nothing reads it yet — the profile menu MR is what consumes it.
The shared frontend helper ee/app/assets/javascripts/painted_door_request/utils.js is also included here. It manages request state, tracks "same session" with a cookie, and posts the request to the group callouts REST endpoint. It has no callers in this MR; both the profile menu MR and the Duo chat panel MR import it, which is why it lives here instead of in either of them.
Both !257338 (merged) and !257330 (merged) are stacked on this MR.
Where this code is used
The sidebar context and the frontend helper have no callers in this MR, so these links show how !257338 (merged) uses them.
user_menu.vue: rendersUserMenuExplorePremiumwhendata.explore_premiumis present, passinggroup_idandrequested.getInitialRequestState: sets the initial request state.recordRequest: posts the request when the user clicks the request button.REQUEST_STATE: drives which modal state is shown.GitlabSubscriptions::PaintedDoorRequestEligibilityis the one piece called in this MR, fromEE::SidebarsHelper#super_sidebar_context, which builds theexplore_premiumcontext.
References
- Related to https://gitlab.com/gitlab-org/gitlab/-/work_items/617948
- !257338 (merged) and !257330 (merged) are both stacked on this MR.
Screenshots or screen recordings
N/A, backend only, no UI.
How to set up and validate locally
- Run GDK in SaaS simulation mode:
export GITLAB_SIMULATE_SAAS=1(or set the equivalentgdk configvalue) and restart the relevant services. - In a rails console, enable the flag:
Feature.enable(:painted_door_request_ctas). - Find a Free top-level group, and both a Developer and the Owner as members. Check eligibility for the Developer:
GitlabSubscriptions::PaintedDoorRequestEligibility.new(user: developer, namespace: group, feature: :explore_premium).eligible? # => true - Check the same for the Owner:
GitlabSubscriptions::PaintedDoorRequestEligibility.new(user: owner, namespace: group, feature: :explore_premium).eligible? # => false
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.