Add re-trial card to the group Billing page

What does this MR do and why?

This MR adds a dismissible card near the top of the group Billing page, telling Free namespace Owners that they can start a GitLab Ultimate trial again.

Previously, the only prompt shown to a re-trial-eligible namespace was a small "Try for free" button low down in the plan-comparison cards, a location owners rarely reached. This card surfaces the same offer higher on the page.

Scoping and behaviour:

  • Renders only for top-level Free namespaces eligible to start a trial again (previous trial expired 90+ days ago).
  • Visible to Owners only, gated on the :edit_billing ability, which excludes auditors who can read the billing page.
  • Does not render for paid namespaces, namespaces with an active trial, or namespaces that never trialled.
  • Dismissible per owner, via a new Users::GroupCallout enum value billing_retrial_card: 42. Dismissal is per user, so one owner dismissing the card does not hide it for other owners of the same namespace. No migration is needed since feature_name is an existing integer column.
  • The CTA links to the existing trial flow (new_trial_path).

Implementation notes for reviewers:

  • Eligibility is exposed to the frontend as eligibleForRetrial, computed in ee/app/helpers/billing_plans_helper.rb as eligible_for_trial? && !never_had_trial?. This is logically equivalent to calling Namespace#eligible_for_retrial?, but reuses a value the page already computes, avoiding a second CustomersDot eligibility lookup (Redis-cached for 8 hours) on every page render.
  • ee/app/assets/javascripts/pages/groups/billings/index.js now passes withApolloProvider: true. The billing app previously had no Apollo provider; the callout dismisser needs one for its mutation. Without it, dismissal fails silently.
  • No new feature flag. The card is gated by the existing saas_ultimate_retrial flag, which Namespace#eligible_for_retrial? already checks.
  • This also corrects the gap above the plan cards from 40px to the 24px specified in the design. Because adjacent CSS margins collapse to the larger value, that gap is governed by the plan-cards row's own margin, so the spacing also changes for namespaces that do not see the card. The Figma block is an auto-layout column with a uniform 24px gap throughout, so 24px appears to be the intended value everywhere, but this is worth a designer's confirmation.

Tracking: two internal events, click_start_trial_on_retrial_card_group_billing and dismiss_retrial_card_group_billing, matching the issue's requirement to track the CTA click and the dismissal. Both event definitions use product_group: acquisition. This was chosen because no event definition in the repo uses monetization, and the analogous existing billing-page trial event (click_start_trial_cta_group_billing) uses acquisition. Worth confirming with reviewers.

Tests:

  • A Jest component spec covering every condition under which the card must not render.
  • A Capybara feature spec (ee/spec/features/groups/owner_views_retrial_card_spec.rb) using :capture_snowplow_events to assert both events are delivered through real browser clicks, and that dismissal persists across page loads.
  • Updates to the billing plans helper spec.

No changelog trailer is included, since the card is behind saas_ultimate_retrial, whose YAML has default_enabled: false.

References

Screenshots or screen recordings

Before After
image.png image.png

How to set up and validate locally

  1. Ensure the GDK is running in SaaS mode (GITLAB_SIMULATE_SAAS=1).

  2. Open a rails console and, for a top-level group you own, run:

    Feature.enable(:saas_ultimate_retrial)
    group = Group.find_by_full_path('your-group')
    group.gitlab_subscription.update!(
      hosted_plan: Plan.find_by(name: Plan::FREE),
      trial: true, trial_starts_on: 200.days.ago, trial_ends_on: 170.days.ago
    )
    Rails.cache.write("namespaces:eligible_trials:#{group.id}", ['ultimate_with_dap'], expires_in: 8.hours)

    The Rails.cache.write line stands in for the CustomersDot eligibility response, so a local CustomersDot instance is not required. After this, group.eligible_for_retrial? should return true.

  3. Visit the group's Billing page. The card should appear above the current-plan cards.

  4. Note: the trial dates must be well in the past. A trial that expired within the last 10 days triggers the separate end-of-trial modal, which renders over the card.

  5. To run the feature spec locally, prefix the rspec command with ENABLE_RSPACK=true, otherwise the asset manifest lookup fails.

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Marco Tomasi

Merge request reports

Loading
Loading