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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-02-13 00:09:01 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-13 00:09:01 +0300
commiteef0c69d45082b370f1e41e50f12488a216944f2 (patch)
tree5c4a5c0e4db3fff89b9b4146b799e5de9dca57b9 /app/experiments
parent6d533fe8b44007d82b8de29a4b706da69e5f5936 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/experiments')
-rw-r--r--app/experiments/application_experiment.rb33
-rw-r--r--app/experiments/members/invite_email_experiment.rb14
-rw-r--r--app/experiments/strategy/round_robin.rb78
3 files changed, 112 insertions, 13 deletions
diff --git a/app/experiments/application_experiment.rb b/app/experiments/application_experiment.rb
index 1c9a3c5f37f..663c58ceda6 100644
--- a/app/experiments/application_experiment.rb
+++ b/app/experiments/application_experiment.rb
@@ -23,16 +23,41 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp
))
end
+ def rollout_strategy
+ # no-op override in inherited class as desired
+ end
+
+ def variants
+ # override as desired in inherited class with all variants + control
+ # %i[variant1 variant2 control]
+ #
+ # this will make sure we supply variants as these go together - rollout_strategy of :round_robin must have variants
+ raise NotImplementedError, "Inheriting class must supply variants as an array if :round_robin strategy is used" if rollout_strategy == :round_robin
+ end
+
private
+ def feature_flag_name
+ name.tr('/', '_')
+ end
+
def resolve_variant_name
- return variant_names.first if Feature.enabled?(feature_flag_name, self, type: :experiment, default_enabled: :yaml)
+ case rollout_strategy
+ when :round_robin
+ round_robin_rollout
+ else
+ percentage_rollout
+ end
+ end
- nil # Returning nil vs. :control is important for not caching and rollouts.
+ def round_robin_rollout
+ Strategy::RoundRobin.new(feature_flag_name, variants).execute
end
- def feature_flag_name
- name.tr('/', '_')
+ def percentage_rollout
+ return variant_names.first if Feature.enabled?(feature_flag_name, self, type: :experiment, default_enabled: :yaml)
+
+ nil # Returning nil vs. :control is important for not caching and rollouts.
end
# Cache is an implementation on top of Gitlab::Redis::SharedState that also
diff --git a/app/experiments/members/invite_email_experiment.rb b/app/experiments/members/invite_email_experiment.rb
index 58703fd505d..4a03ebb7726 100644
--- a/app/experiments/members/invite_email_experiment.rb
+++ b/app/experiments/members/invite_email_experiment.rb
@@ -7,16 +7,12 @@ module Members
INVITE_TYPE = 'initial_email'
- private
+ def rollout_strategy
+ :round_robin
+ end
- def resolve_variant_name
- # we are overriding here so that when we add another experiment
- # we can merely add that variant and check of feature flag here
- if Feature.enabled?(feature_flag_name, self, type: :experiment, default_enabled: :yaml)
- :avatar
- else
- nil # :control
- end
+ def variants
+ %i[avatar permission_info control]
end
end
end
diff --git a/app/experiments/strategy/round_robin.rb b/app/experiments/strategy/round_robin.rb
new file mode 100644
index 00000000000..7b80c0e984d
--- /dev/null
+++ b/app/experiments/strategy/round_robin.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Strategy
+ class RoundRobin
+ CacheError = Class.new(StandardError)
+
+ COUNTER_EXPIRE_TIME = 86400 # one day
+
+ def initialize(key, variants)
+ @key = key
+ @variants = variants
+ end
+
+ def execute
+ increment_counter
+ resolve_variant_name
+ end
+
+ # When the counter would expire
+ #
+ # @api private Used internally by SRE and debugging purpose
+ # @return [Integer] Number in seconds until expiration or false if never
+ def counter_expires_in
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.ttl(key)
+ end
+ end
+
+ # Return the actual counter value
+ #
+ # @return [Integer] value
+ def counter_value
+ Gitlab::Redis::SharedState.with do |redis|
+ (redis.get(key) || 0).to_i
+ end
+ end
+
+ # Reset the counter
+ #
+ # @private Used internally by SRE and debugging purpose
+ # @return [Boolean] whether reset was a success
+ def reset!
+ redis_cmd do |redis|
+ redis.del(key)
+ end
+ end
+
+ private
+
+ attr_reader :key, :variants
+
+ # Increase the counter
+ #
+ # @return [Boolean] whether operation was a success
+ def increment_counter
+ redis_cmd do |redis|
+ redis.incr(key)
+ redis.expire(key, COUNTER_EXPIRE_TIME)
+ end
+ end
+
+ def resolve_variant_name
+ remainder = counter_value % variants.size
+
+ variants[remainder]
+ end
+
+ def redis_cmd
+ Gitlab::Redis::SharedState.with { |redis| yield(redis) }
+
+ true
+ rescue CacheError => e
+ Gitlab::AppLogger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}")
+
+ false
+ end
+ end
+end