posthog-ruby 2.4.3 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/posthog/client.rb +5 -2
- data/lib/posthog/defaults.rb +4 -0
- data/lib/posthog/feature_flags.rb +39 -30
- data/lib/posthog/field_parser.rb +5 -1
- data/lib/posthog/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 654b547b68b6d0ebcfd4ef3523fa46d32a825d01485138e80443cf494d295f4a
|
4
|
+
data.tar.gz: 9ae165ba631600e2ee0f59ce6d80fe3defe5b2c42f8d99a7c6b30393b73d2760
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ce3e4037abd6e861a0f38a9e51ed4c54ae84622b939768139e4bcdc215f32e69b7dabb1c144e291d8e1f832d766c188a01ce5076a70e0dba03add31e4f64587
|
7
|
+
data.tar.gz: 90b385f84d8047eed291d280e07eef79d8c28c217e89bb53cc621c78a6457bf7542780aeee63b48fca53085ba13d625136ac50a8ea9decd47cfc88a999b5a97b
|
data/lib/posthog/client.rb
CHANGED
@@ -23,6 +23,7 @@ class PostHog
|
|
23
23
|
# @option opts [Proc] :on_error Handles error calls from the API.
|
24
24
|
# @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://app.posthog.com`
|
25
25
|
# @option opts [Integer] :feature_flags_polling_interval How often to poll for feature flag definition changes. Measured in seconds, defaults to 30.
|
26
|
+
# @option opts [Integer] :feature_flag_request_timeout_seconds How long to wait for feature flag evaluation. Measured in seconds, defaults to 3.
|
26
27
|
def initialize(opts = {})
|
27
28
|
symbolize_keys!(opts)
|
28
29
|
|
@@ -48,7 +49,9 @@ class PostHog
|
|
48
49
|
opts[:feature_flags_polling_interval],
|
49
50
|
opts[:personal_api_key],
|
50
51
|
@api_key,
|
51
|
-
opts[:host]
|
52
|
+
opts[:host],
|
53
|
+
opts[:feature_flag_request_timeout_seconds] || Defaults::FeatureFlags::FLAG_REQUEST_TIMEOUT_SECONDS,
|
54
|
+
opts[:on_error]
|
52
55
|
)
|
53
56
|
|
54
57
|
@distinct_id_has_sent_flag_calls = SizeLimitedHash.new(Defaults::MAX_HASH_SIZE) { |hash, key| hash[key] = Array.new }
|
@@ -90,7 +93,7 @@ class PostHog
|
|
90
93
|
symbolize_keys! attrs
|
91
94
|
|
92
95
|
if attrs[:send_feature_flags]
|
93
|
-
feature_variants = @feature_flags_poller.
|
96
|
+
feature_variants = @feature_flags_poller.get_feature_variants(attrs[:distinct_id], attrs[:groups] || {})
|
94
97
|
|
95
98
|
attrs[:feature_variants] = feature_variants
|
96
99
|
end
|
data/lib/posthog/defaults.rb
CHANGED
@@ -14,7 +14,7 @@ class PostHog
|
|
14
14
|
include PostHog::Logging
|
15
15
|
include PostHog::Utils
|
16
16
|
|
17
|
-
def initialize(polling_interval, personal_api_key, project_api_key, host)
|
17
|
+
def initialize(polling_interval, personal_api_key, project_api_key, host, feature_flag_request_timeout_seconds, on_error = nil)
|
18
18
|
@polling_interval = polling_interval || 30
|
19
19
|
@personal_api_key = personal_api_key
|
20
20
|
@project_api_key = project_api_key
|
@@ -23,6 +23,8 @@ class PostHog
|
|
23
23
|
@group_type_mapping = Concurrent::Hash.new
|
24
24
|
@loaded_flags_successfully_once = Concurrent::AtomicBoolean.new
|
25
25
|
@feature_flags_by_key = nil
|
26
|
+
@feature_flag_request_timeout_seconds = feature_flag_request_timeout_seconds
|
27
|
+
@on_error = on_error || proc { |status, error| }
|
26
28
|
|
27
29
|
@task =
|
28
30
|
Concurrent::TimerTask.new(
|
@@ -46,30 +48,22 @@ class PostHog
|
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
49
|
-
def get_feature_variants(distinct_id, groups={}, person_properties={}, group_properties={})
|
50
|
-
|
51
|
+
def get_feature_variants(distinct_id, groups={}, person_properties={}, group_properties={}, raise_on_error=false)
|
52
|
+
# TODO: Convert to options hash for easier argument passing
|
53
|
+
decide_data = get_all_flags_and_payloads(distinct_id, groups, person_properties, group_properties, false, raise_on_error)
|
51
54
|
if !decide_data.key?(:featureFlags)
|
52
|
-
logger.
|
55
|
+
logger.debug "Missing feature flags key: #{decide_data.to_json}"
|
56
|
+
{}
|
53
57
|
else
|
54
58
|
stringify_keys(decide_data[:featureFlags] || {})
|
55
59
|
end
|
56
60
|
end
|
57
61
|
|
58
|
-
def _get_active_feature_variants(distinct_id, groups={}, person_properties={}, group_properties={})
|
59
|
-
feature_variants = get_feature_variants(distinct_id, groups, person_properties, group_properties)
|
60
|
-
active_feature_variants = {}
|
61
|
-
feature_variants.each do |key, value|
|
62
|
-
if value != false
|
63
|
-
active_feature_variants[key] = value
|
64
|
-
end
|
65
|
-
end
|
66
|
-
active_feature_variants
|
67
|
-
end
|
68
|
-
|
69
62
|
def get_feature_payloads(distinct_id, groups = {}, person_properties = {}, group_properties = {}, only_evaluate_locally = false)
|
70
|
-
decide_data =
|
63
|
+
decide_data = get_all_flags_and_payloads(distinct_id, groups, person_properties, group_properties)
|
71
64
|
if !decide_data.key?(:featureFlagPayloads)
|
72
|
-
logger.
|
65
|
+
logger.debug "Missing feature flag payloads key: #{decide_data.to_json}"
|
66
|
+
return {}
|
73
67
|
else
|
74
68
|
stringify_keys(decide_data[:featureFlagPayloads] || {})
|
75
69
|
end
|
@@ -115,7 +109,7 @@ class PostHog
|
|
115
109
|
rescue InconclusiveMatchError => e
|
116
110
|
logger.debug "Failed to compute flag #{key} locally: #{e}"
|
117
111
|
rescue StandardError => e
|
118
|
-
|
112
|
+
@on_error.call(-1, "Error computing flag locally: #{e}. #{e.backtrace.join("\n")}")
|
119
113
|
end
|
120
114
|
end
|
121
115
|
|
@@ -123,14 +117,14 @@ class PostHog
|
|
123
117
|
|
124
118
|
if !flag_was_locally_evaluated && !only_evaluate_locally
|
125
119
|
begin
|
126
|
-
flags = get_feature_variants(distinct_id, groups, person_properties, group_properties)
|
120
|
+
flags = get_feature_variants(distinct_id, groups, person_properties, group_properties, true)
|
127
121
|
response = flags[key]
|
128
122
|
if response.nil?
|
129
123
|
response = false
|
130
124
|
end
|
131
125
|
logger.debug "Successfully computed flag remotely: #{key} -> #{response}"
|
132
126
|
rescue StandardError => e
|
133
|
-
|
127
|
+
@on_error.call(-1, "Error computing flag remotely: #{e}. #{e.backtrace.join("\n")}")
|
134
128
|
end
|
135
129
|
end
|
136
130
|
|
@@ -143,7 +137,7 @@ class PostHog
|
|
143
137
|
flags = response[:featureFlags]
|
144
138
|
end
|
145
139
|
|
146
|
-
def get_all_flags_and_payloads(distinct_id, groups = {}, person_properties = {}, group_properties = {}, only_evaluate_locally = false)
|
140
|
+
def get_all_flags_and_payloads(distinct_id, groups = {}, person_properties = {}, group_properties = {}, only_evaluate_locally = false, raise_on_error = false)
|
147
141
|
load_feature_flags
|
148
142
|
|
149
143
|
flags = {}
|
@@ -161,17 +155,22 @@ class PostHog
|
|
161
155
|
rescue InconclusiveMatchError => e
|
162
156
|
fallback_to_decide = true
|
163
157
|
rescue StandardError => e
|
164
|
-
|
158
|
+
@on_error.call(-1, "Error computing flag locally: #{e}. #{e.backtrace.join("\n")} ")
|
165
159
|
fallback_to_decide = true
|
166
160
|
end
|
167
161
|
end
|
168
162
|
if fallback_to_decide && !only_evaluate_locally
|
169
163
|
begin
|
170
164
|
flags_and_payloads = get_decide(distinct_id, groups, person_properties, group_properties)
|
165
|
+
|
166
|
+
unless flags_and_payloads.key?(:featureFlags)
|
167
|
+
raise StandardError.new("Error flags response: #{flags_and_payloads}")
|
168
|
+
end
|
171
169
|
flags = stringify_keys(flags_and_payloads[:featureFlags] || {})
|
172
170
|
payloads = stringify_keys(flags_and_payloads[:featureFlagPayloads] || {})
|
173
171
|
rescue StandardError => e
|
174
|
-
|
172
|
+
@on_error.call(-1, "Error computing flag remotely: #{e}")
|
173
|
+
raise if raise_on_error
|
175
174
|
end
|
176
175
|
end
|
177
176
|
{"featureFlags": flags, "featureFlagPayloads": payloads}
|
@@ -370,8 +369,9 @@ class PostHog
|
|
370
369
|
end
|
371
370
|
|
372
371
|
def _compute_flag_payload_locally(key, match_value)
|
373
|
-
|
372
|
+
return nil if @feature_flags_by_key.nil?
|
374
373
|
|
374
|
+
response = nil
|
375
375
|
if [true, false].include? match_value
|
376
376
|
response = @feature_flags_by_key.dig(key, :filters, :payloads, match_value.to_s.to_sym)
|
377
377
|
elsif match_value.is_a? String
|
@@ -472,10 +472,15 @@ class PostHog
|
|
472
472
|
end
|
473
473
|
|
474
474
|
def _load_feature_flags()
|
475
|
-
|
475
|
+
begin
|
476
|
+
res = _request_feature_flag_definitions
|
477
|
+
rescue StandardError => e
|
478
|
+
@on_error.call(-1, e.to_s)
|
479
|
+
return
|
480
|
+
end
|
476
481
|
|
477
482
|
if !res.key?(:flags)
|
478
|
-
logger.
|
483
|
+
logger.debug "Failed to load feature flags: #{res}"
|
479
484
|
else
|
480
485
|
@feature_flags = res[:flags] || []
|
481
486
|
@feature_flags_by_key = {}
|
@@ -508,16 +513,18 @@ class PostHog
|
|
508
513
|
data['token'] = @project_api_key
|
509
514
|
req.body = data.to_json
|
510
515
|
|
511
|
-
_request(uri, req)
|
516
|
+
_request(uri, req, @feature_flag_request_timeout_seconds)
|
512
517
|
end
|
513
518
|
|
514
|
-
def _request(uri, request_object)
|
519
|
+
def _request(uri, request_object, timeout = nil)
|
515
520
|
|
516
521
|
request_object['User-Agent'] = "posthog-ruby#{PostHog::VERSION}"
|
517
522
|
|
523
|
+
request_timeout = timeout || 10
|
524
|
+
|
518
525
|
begin
|
519
526
|
res_body = nil
|
520
|
-
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
527
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https', :read_timeout => request_timeout) do |http|
|
521
528
|
res = http.request(request_object)
|
522
529
|
JSON.parse(res.body, {symbolize_names: true})
|
523
530
|
end
|
@@ -527,9 +534,11 @@ class PostHog
|
|
527
534
|
EOFError,
|
528
535
|
Net::HTTPBadResponse,
|
529
536
|
Net::HTTPHeaderSyntaxError,
|
537
|
+
Net::ReadTimeout,
|
538
|
+
Net::WriteTimeout,
|
530
539
|
Net::ProtocolError => e
|
531
540
|
logger.debug("Unable to complete request to #{uri}")
|
532
|
-
|
541
|
+
raise
|
533
542
|
end
|
534
543
|
end
|
535
544
|
end
|
data/lib/posthog/field_parser.rb
CHANGED
@@ -137,10 +137,14 @@ class PostHog
|
|
137
137
|
|
138
138
|
if send_feature_flags
|
139
139
|
feature_variants = fields[:feature_variants]
|
140
|
+
active_feature_variants = {}
|
140
141
|
feature_variants.each do |key, value|
|
141
142
|
parsed[:properties]["$feature/#{key}"] = value
|
143
|
+
if value != false
|
144
|
+
active_feature_variants[key] = value
|
145
|
+
end
|
142
146
|
end
|
143
|
-
parsed[:properties]["$active_feature_flags"] =
|
147
|
+
parsed[:properties]["$active_feature_flags"] = active_feature_variants.keys
|
144
148
|
end
|
145
149
|
parsed
|
146
150
|
end
|
data/lib/posthog/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: posthog-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ''
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -128,14 +128,14 @@ dependencies:
|
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: 0.
|
131
|
+
version: 0.6.0
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: 0.
|
138
|
+
version: 0.6.0
|
139
139
|
description: The PostHog ruby library
|
140
140
|
email: hey@posthog.com
|
141
141
|
executables:
|