Commit eb55f93a authored by Dallas Reedy's avatar Dallas Reedy

Add data recording & event tracking for trial status widget experiment

- Record participating top-level groups as ExperimentSubject records
  - record the variant received (candidate or control) as well
- Record click events on the widget itself
- Record an event whenever the popover is displayed
- Record click events on each of the CTA buttons in the popover
parent 11d8fb9f
# frozen_string_literal: true
module TrialStatusWidgetHelper
def show_trial_status_widget?(group)
billing_plans_and_trials_available? &&
trial_status_widget_experiment_enabled?(group) &&
group.trial_active? &&
user_can_administer_group?(group)
end
def plan_title_for_group(group)
group.gitlab_subscription&.plan_title
end
private
def billing_plans_and_trials_available?
::Gitlab::CurrentSettings.should_check_namespace_plan?
end
def trial_status_widget_experiment_enabled?(group)
experiment_enabled?(:show_trial_status_in_sidebar, subject: group)
def eligible_for_trial_status_widget?(group)
group.trial_active? && can?(current_user, :admin_namespace, group)
end
def user_can_administer_group?(group)
can?(current_user, :admin_namespace, group)
def plan_title_for_group(group)
group.gitlab_subscription&.plan_title
end
end
-# Return early if this app instance does not have namespace plans enabled
- return unless billing_plans_and_trials_available?
-# Only top-level groups can have trials & plans
- root_group = group.root_ancestor
- return unless show_trial_status_widget?(root_group)
-# Return without rendering if the top-level group & current user are not
-# eligible to see the trial status widget
- return unless eligible_for_trial_status_widget?(root_group)
-# Growth::Conversion experiment-related stuff...
- experiment_key = :show_trial_status_in_sidebar
-# * Record the top-level group as a Growth::Conversion experiment participant
- record_experiment_group(experiment_key, root_group)
-# * But return without rendering if the group is not receiving the candidate
-# variant experience
- return unless experiment_enabled?(experiment_key, subject: root_group)
= nav_link do
#js-trial-status-widget{ data: { container_id: 'trial-status-sidebar-widget',
......
......@@ -3,61 +3,62 @@
require 'spec_helper'
RSpec.describe TrialStatusWidgetHelper do
describe '#show_trial_status_widget?' do
let_it_be(:user) { create(:user) }
let(:trials_available) { true }
let(:experiment_enabled) { true }
let(:trial_active) { true }
let(:user_can_admin_group) { true }
let(:group) { instance_double(Group, trial_active?: trial_active) }
describe '#billing_plans_and_trials_available?' do
before do
# current_user
allow(helper).to receive(:current_user).and_return(user)
# billing_plans_and_trials_available?
stub_application_setting(check_namespace_plan: trials_available)
# trial_status_widget_experiment_enabled?(group)
allow(helper).to receive(:experiment_enabled?).with(:show_trial_status_in_sidebar, subject: group).and_return(experiment_enabled)
# user_can_administer_group?(group)
allow(helper).to receive(:can?).and_call_original
allow(helper).to receive(:can?).with(user, :admin_namespace, group).and_return(user_can_admin_group)
end
subject { helper.show_trial_status_widget?(group) }
subject { helper.billing_plans_and_trials_available? }
context 'when the check_namespace_plan ApplicationSetting is enabled' do
let(:trials_available) { true }
context 'when all requirements are met for the widget to be shown' do
it { is_expected.to be_truthy }
end
context 'when the app is not configured for billing plans & trials' do
context 'when the check_namespace_plan ApplicationSetting is disabled' do
let(:trials_available) { false }
it { is_expected.to be_falsey }
end
end
context 'when the experiment is not active or not enabled for the group' do
let(:experiment_enabled) { false }
describe '#eligible_for_trial_status_widget?' do
let_it_be(:user) { create(:user) }
let(:group) { instance_double(Group, trial_active?: trial_active) }
let(:user_can_admin_group) { true }
it { is_expected.to be_falsey }
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).and_call_original
allow(helper).to receive(:can?).with(user, :admin_namespace, group).and_return(user_can_admin_group)
end
context 'when the group is not in an active trial' do
let(:trial_active) { false }
subject { helper.eligible_for_trial_status_widget?(group) }
it { is_expected.to be_falsey }
context 'when the group has an active trial' do
let(:trial_active) { true }
context 'and the user can admin the group' do
let(:user_can_admin_group) { true }
it { is_expected.to be_truthy }
end
context 'when the user is not an admin/owner of the group' do
context 'but the user cannot admin the group' do
let(:user_can_admin_group) { false }
it { is_expected.to be_falsey }
end
end
context 'when the group does not have an active trial' do
let(:trial_active) { false }
it { is_expected.to be_falsey }
end
end
describe '#plan_title_for_group' do
using RSpec::Parameterized::TableSyntax
......
......@@ -13,25 +13,31 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
describe 'trial status widget', :aggregate_failures do
let!(:gitlab_subscription) { create(:gitlab_subscription, :active_trial, namespace: group) }
let(:show_widget) { false }
let(:trials_available) { false }
let(:experiment_enabled) { false }
let(:eligible_for_widget) { false }
before do
allow(view).to receive(:show_trial_status_widget?).and_return(show_widget)
allow(view).to receive(:billing_plans_and_trials_available?).and_return(trials_available)
allow(view).to receive(:eligible_for_trial_status_widget?).and_return(eligible_for_widget)
allow(view).to receive(:record_experiment_group)
allow(view).to receive(:experiment_enabled?).and_return(experiment_enabled)
render
end
subject { rendered }
context 'when the widget should not be shown' do
shared_examples 'does not render' do
it 'does not render' do
is_expected.not_to have_selector '#js-trial-status-widget'
is_expected.not_to have_selector '#js-trial-status-popover'
end
end
context 'when the widget should be shown' do
let(:show_widget) { true }
shared_examples 'does render' do
it 'renders both the widget & popover component initialization elements' do
is_expected.to have_selector '#js-trial-status-widget'
is_expected.to have_selector '#js-trial-status-popover'
......@@ -44,6 +50,32 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
is_expected.to have_selector "[data-target-id=#{expected_id}]"
end
end
context 'when billing plans & trials are not available' do
include_examples 'does not render'
end
context 'when billing plans & trials are available' do
let(:trials_available) { true }
context 'but the group and/or user are not eligible to see the widget' do
include_examples 'does not render'
end
context 'and the group and/or user are eligible to see the widget' do
let(:eligible_for_widget) { true }
context 'but the experiment is not enabled for the group' do
include_examples 'does not render'
end
context 'and the experiment is enabled for the group' do
let(:experiment_enabled) { true }
include_examples 'does render'
end
end
end
end
describe 'DevOps adoption link' do
......
......@@ -15,7 +15,7 @@ module Gitlab
included do
before_action :set_experimentation_subject_id_cookie, unless: :dnt_enabled?
helper_method :experiment_enabled?, :experiment_tracking_category_and_group, :tracking_label
helper_method :experiment_enabled?, :experiment_tracking_category_and_group, :record_experiment_group, :tracking_label
end
def set_experimentation_subject_id_cookie
......@@ -72,6 +72,16 @@ module Gitlab
::Experiment.add_user(experiment_key, tracking_group(experiment_key, nil, subject: subject), current_user, context)
end
def record_experiment_group(experiment_key, group)
return if dnt_enabled?
return unless Experimentation.active?(experiment_key) && group
variant_subject = Experimentation.rollout_strategy(experiment_key) == :cookie ? nil : group
variant = tracking_group(experiment_key, nil, subject: variant_subject)
::Experiment.add_group(experiment_key, group: group, variant: variant)
end
def record_experiment_conversion_event(experiment_key, context = {})
return if dnt_enabled?
return unless current_user
......
......@@ -520,6 +520,79 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
end
end
describe '#record_experiment_group' do
let(:group) { 'a group object' }
let(:experiment_key) { :some_experiment_key }
let(:dnt_enabled) { false }
let(:experiment_active) { true }
let(:rollout_strategy) { :whatever }
let(:variant) { 'variant' }
before do
allow(controller).to receive(:dnt_enabled?).and_return(dnt_enabled)
allow(::Gitlab::Experimentation).to receive(:active?).and_return(experiment_active)
allow(::Gitlab::Experimentation).to receive(:rollout_strategy).and_return(rollout_strategy)
allow(controller).to receive(:tracking_group).and_return(variant)
allow(::Experiment).to receive(:add_group)
end
subject(:record_experiment_group) { controller.record_experiment_group(experiment_key, group) }
shared_examples 'exits early without recording' do
it 'returns early without recording the group as an ExperimentSubject' do
expect(::Experiment).not_to receive(:add_group)
record_experiment_group
end
end
shared_examples 'calls tracking_group' do |using_cookie_rollout|
it "calls tracking_group with #{using_cookie_rollout ? 'a nil' : 'the group as the'} subject" do
expect(controller).to receive(:tracking_group).with(experiment_key, nil, subject: using_cookie_rollout ? nil : group).and_return(variant)
record_experiment_group
end
end
shared_examples 'records the group' do
it 'records the group' do
expect(::Experiment).to receive(:add_group).with(experiment_key, group: group, variant: variant)
record_experiment_group
end
end
context 'when DNT is enabled' do
let(:dnt_enabled) { true }
include_examples 'exits early without recording'
end
context 'when the experiment is not active' do
let(:experiment_active) { false }
include_examples 'exits early without recording'
end
context 'when a nil group is given' do
let(:group) { nil }
include_examples 'exits early without recording'
end
context 'when the experiment uses a cookie-based rollout strategy' do
let(:rollout_strategy) { :cookie }
include_examples 'calls tracking_group', true
include_examples 'records the group'
end
context 'when the experiment uses a non-cookie-based rollout strategy' do
let(:rollout_strategy) { :group }
include_examples 'calls tracking_group', false
include_examples 'records the group'
end
end
describe '#record_experiment_conversion_event' do
let(:user) { build(:user) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment