Add Request GitLab Duo CTA to the Duo chat panel

What does this MR do and why?

This implements a painted door test for a GitLab Duo request in the Duo chat panel, per https://gitlab.com/gitlab-org/gitlab/-/work_items/617948. The goal is to measure whether Free-group members who aren't Owners will actually ask for an upgrade, before we build a real request flow. Nothing is sent to Owners; this only records that a request happened.

In the "Your group doesn't have GitLab Duo yet" access-denied empty state, there's a "Request GitLab Duo" button. There are three states: not requested (button shown), requested (after clicking, the button becomes "Requested" with a check icon and a thank-you line), and later session (neither the button nor the line is shown). This is shown to Developers and below; Maintainers still see the existing start-trial state, unchanged, and expired-trial groups keep their existing trial-expired state, unaffected by this change.

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; expired-trial groups pass this check, but the panel shows them its trial-expired state instead), 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. For the chat panel specifically, the group must also have no active GitLab Credits add-on.

Everything is behind one gitlab_com_derisk feature flag, painted_door_request_ctas, with the root group as the actor. A request is stored as a user group callout (duo_panel_request_painted_door), giving one request per user per root group along with the time it was made — no migration needed. "Same session" is tracked with a cookie set when the request button is clicked. Signing out clears it, and otherwise it expires after one day, so the requested state carries across tabs and reloads until the user signs out or a day passes.

Three new internal events (tier: free, product_group: acquisition) cover the empty state view, the request click, and the Learn more click: view_duo_panel_request_empty_state, click_request_gitlab_duo_duo_panel_empty_state, click_learn_more_duo_panel_request_empty_state. label carries the state (not_requested / requested / previously_requested) and property carries the root group id.

It builds on the painted door eligibility in !258282 (merged) and the Duo panel empty state rework in !257025 (merged) (https://gitlab.com/gitlab-org/gitlab/-/work_items/607724), both merged.

References

Screenshots or screen recordings

State Chat panel
Not requested 01-not-requested
Requested 02-requested
Later session 03-later-session

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. Sign in as a Developer (not Owner) of a Free top-level group that has no GitLab Credits add-on.
  4. Open a project or group page under that namespace.
  5. Open the Duo chat panel and confirm the "Request GitLab Duo" button appears; click it and confirm it switches to "Requested" with the check icon and thank-you line.
  6. Reload the page and confirm the panel still shows the "Requested" state.
  7. Sign out and sign back in, then confirm the panel falls back to the later-session state: neither the button nor the thank-you line is shown.
  8. To reset and retest, delete the painted_door_* cookie (or sign out) and delete the corresponding Users::GroupCallout row for your user/group.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Edited by Roy Liu

Merge request reports

Loading
Loading