1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
# frozen_string_literal: true
require 'zlib'
# Controller concern that checks if an `experimentation_subject_id cookie` is present and sets it if absent.
# Used for A/B testing of experimental features. Exposes the `experiment_enabled?(experiment_name, subject: nil)` method
# to controllers and views. It returns true when the experiment is enabled and the user is selected as part
# of the experimental group.
#
module Gitlab
module Experimentation
module ControllerConcern
include ::Gitlab::Experimentation::GroupTypes
include Gitlab::Tracking::Helpers
extend ActiveSupport::Concern
included do
before_action :set_experimentation_subject_id_cookie, unless: :dnt_enabled?
helper_method :experiment_enabled?, :experiment_tracking_category_and_group, :record_experiment_group, :tracking_label
end
def set_experimentation_subject_id_cookie
if Gitlab.dev_env_or_com?
return if cookies[:experimentation_subject_id].present?
cookies.permanent.signed[:experimentation_subject_id] = {
value: SecureRandom.uuid,
secure: ::Gitlab.config.gitlab.https,
httponly: true
}
else
# We set the cookie before, although experiments are not conducted on self managed instances.
cookies.delete(:experimentation_subject_id)
end
end
def push_frontend_experiment(experiment_key, subject: nil)
var_name = experiment_key.to_s.camelize(:lower)
enabled = experiment_enabled?(experiment_key, subject: subject)
gon.push({ experiments: { var_name => enabled } }, true)
end
def experiment_enabled?(experiment_key, subject: nil)
return true if forced_enabled?(experiment_key)
return false if dnt_enabled?
Experimentation.log_invalid_rollout(experiment_key, subject)
subject ||= experimentation_subject_id
Experimentation.in_experiment_group?(experiment_key, subject: subject)
end
def track_experiment_event(experiment_key, action, value = nil, subject: nil)
return if dnt_enabled?
track_experiment_event_for(experiment_key, action, value, subject: subject) do |tracking_data|
::Gitlab::Tracking.event(tracking_data.delete(:category), tracking_data.delete(:action), **tracking_data.merge!(user: current_user))
end
end
def frontend_experimentation_tracking_data(experiment_key, action, value = nil, subject: nil)
return if dnt_enabled?
track_experiment_event_for(experiment_key, action, value, subject: subject) do |tracking_data|
gon.push(tracking_data: tracking_data)
end
end
def record_experiment_user(experiment_key, context = {})
return if dnt_enabled?
return unless Experimentation.active?(experiment_key) && current_user
subject = Experimentation.rollout_strategy(experiment_key) == :cookie ? nil : current_user
::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
return unless Experimentation.active?(experiment_key)
::Experiment.record_conversion_event(experiment_key, current_user, context)
end
def experiment_tracking_category_and_group(experiment_key, subject: nil)
"#{tracking_category(experiment_key)}:#{tracking_group(experiment_key, '_group', subject: subject)}"
end
private
def experimentation_subject_id
cookies.signed[:experimentation_subject_id]
end
def track_experiment_event_for(experiment_key, action, value, subject: nil)
return unless Experimentation.active?(experiment_key)
yield experimentation_tracking_data(experiment_key, action, value, subject: subject)
end
def experimentation_tracking_data(experiment_key, action, value, subject: nil)
{
category: tracking_category(experiment_key),
action: action,
property: tracking_group(experiment_key, "_group", subject: subject),
label: tracking_label(subject),
value: value
}.compact
end
def tracking_category(experiment_key)
Experimentation.get_experiment(experiment_key).tracking_category
end
def tracking_group(experiment_key, suffix = nil, subject: nil)
return unless Experimentation.active?(experiment_key)
subject ||= experimentation_subject_id
group = experiment_enabled?(experiment_key, subject: subject) ? GROUP_EXPERIMENTAL : GROUP_CONTROL
suffix ? "#{group}#{suffix}" : group
end
def forced_enabled?(experiment_key)
return true if params.has_key?(:force_experiment) && params[:force_experiment] == experiment_key.to_s
return false if cookies[:force_experiment].blank?
cookies[:force_experiment].to_s.split(',').any? { |experiment| experiment.strip == experiment_key.to_s }
end
def tracking_label(subject = nil)
return experimentation_subject_id if subject.blank?
if subject.respond_to?(:to_global_id)
Digest::MD5.hexdigest(subject.to_global_id.to_s)
else
Digest::MD5.hexdigest(subject.to_s)
end
end
end
end
end
|