Class: Feature

Inherits:
Object
  • Object
show all
Defined in:
lib/feature/shared.rb,
lib/feature.rb,
lib/feature/gitaly.rb,
lib/feature/definition.rb

Overview

This file can contain only simple constructs as it is shared between:

  1. `Pure Ruby`: `bin/feature-flag`

  2. `GitLab Rails`: `lib/feature/definition.rb`

Defined Under Namespace

Modules: Shared Classes: Definition, FlipperFeature, FlipperGate, Gitaly, Target

Constant Summary collapse

InvalidFeatureFlagError =

rubocop:disable Lint/InheritException

Class.new(Exception)

Class Method Summary collapse

Class Method Details

.allObject


26
27
28
# File 'lib/feature.rb', line 26

def all
  flipper.features.to_a
end

.disable(key, thing = false) ⇒ Object


89
90
91
# File 'lib/feature.rb', line 89

def disable(key, thing = false)
  get(key).disable(thing)
end

.disable_group(key, group) ⇒ Object


97
98
99
# File 'lib/feature.rb', line 97

def disable_group(key, group)
  get(key).disable_group(group)
end

.disable_percentage_of_actors(key) ⇒ Object


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

def disable_percentage_of_actors(key)
  get(key).disable_percentage_of_actors
end

.disable_percentage_of_time(key) ⇒ Object


105
106
107
# File 'lib/feature.rb', line 105

def disable_percentage_of_time(key)
  get(key).disable_percentage_of_time
end

.disabled?(key, thing = nil, type: :development, default_enabled: false) ⇒ Boolean

Returns:

  • (Boolean)

80
81
82
83
# File 'lib/feature.rb', line 80

def disabled?(key, thing = nil, type: :development, default_enabled: false)
  # we need to make different method calls to make it easy to mock / define expectations in test mode
  thing.nil? ? !enabled?(key, type: type, default_enabled: default_enabled) : !enabled?(key, thing, type: type, default_enabled: default_enabled)
end

.enable(key, thing = true) ⇒ Object


85
86
87
# File 'lib/feature.rb', line 85

def enable(key, thing = true)
  get(key).enable(thing)
end

.enable_group(key, group) ⇒ Object


93
94
95
# File 'lib/feature.rb', line 93

def enable_group(key, group)
  get(key).enable_group(group)
end

.enable_percentage_of_actors(key, percentage) ⇒ Object


109
110
111
# File 'lib/feature.rb', line 109

def enable_percentage_of_actors(key, percentage)
  get(key).enable_percentage_of_actors(percentage)
end

.enable_percentage_of_time(key, percentage) ⇒ Object


101
102
103
# File 'lib/feature.rb', line 101

def enable_percentage_of_time(key, percentage)
  get(key).enable_percentage_of_time(percentage)
end

.enabled?(key, thing = nil, type: :development, default_enabled: false) ⇒ Boolean

use `default_enabled: true` to default the flag to being `enabled` unless set explicitly. The default is `disabled` TODO: remove the `default_enabled:` and read it from the `defintion_yaml` check: gitlab.com/gitlab-org/gitlab/-/issues/30228

Returns:

  • (Boolean)

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/feature.rb', line 57

def enabled?(key, thing = nil, type: :development, default_enabled: false)
  if check_feature_flags_definition?
    if thing && !thing.respond_to?(:flipper_id)
      raise InvalidFeatureFlagError,
        "The thing '#{thing.class.name}' for feature flag '#{key}' needs to include `FeatureGate` or implement `flipper_id`"
    end

    Feature::Definition.valid_usage!(key, type: type, default_enabled: default_enabled)
  end

  # During setup the database does not exist yet. So we haven't stored a value
  # for the feature yet and return the default.
  return default_enabled unless Gitlab::Database.exists?

  feature = get(key)

  # If we're not default enabling the flag or the feature has been set, always evaluate.
  # `persisted?` can potentially generate DB queries and also checks for inclusion
  # in an array of feature names (177 at last count), possibly reducing performance by half.
  # So we only perform the `persisted` check if `default_enabled: true`
  !default_enabled || Feature.persisted_name?(feature.name) ? feature.enabled?(thing) : true
end

.get(key) ⇒ Object


30
31
32
# File 'lib/feature.rb', line 30

def get(key)
  flipper.feature(key)
end

.persisted_name?(feature_name) ⇒ Boolean

Returns:

  • (Boolean)

46
47
48
49
50
51
# File 'lib/feature.rb', line 46

def persisted_name?(feature_name)
  # Flipper creates on-memory features when asked for a not-yet-created one.
  # If we want to check if a feature has been actually set, we look for it
  # on the persisted features list.
  persisted_names.include?(feature_name.to_s)
end

.persisted_namesObject


34
35
36
37
38
39
40
41
42
43
44
# File 'lib/feature.rb', line 34

def persisted_names
  return [] unless Gitlab::Database.exists?

  # This loads names of all stored feature flags
  # and returns a stable Set in the following order:
  # - Memoized: using Gitlab::SafeRequestStore or @flipper
  # - L1: using Process cache
  # - L2: using Redis cache
  # - DB: using a single SQL query
  flipper.adapter.features
end

.register_definitionsObject


134
135
136
137
138
# File 'lib/feature.rb', line 134

def register_definitions
  return unless check_feature_flags_definition?

  Feature::Definition.load_all!
end

.register_feature_groupsObject

This method is called from config/initializers/flipper.rb and can be used to register Flipper groups. See docs.gitlab.com/ee/development/feature_flags.html#feature-groups


131
132
# File 'lib/feature.rb', line 131

def register_feature_groups
end

.register_hot_reloaderObject


140
141
142
143
144
# File 'lib/feature.rb', line 140

def register_hot_reloader
  return unless check_feature_flags_definition?

  Feature::Definition.register_hot_reloader!
end

.remove(key) ⇒ Object


117
118
119
120
121
# File 'lib/feature.rb', line 117

def remove(key)
  return unless persisted_name?(key)

  get(key).remove
end

.resetObject


123
124
125
126
# File 'lib/feature.rb', line 123

def reset
  Gitlab::SafeRequestStore.delete(:flipper) if Gitlab::SafeRequestStore.active?
  @flipper = nil
end