flagsmith 3.1.1 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +12 -19
- data/.ruby-version +1 -1
- data/Gemfile.lock +52 -60
- data/example/Gemfile +1 -1
- data/example/Gemfile.lock +25 -20
- data/flagsmith.gemspec +6 -5
- data/lib/flagsmith/engine/features/models.rb +2 -4
- data/lib/flagsmith/engine/segments/constants.rb +3 -1
- data/lib/flagsmith/engine/segments/evaluator.rb +8 -5
- data/lib/flagsmith/engine/segments/models.rb +11 -6
- data/lib/flagsmith/sdk/config.rb +14 -3
- data/lib/flagsmith/sdk/models/flags.rb +23 -7
- data/lib/flagsmith/sdk/models/segments.rb +2 -1
- data/lib/flagsmith/sdk/offline_handlers.rb +17 -0
- data/lib/flagsmith/version.rb +1 -1
- data/lib/flagsmith.rb +95 -36
- metadata +20 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7e45006cc3caabc6941c7daaf58f339940cb1ea1fc3ec76be0a7897d84212fa
|
4
|
+
data.tar.gz: 32703828745194ea1befb791266e592e888c7fac888fd35c4cc6b6e9b6f3e204
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa77df821acafa8ccc9d0580845573b47fe0287c1937a7f8200d08532f559d77af1c41e6862a52a0ab90fec30cc020ea63068120aa95a95da41cff2b2a48e84d
|
7
|
+
data.tar.gz: 25f98c085b4b8253356fbc203e5605fa6ac53930e7ac8bbe7d2be1f5ba8dc8bf0374ca9a85b57bebafb08b9e57077dcc18637fca9b9d91a758ec404398de663a
|
data/.rubocop.yml
CHANGED
@@ -4,8 +4,10 @@ Layout/HashAlignment:
|
|
4
4
|
AllowMultipleStyles: true
|
5
5
|
EnforcedColonStyle: key
|
6
6
|
AllCops:
|
7
|
+
TargetRubyVersion: 3.0 # Pin to flagsmith.gemspec required_ruby_version
|
7
8
|
NewCops: enable
|
8
9
|
SuggestExtensions: false
|
9
10
|
Exclude:
|
10
11
|
- 'spec/**/*'
|
11
12
|
- 'example/**/*'
|
13
|
+
- 'vendor/**/*'
|
data/.rubocop_todo.yml
CHANGED
@@ -1,29 +1,22 @@
|
|
1
1
|
# This configuration was generated by
|
2
|
-
# `rubocop --auto-gen-config
|
3
|
-
# on
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2023-07-20 10:26:52 UTC using RuboCop version 1.54.2.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
8
|
|
9
|
-
# Offense count:
|
10
|
-
# Configuration parameters:
|
11
|
-
#
|
12
|
-
|
9
|
+
# Offense count: 6
|
10
|
+
# Configuration parameters: EnforcedStyle, AllowedGems, Include.
|
11
|
+
# SupportedStyles: Gemfile, gems.rb, gemspec
|
12
|
+
# Include: **/*.gemspec, **/Gemfile, **/gems.rb
|
13
|
+
Gemspec/DevelopmentDependencies:
|
13
14
|
Exclude:
|
14
|
-
- '
|
15
|
-
|
16
|
-
# Offense count: 1
|
17
|
-
# Configuration parameters: AllowedMethods.
|
18
|
-
# AllowedMethods: respond_to_missing?
|
19
|
-
Style/OptionalBooleanParameter:
|
20
|
-
Exclude:
|
21
|
-
- 'lib/flagsmith.rb'
|
15
|
+
- 'flagsmith.gemspec'
|
22
16
|
|
23
|
-
# Offense count:
|
24
|
-
#
|
25
|
-
# Configuration parameters:
|
17
|
+
# Offense count: 4
|
18
|
+
# This cop supports safe autocorrection (--autocorrect).
|
19
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
|
26
20
|
# URISchemes: http, https
|
27
21
|
Layout/LineLength:
|
28
|
-
|
29
|
-
- 'flagsmith.gemspec'
|
22
|
+
Max: 183
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.1.4
|
data/Gemfile.lock
CHANGED
@@ -1,84 +1,75 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
flagsmith (
|
5
|
-
faraday
|
4
|
+
flagsmith (4.0.0)
|
5
|
+
faraday (>= 2.0.1)
|
6
6
|
faraday-retry
|
7
|
-
faraday_middleware
|
8
7
|
semantic
|
9
8
|
|
10
9
|
GEM
|
11
10
|
remote: https://rubygems.org/
|
12
11
|
specs:
|
13
|
-
ast (2.4.
|
12
|
+
ast (2.4.2)
|
13
|
+
byebug (11.1.3)
|
14
14
|
coderay (1.1.3)
|
15
|
-
diff-lcs (1.
|
16
|
-
faraday (
|
17
|
-
faraday-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
faraday
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
faraday-rack (~> 1.0)
|
26
|
-
faraday-retry (~> 1.0)
|
27
|
-
ruby2_keywords (>= 0.0.4)
|
28
|
-
faraday-em_http (1.0.0)
|
29
|
-
faraday-em_synchrony (1.0.0)
|
30
|
-
faraday-excon (1.1.0)
|
31
|
-
faraday-httpclient (1.0.1)
|
32
|
-
faraday-multipart (1.0.4)
|
33
|
-
multipart-post (~> 2)
|
34
|
-
faraday-net_http (1.0.1)
|
35
|
-
faraday-net_http_persistent (1.2.0)
|
36
|
-
faraday-patron (1.0.0)
|
37
|
-
faraday-rack (1.0.0)
|
38
|
-
faraday-retry (1.0.3)
|
39
|
-
faraday_middleware (1.2.0)
|
40
|
-
faraday (~> 1.0)
|
41
|
-
gem-release (2.2.0)
|
15
|
+
diff-lcs (1.5.0)
|
16
|
+
faraday (2.9.0)
|
17
|
+
faraday-net_http (>= 2.0, < 3.2)
|
18
|
+
faraday-net_http (3.1.0)
|
19
|
+
net-http
|
20
|
+
faraday-retry (2.2.0)
|
21
|
+
faraday (~> 2.0)
|
22
|
+
gem-release (2.2.2)
|
23
|
+
json (2.7.1)
|
24
|
+
language_server-protocol (3.17.0.3)
|
42
25
|
method_source (1.0.0)
|
43
|
-
|
44
|
-
|
45
|
-
|
26
|
+
net-http (0.4.1)
|
27
|
+
uri
|
28
|
+
parallel (1.24.0)
|
29
|
+
parser (3.3.0.4)
|
46
30
|
ast (~> 2.4.1)
|
47
|
-
|
31
|
+
racc
|
32
|
+
pry (0.14.2)
|
48
33
|
coderay (~> 1.1)
|
49
34
|
method_source (~> 1.0)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
rspec
|
59
|
-
rspec-
|
60
|
-
|
35
|
+
pry-byebug (3.10.1)
|
36
|
+
byebug (~> 11.0)
|
37
|
+
pry (>= 0.13, < 0.15)
|
38
|
+
racc (1.7.3)
|
39
|
+
rainbow (3.1.1)
|
40
|
+
rake (13.1.0)
|
41
|
+
regexp_parser (2.9.0)
|
42
|
+
rexml (3.2.6)
|
43
|
+
rspec (3.12.0)
|
44
|
+
rspec-core (~> 3.12.0)
|
45
|
+
rspec-expectations (~> 3.12.0)
|
46
|
+
rspec-mocks (~> 3.12.0)
|
47
|
+
rspec-core (3.12.2)
|
48
|
+
rspec-support (~> 3.12.0)
|
49
|
+
rspec-expectations (3.12.3)
|
61
50
|
diff-lcs (>= 1.2.0, < 2.0)
|
62
|
-
rspec-support (~> 3.
|
63
|
-
rspec-mocks (3.
|
51
|
+
rspec-support (~> 3.12.0)
|
52
|
+
rspec-mocks (3.12.6)
|
64
53
|
diff-lcs (>= 1.2.0, < 2.0)
|
65
|
-
rspec-support (~> 3.
|
66
|
-
rspec-support (3.
|
67
|
-
rubocop (1.
|
54
|
+
rspec-support (~> 3.12.0)
|
55
|
+
rspec-support (3.12.1)
|
56
|
+
rubocop (1.60.0)
|
57
|
+
json (~> 2.3)
|
58
|
+
language_server-protocol (>= 3.17.0)
|
68
59
|
parallel (~> 1.10)
|
69
|
-
parser (>=
|
60
|
+
parser (>= 3.3.0.2)
|
70
61
|
rainbow (>= 2.2.2, < 4.0)
|
71
62
|
regexp_parser (>= 1.8, < 3.0)
|
72
|
-
rexml
|
73
|
-
rubocop-ast (>= 1.
|
63
|
+
rexml (>= 3.2.5, < 4.0)
|
64
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
74
65
|
ruby-progressbar (~> 1.7)
|
75
|
-
unicode-display_width (>=
|
76
|
-
rubocop-ast (1.
|
77
|
-
parser (>= 2.
|
78
|
-
ruby-progressbar (1.
|
79
|
-
ruby2_keywords (0.0.5)
|
66
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
67
|
+
rubocop-ast (1.30.0)
|
68
|
+
parser (>= 3.2.1.0)
|
69
|
+
ruby-progressbar (1.13.0)
|
80
70
|
semantic (1.6.1)
|
81
|
-
unicode-display_width (
|
71
|
+
unicode-display_width (2.5.0)
|
72
|
+
uri (0.13.0)
|
82
73
|
|
83
74
|
PLATFORMS
|
84
75
|
ruby
|
@@ -88,6 +79,7 @@ DEPENDENCIES
|
|
88
79
|
flagsmith!
|
89
80
|
gem-release
|
90
81
|
pry
|
82
|
+
pry-byebug
|
91
83
|
rake
|
92
84
|
rspec
|
93
85
|
rubocop
|
data/example/Gemfile
CHANGED
data/example/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
flagsmith (3.
|
4
|
+
flagsmith (3.1.1)
|
5
5
|
faraday
|
6
6
|
faraday-retry
|
7
7
|
faraday_middleware
|
@@ -10,8 +10,8 @@ PATH
|
|
10
10
|
GEM
|
11
11
|
remote: https://rubygems.org/
|
12
12
|
specs:
|
13
|
-
addressable (2.8.
|
14
|
-
public_suffix (>= 2.0.2, <
|
13
|
+
addressable (2.8.4)
|
14
|
+
public_suffix (>= 2.0.2, < 6.0)
|
15
15
|
ast (2.4.2)
|
16
16
|
better_errors (2.9.1)
|
17
17
|
coderay (>= 1.0.0)
|
@@ -20,13 +20,14 @@ GEM
|
|
20
20
|
bigdecimal (1.4.4)
|
21
21
|
binding_of_caller (0.8.0)
|
22
22
|
debug_inspector (>= 0.0.1)
|
23
|
-
capybara (3.
|
23
|
+
capybara (3.39.0)
|
24
24
|
addressable
|
25
|
+
matrix
|
25
26
|
mini_mime (>= 0.1.3)
|
26
27
|
nokogiri (~> 1.8)
|
27
28
|
rack (>= 1.6.0)
|
28
29
|
rack-test (>= 0.6.3)
|
29
|
-
regexp_parser (
|
30
|
+
regexp_parser (>= 1.5, < 3.0)
|
30
31
|
xpath (~> 3.2)
|
31
32
|
coderay (1.1.3)
|
32
33
|
concurrent-ruby (1.1.10)
|
@@ -71,7 +72,7 @@ GEM
|
|
71
72
|
dry-logic (~> 0.4, >= 0.4.0)
|
72
73
|
dry-types (~> 0.11.0)
|
73
74
|
erubi (1.10.0)
|
74
|
-
faraday (1.10.
|
75
|
+
faraday (1.10.3)
|
75
76
|
faraday-em_http (~> 1.0)
|
76
77
|
faraday-em_synchrony (~> 1.0)
|
77
78
|
faraday-excon (~> 1.1)
|
@@ -87,8 +88,8 @@ GEM
|
|
87
88
|
faraday-em_synchrony (1.0.0)
|
88
89
|
faraday-excon (1.1.0)
|
89
90
|
faraday-httpclient (1.0.1)
|
90
|
-
faraday-multipart (1.0.
|
91
|
-
multipart-post (
|
91
|
+
faraday-multipart (1.0.4)
|
92
|
+
multipart-post (~> 2)
|
92
93
|
faraday-net_http (1.0.1)
|
93
94
|
faraday-net_http_persistent (1.2.0)
|
94
95
|
faraday-patron (1.0.0)
|
@@ -156,28 +157,31 @@ GEM
|
|
156
157
|
inflecto (0.0.2)
|
157
158
|
mail (2.7.1)
|
158
159
|
mini_mime (>= 0.1.1)
|
160
|
+
matrix (0.4.2)
|
159
161
|
method_source (1.0.0)
|
160
162
|
mini_mime (1.1.2)
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
163
|
+
multipart-post (2.3.0)
|
164
|
+
nio4r (2.5.9)
|
165
|
+
nokogiri (1.14.3-arm64-darwin)
|
166
|
+
racc (~> 1.4)
|
167
|
+
nokogiri (1.14.3-x86_64-linux)
|
168
|
+
racc (~> 1.4)
|
166
169
|
parallel (1.20.1)
|
167
170
|
parser (3.1.2.0)
|
168
171
|
ast (~> 2.4.1)
|
169
172
|
pry (0.14.1)
|
170
173
|
coderay (~> 1.1)
|
171
174
|
method_source (~> 1.0)
|
172
|
-
public_suffix (
|
173
|
-
puma (
|
175
|
+
public_suffix (5.0.1)
|
176
|
+
puma (6.3.1)
|
174
177
|
nio4r (~> 2.0)
|
175
|
-
|
176
|
-
rack
|
177
|
-
|
178
|
+
racc (1.6.2)
|
179
|
+
rack (2.2.7)
|
180
|
+
rack-test (2.1.0)
|
181
|
+
rack (>= 1.3)
|
178
182
|
rainbow (3.1.1)
|
179
183
|
rake (13.0.6)
|
180
|
-
regexp_parser (
|
184
|
+
regexp_parser (2.8.0)
|
181
185
|
rexml (3.2.5)
|
182
186
|
rom (3.3.3)
|
183
187
|
concurrent-ruby (~> 1.0)
|
@@ -245,6 +249,7 @@ GEM
|
|
245
249
|
nokogiri (~> 1.8)
|
246
250
|
|
247
251
|
PLATFORMS
|
252
|
+
arm64-darwin-21
|
248
253
|
x86_64-linux
|
249
254
|
|
250
255
|
DEPENDENCIES
|
@@ -257,7 +262,7 @@ DEPENDENCIES
|
|
257
262
|
hanami-model (~> 1.3)
|
258
263
|
hanami-webconsole
|
259
264
|
pry
|
260
|
-
puma (~>
|
265
|
+
puma (~> 6.3)
|
261
266
|
rake
|
262
267
|
rspec
|
263
268
|
rubocop
|
data/flagsmith.gemspec
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
require File.expand_path('lib/flagsmith/version', __dir__)
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.required_ruby_version = '>=
|
6
|
+
spec.required_ruby_version = '>= 3.0.0'
|
7
7
|
spec.name = 'flagsmith'
|
8
8
|
spec.version = Flagsmith::VERSION
|
9
|
-
spec.authors = ['Tom Stuart', 'Brian Moelk']
|
10
|
-
spec.email = ['tom@solidstategroup.com', 'bmoelk@gmail.com']
|
9
|
+
spec.authors = ['Tom Stuart', 'Brian Moelk', 'Zach Aysan']
|
10
|
+
spec.email = ['tom@solidstategroup.com', 'bmoelk@gmail.com', 'zachaysan@gmail.com']
|
11
11
|
# Specify which files should be added to the gem when it is released.
|
12
12
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
13
13
|
spec.files = Dir.chdir(__dir__) do
|
@@ -27,12 +27,13 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_development_dependency 'bundler'
|
28
28
|
spec.add_development_dependency 'gem-release'
|
29
29
|
spec.add_development_dependency 'pry'
|
30
|
+
spec.add_development_dependency 'pry-byebug'
|
30
31
|
spec.add_development_dependency 'rake'
|
31
32
|
spec.add_development_dependency 'rspec'
|
32
33
|
spec.add_development_dependency 'rubocop'
|
33
34
|
|
34
|
-
spec.add_dependency 'faraday'
|
35
|
-
spec.add_dependency 'faraday_middleware'
|
35
|
+
spec.add_dependency 'faraday', '>= 2.0.1'
|
36
36
|
spec.add_dependency 'faraday-retry'
|
37
37
|
spec.add_dependency 'semantic'
|
38
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
38
39
|
end
|
@@ -40,9 +40,7 @@ module Flagsmith
|
|
40
40
|
multivariate_feature_state_values.sort.each do |multi_fs_value|
|
41
41
|
limit = multi_fs_value.percentage_allocation + start_percentage
|
42
42
|
|
43
|
-
if start_percentage <= percentage_value && percentage_value < limit
|
44
|
-
return multi_fs_value.multivariate_feature_option.value
|
45
|
-
end
|
43
|
+
return multi_fs_value.multivariate_feature_option.value if start_percentage <= percentage_value && percentage_value < limit
|
46
44
|
|
47
45
|
start_percentage = limit
|
48
46
|
end
|
@@ -165,7 +163,7 @@ module Flagsmith
|
|
165
163
|
attr_reader :priority
|
166
164
|
|
167
165
|
def initialize(params = {})
|
168
|
-
@priority = params[:priority]
|
166
|
+
@priority = params[:priority]&.to_i
|
169
167
|
end
|
170
168
|
end
|
171
169
|
end
|
@@ -25,6 +25,7 @@ module Flagsmith
|
|
25
25
|
IS_SET = 'IS_SET'
|
26
26
|
IS_NOT_SET = 'IS_NOT_SET'
|
27
27
|
MODULO = 'MODULO'
|
28
|
+
IN = 'IN'
|
28
29
|
|
29
30
|
CONDITION_OPERATORS = [
|
30
31
|
EQUAL,
|
@@ -37,7 +38,8 @@ module Flagsmith
|
|
37
38
|
NOT_EQUAL,
|
38
39
|
REGEX,
|
39
40
|
PERCENTAGE_SPLIT,
|
40
|
-
MODULO
|
41
|
+
MODULO,
|
42
|
+
IN
|
41
43
|
].freeze
|
42
44
|
end
|
43
45
|
end
|
@@ -36,6 +36,7 @@ module Flagsmith
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
# rubocop:disable Metrics/MethodLength
|
39
40
|
def traits_match_segment_rule(identity_traits, rule, segment_id, identity_id)
|
40
41
|
matching_block = lambda { |condition|
|
41
42
|
traits_match_segment_condition(identity_traits, condition, segment_id, identity_id)
|
@@ -44,23 +45,25 @@ module Flagsmith
|
|
44
45
|
matches_conditions =
|
45
46
|
if rule.conditions&.length&.positive?
|
46
47
|
rule.conditions.send(rule.matching_function, &matching_block)
|
47
|
-
else
|
48
|
+
else
|
49
|
+
true
|
48
50
|
end
|
49
51
|
|
50
52
|
matches_conditions &&
|
51
53
|
rule.rules.all? { |r| traits_match_segment_rule(identity_traits, r, segment_id, identity_id) }
|
52
54
|
end
|
55
|
+
# rubocop:enable Metrics/MethodLength
|
53
56
|
|
54
57
|
def traits_match_segment_condition(identity_traits, condition, segment_id, identity_id)
|
55
58
|
if condition.operator == PERCENTAGE_SPLIT
|
56
|
-
return hashed_percentage_for_object_ids([segment_id,
|
59
|
+
return hashed_percentage_for_object_ids([segment_id,
|
60
|
+
identity_id]) <= condition.value.to_f
|
57
61
|
end
|
58
62
|
|
59
63
|
trait = identity_traits.find { |t| t.key.to_s == condition.property }
|
60
64
|
|
61
|
-
|
62
|
-
|
63
|
-
end
|
65
|
+
return handle_trait_existence_conditions(trait, condition.operator) if [IS_SET,
|
66
|
+
IS_NOT_SET].include?(condition.operator)
|
64
67
|
|
65
68
|
return condition.match_trait_value?(trait.trait_value) if trait
|
66
69
|
|
@@ -56,10 +56,9 @@ module Flagsmith
|
|
56
56
|
|
57
57
|
def match_trait_value?(trait_value)
|
58
58
|
# handle some exceptions
|
59
|
-
if @value.is_a?(String) && @value.match?(/:semver$/)
|
60
|
-
trait_value = Semantic::Version.new(trait_value.gsub(/:semver$/, ''))
|
61
|
-
end
|
59
|
+
trait_value = Semantic::Version.new(trait_value.gsub(/:semver$/, '')) if @value.is_a?(String) && @value.match?(/:semver$/)
|
62
60
|
|
61
|
+
return match_in_value(trait_value) if @operator == IN
|
63
62
|
return match_modulo_value(trait_value) if @operator == MODULO
|
64
63
|
|
65
64
|
type_as_trait_value = format_to_type_of(trait_value)
|
@@ -73,8 +72,8 @@ module Flagsmith
|
|
73
72
|
{
|
74
73
|
'String' => ->(v) { v.to_s },
|
75
74
|
'Semantic::Version' => ->(v) { Semantic::Version.new(v.to_s.gsub(/:semver$/, '')) },
|
76
|
-
'TrueClass' => ->(v) { ['True', 'true', 'TRUE', true, 1, '1'].include?(v)
|
77
|
-
'FalseClass' => ->(v) { ['False', 'false', 'FALSE', false, 0, '0'].include?(v)
|
75
|
+
'TrueClass' => ->(v) { ['True', 'true', 'TRUE', true, 1, '1'].include?(v) },
|
76
|
+
'FalseClass' => ->(v) { !['False', 'false', 'FALSE', false, 0, '0'].include?(v) },
|
78
77
|
'Integer' => ->(v) { v.to_i },
|
79
78
|
'Float' => ->(v) { v.to_f }
|
80
79
|
}[input.class.to_s]
|
@@ -83,11 +82,17 @@ module Flagsmith
|
|
83
82
|
|
84
83
|
def match_modulo_value(trait_value)
|
85
84
|
divisor, remainder = @value.split('|')
|
86
|
-
trait_value.is_a?(Numeric) && trait_value % divisor.to_f == remainder.to_f
|
85
|
+
trait_value.is_a?(Numeric) && trait_value % divisor.to_f == remainder.to_f # rubocop:disable Lint/FloatComparison
|
87
86
|
rescue StandardError
|
88
87
|
false
|
89
88
|
end
|
90
89
|
|
90
|
+
def match_in_value(trait_value)
|
91
|
+
return @value.split(',').include?(trait_value.to_s) if trait_value.is_a?(String) || trait_value.is_a?(Integer)
|
92
|
+
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
91
96
|
class << self
|
92
97
|
def build(json)
|
93
98
|
new(**json.slice(:operator, :value).merge(property: json[:property_]))
|
data/lib/flagsmith/sdk/config.rb
CHANGED
@@ -6,7 +6,8 @@ module Flagsmith
|
|
6
6
|
DEFAULT_API_URL = 'https://edge.api.flagsmith.com/api/v1/'
|
7
7
|
OPTIONS = %i[
|
8
8
|
environment_key api_url custom_headers request_timeout_seconds enable_local_evaluation
|
9
|
-
environment_refresh_interval_seconds retries enable_analytics default_flag_handler
|
9
|
+
environment_refresh_interval_seconds retries enable_analytics default_flag_handler
|
10
|
+
offline_mode offline_handler logger
|
10
11
|
].freeze
|
11
12
|
|
12
13
|
# Available Configs
|
@@ -31,8 +32,12 @@ module Flagsmith
|
|
31
32
|
# API to power flag analytics charts
|
32
33
|
# +default_flag_handler+ - ruby block which will be used in the case where
|
33
34
|
# flags cannot be retrieved from the API or
|
34
|
-
# a non
|
35
|
+
# a non-existent feature is requested.
|
35
36
|
# The searched feature#name will be passed to the block as an argument.
|
37
|
+
# +offline_mode+ - if enabled, uses a locally provided file and
|
38
|
+
# bypasses requests to the api.
|
39
|
+
# +offline_handler+ - A file object that contains a JSON serialization of
|
40
|
+
# the entire environment, project, flags, etc.
|
36
41
|
# +logger+ - Pass your logger, default is Logger.new($stdout)
|
37
42
|
#
|
38
43
|
attr_reader(*OPTIONS)
|
@@ -51,6 +56,10 @@ module Flagsmith
|
|
51
56
|
@enable_analytics
|
52
57
|
end
|
53
58
|
|
59
|
+
def offline_mode?
|
60
|
+
@offline_mode
|
61
|
+
end
|
62
|
+
|
54
63
|
def environment_flags_url
|
55
64
|
'flags/'
|
56
65
|
end
|
@@ -78,13 +87,15 @@ module Flagsmith
|
|
78
87
|
@environment_refresh_interval_seconds = opts.fetch(:environment_refresh_interval_seconds, 60)
|
79
88
|
@enable_analytics = opts.fetch(:enable_analytics, false)
|
80
89
|
@default_flag_handler = opts[:default_flag_handler]
|
90
|
+
@offline_mode = opts.fetch(:offline_mode, false)
|
91
|
+
@offline_handler = opts[:offline_handler]
|
81
92
|
@logger = options.fetch(:logger, Logger.new($stdout).tap { |l| l.level = :debug })
|
82
93
|
end
|
83
94
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
84
95
|
|
85
96
|
class << self
|
86
97
|
def environment_key
|
87
|
-
ENV
|
98
|
+
ENV.fetch('FLAGSMITH_ENVIRONMENT_KEY', nil)
|
88
99
|
end
|
89
100
|
end
|
90
101
|
end
|
@@ -4,32 +4,34 @@ module Flagsmith
|
|
4
4
|
module Flags
|
5
5
|
class NotFound < StandardError; end
|
6
6
|
|
7
|
+
# Base data class for the flag entity
|
7
8
|
class BaseFlag
|
8
9
|
include Comparable
|
9
10
|
|
10
11
|
attr_reader :enabled, :value, :default
|
11
|
-
|
12
|
+
|
12
13
|
def initialize(enabled:, value:, default:)
|
13
14
|
@enabled = enabled
|
14
15
|
@value = value
|
15
16
|
@default = default
|
16
17
|
end
|
17
|
-
|
18
|
+
|
18
19
|
def enabled?
|
19
20
|
enabled
|
20
21
|
end
|
21
|
-
|
22
|
+
|
22
23
|
alias is_default default
|
23
24
|
end
|
24
25
|
|
26
|
+
# Flag class to be used by default handler logic
|
25
27
|
class DefaultFlag < BaseFlag
|
26
28
|
def initialize(enabled:, value:)
|
27
29
|
super(enabled: enabled, value: value, default: true)
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
33
|
+
# 'live' Flag class as returned by API or local evaluation
|
31
34
|
class Flag < BaseFlag
|
32
|
-
|
33
35
|
attr_reader :feature_name, :feature_id
|
34
36
|
|
35
37
|
def initialize(feature_name:, enabled:, value:, feature_id:)
|
@@ -77,15 +79,18 @@ module Flagsmith
|
|
77
79
|
end
|
78
80
|
end
|
79
81
|
|
82
|
+
# Implementation of a class to hold a collection of flags.
|
83
|
+
# Implements methods for working with the list to avoid requesting flags for each feature evaluation.
|
80
84
|
class Collection
|
81
85
|
include Enumerable
|
82
86
|
|
83
|
-
attr_reader :flags, :default_flag_handler, :analytics_processor
|
87
|
+
attr_reader :flags, :default_flag_handler, :analytics_processor, :offline_handler
|
84
88
|
|
85
|
-
def initialize(flags = {}, analytics_processor: nil, default_flag_handler: nil)
|
89
|
+
def initialize(flags = {}, analytics_processor: nil, default_flag_handler: nil, offline_handler: nil)
|
86
90
|
@flags = flags
|
87
91
|
@default_flag_handler = default_flag_handler
|
88
92
|
@analytics_processor = analytics_processor
|
93
|
+
@offline_handler = offline_handler
|
89
94
|
end
|
90
95
|
|
91
96
|
def each(&block)
|
@@ -115,16 +120,27 @@ module Flagsmith
|
|
115
120
|
end
|
116
121
|
alias get_feature_value feature_value
|
117
122
|
|
123
|
+
def get_flag_from_offline_handler(key)
|
124
|
+
@offline_handler.environment.feature_states.each do |feature_state|
|
125
|
+
return Flag.from_feature_state_model(feature_state, nil) if key == Flagsmith::Flags::Collection.normalize_key(feature_state.feature.name)
|
126
|
+
end
|
127
|
+
raise Flagsmith::Flags::NotFound,
|
128
|
+
"Feature does not exist: #{key}, offline_handler did not find a flag in this case."
|
129
|
+
end
|
130
|
+
|
118
131
|
# Get a specific flag given the feature name.
|
119
132
|
# :param feature_name: the name of the feature to retrieve the flag for.
|
120
133
|
# :return: BaseFlag object.
|
121
134
|
# :raises FlagsmithClientError: if feature doesn't exist
|
122
135
|
def get_flag(feature_name)
|
123
136
|
key = Flagsmith::Flags::Collection.normalize_key(feature_name)
|
137
|
+
|
124
138
|
flag = flags.fetch(key)
|
125
139
|
@analytics_processor.track_feature(flag.feature_name) if @analytics_processor && flag.feature_id
|
126
140
|
flag
|
127
141
|
rescue KeyError
|
142
|
+
return get_flag_from_offline_handler(key) if @offline_handler
|
143
|
+
|
128
144
|
return @default_flag_handler.call(feature_name) if @default_flag_handler
|
129
145
|
|
130
146
|
raise Flagsmith::Flags::NotFound,
|
@@ -159,7 +175,7 @@ module Flagsmith
|
|
159
175
|
def from_feature_state_models(feature_states, identity_id: nil, **args)
|
160
176
|
to_flag_object = lambda { |feature_state, acc|
|
161
177
|
acc[normalize_key(feature_state.feature.name)] =
|
162
|
-
|
178
|
+
Flagsmith::Flags::Flag.from_feature_state_model(feature_state, identity_id)
|
163
179
|
}
|
164
180
|
|
165
181
|
new(
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flagsmith
|
4
|
+
module OfflineHandlers
|
5
|
+
# Provides the offline_handler to the Flagsmith::Client.
|
6
|
+
class LocalFileHandler
|
7
|
+
attr_reader :environment
|
8
|
+
|
9
|
+
def initialize(environment_document_path)
|
10
|
+
environment_file = File.open(environment_document_path)
|
11
|
+
|
12
|
+
data = JSON.parse(environment_file.read, symbolize_names: true)
|
13
|
+
@environment = Flagsmith::Engine::Environment.build(data)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/flagsmith/version.rb
CHANGED
data/lib/flagsmith.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'pry'
|
4
|
+
require 'pry-byebug'
|
5
|
+
|
3
6
|
require 'faraday'
|
4
7
|
require 'faraday/retry'
|
5
|
-
require 'faraday_middleware'
|
6
8
|
|
7
9
|
# Hash#slice was added in ruby version 2.5
|
8
10
|
# This is the patch to use slice in earler versions
|
@@ -16,13 +18,14 @@ require 'flagsmith/sdk/intervals'
|
|
16
18
|
require 'flagsmith/sdk/pooling_manager'
|
17
19
|
require 'flagsmith/sdk/models/flags'
|
18
20
|
require 'flagsmith/sdk/models/segments'
|
21
|
+
require 'flagsmith/sdk/offline_handlers'
|
19
22
|
|
20
23
|
require 'flagsmith/engine/core'
|
21
24
|
|
22
25
|
# no-doc
|
23
26
|
module Flagsmith
|
24
27
|
# Ruby client for flagsmith.com
|
25
|
-
class Client
|
28
|
+
class Client # rubocop:disable Metrics/ClassLength
|
26
29
|
extend Forwardable
|
27
30
|
# A Flagsmith client.
|
28
31
|
#
|
@@ -44,7 +47,9 @@ module Flagsmith
|
|
44
47
|
# Available Configs.
|
45
48
|
#
|
46
49
|
# :environment_key, :api_url, :custom_headers, :request_timeout_seconds, :enable_local_evaluation,
|
47
|
-
# :environment_refresh_interval_seconds, :retries, :enable_analytics, :default_flag_handler
|
50
|
+
# :environment_refresh_interval_seconds, :retries, :enable_analytics, :default_flag_handler,
|
51
|
+
# :offline_mode, :offline_handler
|
52
|
+
#
|
48
53
|
# You can see full description in the Flagsmith::Config
|
49
54
|
|
50
55
|
attr_reader :config, :environment
|
@@ -55,10 +60,24 @@ module Flagsmith
|
|
55
60
|
@_mutex = Mutex.new
|
56
61
|
@config = Flagsmith::Config.new(config)
|
57
62
|
|
63
|
+
validate_offline_mode!
|
64
|
+
|
58
65
|
api_client
|
59
66
|
analytics_processor
|
60
67
|
environment_data_polling_manager
|
61
68
|
engine
|
69
|
+
load_offline_handler
|
70
|
+
end
|
71
|
+
|
72
|
+
def validate_offline_mode!
|
73
|
+
if @config.offline_mode? && !@config.offline_handler
|
74
|
+
raise Flagsmith::ClientError,
|
75
|
+
'The offline_mode config param requires a matching offline_handler.'
|
76
|
+
end
|
77
|
+
return unless @config.offline_handler && @config.default_flag_handler
|
78
|
+
|
79
|
+
raise Flagsmith::ClientError,
|
80
|
+
'Cannot use offline_handler and default_flag_handler at the same time.'
|
62
81
|
end
|
63
82
|
|
64
83
|
def api_client
|
@@ -79,6 +98,10 @@ module Flagsmith
|
|
79
98
|
)
|
80
99
|
end
|
81
100
|
|
101
|
+
def load_offline_handler
|
102
|
+
@environment = offline_handler.environment if offline_handler
|
103
|
+
end
|
104
|
+
|
82
105
|
def environment_data_polling_manager
|
83
106
|
return nil unless @config.local_evaluation?
|
84
107
|
|
@@ -103,7 +126,7 @@ module Flagsmith
|
|
103
126
|
# Get all the default for flags for the current environment.
|
104
127
|
# @returns Flags object holding all the flags for the current environment.
|
105
128
|
def get_environment_flags # rubocop:disable Naming/AccessorMethodName
|
106
|
-
return environment_flags_from_document if @config.local_evaluation?
|
129
|
+
return environment_flags_from_document if @config.local_evaluation? || @config.offline_mode
|
107
130
|
|
108
131
|
environment_flags_from_api
|
109
132
|
end
|
@@ -153,13 +176,13 @@ module Flagsmith
|
|
153
176
|
|
154
177
|
def get_identity_segments(identifier, traits = {})
|
155
178
|
unless environment
|
156
|
-
raise Flagsmith::ClientError,
|
157
|
-
|
179
|
+
raise Flagsmith::ClientError,
|
180
|
+
'Local evaluation or offline handler is required to obtain identity segments.'
|
158
181
|
end
|
159
182
|
|
160
183
|
identity_model = build_identity_model(identifier, traits)
|
161
184
|
segment_models = engine.get_identity_segments(environment, identity_model)
|
162
|
-
|
185
|
+
segment_models.map { |sm| Flagsmith::Segments::Segment.new(id: sm.id, name: sm.name) }.compact
|
163
186
|
end
|
164
187
|
|
165
188
|
private
|
@@ -168,7 +191,8 @@ module Flagsmith
|
|
168
191
|
Flagsmith::Flags::Collection.from_feature_state_models(
|
169
192
|
engine.get_environment_feature_states(environment),
|
170
193
|
analytics_processor: analytics_processor,
|
171
|
-
default_flag_handler: default_flag_handler
|
194
|
+
default_flag_handler: default_flag_handler,
|
195
|
+
offline_handler: offline_handler
|
172
196
|
)
|
173
197
|
end
|
174
198
|
|
@@ -178,45 +202,80 @@ module Flagsmith
|
|
178
202
|
Flagsmith::Flags::Collection.from_feature_state_models(
|
179
203
|
engine.get_identity_feature_states(environment, identity_model),
|
180
204
|
analytics_processor: analytics_processor,
|
181
|
-
default_flag_handler: default_flag_handler
|
205
|
+
default_flag_handler: default_flag_handler,
|
206
|
+
offline_handler: offline_handler
|
182
207
|
)
|
183
208
|
end
|
184
209
|
|
210
|
+
# rubocop:disable Metrics/MethodLength
|
185
211
|
def environment_flags_from_api
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
212
|
+
if offline_handler
|
213
|
+
begin
|
214
|
+
process_environment_flags_from_api
|
215
|
+
rescue StandardError
|
216
|
+
environment_flags_from_document
|
217
|
+
end
|
218
|
+
else
|
219
|
+
begin
|
220
|
+
process_environment_flags_from_api
|
221
|
+
rescue StandardError
|
222
|
+
if default_flag_handler
|
223
|
+
return Flagsmith::Flags::Collection.new(
|
224
|
+
{},
|
225
|
+
default_flag_handler: default_flag_handler
|
226
|
+
)
|
227
|
+
end
|
228
|
+
raise
|
229
|
+
end
|
194
230
|
end
|
195
231
|
end
|
232
|
+
# rubocop:enable Metrics/MethodLength
|
233
|
+
|
234
|
+
def process_environment_flags_from_api
|
235
|
+
api_flags = api_client.get(@config.environment_flags_url).body
|
236
|
+
api_flags = api_flags.select { |flag| flag[:feature_segment].nil? }
|
237
|
+
Flagsmith::Flags::Collection.from_api(
|
238
|
+
api_flags,
|
239
|
+
analytics_processor: analytics_processor,
|
240
|
+
default_flag_handler: default_flag_handler,
|
241
|
+
offline_handler: offline_handler
|
242
|
+
)
|
243
|
+
end
|
196
244
|
|
245
|
+
# rubocop:disable Metrics/MethodLength
|
197
246
|
def get_identity_flags_from_api(identifier, traits = {})
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
247
|
+
if offline_handler
|
248
|
+
begin
|
249
|
+
process_identity_flags_from_api(identifier, traits)
|
250
|
+
rescue StandardError
|
251
|
+
get_identity_flags_from_document(identifier, traits)
|
252
|
+
end
|
253
|
+
else
|
254
|
+
begin
|
255
|
+
process_identity_flags_from_api(identifier, traits)
|
256
|
+
rescue StandardError
|
257
|
+
if default_flag_handler
|
258
|
+
return Flagsmith::Flags::Collection.new(
|
259
|
+
{},
|
260
|
+
default_flag_handler: default_flag_handler
|
261
|
+
)
|
262
|
+
end
|
263
|
+
raise
|
264
|
+
end
|
207
265
|
end
|
208
266
|
end
|
267
|
+
# rubocop:enable Metrics/MethodLength
|
209
268
|
|
210
|
-
def
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
269
|
+
def process_identity_flags_from_api(identifier, traits = {})
|
270
|
+
data = generate_identities_data(identifier, traits)
|
271
|
+
json_response = api_client.post(@config.identities_url, data.to_json).body
|
272
|
+
|
273
|
+
Flagsmith::Flags::Collection.from_api(
|
274
|
+
json_response[:flags],
|
275
|
+
analytics_processor: analytics_processor,
|
276
|
+
default_flag_handler: default_flag_handler,
|
277
|
+
offline_handler: offline_handler
|
278
|
+
)
|
220
279
|
end
|
221
280
|
|
222
281
|
def build_identity_model(identifier, traits = {})
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flagsmith
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Stuart
|
8
8
|
- Brian Moelk
|
9
|
-
|
9
|
+
- Zach Aysan
|
10
|
+
autorequire:
|
10
11
|
bindir: exe
|
11
12
|
cert_chain: []
|
12
|
-
date:
|
13
|
+
date: 2024-01-30 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: bundler
|
@@ -54,7 +55,7 @@ dependencies:
|
|
54
55
|
- !ruby/object:Gem::Version
|
55
56
|
version: '0'
|
56
57
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
58
|
+
name: pry-byebug
|
58
59
|
requirement: !ruby/object:Gem::Requirement
|
59
60
|
requirements:
|
60
61
|
- - ">="
|
@@ -68,7 +69,7 @@ dependencies:
|
|
68
69
|
- !ruby/object:Gem::Version
|
69
70
|
version: '0'
|
70
71
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
72
|
+
name: rake
|
72
73
|
requirement: !ruby/object:Gem::Requirement
|
73
74
|
requirements:
|
74
75
|
- - ">="
|
@@ -82,7 +83,7 @@ dependencies:
|
|
82
83
|
- !ruby/object:Gem::Version
|
83
84
|
version: '0'
|
84
85
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
86
|
+
name: rspec
|
86
87
|
requirement: !ruby/object:Gem::Requirement
|
87
88
|
requirements:
|
88
89
|
- - ">="
|
@@ -96,13 +97,13 @@ dependencies:
|
|
96
97
|
- !ruby/object:Gem::Version
|
97
98
|
version: '0'
|
98
99
|
- !ruby/object:Gem::Dependency
|
99
|
-
name:
|
100
|
+
name: rubocop
|
100
101
|
requirement: !ruby/object:Gem::Requirement
|
101
102
|
requirements:
|
102
103
|
- - ">="
|
103
104
|
- !ruby/object:Gem::Version
|
104
105
|
version: '0'
|
105
|
-
type: :
|
106
|
+
type: :development
|
106
107
|
prerelease: false
|
107
108
|
version_requirements: !ruby/object:Gem::Requirement
|
108
109
|
requirements:
|
@@ -110,19 +111,19 @@ dependencies:
|
|
110
111
|
- !ruby/object:Gem::Version
|
111
112
|
version: '0'
|
112
113
|
- !ruby/object:Gem::Dependency
|
113
|
-
name:
|
114
|
+
name: faraday
|
114
115
|
requirement: !ruby/object:Gem::Requirement
|
115
116
|
requirements:
|
116
117
|
- - ">="
|
117
118
|
- !ruby/object:Gem::Version
|
118
|
-
version:
|
119
|
+
version: 2.0.1
|
119
120
|
type: :runtime
|
120
121
|
prerelease: false
|
121
122
|
version_requirements: !ruby/object:Gem::Requirement
|
122
123
|
requirements:
|
123
124
|
- - ">="
|
124
125
|
- !ruby/object:Gem::Version
|
125
|
-
version:
|
126
|
+
version: 2.0.1
|
126
127
|
- !ruby/object:Gem::Dependency
|
127
128
|
name: faraday-retry
|
128
129
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,6 +157,7 @@ description: Ruby Client for Flagsmith. Ship features with confidence using feat
|
|
156
157
|
email:
|
157
158
|
- tom@solidstategroup.com
|
158
159
|
- bmoelk@gmail.com
|
160
|
+
- zachaysan@gmail.com
|
159
161
|
executables: []
|
160
162
|
extensions: []
|
161
163
|
extra_rdoc_files: []
|
@@ -224,12 +226,14 @@ files:
|
|
224
226
|
- lib/flagsmith/sdk/intervals.rb
|
225
227
|
- lib/flagsmith/sdk/models/flags.rb
|
226
228
|
- lib/flagsmith/sdk/models/segments.rb
|
229
|
+
- lib/flagsmith/sdk/offline_handlers.rb
|
227
230
|
- lib/flagsmith/sdk/pooling_manager.rb
|
228
231
|
- lib/flagsmith/version.rb
|
229
232
|
homepage: https://flagsmith.com
|
230
233
|
licenses: []
|
231
|
-
metadata:
|
232
|
-
|
234
|
+
metadata:
|
235
|
+
rubygems_mfa_required: 'true'
|
236
|
+
post_install_message:
|
233
237
|
rdoc_options: []
|
234
238
|
require_paths:
|
235
239
|
- lib
|
@@ -237,15 +241,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
237
241
|
requirements:
|
238
242
|
- - ">="
|
239
243
|
- !ruby/object:Gem::Version
|
240
|
-
version:
|
244
|
+
version: 3.0.0
|
241
245
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
242
246
|
requirements:
|
243
247
|
- - ">="
|
244
248
|
- !ruby/object:Gem::Version
|
245
249
|
version: '0'
|
246
250
|
requirements: []
|
247
|
-
rubygems_version: 3.3.
|
248
|
-
signing_key:
|
251
|
+
rubygems_version: 3.3.26
|
252
|
+
signing_key:
|
249
253
|
specification_version: 4
|
250
254
|
summary: Flagsmith - Ship features with confidence
|
251
255
|
test_files: []
|