Re-trial Ultimate eligibility for GitLab.com

What does this MR do and why?

Part of the GitLab.com re-trial initiative (#598191, epic &20504). This is the GitLab.com monolith side; the CustomersDot side is gitlab-org/customers-gitlab-com!16239.

Today a Free namespace that already trialed Ultimate can never re-trial — Namespace#eligible_for_trial? requires never_had_trial?, so the CTAs and the :start_trial ability vanish forever. Behind the saas_ultimate_retrial flag, this MR lets a Free namespace become eligible again once its previous Ultimate trial ended more than the waiting window ago, treating it like a first-time trialer.

Scope (iteration 1)

Re-surface the existing "Start a free trial" CTAs/badge only — no re-trial-specific copy (matches the UX decision in #584569; the differentiated/persistent post-trial experience is iteration 2, #603646).

In .com we cache whether a namespace is eligible for a trial for 8 hours (Namespaces::TrialEligibleFinder, TTL-only, no active invalidation). Worst case, we don't show the trial button for up to 8 hours after the window has elapsed even though the trial would be allowed — never the reverse: during the waiting window CustomersDot reports the namespace as not eligible and the button stays hidden.

How to set up and validate locally

These instructions require CustomersDot to be running locally.

  1. Create new group in gdk.test:3000/dashboard/groups . Let's say the name of the group is test-trial and its id is 123.

  2. Enable both flags

    • .com console:

      ns = Group.find_by_full_path('test-trial')
      Feature.enable(:saas_ultimate_retrial, ns.root_ancestor)
    • CusotmersDot: To make it simple, in app/models/trial_history.rb update the method allows_retrial? and replace Unleash.enabled?(:saas_ultimate_retrial) with true.

  3. Start the first Ultimate trial from gdk.test:3000/groups/test-trial/-/billings .

  4. Backdate the trial dates:

    # CustomersDot console - Update trial
    trial = Trial.where(gl_namespace_name: 'test-trial').last
    trial.update!(start_date: 200.days.ago, end_date: 170.days.ago)
    
    # CustomersDot console - Expire + sync to .com
    Gitlab::SyncExpiredOrderJob.with_transaction(order_or_trial: trial) do
      trial.update!(product_rate_plan_id: Plan::FREE_PLAN, quantity: 0)
    end
    
    # .com console - Verify sync:
    ns.reload.free_plan?    # => true
    GitlabSubscriptions::Trials.free_plan_expired?(ns)     # => true
    
    # CustomersDot console - Age the eligibility ledger
    TrialHistory.where(gl_namespace_id: ns.id.to_s, trial_type: 'ultimate_with_dap')
      .order(:start_date).last.update!(start_date: 200.days.ago)
    
    # .com console - Bust the cache and verify
    Rails.cache.delete("namespaces:eligible_trials:#{ns.id}")
    ns.eligible_for_trial?  # => true
  5. Reload the billing page → the Ultimate trial option should reappear.

  6. Do step 3 again.

  7. Verify multiple TrialHistory records:

    # CustomersDot console - Verify
    TrialHistory.where(gl_namespace_id: ns.id.to_s, trial_type: 'ultimate_with_dap').count   # => 2
  • Engineering issue: #598191
  • UX issue: #584569
  • CustomersDot MR: gitlab-org/customers-gitlab-com!16239
  • Epic: &20504
Edited by Marco Tomasi

Merge request reports

Loading