summaryrefslogtreecommitdiff
path: root/app/experiments
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-17 11:59:07 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-17 11:59:07 +0000
commit8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca (patch)
tree544930fb309b30317ae9797a9683768705d664c4 /app/experiments
parent4b1de649d0168371549608993deac953eb692019 (diff)
downloadgitlab-ce-8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca.tar.gz
Add latest changes from gitlab-org/gitlab@13-7-stable-eev13.7.0-rc42
Diffstat (limited to 'app/experiments')
-rw-r--r--app/experiments/application_experiment.rb83
1 files changed, 83 insertions, 0 deletions
diff --git a/app/experiments/application_experiment.rb b/app/experiments/application_experiment.rb
new file mode 100644
index 00000000000..e8f7d22bf77
--- /dev/null
+++ b/app/experiments/application_experiment.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+class ApplicationExperiment < Gitlab::Experiment
+ def publish(_result)
+ track(:assignment) # track that we've assigned a variant for this context
+ Gon.global.push({ experiment: { name => signature } }, true) # push to client
+ end
+
+ def track(action, **event_args)
+ return if excluded? # no events for opted out actors or excluded subjects
+
+ Gitlab::Tracking.event(name, action.to_s, **event_args.merge(
+ context: (event_args[:context] || []) << SnowplowTracker::SelfDescribingJson.new(
+ 'iglu:com.gitlab/gitlab_experiment/jsonschema/0-3-0', signature
+ )
+ ))
+ end
+
+ private
+
+ def resolve_variant_name
+ variant_names.first if Feature.enabled?(name, self, type: :experiment)
+ end
+
+ # Cache is an implementation on top of Gitlab::Redis::SharedState that also
+ # adheres to the ActiveSupport::Cache::Store interface and uses the redis
+ # hash data type.
+ #
+ # Since Gitlab::Experiment can use any type of caching layer, utilizing the
+ # long lived shared state interface here gives us an efficient way to store
+ # context keys and the variant they've been assigned -- while also giving us
+ # a simple way to clean up an experiments data upon resolution.
+ #
+ # The data structure:
+ # key: experiment.name
+ # fields: context key => variant name
+ #
+ # The keys are expected to be `experiment_name:context_key`, which is the
+ # default cache key strategy. So running `cache.fetch("foo:bar", "value")`
+ # would create/update a hash with the key of "foo", with a field named
+ # "bar" that has "value" assigned to it.
+ class Cache < ActiveSupport::Cache::Store
+ # Clears the entire cache for a given experiment. Be careful with this
+ # since it would reset all resolved variants for the entire experiment.
+ def clear(key:)
+ key = hkey(key)[0] # extract only the first part of the key
+ pool do |redis|
+ case redis.type(key)
+ when 'hash', 'none' then redis.del(key)
+ else raise ArgumentError, 'invalid call to clear a non-hash cache key'
+ end
+ end
+ end
+
+ private
+
+ def pool
+ raise ArgumentError, 'missing block' unless block_given?
+
+ Gitlab::Redis::SharedState.with { |redis| yield redis }
+ end
+
+ def hkey(key)
+ key.split(':') # this assumes the default strategy in gitlab-experiment
+ end
+
+ def read_entry(key, **options)
+ value = pool { |redis| redis.hget(*hkey(key)) }
+ value.nil? ? nil : ActiveSupport::Cache::Entry.new(value)
+ end
+
+ def write_entry(key, entry, **options)
+ return false unless Feature.enabled?(:caching_experiments)
+ return false if entry.value.blank? # don't cache any empty values
+
+ pool { |redis| redis.hset(*hkey(key), entry.value) }
+ end
+
+ def delete_entry(key, **options)
+ pool { |redis| redis.hdel(*hkey(key)) }
+ end
+ end
+end