posthog-ruby 2.4.3 → 2.5.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/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:
|