Welcome to mirror list, hosted at ThFree Co, Russian Federation.

experimentation.rb « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 4fbf15d521a2df6bf1b292917076883dea5f0eac (plain)
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
# frozen_string_literal: true

# == Experimentation
#
# Utility module used for A/B testing experimental features. Define your experiments in the `EXPERIMENTS` constant.
# The feature_toggle and environment keys are optional. If the feature_toggle is not set, a feature with the name of
# the experiment will be checked, with a default value of true. The enabled_ratio is required and should be
# the ratio for the number of users for which this experiment is enabled. For example: a ratio of 0.1 will
# enable the experiment for 10% of the users (determined by the `experimentation_subject_index`).
#
module Gitlab
  module Experimentation
    EXPERIMENTS = {
      signup_flow: {
        feature_toggle: :experimental_separate_sign_up_flow,
        environment: ::Gitlab.dev_env_or_com?,
        enabled_ratio: 1,
        tracking_category: 'Growth::Acquisition::Experiment::SignUpFlow'
      }
    }.freeze

    # 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)` 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 ControllerConcern
      extend ActiveSupport::Concern

      included do
        before_action :set_experimentation_subject_id_cookie
        helper_method :experiment_enabled?
      end

      def set_experimentation_subject_id_cookie
        return if cookies[:experimentation_subject_id].present?

        cookies.permanent.signed[:experimentation_subject_id] = {
          value: SecureRandom.uuid,
          domain: :all,
          secure: ::Gitlab.config.gitlab.https,
          httponly: true
        }
      end

      def experiment_enabled?(experiment_key)
        Experimentation.enabled_for_user?(experiment_key, experimentation_subject_index) || forced_enabled?(experiment_key)
      end

      def track_experiment_event(experiment_key, action)
        track_experiment_event_for(experiment_key, action) do |tracking_data|
          ::Gitlab::Tracking.event(tracking_data.delete(:category), tracking_data.delete(:action), tracking_data)
        end
      end

      def frontend_experimentation_tracking_data(experiment_key, action)
        track_experiment_event_for(experiment_key, action) do |tracking_data|
          gon.push(tracking_data: tracking_data)
        end
      end

      private

      def experimentation_subject_id
        cookies.signed[:experimentation_subject_id]
      end

      def experimentation_subject_index
        return if experimentation_subject_id.blank?

        experimentation_subject_id.delete('-').hex % 100
      end

      def track_experiment_event_for(experiment_key, action)
        return unless Experimentation.enabled?(experiment_key)

        yield experimentation_tracking_data(experiment_key, action)
      end

      def experimentation_tracking_data(experiment_key, action)
        {
          category: tracking_category(experiment_key),
          action: action,
          property: tracking_group(experiment_key),
          label: experimentation_subject_id
        }
      end

      def tracking_category(experiment_key)
        Experimentation.experiment(experiment_key).tracking_category
      end

      def tracking_group(experiment_key)
        return unless Experimentation.enabled?(experiment_key)

        experiment_enabled?(experiment_key) ? 'experimental_group' : 'control_group'
      end

      def forced_enabled?(experiment_key)
        params.has_key?(:force_experiment) && params[:force_experiment] == experiment_key.to_s
      end
    end

    class << self
      def experiment(key)
        Experiment.new(EXPERIMENTS[key].merge(key: key))
      end

      def enabled?(experiment_key)
        return false unless EXPERIMENTS.key?(experiment_key)

        experiment = experiment(experiment_key)
        experiment.feature_toggle_enabled? && experiment.enabled_for_environment?
      end

      def enabled_for_user?(experiment_key, experimentation_subject_index)
        enabled?(experiment_key) &&
          experiment(experiment_key).enabled_for_experimentation_subject?(experimentation_subject_index)
      end
    end

    Experiment = Struct.new(:key, :feature_toggle, :environment, :enabled_ratio, :tracking_category, keyword_init: true) do
      def feature_toggle_enabled?
        return Feature.enabled?(key, default_enabled: true) if feature_toggle.nil?

        Feature.enabled?(feature_toggle)
      end

      def enabled_for_environment?
        return true if environment.nil?

        environment
      end

      def enabled_for_experimentation_subject?(experimentation_subject_index)
        return false if enabled_ratio.nil? || experimentation_subject_index.blank?

        experimentation_subject_index <= enabled_ratio * 100
      end
    end
  end
end