summaryrefslogtreecommitdiff
path: root/lib/gitlab/experiment/rollout/feature.rb
blob: 4bef92f5c23e8f8de2178cce51bdd0d742d6ce0c (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
# frozen_string_literal: true

module Gitlab
  class Experiment
    module Rollout
      class Feature < Percent
        # For this rollout strategy to consider an experiment as enabled, we
        # must:
        #
        # - have a feature flag yaml file that declares it.
        # - be in an environment that permits it.
        # - not have rolled out the feature flag at all (no percent of actors,
        #   no inclusions, etc.)
        def enabled?
          return false unless feature_flag_defined?
          return false unless Gitlab.com?
          return false unless ::Feature.enabled?(:gitlab_experiment, type: :ops, default_enabled: :yaml)

          feature_flag_instance.state != :off
        end

        # For assignment we first check to see if our feature flag is enabled
        # for "self". This is done by calling `#flipper_id` (used behind the
        # scenes by `Feature`). By default this is our `experiment.id` (or more
        # specifically, the context key, which is an anonymous SHA generated
        # using the details of an experiment.
        #
        # If the `Feature.enabled?` check is false, we return nil implicitly,
        # which will assign the control. Otherwise we call super, which will
        # assign a variant evenly, or based on our provided distribution rules.
        def execute_assignment
          super if ::Feature.enabled?(feature_flag_name, self, type: :experiment, default_enabled: :yaml)
        end

        # This is what's provided to the `Feature.enabled?` call that will be
        # used to determine experiment inclusion. An experiment may provide an
        # override for this method to make the experiment work on user, group,
        # or projects.
        #
        # For example, when running an experiment on a project, you could make
        # the experiment assignable by project (using chatops) by implementing
        # a `flipper_id` method in the experiment:
        #
        # def flipper_id
        #   context.project.flipper_id
        # end
        #
        # Or even cleaner, simply delegate it:
        #
        # delegate :flipper_id, to: -> { context.project }
        def flipper_id
          return experiment.flipper_id if experiment.respond_to?(:flipper_id)

          "Experiment;#{id}"
        end

        private

        def feature_flag_instance
          ::Feature.get(feature_flag_name) # rubocop:disable Gitlab/AvoidFeatureGet
        end

        def feature_flag_defined?
          ::Feature::Definition.get(feature_flag_name).present?
        end

        def feature_flag_name
          experiment.name.tr('/', '_')
        end
      end
    end
  end
end