Send SaaS promotion CTAs to checkout instead of trial

What does this MR do and why?

On GitLab.com, the shared promotion CTA (shared/promotions/_promotion_link_project.html.haml) shows namespace owners a Try it for free trial link. This MR replaces it as follows:

  • Owners whose root namespace has active, non-trial GitLab Credits get a Talk to sales hand-raise lead trigger. The modal itself is already rendered by the group and project layouts.
  • All other owners get an Upgrade to Premium link to the root namespace's in-product checkout. It's built the same way as PremiumOfferComponent: FetchSubscriptionPlansService + PurchaseUrlBuilder, with source set to the promotion location.
  • Non-owners and self-managed: no change.

The partial is used on the audit events, group webhooks, issue weight and epics promotions.

Implementation notes:

  • I added two helpers to BillingPlansHelper: promotion_upgrade_to_premium_url and promotion_purchased_gitlab_credits?. The credits check filters with .non_trial so trial credits don't send owners to sales. Namespace#has_active_gitlab_credits_add_on? doesn't exclude trials.
  • Click tracking: the checkout link uses Internal Events (data-event-tracking="click_upgrade_to_premium_on_promotion", data-event-property = location, defined in ee/config/events/click_upgrade_to_premium_on_promotion.yml). The hand-raise button uses cta_tracking with property = location. The checkout URL also carries source=<location> for downstream attribution.
  • The hand-raise trigger dataset lives in GitlabSubscriptions::HandRaiseLeadsHelper#promotion_hand_raise_lead_data, next to the other hand-raise helpers. It relies on the hand-raise JS default product_interaction (Hand Raise PQL); glm_content carries the promotion location.
  • For projects in a personal namespace, promotion_upgrade_to_premium_url passes nil as the namespace to PurchaseUrlBuilder. A subscription can't be applied to a personal namespace, so the link goes to the existing group selection/creation page (new_gitlab_subscriptions_group_path) before checkout. Without this, the link would send gl_namespace_id=<user namespace id> to CustomersDot.
  • No feature flag, following the work item plan. Reviewers: please say if Growth wants this behind a flag.

Not included (possible follow-ups):

  • CTA impression tracking from the issue's Measurement section. Click tracking for the checkout link now uses Internal Events; the hand-raise button keeps cta_tracking.
  • A dedicated product_interaction value for promotion leads. This needs Marketing Ops routing first.
  • Capturing a baseline before the change ships. This has to be coordinated with PM/analytics.

References

Screenshots or screen recordings

The changes are the button text (Try it for free becomes Upgrade to Premium or Talk to sales) and the button's destination.

Before After

Webhooks - before

Screenshot 2026-10-06 at 7.24.28 PM.png

Audit Events - before

Screenshot 2026-10-06 at 7.28.03 PM.png

Webhooks - Upgrade to Premium

Screenshot 2026-10-06 at 7.53.40 PM.png

Webhooks - Talk to Sales

Screenshot 2026-10-06 at 7.57.49 PM.png

Screenshot 2026-10-06 at 7.58.09 PM.png

Audit Events - Upgrade to Premium

Screenshot 2026-10-06 at 7.54.10 PM.png

Audit Events - Talk to Sales

Screenshot 2026-10-06 at 7.57.17 PM.png

Screenshot 2026-10-06 at 7.58.09 PM.png

Validated locally in GDK (SaaS mode, Free group I own, credits states set via Rails console). All cases behave as described.

Group's GitLab Credits Webhooks Audit events
None Upgrade to Premium Upgrade to Premium
Paid, active Talk to sales Talk to sales
Trial, active Upgrade to Premium Upgrade to Premium
Paid, expired Upgrade to Premium Upgrade to Premium
Personal namespace project n/a Upgrade to Premium → group selection/creation page
  1. Upgrade link includes gl_namespace_id, plan_id and source, with a distinct source per page.
  2. Talk to sales opens the hand-raise modal on both group and project pages.
  3. Tracking is wired up: Internal Events (data-event-tracking) on the checkout link, and cta_tracking (promotion_talk_to_sales, property = location) passed to the hand-raise button.
  4. Non-owner sees "Contact an owner…" text with no button.
  5. Specs pass locally: billing_plans_helper_spec.rb and _promotion_link_project.html.haml_spec.rb, [N] examples, 0 failures.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Duo Developer

Merge request reports

Loading
Loading