Class: Feature

Inherits:
Object
  • Object
show all
Defined in:
lib/feature/shared.rb,
lib/feature.rb,
lib/feature/gitaly.rb,
lib/feature/logger.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: ActiveSupportCacheStoreAdapter, Definition, FlipperFeature, FlipperGate, Gitaly, Logger, Target

Constant Summary collapse

InvalidFeatureFlagError =

rubocop:disable Lint/InheritException

Class.new(Exception)

Class Method Summary collapse

Class Method Details

.allObject


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

def all
  flipper.features.to_a
end

.disable(key, thing = false) ⇒ Object


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

def disable(key, thing = false)
  log(key: key, action: __method__, thing: thing)
  get(key).disable(thing)
end

.disable_percentage_of_actors(key) ⇒ Object


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

def disable_percentage_of_actors(key)
  log(key: key, action: __method__)
  get(key).disable_percentage_of_actors
end

.disable_percentage_of_time(key) ⇒ Object


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

def disable_percentage_of_time(key)
  log(key: key, action: __method__)
  get(key).disable_percentage_of_time
end

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

Returns:

  • (Boolean)

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

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


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

def enable(key, thing = true)
  log(key: key, action: __method__, thing: thing)
  get(key).enable(thing)
end

.enable_percentage_of_actors(key, percentage) ⇒ Object


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

def enable_percentage_of_actors(key, percentage)
  log(key: key, action: __method__, percentage: percentage)
  get(key).enable_percentage_of_actors(percentage)
end

.enable_percentage_of_time(key, percentage) ⇒ Object


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

def enable_percentage_of_time(key, percentage)
  log(key: key, action: __method__, percentage: 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)

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/feature.rb', line 61

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


34
35
36
# File 'lib/feature.rb', line 34

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

.loggerObject


147
148
149
# File 'lib/feature.rb', line 147

def logger
  @logger ||= Feature::Logger.build
end

.persisted_name?(feature_name) ⇒ Boolean

Returns:

  • (Boolean)

50
51
52
53
54
55
# File 'lib/feature.rb', line 50

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


38
39
40
41
42
43
44
45
46
47
48
# File 'lib/feature.rb', line 38

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


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

def register_definitions
  Feature::Definition.reload!
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


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

def register_feature_groups
end

.register_hot_reloaderObject


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

def register_hot_reloader
  return unless check_feature_flags_definition?

  Feature::Definition.register_hot_reloader!
end

.remove(key) ⇒ Object


119
120
121
122
123
124
# File 'lib/feature.rb', line 119

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

  log(key: key, action: __method__)
  get(key).remove
end

.resetObject


126
127
128
129
# File 'lib/feature.rb', line 126

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