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: renders UserMenuExplorePremium when data.explore_premium is present, passing group_id and requested.
  • 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::PaintedDoorRequestEligibility is the one piece called in this MR, from EE::SidebarsHelper#super_sidebar_context, which builds the explore_premium context.

References

Screenshots or screen recordings

N/A, backend only, no UI.

How to set up and validate locally

  1. Run GDK in SaaS simulation mode: export GITLAB_SIMULATE_SAAS=1 (or set the equivalent gdk config value) and restart the relevant services.
  2. In a rails console, enable the flag: Feature.enable(:painted_door_request_ctas).
  3. 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
  4. 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.

Edited by Roy Liu

Merge request reports

Loading
Loading