From 6b9d987157fdb1ba0bf92ca65476ea3f31e2ae6e Mon Sep 17 00:00:00 2001
From: Ross Byrne <robyrne@gitlab.com>
Date: Wed, 14 Feb 2024 12:27:58 +0000
Subject: [PATCH] Implementing Trial Discover Page Experiment Stage 2

---
 .../page_bundles/trial_discover_page.scss     |  16 ++
 .../components/constants.js                   |   4 +
 .../components/trial_status_popover.vue       |   3 +-
 .../components/trial_status_widget.vue        |  36 +++-
 .../groups/discovers_controller.rb            |   3 +-
 .../trial_discover_page_experiment.rb         |  11 ++
 .../groups/trial_discover_page_helper.rb      | 160 ++++++++++++++++++
 .../discovers/_discover_detail.html.haml      |   9 +
 .../discovers/_discover_feature.html.haml     |  24 +++
 .../_discover_page_actions.html.haml          |   9 +
 ee/app/views/groups/discovers/show.html.haml  |  17 ++
 .../trial_discover_page_experiment_spec.rb    |  24 ++-
 .../trial_status_widget_spec.js.snap          |  39 ++---
 .../trial_status_popover_spec.js              |  17 ++
 .../trial_status_widget_spec.js               |  59 +++++--
 .../groups/trial_discover_page_helper_spec.rb |  85 ++++++++++
 .../groups/discovers/show.html.haml_spec.rb   | 117 +++++++++++++
 locale/gitlab.pot                             |  99 +++++++++++
 18 files changed, 673 insertions(+), 59 deletions(-)
 create mode 100644 app/assets/stylesheets/page_bundles/trial_discover_page.scss
 create mode 100644 ee/app/helpers/groups/trial_discover_page_helper.rb
 create mode 100644 ee/app/views/groups/discovers/_discover_detail.html.haml
 create mode 100644 ee/app/views/groups/discovers/_discover_feature.html.haml
 create mode 100644 ee/app/views/groups/discovers/_discover_page_actions.html.haml
 create mode 100644 ee/spec/helpers/groups/trial_discover_page_helper_spec.rb
 create mode 100644 ee/spec/views/groups/discovers/show.html.haml_spec.rb

diff --git a/app/assets/stylesheets/page_bundles/trial_discover_page.scss b/app/assets/stylesheets/page_bundles/trial_discover_page.scss
new file mode 100644
index 0000000000000..bfcbf2b12b1ca
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/trial_discover_page.scss
@@ -0,0 +1,16 @@
+@import 'mixins_and_variables_and_functions';
+
+.trial-discover-page-card {
+  @include media-breakpoint-down(md) {
+    .description-text {
+      height: 6em;
+    }
+  }
+}
+
+.trial-discover-page-card.gl-flex-basis-third,
+.trial-discover-page-card.gl-flex-basis-half {
+  @include media-breakpoint-down(md) {
+    flex-basis: 100%;
+  }
+}
diff --git a/ee/app/assets/javascripts/contextual_sidebar/components/constants.js b/ee/app/assets/javascripts/contextual_sidebar/components/constants.js
index 74daffbfcfdc2..aeb3537f6b442 100644
--- a/ee/app/assets/javascripts/contextual_sidebar/components/constants.js
+++ b/ee/app/assets/javascripts/contextual_sidebar/components/constants.js
@@ -55,6 +55,10 @@ export const POPOVER = {
       action: CLICK_BUTTON_ACTION,
       label: 'compare_all_plans',
     },
+    learnAboutFeaturesClick: {
+      action: CLICK_BUTTON_ACTION,
+      label: 'learn_about_features',
+    },
   },
   resizeEventDebounceMS: RESIZE_EVENT_DEBOUNCE_MS,
   disabledBreakpoints: ['xs', 'sm'],
diff --git a/ee/app/assets/javascripts/contextual_sidebar/components/trial_status_popover.vue b/ee/app/assets/javascripts/contextual_sidebar/components/trial_status_popover.vue
index e51e6b7df4788..750b8b5c2b12f 100644
--- a/ee/app/assets/javascripts/contextual_sidebar/components/trial_status_popover.vue
+++ b/ee/app/assets/javascripts/contextual_sidebar/components/trial_status_popover.vue
@@ -17,7 +17,7 @@ const {
   resizeEventDebounceMS,
   disabledBreakpoints,
 } = POPOVER;
-const trackingMixin = Tracking.mixin();
+const trackingMixin = Tracking.mixin({ experiment: 'trial_discover_page' });
 
 export default {
   components: {
@@ -198,6 +198,7 @@ export default {
             block
             data-testid="learn-about-features-btn"
             :title="$options.i18n.learnAboutButtonTitle"
+            @click="trackPageAction('learnAboutFeaturesClick')"
           >
             <span class="gl-font-sm">{{ $options.i18n.learnAboutButtonTitle }}</span>
           </gl-button>
diff --git a/ee/app/assets/javascripts/contextual_sidebar/components/trial_status_widget.vue b/ee/app/assets/javascripts/contextual_sidebar/components/trial_status_widget.vue
index 224710f093b08..121a3a482df53 100644
--- a/ee/app/assets/javascripts/contextual_sidebar/components/trial_status_widget.vue
+++ b/ee/app/assets/javascripts/contextual_sidebar/components/trial_status_widget.vue
@@ -4,10 +4,11 @@ import { removeTrialSuffix } from 'ee/billings/billings_util';
 import { sprintf } from '~/locale';
 import Tracking from '~/tracking';
 import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
+import { isExperimentVariant } from '~/experimentation/utils';
 import { WIDGET } from './constants';
 
 const { i18n, trackingEvents } = WIDGET;
-const trackingMixin = Tracking.mixin();
+const trackingMixin = Tracking.mixin({ experiment: 'trial_discover_page' });
 
 export default {
   components: {
@@ -30,6 +31,11 @@ export default {
   },
   i18n,
   computed: {
+    widgetLink() {
+      return isExperimentVariant('trial_discover_page', 'candidate')
+        ? this.trialDiscoverPagePath
+        : this.plansHref;
+    },
     isTrialActive() {
       return this.percentageComplete <= 100;
     },
@@ -45,21 +51,25 @@ export default {
         duration: this.trialDuration,
       });
     },
+    trackingOptions() {
+      return this.isTrialActive
+        ? trackingEvents.activeTrialOptions
+        : trackingEvents.trialEndedOptions;
+    },
   },
   methods: {
+    onLearnAboutFeaturesClick() {
+      this.track(trackingEvents.action, { ...this.trackingOptions, label: 'learn_about_features' });
+    },
     onWidgetClick() {
-      const options = this.isTrialActive
-        ? trackingEvents.activeTrialOptions
-        : trackingEvents.trialEndedOptions;
-
-      this.track(trackingEvents.action, { ...options });
+      this.track(trackingEvents.action, { ...this.trackingOptions });
     },
   },
 };
 </script>
 
 <template>
-  <gl-link :id="containerId" :title="widgetTitle" :href="plansHref">
+  <gl-link :id="containerId" :title="widgetTitle" :href="widgetLink">
     <div
       data-testid="widget-menu"
       class="gl-display-flex gl-flex-direction-column gl-align-items-stretch gl-w-full"
@@ -91,6 +101,7 @@ export default {
               class="gl-mt-3"
               data-testid="learn-about-features-btn"
               :title="$options.i18n.learnAboutButtonTitle"
+              @click.stop="onLearnAboutFeaturesClick()"
             >
               {{ $options.i18n.learnAboutButtonTitle }}
             </gl-button>
@@ -107,9 +118,16 @@ export default {
             {{ $options.i18n.widgetBodyExpiredTrial }}
             <gitlab-experiment name="trial_discover_page">
               <template #candidate>
-                <a data-testid="learn-about-features-link" :href="trialDiscoverPagePath">
+                <gl-button
+                  :href="trialDiscoverPagePath"
+                  variant="link"
+                  size="small"
+                  data-testid="learn-about-features-btn"
+                  :title="$options.i18n.learnAboutButtonTitle"
+                  @click.stop="onLearnAboutFeaturesClick()"
+                >
                   {{ $options.i18n.learnAboutButtonTitle }}
-                </a>
+                </gl-button>
               </template>
             </gitlab-experiment>
           </div>
diff --git a/ee/app/controllers/groups/discovers_controller.rb b/ee/app/controllers/groups/discovers_controller.rb
index c8cca5d0beeca..b80fe2b3cafff 100644
--- a/ee/app/controllers/groups/discovers_controller.rb
+++ b/ee/app/controllers/groups/discovers_controller.rb
@@ -16,7 +16,8 @@ def show; end
     private
 
     def authorize_discover_page
-      render_404 if experiment(:trial_discover_page, actor: current_user).assigned[:name] == :control
+      return render_404 if experiment(:trial_discover_page, actor: current_user).assigned[:name] == :control
+
       render_404 unless group.trial_active?
     end
   end
diff --git a/ee/app/experiments/trial_discover_page_experiment.rb b/ee/app/experiments/trial_discover_page_experiment.rb
index 157612792dad5..cd5bbaa6a788f 100644
--- a/ee/app/experiments/trial_discover_page_experiment.rb
+++ b/ee/app/experiments/trial_discover_page_experiment.rb
@@ -1,11 +1,22 @@
 # frozen_string_literal: true
 
 class TrialDiscoverPageExperiment < ApplicationExperiment
+  EXCLUDE_USERS_OLDER_THAN = Date.new(2024, 2, 14)
+
   control
   variant(:candidate)
 
+  exclude :previously_existing_users
+
   private
 
   def control_behavior; end
   def candidate_behavior; end
+
+  def previously_existing_users
+    actor_created_at = context&.actor&.created_at
+    return true if actor_created_at.nil?
+
+    actor_created_at < EXCLUDE_USERS_OLDER_THAN
+  end
 end
diff --git a/ee/app/helpers/groups/trial_discover_page_helper.rb b/ee/app/helpers/groups/trial_discover_page_helper.rb
new file mode 100644
index 0000000000000..625ae9b056ba7
--- /dev/null
+++ b/ee/app/helpers/groups/trial_discover_page_helper.rb
@@ -0,0 +1,160 @@
+# frozen_string_literal: true
+
+module Groups
+  module TrialDiscoverPageHelper
+    include Gitlab::Utils::StrongMemoize
+
+    def trial_discover_page_features
+      [
+        {
+          icon: "epic",
+          title: s_("TrialDiscoverPage|Epics"),
+          description: s_("TrialDiscoverPage|Collaborate on high-level ideas that share a common theme. " \
+                          "Use epics to group issues that cross milestones and projects."),
+          plan: s_("TrialDiscoverPage|Premium"),
+          doc_url: help_page_url("user/group/epics/index"),
+          video_url: "https://vimeo.com/693759778",
+          container_class: 'gl-flex-basis-third',
+          description_class: 'gl-h-13',
+          tracking_label: 'epics_feature'
+        },
+        {
+          icon: "location",
+          title: s_("TrialDiscoverPage|Roadmaps"),
+          description: s_("TrialDiscoverPage|Visualize your epics and milestones in a timeline."),
+          plan: s_("TrialDiscoverPage|Premium"),
+          doc_url: help_page_url("user/group/roadmap/index"),
+          video_url: "https://vimeo.com/670922063",
+          container_class: 'gl-flex-basis-third',
+          description_class: 'gl-h-13',
+          tracking_label: 'roadmaps_feature'
+        },
+        {
+          icon: "label",
+          title: s_("TrialDiscoverPage|Scoped Labels"),
+          description: s_("TrialDiscoverPage|Create a more advanced workflow for issues, merge requests, " \
+                          "and epics by using scoped, mutually exclusive labels."),
+          plan: s_("TrialDiscoverPage|Premium"),
+          doc_url: help_page_url("user/project/labels", anchor: "scoped-labels"),
+          video_url: "https://vimeo.com/670906315",
+          container_class: 'gl-flex-basis-third',
+          description_class: 'gl-h-13',
+          tracking_label: 'scoped_labels_feature'
+        },
+        {
+          icon: "merge-request",
+          title: s_("TrialDiscoverPage|Merge request approval rule"),
+          description: s_("TrialDiscoverPage|Maintain high quality code by requiring approval " \
+                          "from specific users on your merge requests."),
+          plan: s_("TrialDiscoverPage|Premium"),
+          doc_url: help_page_url("user/project/merge_requests/approvals/rules"),
+          video_url: "https://vimeo.com/670904904",
+          container_class: 'gl-flex-basis-third',
+          description_class: 'gl-h-13',
+          tracking_label: 'merge_request_rule_feature'
+        },
+        {
+          icon: "progress",
+          title: s_("TrialDiscoverPage|Burn down charts"),
+          description: s_("TrialDiscoverPage|Track your development progress by viewing issues in a burndown chart."),
+          plan: s_("TrialDiscoverPage|Premium"),
+          doc_url: help_page_url("user/project/milestones/burndown_and_burnup_charts"),
+          video_url: "https://vimeo.com/670905639",
+          container_class: 'gl-flex-basis-third',
+          description_class: 'gl-h-13',
+          tracking_label: 'burn_down_chart_feature'
+        },
+        {
+          icon: "account",
+          title: s_("TrialDiscoverPage|Code owners"),
+          description: s_("TrialDiscoverPage|Target the right approvers for your merge request by assigning " \
+                          "owners to specific files."),
+          plan: s_("TrialDiscoverPage|Premium"),
+          doc_url: help_page_url("user/project/codeowners/index"),
+          video_url: "https://vimeo.com/670896787",
+          container_class: 'gl-flex-basis-third',
+          description_class: 'gl-h-13',
+          tracking_label: 'code_owners_feature'
+        },
+        {
+          icon: "chart",
+          title: s_("TrialDiscoverPage|Code review analytics"),
+          description: s_("TrialDiscoverPage|Find and fix bottlenecks in your code review process by understanding " \
+                          "how long open merge requests have been in review."),
+          plan: s_("TrialDiscoverPage|Premium"),
+          doc_url: help_page_url("user/analytics/code_review_analytics"),
+          video_url: "https://vimeo.com/670893940",
+          container_class: 'gl-flex-basis-half',
+          description_class: 'gl-h-10',
+          tracking_label: 'code_review_feature'
+        },
+        {
+          icon: "shield",
+          title: s_("TrialDiscoverPage|Free guest users"),
+          description: s_("TrialDiscoverPage|Let users view what GitLab has to offer without using a " \
+                          "subscription seat."),
+          plan: s_("TrialDiscoverPage|Ultimate"),
+          doc_url: help_page_url("user/permissions"),
+          calculator_url: "https://about.gitlab.com/pricing/ultimate/#wu-guest-calculator",
+          container_class: 'gl-flex-basis-half',
+          description_class: 'gl-h-10',
+          tracking_label: 'free_guests_feature'
+        },
+        {
+          icon: "shield",
+          title: s_("TrialDiscoverPage|Dependency scanning"),
+          description: s_("TrialDiscoverPage|Keep your application secure by checking your libraries for " \
+                          "vulnerabilities."),
+          plan: s_("TrialDiscoverPage|Ultimate"),
+          doc_url: help_page_url("user/application_security/dependency_scanning/index"),
+          video_url: "https://vimeo.com/670886968",
+          container_class: 'gl-flex-basis-half',
+          description_class: 'gl-h-10',
+          tracking_label: 'dependency_scanning_feature'
+        },
+        {
+          icon: "issue-type-test-case",
+          title: s_("TrialDiscoverPage|Dynamic application security testing (DAST)"),
+          description: s_("TrialDiscoverPage|Keep your application secure by checking your deployed environments " \
+                          "for vulnerabilities."),
+          plan: s_("TrialDiscoverPage|Ultimate"),
+          doc_url: help_page_url("user/application_security/dast/index"),
+          video_url: "https://vimeo.com/670891385",
+          container_class: 'gl-flex-basis-half',
+          description_class: 'gl-h-10',
+          tracking_label: 'dast_feature'
+        }
+      ]
+    end
+
+    def trial_discover_page_details
+      [
+        {
+          icon: "users",
+          title: s_("TrialDiscoverPage|Collaboration made easy"),
+          description: s_("TrialDiscoverPage|Break down silos to coordinate seamlessly across development, " \
+                          "operations, and security with a consistent experience across the development lifecycle.")
+        },
+        {
+          icon: "code",
+          title: s_("TrialDiscoverPage|Lower cost of development"),
+          description: s_("TrialDiscoverPage|A single application eliminates complex integrations, data " \
+                          "checkpoints, and toolchain maintenance, resulting in greater productivity and lower cost.")
+        },
+        {
+          icon: "deployments",
+          title: s_("TrialDiscoverPage|Your software, deployed your way"),
+          description: s_("TrialDiscoverPage|GitLab is infrastructure agnostic. GitLab supports GCP, AWS, " \
+                          "OpenShift, VMware, on-premises, bare metal, and more.")
+
+        }
+      ]
+    end
+
+    def group_trial_status(group)
+      strong_memoize_with(:group_trial_status, group) do
+        group.trial_active? ? 'trial_active' : 'trial_expired'
+      end
+    end
+  end
+end
diff --git a/ee/app/views/groups/discovers/_discover_detail.html.haml b/ee/app/views/groups/discovers/_discover_detail.html.haml
new file mode 100644
index 0000000000000..8df4f05da5404
--- /dev/null
+++ b/ee/app/views/groups/discovers/_discover_detail.html.haml
@@ -0,0 +1,9 @@
+.trial-discover-page-card.gl-flex-grow-1.gl-pr-6.gl-py-3.gl-flex-basis-third
+  = render Pajamas::CardComponent.new(card_options: { class: 'gl-rounded-lg' }) do |c|
+    - c.with_body do
+      .gl-mb-3
+        = sprite_icon(item[:icon], css_class: "gl-text-blue-400")
+      %h5.gl-mt-3
+        = item[:title]
+      .gl-h-13
+        = item[:description]
diff --git a/ee/app/views/groups/discovers/_discover_feature.html.haml b/ee/app/views/groups/discovers/_discover_feature.html.haml
new file mode 100644
index 0000000000000..f47cfca160dbc
--- /dev/null
+++ b/ee/app/views/groups/discovers/_discover_feature.html.haml
@@ -0,0 +1,24 @@
+.trial-discover-page-card.gl-flex-grow-1.gl-pr-6.gl-py-4{ class: item[:container_class] }
+  = render Pajamas::CardComponent.new(card_options: { class: 'gl-rounded-lg' }) do |c|
+    - c.with_body do
+      .gl-display-flex.gl-justify-content-space-between
+        = sprite_icon(item[:icon], css_class: "gl-text-blue-400")
+        = gl_badge_tag(item[:plan], variant: :tier)
+      %h5.gl-mt-1
+        = item[:title]
+      .description-text{ class: item[:description_class] }
+        = item[:description]
+      .gl-display-flex
+        - if item[:video_url].present?
+          = render Pajamas::ButtonComponent.new(icon: "live-preview", href: item[:video_url], variant: :link, target: "_blank",
+            button_options: { data: { track_action: "click_video_link_#{group_trial_status(group)}", track_label: item[:tracking_label] } })
+        - else
+          = render Pajamas::ButtonComponent.new(href: item[:calculator_url], variant: :link, target: "_blank",
+            button_options: { data: { track_action: "click_calculate_seats_#{group_trial_status(group)}", track_label: item[:tracking_label] } }) do
+            = s_("TrialDiscoverPage|Calculate seats")
+
+        .gl-px-2
+          |
+        = render Pajamas::ButtonComponent.new(href: item[:doc_url], variant: :link, target: "_blank",
+          button_options: { data: { track_action: "click_documentation_link_#{group_trial_status(group)}", track_label: item[:tracking_label] } }) do
+          = s_("TrialDiscoverPage|Documentation")
diff --git a/ee/app/views/groups/discovers/_discover_page_actions.html.haml b/ee/app/views/groups/discovers/_discover_page_actions.html.haml
new file mode 100644
index 0000000000000..57b0c0933c39f
--- /dev/null
+++ b/ee/app/views/groups/discovers/_discover_page_actions.html.haml
@@ -0,0 +1,9 @@
+.gl-display-flex
+  = render Pajamas::ButtonComponent.new(href: group_billings_path(group), variant: :confirm,
+    button_options: { class: 'gl-mr-3', data: { track_action: "click_compare_plans", track_label: group_trial_status(group) } }) do
+    = s_('TrialDiscoverPage|Compare all plans')
+
+  .js-hand-raise-lead-button{ data: { **hand_raise_props(group, glm_content: 'trial_discover_page'),
+                                      button_attributes: { variant: 'confirm', category: 'secondary' }.to_json,
+                                      track_action: 'click_contact_sales',
+                                      track_label: group_trial_status(group) } }
diff --git a/ee/app/views/groups/discovers/show.html.haml b/ee/app/views/groups/discovers/show.html.haml
index 21c88bfd7c938..f362de8d9a1ae 100644
--- a/ee/app/views/groups/discovers/show.html.haml
+++ b/ee/app/views/groups/discovers/show.html.haml
@@ -1,4 +1,21 @@
 - page_title s_("TrialDiscoverPage|Discover")
+- add_page_specific_style 'page_bundles/trial_discover_page', defer: false
 
 %h2= s_('TrialDiscoverPage|Discover Premium & Ultimate')
 %p= s_('TrialDiscoverPage|Access advanced features, build more efficiently, strengthen security and compliance.')
+
+= render 'discover_page_actions', group: @group
+
+%h4.gl-mt-8= s_('TrialDiscoverPage|Access advanced features')
+
+.gl-display-flex.gl-flex-wrap
+  = render partial: 'discover_feature', collection: trial_discover_page_features, as: :item, locals: { group: @group }
+
+%h4.gl-mt-8
+  = s_('TrialDiscoverPage|Speed. Efficiency. Trust.')
+
+.gl-display-flex.gl-flex-wrap
+  = render partial: 'discover_detail', collection: trial_discover_page_details, as: :item
+
+.gl-mt-5
+  = render 'discover_page_actions', group: @group
diff --git a/ee/spec/experiments/trial_discover_page_experiment_spec.rb b/ee/spec/experiments/trial_discover_page_experiment_spec.rb
index 1cf3e02b3a956..ff7b9841cda17 100644
--- a/ee/spec/experiments/trial_discover_page_experiment_spec.rb
+++ b/ee/spec/experiments/trial_discover_page_experiment_spec.rb
@@ -3,15 +3,29 @@
 require 'spec_helper'
 
 RSpec.describe TrialDiscoverPageExperiment, :experiment, feature_category: :activation do
+  let_it_be(:excluded) { build_stubbed(:user, created_at: Date.new(2024, 1, 1)) }
+  let_it_be(:assigned) do
+    build_stubbed(:user, created_at: TrialDiscoverPageExperiment::EXCLUDE_USERS_OLDER_THAN)
+  end
+
+  shared_examples 'existing_users_are_excluded' do
+    it "excludes existing users" do
+      expect(experiment(:trial_discover_page, actor: excluded)).to exclude(actor: excluded)
+      expect(experiment(:trial_discover_page, actor: assigned)).not_to exclude(actor: assigned)
+    end
+  end
+
   context 'with control experience' do
     before do
       stub_experiments(trial_discover_page: :control)
     end
 
     it 'registers control behavior' do
-      expect(experiment(:trial_discover_page)).to register_behavior(:control).with(nil)
-      expect { experiment(:trial_discover_page).run }.not_to raise_error
+      expect(experiment(:trial_discover_page, actor: assigned)).to register_behavior(:control).with(nil)
+      expect { experiment(:trial_discover_page, actor: assigned).run }.not_to raise_error
     end
+
+    it_behaves_like 'existing_users_are_excluded'
   end
 
   context 'with candidate experience' do
@@ -20,8 +34,10 @@
     end
 
     it 'registers candidate behavior' do
-      expect(experiment(:trial_discover_page)).to register_behavior(:candidate).with(nil)
-      expect { experiment(:trial_discover_page).run }.not_to raise_error
+      expect(experiment(:trial_discover_page, actor: assigned)).to register_behavior(:candidate).with(nil)
+      expect { experiment(:trial_discover_page, actor: assigned).run }.not_to raise_error
     end
+
+    it_behaves_like 'existing_users_are_excluded'
   end
 end
diff --git a/ee/spec/frontend/contextual_sidebar/__snapshots__/trial_status_widget_spec.js.snap b/ee/spec/frontend/contextual_sidebar/__snapshots__/trial_status_widget_spec.js.snap
index ab9054c15a947..3322edc51474c 100644
--- a/ee/spec/frontend/contextual_sidebar/__snapshots__/trial_status_widget_spec.js.snap
+++ b/ee/spec/frontend/contextual_sidebar/__snapshots__/trial_status_widget_spec.js.snap
@@ -1,7 +1,7 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`TrialStatusWidget component without the optional containerId prop matches the snapshot for namespace in active trial 1`] = `
-<gllink-stub
+<gl-link-stub
   href="billing/path-for/group"
   title="Ultimate Trial"
 >
@@ -37,27 +37,19 @@ exports[`TrialStatusWidget component without the optional containerId prop match
       <div
         class="gl-align-items-stretch gl-display-flex gl-mt-2"
       >
-        <div
+        <gl-progress-bar-stub
           aria-hidden="true"
-          class="gl-flex-grow-1 progress"
-        >
-          <div
-            aria-valuemax="100"
-            aria-valuemin="0"
-            aria-valuenow="10"
-            class="progress-bar"
-            role="progressbar"
-            style="width: 10%;"
-          />
-        </div>
+          class="gl-flex-grow-1"
+          value="10"
+        />
       </div>
     </div>
   </div>
-</gllink-stub>
+</gl-link-stub>
 `;
 
 exports[`TrialStatusWidget component without the optional containerId prop matches the snapshot for namespace not in active trial 1`] = `
-<gllink-stub
+<gl-link-stub
   href="billing/path-for/group"
   title="Your 30-day trial has ended"
 >
@@ -68,16 +60,11 @@ exports[`TrialStatusWidget component without the optional containerId prop match
     <div
       class="gl-display-flex gl-gap-5 gl-px-2 gl-w-full"
     >
-      <svg
-        aria-hidden="true"
-        class="gl-icon gl-text-blue-600! s16"
-        data-testid="information-o-icon"
-        role="img"
-      >
-        <use
-          href="file-mock#information-o"
-        />
-      </svg>
+      <gl-icon-stub
+        class="gl-text-blue-600!"
+        name="information-o"
+        size="16"
+      />
       <div>
         <div
           class="gl-font-weight-bold"
@@ -92,5 +79,5 @@ exports[`TrialStatusWidget component without the optional containerId prop match
       </div>
     </div>
   </div>
-</gllink-stub>
+</gl-link-stub>
 `;
diff --git a/ee/spec/frontend/contextual_sidebar/trial_status_popover_spec.js b/ee/spec/frontend/contextual_sidebar/trial_status_popover_spec.js
index 8d2c1a8e04a01..db271798580f5 100644
--- a/ee/spec/frontend/contextual_sidebar/trial_status_popover_spec.js
+++ b/ee/spec/frontend/contextual_sidebar/trial_status_popover_spec.js
@@ -255,6 +255,23 @@ describe('TrialStatusPopover component', () => {
         expect(findLearnAboutFeaturesBtn().exists()).toBe(true);
         expect(findLearnAboutFeaturesBtn().attributes('href')).toBe('discover-path');
       });
+
+      it('tracks click event', () => {
+        wrapper = createComponent({ providers: { daysRemaining: 5 } });
+
+        findLearnAboutFeaturesBtn().vm.$emit('click');
+
+        expectTracking(trackingEvents.activeTrialCategory, {
+          ...trackingEvents.learnAboutFeaturesClick,
+          context: {
+            data: {
+              experiment: 'trial_discover_page',
+              variant: 'candidate',
+            },
+            schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/1-0-0',
+          },
+        });
+      });
     });
   });
 });
diff --git a/ee/spec/frontend/contextual_sidebar/trial_status_widget_spec.js b/ee/spec/frontend/contextual_sidebar/trial_status_widget_spec.js
index a0eb1f219f2ba..d02114c95db3b 100644
--- a/ee/spec/frontend/contextual_sidebar/trial_status_widget_spec.js
+++ b/ee/spec/frontend/contextual_sidebar/trial_status_widget_spec.js
@@ -1,13 +1,15 @@
-import { GlLink } from '@gitlab/ui';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { GlLink, GlButton } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import { WIDGET } from 'ee/contextual_sidebar/components/constants';
 import TrialStatusWidget from 'ee/contextual_sidebar/components/trial_status_widget.vue';
 import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
 import { stubExperiments } from 'helpers/experimentation_helper';
 import { __ } from '~/locale';
+import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
 
 describe('TrialStatusWidget component', () => {
   let wrapper;
+  let trackingSpy;
 
   const { trackingEvents } = WIDGET;
   const trialDaysUsed = 10;
@@ -15,10 +17,9 @@ describe('TrialStatusWidget component', () => {
 
   const findGlLink = () => wrapper.findComponent(GlLink);
   const findLearnAboutFeaturesBtn = () => wrapper.findByTestId('learn-about-features-btn');
-  const findLearnAboutFeaturesLink = () => wrapper.findByTestId('learn-about-features-link');
 
   const createComponent = (providers = {}) => {
-    return mountExtended(TrialStatusWidget, {
+    return shallowMountExtended(TrialStatusWidget, {
       provide: {
         trialDaysUsed,
         trialDuration,
@@ -29,10 +30,18 @@ describe('TrialStatusWidget component', () => {
         trialDiscoverPagePath: 'discover-path',
         ...providers,
       },
-      stubs: { GlLink: true },
+      stubs: { GitlabExperiment, GlButton },
     });
   };
 
+  beforeEach(() => {
+    trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
+  });
+
+  afterEach(() => {
+    unmockTracking();
+  });
+
   describe('interpolated strings', () => {
     it('correctly interpolates them all', () => {
       wrapper = createComponent();
@@ -61,16 +70,6 @@ describe('TrialStatusWidget component', () => {
     });
 
     describe('tracks when the widget menu is clicked', () => {
-      let trackingSpy;
-
-      beforeEach(() => {
-        trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
-      });
-
-      afterEach(() => {
-        unmockTracking();
-      });
-
       it('tracks with correct information when namespace is in an active trial', async () => {
         const { category, label } = trackingEvents.activeTrialOptions;
         await wrapper.findByTestId('widget-menu').trigger('click');
@@ -143,7 +142,7 @@ describe('TrialStatusWidget component', () => {
           wrapper = createComponent({ percentageComplete: 110 });
 
           expect(wrapper.text()).not.toContain(__('Learn about features'));
-          expect(findLearnAboutFeaturesLink().exists()).toBe(false);
+          expect(findLearnAboutFeaturesBtn().exists()).toBe(false);
         });
       });
     });
@@ -161,6 +160,18 @@ describe('TrialStatusWidget component', () => {
           expect(findLearnAboutFeaturesBtn().exists()).toBe(true);
           expect(findLearnAboutFeaturesBtn().attributes('href')).toBe('discover-path');
         });
+
+        it('tracks clicking learn about features button', async () => {
+          wrapper = createComponent();
+
+          const { category } = trackingEvents.activeTrialOptions;
+          await findLearnAboutFeaturesBtn().trigger('click');
+
+          expect(trackingSpy).toHaveBeenCalledWith(category, trackingEvents.action, {
+            category,
+            label: 'learn_about_features',
+          });
+        });
       });
 
       describe('when trial is expired', () => {
@@ -168,8 +179,20 @@ describe('TrialStatusWidget component', () => {
           wrapper = createComponent({ percentageComplete: 110 });
 
           expect(wrapper.text()).toContain(__('Learn about features'));
-          expect(findLearnAboutFeaturesLink().exists()).toBe(true);
-          expect(findLearnAboutFeaturesLink().attributes('href')).toBe('discover-path');
+          expect(findLearnAboutFeaturesBtn().exists()).toBe(true);
+          expect(findLearnAboutFeaturesBtn().attributes('href')).toBe('discover-path');
+        });
+
+        it('tracks clicking learn about features link', async () => {
+          wrapper = createComponent({ percentageComplete: 110 });
+
+          const { category } = trackingEvents.trialEndedOptions;
+          await findLearnAboutFeaturesBtn().trigger('click');
+
+          expect(trackingSpy).toHaveBeenCalledWith(category, trackingEvents.action, {
+            category,
+            label: 'learn_about_features',
+          });
         });
       });
     });
diff --git a/ee/spec/helpers/groups/trial_discover_page_helper_spec.rb b/ee/spec/helpers/groups/trial_discover_page_helper_spec.rb
new file mode 100644
index 0000000000000..b5b641d4ab171
--- /dev/null
+++ b/ee/spec/helpers/groups/trial_discover_page_helper_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::TrialDiscoverPageHelper, feature_category: :activation do
+  describe '#discover_details' do
+    subject(:discover_details) { helper.trial_discover_page_details }
+
+    it 'returns an array' do
+      expect(discover_details).to be_a(Array)
+    end
+
+    it 'contains correct number of items' do
+      expect(discover_details.count).to be(3)
+    end
+
+    it 'returns correct features' do
+      expect(discover_details[0][:title]).to eq(s_("TrialDiscoverPage|Collaboration made easy"))
+      expect(discover_details[1][:title]).to eq(s_("TrialDiscoverPage|Lower cost of development"))
+      expect(discover_details[2][:title]).to eq(s_("TrialDiscoverPage|Your software, deployed your way"))
+    end
+  end
+
+  describe '#discover_features' do
+    subject(:discover_features) { helper.trial_discover_page_features }
+
+    it 'returns an array' do
+      expect(discover_features).to be_a(Array)
+    end
+
+    it 'contains correct number of items' do
+      expect(discover_features.count).to be(10)
+    end
+
+    it 'returns correct features' do
+      expect(discover_features[0][:title]).to eq(s_("TrialDiscoverPage|Epics"))
+      expect(discover_features[1][:title]).to eq(s_("TrialDiscoverPage|Roadmaps"))
+      expect(discover_features[2][:title]).to eq(s_("TrialDiscoverPage|Scoped Labels"))
+      expect(discover_features[3][:title]).to eq(s_("TrialDiscoverPage|Merge request approval rule"))
+      expect(discover_features[4][:title]).to eq(s_("TrialDiscoverPage|Burn down charts"))
+      expect(discover_features[5][:title]).to eq(s_("TrialDiscoverPage|Code owners"))
+      expect(discover_features[6][:title]).to eq(s_("TrialDiscoverPage|Code review analytics"))
+      expect(discover_features[7][:title]).to eq(s_("TrialDiscoverPage|Free guest users"))
+      expect(discover_features[8][:title]).to eq(s_("TrialDiscoverPage|Dependency scanning"))
+      expect(discover_features[9][:title]).to eq(s_("TrialDiscoverPage|Dynamic application security testing (DAST)"))
+    end
+
+    it 'returns correct tracking labels' do
+      expect(discover_features[0][:tracking_label]).to eq("epics_feature")
+      expect(discover_features[1][:tracking_label]).to eq("roadmaps_feature")
+      expect(discover_features[2][:tracking_label]).to eq("scoped_labels_feature")
+      expect(discover_features[3][:tracking_label]).to eq("merge_request_rule_feature")
+      expect(discover_features[4][:tracking_label]).to eq("burn_down_chart_feature")
+      expect(discover_features[5][:tracking_label]).to eq("code_owners_feature")
+      expect(discover_features[6][:tracking_label]).to eq("code_review_feature")
+      expect(discover_features[7][:tracking_label]).to eq("free_guests_feature")
+      expect(discover_features[8][:tracking_label]).to eq("dependency_scanning_feature")
+      expect(discover_features[9][:tracking_label]).to eq("dast_feature")
+    end
+  end
+
+  describe '#group_trial_status' do
+    let_it_be(:group) { build_stubbed(:group) }
+
+    context 'when trial is active' do
+      before do
+        allow(group).to receive(:trial_active?).and_return(true)
+      end
+
+      it 'returns correct status' do
+        expect(helper.group_trial_status(group)).to eq 'trial_active'
+      end
+    end
+
+    context 'when trial is expired' do
+      before do
+        allow(group).to receive(:trial_active?).and_return(false)
+      end
+
+      it 'returns correct status' do
+        expect(helper.group_trial_status(group)).to eq 'trial_expired'
+      end
+    end
+  end
+end
diff --git a/ee/spec/views/groups/discovers/show.html.haml_spec.rb b/ee/spec/views/groups/discovers/show.html.haml_spec.rb
new file mode 100644
index 0000000000000..d843d5cb983fa
--- /dev/null
+++ b/ee/spec/views/groups/discovers/show.html.haml_spec.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'groups/discovers/show', :saas, :aggregate_failures, feature_category: :activation do
+  let_it_be(:user) { build_stubbed(:user) }
+  let_it_be(:group) { build_stubbed(:group) }
+  let(:tracking_labels) do
+    [
+      :epics_feature,
+      :roadmaps_feature,
+      :scoped_labels_feature,
+      :merge_request_rule_feature,
+      :burn_down_chart_feature,
+      :code_owners_feature,
+      :code_review_feature,
+      :dependency_scanning_feature,
+      :dast_feature
+    ]
+  end
+
+  before do
+    allow(view).to receive(:current_user).and_return(user)
+    assign(:group, group)
+  end
+
+  it 'renders the discover trial page' do
+    render
+
+    expect(rendered).to have_text(s_('TrialDiscoverPage|Discover Premium & Ultimate'))
+    expect(rendered).to have_text(s_('TrialDiscoverPage|Access advanced features'))
+    expect(rendered).to render_template('groups/discovers/_discover_page_actions')
+  end
+
+  it 'renders all feature cards' do
+    render
+
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Epics"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Roadmaps"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Scoped Labels"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Merge request approval rule"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Burn down charts"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Code owners"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Code review analytics"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Free guest users"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Dependency scanning"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Dynamic application security testing (DAST)"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Collaboration made easy"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Lower cost of development"))
+    expect(rendered).to have_text(s_("TrialDiscoverPage|Your software, deployed your way"))
+  end
+
+  context 'when trial is active' do
+    before do
+      allow(group).to receive(:trial_active?).and_return(true)
+    end
+
+    it 'has tracking items set as expected' do
+      render
+
+      tracking_labels.each do |label|
+        expect_to_have_tracking(action: 'click_video_link_trial_active', label: label)
+        expect_to_have_tracking(action: 'click_documentation_link_trial_active', label: label)
+      end
+    end
+
+    it 'has tracking for Free guest users' do
+      render
+
+      expect_to_have_tracking(action: 'click_calculate_seats_trial_active', label: :free_guests_feature)
+      expect_to_have_tracking(action: 'click_documentation_link_trial_active', label: :free_guests_feature)
+    end
+
+    it 'has tracking for page actions' do
+      render
+
+      expect_to_have_tracking(action: 'click_compare_plans', label: :trial_active)
+      expect_to_have_tracking(action: 'click_contact_sales', label: :trial_active)
+    end
+  end
+
+  context 'when trial is expired' do
+    before do
+      allow(group).to receive(:trial_active?).and_return(false)
+    end
+
+    it 'has tracking items set as expected' do
+      render
+
+      tracking_labels.each do |label|
+        expect_to_have_tracking(action: 'click_video_link_trial_expired', label: label)
+        expect_to_have_tracking(action: 'click_documentation_link_trial_expired', label: label)
+      end
+    end
+
+    it 'has tracking for Free guest users' do
+      render
+
+      expect_to_have_tracking(action: 'click_calculate_seats_trial_expired', label: :free_guests_feature)
+      expect_to_have_tracking(action: 'click_documentation_link_trial_expired', label: :free_guests_feature)
+    end
+
+    it 'has tracking for page actions' do
+      render
+
+      expect_to_have_tracking(action: 'click_compare_plans', label: :trial_expired)
+      expect_to_have_tracking(action: 'click_contact_sales', label: :trial_expired)
+    end
+  end
+
+  def expect_to_have_tracking(action:, label: nil)
+    css = "[data-track-action='#{action}']"
+    css += "[data-track-label='#{label}']" if label
+
+    expect(rendered).to have_css(css)
+  end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2c617b32eb9ec..453830c711a10 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -52431,15 +52431,114 @@ msgstr ""
 msgid "Trending"
 msgstr ""
 
+msgid "TrialDiscoverPage|A single application eliminates complex integrations, data checkpoints, and toolchain maintenance, resulting in greater productivity and lower cost."
+msgstr ""
+
+msgid "TrialDiscoverPage|Access advanced features"
+msgstr ""
+
 msgid "TrialDiscoverPage|Access advanced features, build more efficiently, strengthen security and compliance."
 msgstr ""
 
+msgid "TrialDiscoverPage|Break down silos to coordinate seamlessly across development, operations, and security with a consistent experience across the development lifecycle."
+msgstr ""
+
+msgid "TrialDiscoverPage|Burn down charts"
+msgstr ""
+
+msgid "TrialDiscoverPage|Calculate seats"
+msgstr ""
+
+msgid "TrialDiscoverPage|Code owners"
+msgstr ""
+
+msgid "TrialDiscoverPage|Code review analytics"
+msgstr ""
+
+msgid "TrialDiscoverPage|Collaborate on high-level ideas that share a common theme. Use epics to group issues that cross milestones and projects."
+msgstr ""
+
+msgid "TrialDiscoverPage|Collaboration made easy"
+msgstr ""
+
+msgid "TrialDiscoverPage|Compare all plans"
+msgstr ""
+
+msgid "TrialDiscoverPage|Create a more advanced workflow for issues, merge requests, and epics by using scoped, mutually exclusive labels."
+msgstr ""
+
+msgid "TrialDiscoverPage|Dependency scanning"
+msgstr ""
+
 msgid "TrialDiscoverPage|Discover"
 msgstr ""
 
 msgid "TrialDiscoverPage|Discover Premium & Ultimate"
 msgstr ""
 
+msgid "TrialDiscoverPage|Documentation"
+msgstr ""
+
+msgid "TrialDiscoverPage|Dynamic application security testing (DAST)"
+msgstr ""
+
+msgid "TrialDiscoverPage|Epics"
+msgstr ""
+
+msgid "TrialDiscoverPage|Find and fix bottlenecks in your code review process by understanding how long open merge requests have been in review."
+msgstr ""
+
+msgid "TrialDiscoverPage|Free guest users"
+msgstr ""
+
+msgid "TrialDiscoverPage|GitLab is infrastructure agnostic. GitLab supports GCP, AWS, OpenShift, VMware, on-premises, bare metal, and more."
+msgstr ""
+
+msgid "TrialDiscoverPage|Keep your application secure by checking your deployed environments for vulnerabilities."
+msgstr ""
+
+msgid "TrialDiscoverPage|Keep your application secure by checking your libraries for vulnerabilities."
+msgstr ""
+
+msgid "TrialDiscoverPage|Let users view what GitLab has to offer without using a subscription seat."
+msgstr ""
+
+msgid "TrialDiscoverPage|Lower cost of development"
+msgstr ""
+
+msgid "TrialDiscoverPage|Maintain high quality code by requiring approval from specific users on your merge requests."
+msgstr ""
+
+msgid "TrialDiscoverPage|Merge request approval rule"
+msgstr ""
+
+msgid "TrialDiscoverPage|Premium"
+msgstr ""
+
+msgid "TrialDiscoverPage|Roadmaps"
+msgstr ""
+
+msgid "TrialDiscoverPage|Scoped Labels"
+msgstr ""
+
+msgid "TrialDiscoverPage|Speed. Efficiency. Trust."
+msgstr ""
+
+msgid "TrialDiscoverPage|Target the right approvers for your merge request by assigning owners to specific files."
+msgstr ""
+
+msgid "TrialDiscoverPage|Track your development progress by viewing issues in a burndown chart."
+msgstr ""
+
+msgid "TrialDiscoverPage|Ultimate"
+msgstr ""
+
+msgid "TrialDiscoverPage|Visualize your epics and milestones in a timeline."
+msgstr ""
+
+msgid "TrialDiscoverPage|Your software, deployed your way"
+msgstr ""
+
 msgid "TrialRegistration|Start GitLab Ultimate free trial"
 msgstr ""
 
-- 
GitLab