Class: Gitlab::Experiment

Inherits:
Object
  • Object
show all
Includes:
Caching, Callbacks, Scientist::Experiment
Defined in:
lib/gitlab/experiment.rb,
lib/gitlab/experiment/dsl.rb,
lib/gitlab/experiment/engine.rb,
lib/gitlab/experiment/caching.rb,
lib/gitlab/experiment/context.rb,
lib/gitlab/experiment/cookies.rb,
lib/gitlab/experiment/variant.rb,
lib/gitlab/experiment/version.rb,
lib/gitlab/experiment/callbacks.rb,
lib/gitlab/experiment/configuration.rb

Defined Under Namespace

Modules: Caching, Callbacks, Cookies, Dsl Classes: Configuration, Context, Engine, Variant

Constant Summary collapse

VERSION =
'0.4.2'

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Caching

#cache

Constructor Details

#initialize(name = nil, variant_name = nil, **context) {|_self| ... } ⇒ Experiment

Returns a new instance of Experiment.

Yields:

  • (_self)

Yield Parameters:

Raises:

  • (ArgumentError)


57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/gitlab/experiment.rb', line 57

def initialize(name = nil, variant_name = nil, **context)
  raise ArgumentError, 'name is required' if name.blank? && self.class.base?

  @name = self.class.experiment_name(name, suffix: false)
  @variant_name = variant_name
  @excluded = []
  @context = Context.new(self, context)

  exclude { !@context.trackable? }
  compare { false }

  yield self if block_given?
end

Class Method Details

.base?Boolean

Returns:

  • (Boolean)


44
45
46
# File 'lib/gitlab/experiment.rb', line 44

def base?
  self == Gitlab::Experiment || name == Configuration.base_class
end

.configure {|Configuration| ... } ⇒ Object

Yields:



25
26
27
# File 'lib/gitlab/experiment.rb', line 25

def configure
  yield Configuration
end

.experiment_name(name = nil, suffix: true, suffix_word: 'experiment') ⇒ Object



38
39
40
41
42
# File 'lib/gitlab/experiment.rb', line 38

def experiment_name(name = nil, suffix: true, suffix_word: 'experiment')
  name = (name.presence || self.name).to_s.underscore.sub(%r{(?<char>[_/]|)#{suffix_word}$}, '')
  name = "#{name}#{Regexp.last_match(:char) || '_'}#{suffix_word}"
  suffix ? name : name.sub(/_#{suffix_word}$/, '')
end

.run(name = nil, variant_name = nil, **context, &block) ⇒ Object

Raises:

  • (ArgumentError)


29
30
31
32
33
34
35
36
# File 'lib/gitlab/experiment.rb', line 29

def run(name = nil, variant_name = nil, **context, &block)
  raise ArgumentError, 'name is required' if name.nil? && base?

  instance = constantize(name).new(name, variant_name, **context, &block)
  return instance unless block_given?

  instance.context.frozen? ? instance.run : instance.tap(&:run)
end

Instance Method Details

#context(value = nil) ⇒ Object



71
72
73
74
75
76
# File 'lib/gitlab/experiment.rb', line 71

def context(value = nil)
  return @context if value.blank?

  @context.value(value)
  @context
end

#enabled?Boolean

Returns:

  • (Boolean)


125
126
127
# File 'lib/gitlab/experiment.rb', line 125

def enabled?
  true
end

#exclude(&block) ⇒ Object



86
87
88
# File 'lib/gitlab/experiment.rb', line 86

def exclude(&block)
  @excluded << block
end

#excluded?Boolean

Returns:

  • (Boolean)


129
130
131
# File 'lib/gitlab/experiment.rb', line 129

def excluded?
  @excluded.any? { |exclude| exclude.call(self) }
end

#flipper_idObject



142
143
144
# File 'lib/gitlab/experiment.rb', line 142

def flipper_id
  "Experiment;#{id}"
end

#idObject Also known as: session_id



137
138
139
# File 'lib/gitlab/experiment.rb', line 137

def id
  "#{name}:#{key_for(context.value)}"
end

#key_for(hash) ⇒ Object



146
147
148
# File 'lib/gitlab/experiment.rb', line 146

def key_for(hash)
  instance_exec(hash, &Configuration.context_hash_strategy)
end

#nameObject



113
114
115
# File 'lib/gitlab/experiment.rb', line 113

def name
  [Configuration.name_prefix, @name].compact.join('_')
end

#publish(result) ⇒ Object



103
104
105
# File 'lib/gitlab/experiment.rb', line 103

def publish(result)
  instance_exec(result, &Configuration.publishing_behavior)
end

#run(variant_name = nil) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/gitlab/experiment.rb', line 90

def run(variant_name = nil)
  @result ||= begin
    variant_name = variant(variant_name).name
    run_callbacks(variant_assigned? ? :unsegmented_run : :segmented_run) do
      if respond_to?((behavior_name = "#{variant_name}_behavior"))
        behaviors[variant_name] ||= -> { send(behavior_name) } # rubocop:disable GitlabSecurity/PublicSend
      end

      super(@variant_name = variant_name)
    end
  end
end

#signatureObject



121
122
123
# File 'lib/gitlab/experiment.rb', line 121

def signature
  { variant: variant.name, experiment: name }.merge(context.signature)
end

#track(action, **event_args) ⇒ Object



107
108
109
110
111
# File 'lib/gitlab/experiment.rb', line 107

def track(action, **event_args)
  return if excluded?

  instance_exec(action, event_args, &Configuration.tracking_behavior)
end

#variant(value = nil) ⇒ Object



78
79
80
81
82
83
84
# File 'lib/gitlab/experiment.rb', line 78

def variant(value = nil)
  @variant_name = value unless value.blank?
  @variant_name ||= :control if excluded?

  resolved = cache { resolve_variant_name }
  Variant.new(name: (resolved.presence || :control).to_s)
end

#variant_assigned?Boolean

Returns:

  • (Boolean)


133
134
135
# File 'lib/gitlab/experiment.rb', line 133

def variant_assigned?
  !@variant_name.nil?
end

#variant_namesObject



117
118
119
# File 'lib/gitlab/experiment.rb', line 117

def variant_names
  @variant_names ||= behaviors.keys.map(&:to_sym) - [:control]
end