flagsmith 4.1.1 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -4
- data/lib/flagsmith/engine/segments/models.rb +7 -7
- data/lib/flagsmith/sdk/config.rb +14 -1
- data/lib/flagsmith/sdk/models/flags.rb +1 -1
- data/lib/flagsmith/sdk/realtime_client.rb +63 -0
- data/lib/flagsmith/version.rb +1 -1
- data/lib/flagsmith.rb +36 -9
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d20d6b2aee787d2d25478b00eea5f1e06ad4f345097102b1381faf7455774d2
|
4
|
+
data.tar.gz: c782d13647ae7c5905e0a0b46cb0085a395adbb71c95101e0b424b719e2b1a52
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 135da46a33bde44fb78a54c769dc25caab7566b18c68a870306ef645cad2de6de75ee599af0fd851fa3a3562bae92bec57e57089f31fa4f9cb282fadf47df01a
|
7
|
+
data.tar.gz: ead1e0df360be3a4637892f8fec1c10415967a072604b42dc113ee5034a06a235294300de59014ab35c829aceb46acca9454f29138e02dd610fd97abaadae293
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
flagsmith (4.
|
4
|
+
flagsmith (4.3.0)
|
5
5
|
faraday (>= 2.0.1)
|
6
6
|
faraday-retry
|
7
7
|
semantic
|
@@ -39,8 +39,7 @@ GEM
|
|
39
39
|
rainbow (3.1.1)
|
40
40
|
rake (13.1.0)
|
41
41
|
regexp_parser (2.9.0)
|
42
|
-
rexml (3.
|
43
|
-
strscan (>= 3.0.9)
|
42
|
+
rexml (3.3.9)
|
44
43
|
rspec (3.12.0)
|
45
44
|
rspec-core (~> 3.12.0)
|
46
45
|
rspec-expectations (~> 3.12.0)
|
@@ -69,7 +68,6 @@ GEM
|
|
69
68
|
parser (>= 3.2.1.0)
|
70
69
|
ruby-progressbar (1.13.0)
|
71
70
|
semantic (1.6.1)
|
72
|
-
strscan (3.1.0)
|
73
71
|
unicode-display_width (2.5.0)
|
74
72
|
uri (0.13.0)
|
75
73
|
|
@@ -37,15 +37,15 @@ module Flagsmith
|
|
37
37
|
|
38
38
|
MATCHING_FUNCTIONS = {
|
39
39
|
EQUAL => ->(other_value, self_value) { other_value == self_value },
|
40
|
-
GREATER_THAN => ->(other_value, self_value) { other_value > self_value },
|
41
|
-
GREATER_THAN_INCLUSIVE => ->(other_value, self_value) { other_value >= self_value },
|
42
|
-
LESS_THAN => ->(other_value, self_value) { other_value < self_value },
|
43
|
-
LESS_THAN_INCLUSIVE => ->(other_value, self_value) { other_value <= self_value },
|
40
|
+
GREATER_THAN => ->(other_value, self_value) { (other_value || false) && other_value > self_value },
|
41
|
+
GREATER_THAN_INCLUSIVE => ->(other_value, self_value) { (other_value || false) && other_value >= self_value },
|
42
|
+
LESS_THAN => ->(other_value, self_value) { (other_value || false) && other_value < self_value },
|
43
|
+
LESS_THAN_INCLUSIVE => ->(other_value, self_value) { (other_value || false) && other_value <= self_value },
|
44
44
|
NOT_EQUAL => ->(other_value, self_value) { other_value != self_value },
|
45
|
-
CONTAINS => ->(other_value, self_value) { other_value.include?
|
45
|
+
CONTAINS => ->(other_value, self_value) { (other_value || false) && other_value.include?(self_value) },
|
46
46
|
|
47
|
-
NOT_CONTAINS => ->(other_value, self_value) { !other_value.include?
|
48
|
-
REGEX => ->(other_value, self_value) { other_value.match?
|
47
|
+
NOT_CONTAINS => ->(other_value, self_value) { (other_value || false) && !other_value.include?(self_value) },
|
48
|
+
REGEX => ->(other_value, self_value) { (other_value || false) && other_value.match?(self_value) }
|
49
49
|
}.freeze
|
50
50
|
|
51
51
|
def initialize(operator:, value:, property: nil)
|
data/lib/flagsmith/sdk/config.rb
CHANGED
@@ -4,10 +4,13 @@ module Flagsmith
|
|
4
4
|
# Config options shared around Engine
|
5
5
|
class Config
|
6
6
|
DEFAULT_API_URL = 'https://edge.api.flagsmith.com/api/v1/'
|
7
|
+
DEFAULT_REALTIME_API_URL = 'https://realtime.flagsmith.com/'
|
8
|
+
|
7
9
|
OPTIONS = %i[
|
8
10
|
environment_key api_url custom_headers request_timeout_seconds enable_local_evaluation
|
9
11
|
environment_refresh_interval_seconds retries enable_analytics default_flag_handler
|
10
|
-
offline_mode offline_handler polling_manager_failure_limit
|
12
|
+
offline_mode offline_handler polling_manager_failure_limit
|
13
|
+
realtime_api_url enable_realtime_updates logger
|
11
14
|
].freeze
|
12
15
|
|
13
16
|
# Available Configs
|
@@ -40,6 +43,9 @@ module Flagsmith
|
|
40
43
|
# the entire environment, project, flags, etc.
|
41
44
|
# +polling_manager_failure_limit+ - An integer to control how long to suppress errors in
|
42
45
|
# the polling manager for local evaluation mode.
|
46
|
+
# +realtime_api_url+ - Override the realtime api URL to communicate with a
|
47
|
+
# non-standard realtime endpoint.
|
48
|
+
# +enable_realtime_updates+ - A boolean to enable realtime updates.
|
43
49
|
# +logger+ - Pass your logger, default is Logger.new($stdout)
|
44
50
|
#
|
45
51
|
attr_reader(*OPTIONS)
|
@@ -62,6 +68,10 @@ module Flagsmith
|
|
62
68
|
@offline_mode
|
63
69
|
end
|
64
70
|
|
71
|
+
def realtime_mode?
|
72
|
+
@enable_realtime_updates
|
73
|
+
end
|
74
|
+
|
65
75
|
def environment_flags_url
|
66
76
|
'flags/'
|
67
77
|
end
|
@@ -92,6 +102,9 @@ module Flagsmith
|
|
92
102
|
@offline_mode = opts.fetch(:offline_mode, false)
|
93
103
|
@offline_handler = opts[:offline_handler]
|
94
104
|
@polling_manager_failure_limit = opts.fetch(:polling_manager_failure_limit, 10)
|
105
|
+
@realtime_api_url = opts.fetch(:realtime_api_url, Flagsmith::Config::DEFAULT_REALTIME_API_URL)
|
106
|
+
@realtime_api_url << '/' unless @realtime_api_url.end_with? '/'
|
107
|
+
@enable_realtime_updates = opts.fetch(:enable_realtime_updates, false)
|
95
108
|
@logger = options.fetch(:logger, Logger.new($stdout).tap { |l| l.level = :debug })
|
96
109
|
end
|
97
110
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
@@ -71,7 +71,7 @@ module Flagsmith
|
|
71
71
|
def from_api(json_flag_data)
|
72
72
|
new(
|
73
73
|
enabled: json_flag_data[:enabled],
|
74
|
-
value: json_flag_data
|
74
|
+
value: json_flag_data.fetch(:feature_state_value) { json_flag_data[:value] },
|
75
75
|
feature_name: json_flag_data.dig(:feature, :name),
|
76
76
|
feature_id: json_flag_data.dig(:feature, :id)
|
77
77
|
)
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'faraday'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Flagsmith
|
8
|
+
# Ruby client for realtime access to flagsmith.com
|
9
|
+
class RealtimeClient
|
10
|
+
attr_accessor :running
|
11
|
+
|
12
|
+
def initialize(config)
|
13
|
+
@config = config
|
14
|
+
@thread = nil
|
15
|
+
@running = false
|
16
|
+
@main = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def endpoint
|
20
|
+
"#{@config.realtime_api_url}sse/environments/#{@main.environment.api_key}/stream"
|
21
|
+
end
|
22
|
+
|
23
|
+
def listen(main, remaining_attempts: Float::INFINITY, retry_interval: 0.5) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength
|
24
|
+
last_updated_at = 0
|
25
|
+
@main = main
|
26
|
+
@running = true
|
27
|
+
@thread = Thread.new do
|
28
|
+
while @running && remaining_attempts.positive?
|
29
|
+
remaining_attempts -= 1
|
30
|
+
@config.logger.warn 'Beginning to pull down realtime endpoint'
|
31
|
+
begin
|
32
|
+
sleep retry_interval
|
33
|
+
# Open connection to SSE endpoint
|
34
|
+
Faraday.new(url: endpoint).get do |req|
|
35
|
+
req.options.timeout = nil # Keep connection alive indefinitely
|
36
|
+
req.options.open_timeout = 10
|
37
|
+
end.body.each_line do |line| # rubocop:disable Style/MultilineBlockChain
|
38
|
+
# SSE protocol: Skip non-event lines
|
39
|
+
next if line.strip.empty? || line.start_with?(':')
|
40
|
+
|
41
|
+
# Parse SSE fields
|
42
|
+
next unless line.start_with?('data: ')
|
43
|
+
|
44
|
+
data = JSON.parse(line[6..].strip)
|
45
|
+
updated_at = data['updated_at']
|
46
|
+
next unless updated_at > last_updated_at
|
47
|
+
|
48
|
+
@config.logger.info "Realtime updating environment from #{last_updated_at} to #{updated_at}"
|
49
|
+
@main.update_environment
|
50
|
+
last_updated_at = updated_at
|
51
|
+
end
|
52
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
53
|
+
@config.logger.warn "Connection failed: #{e.message}. Retrying in #{retry_interval} seconds..."
|
54
|
+
rescue StandardError => e
|
55
|
+
@config.logger.error "Error: #{e.message}. Retrying in #{retry_interval} seconds..."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
@running = false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/flagsmith/version.rb
CHANGED
data/lib/flagsmith.rb
CHANGED
@@ -16,6 +16,7 @@ require 'flagsmith/sdk/pooling_manager'
|
|
16
16
|
require 'flagsmith/sdk/models/flags'
|
17
17
|
require 'flagsmith/sdk/models/segments'
|
18
18
|
require 'flagsmith/sdk/offline_handlers'
|
19
|
+
require 'flagsmith/sdk/realtime_client'
|
19
20
|
|
20
21
|
require 'flagsmith/engine/core'
|
21
22
|
|
@@ -46,6 +47,7 @@ module Flagsmith
|
|
46
47
|
# :environment_key, :api_url, :custom_headers, :request_timeout_seconds, :enable_local_evaluation,
|
47
48
|
# :environment_refresh_interval_seconds, :retries, :enable_analytics, :default_flag_handler,
|
48
49
|
# :offline_mode, :offline_handler, :polling_manager_failure_limit
|
50
|
+
# :realtime_api_url, :enable_realtime_updates, :logger
|
49
51
|
#
|
50
52
|
# You can see full description in the Flagsmith::Config
|
51
53
|
|
@@ -59,6 +61,7 @@ module Flagsmith
|
|
59
61
|
@identity_overrides_by_identifier = {}
|
60
62
|
|
61
63
|
validate_offline_mode!
|
64
|
+
validate_realtime_mode!
|
62
65
|
|
63
66
|
api_client
|
64
67
|
analytics_processor
|
@@ -78,10 +81,21 @@ module Flagsmith
|
|
78
81
|
'Cannot use offline_handler and default_flag_handler at the same time.'
|
79
82
|
end
|
80
83
|
|
84
|
+
def validate_realtime_mode!
|
85
|
+
return unless @config.realtime_mode? && !@config.local_evaluation?
|
86
|
+
|
87
|
+
raise Flagsmith::ClientError,
|
88
|
+
'The enable_realtime_updates config param requires a matching enable_local_evaluation param.'
|
89
|
+
end
|
90
|
+
|
81
91
|
def api_client
|
82
92
|
@api_client ||= Flagsmith::ApiClient.new(@config)
|
83
93
|
end
|
84
94
|
|
95
|
+
def realtime_client
|
96
|
+
@realtime_client ||= Flagsmith::RealtimeClient.new(@config)
|
97
|
+
end
|
98
|
+
|
85
99
|
def engine
|
86
100
|
@engine ||= Flagsmith::Engine::Engine.new
|
87
101
|
end
|
@@ -104,6 +118,14 @@ module Flagsmith
|
|
104
118
|
def environment_data_polling_manager
|
105
119
|
return nil unless @config.local_evaluation?
|
106
120
|
|
121
|
+
# Bypass the environment data polling manager if realtime
|
122
|
+
# is present in the configuration.
|
123
|
+
if @config.realtime_mode?
|
124
|
+
update_environment
|
125
|
+
realtime_client.listen self unless realtime_client.running
|
126
|
+
return
|
127
|
+
end
|
128
|
+
|
107
129
|
update_environment if @environment_data_polling_manager.nil?
|
108
130
|
|
109
131
|
@environment_data_polling_manager ||= Flagsmith::EnvironmentDataPollingManager.new(
|
@@ -148,11 +170,13 @@ module Flagsmith
|
|
148
170
|
# environment, e.g. email address, username, uuid
|
149
171
|
# traits { key => value } is a dictionary of traits to add / update on the identity in
|
150
172
|
# Flagsmith, e.g. { "num_orders": 10 }
|
173
|
+
# in lieu of a trait value, a trait coniguration dictionary can be provided,
|
174
|
+
# e.g. { "num_orders": { "value": 10, "transient": true } }
|
151
175
|
# returns Flags object holding all the flags for the given identity.
|
152
|
-
def get_identity_flags(identifier, **traits)
|
176
|
+
def get_identity_flags(identifier, transient = false, **traits) # rubocop:disable Style/OptionalBooleanParameter
|
153
177
|
return get_identity_flags_from_document(identifier, traits) if environment
|
154
178
|
|
155
|
-
get_identity_flags_from_api(identifier, traits)
|
179
|
+
get_identity_flags_from_api(identifier, traits, transient)
|
156
180
|
end
|
157
181
|
|
158
182
|
def feature_enabled?(feature_name, default: false)
|
@@ -253,16 +277,16 @@ module Flagsmith
|
|
253
277
|
end
|
254
278
|
|
255
279
|
# rubocop:disable Metrics/MethodLength
|
256
|
-
def get_identity_flags_from_api(identifier, traits
|
280
|
+
def get_identity_flags_from_api(identifier, traits, transient)
|
257
281
|
if offline_handler
|
258
282
|
begin
|
259
|
-
process_identity_flags_from_api(identifier, traits)
|
283
|
+
process_identity_flags_from_api(identifier, traits, transient)
|
260
284
|
rescue StandardError
|
261
285
|
get_identity_flags_from_document(identifier, traits)
|
262
286
|
end
|
263
287
|
else
|
264
288
|
begin
|
265
|
-
process_identity_flags_from_api(identifier, traits)
|
289
|
+
process_identity_flags_from_api(identifier, traits, transient)
|
266
290
|
rescue StandardError
|
267
291
|
if default_flag_handler
|
268
292
|
return Flagsmith::Flags::Collection.new(
|
@@ -276,8 +300,8 @@ module Flagsmith
|
|
276
300
|
end
|
277
301
|
# rubocop:enable Metrics/MethodLength
|
278
302
|
|
279
|
-
def process_identity_flags_from_api(identifier, traits
|
280
|
-
data = generate_identities_data(identifier, traits)
|
303
|
+
def process_identity_flags_from_api(identifier, traits, transient)
|
304
|
+
data = generate_identities_data(identifier, traits, transient)
|
281
305
|
json_response = api_client.post(@config.identities_url, data.to_json).body
|
282
306
|
|
283
307
|
Flagsmith::Flags::Collection.from_api(
|
@@ -311,10 +335,13 @@ module Flagsmith
|
|
311
335
|
end
|
312
336
|
# rubocop:enable Metrics/MethodLength
|
313
337
|
|
314
|
-
def generate_identities_data(identifier, traits
|
338
|
+
def generate_identities_data(identifier, traits, transient)
|
315
339
|
{
|
316
340
|
identifier: identifier,
|
317
|
-
|
341
|
+
transient: transient,
|
342
|
+
traits: traits.map do |key, value|
|
343
|
+
value.is_a?(Hash) ? { trait_key: key, trait_value: value[:value], transient: value[:transient] || false } : { trait_key: key, trait_value: value }
|
344
|
+
end
|
318
345
|
}
|
319
346
|
end
|
320
347
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flagsmith
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Stuart
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-12-06 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -193,6 +193,7 @@ files:
|
|
193
193
|
- lib/flagsmith/sdk/models/segments.rb
|
194
194
|
- lib/flagsmith/sdk/offline_handlers.rb
|
195
195
|
- lib/flagsmith/sdk/pooling_manager.rb
|
196
|
+
- lib/flagsmith/sdk/realtime_client.rb
|
196
197
|
- lib/flagsmith/version.rb
|
197
198
|
homepage: https://flagsmith.com
|
198
199
|
licenses: []
|