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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4045560a50a4339e398f41b14198c8f7e8a89b03e9c67c4051f9c3e8a3ddc023
4
- data.tar.gz: f5db284d85ca9b085205e435c236a0b74da89c141c2c25254307811308ed038f
3
+ metadata.gz: 654b547b68b6d0ebcfd4ef3523fa46d32a825d01485138e80443cf494d295f4a
4
+ data.tar.gz: 9ae165ba631600e2ee0f59ce6d80fe3defe5b2c42f8d99a7c6b30393b73d2760
5
5
  SHA512:
6
- metadata.gz: b841977f8e744a5420d9fdec891e98d82b486c558dcbbf33c1ecf2441031b454bd5ea62323904e5600458a31fb11263513d49944edfd0c916359fecd1f4881a6
7
- data.tar.gz: 2f959dc54d97743c25abe0e3d3ae385f781a05fcdd2c8bc97d84d0909475ba1b183d546086b4cc943cc416c2c2f642810e838b869608779ae518b3482b85a77b
6
+ metadata.gz: 4ce3e4037abd6e861a0f38a9e51ed4c54ae84622b939768139e4bcdc215f32e69b7dabb1c144e291d8e1f832d766c188a01ce5076a70e0dba03add31e4f64587
7
+ data.tar.gz: 90b385f84d8047eed291d280e07eef79d8c28c217e89bb53cc621c78a6457bf7542780aeee63b48fca53085ba13d625136ac50a8ea9decd47cfc88a999b5a97b
@@ -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._get_active_feature_variants(attrs[:distinct_id], attrs[:groups])
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
@@ -16,6 +16,10 @@ class PostHog
16
16
  RETRIES = 10
17
17
  end
18
18
 
19
+ module FeatureFlags
20
+ FLAG_REQUEST_TIMEOUT_SECONDS = 3
21
+ end
22
+
19
23
  module Queue
20
24
  MAX_SIZE = 10_000
21
25
  end
@@ -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
- decide_data = get_decide(distinct_id, groups, person_properties, group_properties)
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.error "Missing feature flags key: #{decide_data.to_json}"
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 = get_decide(distinct_id, groups, person_properties, group_properties)
63
+ decide_data = get_all_flags_and_payloads(distinct_id, groups, person_properties, group_properties)
71
64
  if !decide_data.key?(:featureFlagPayloads)
72
- logger.error "Missing feature flag payloads key: #{decide_data.to_json}"
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
- logger.error "Error computing flag locally: #{e}. #{e.backtrace.join("\n")}"
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
- logger.error "Error computing flag remotely: #{e}. #{e.backtrace.join("\n")}"
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
- logger.error "Error computing flag locally: #{e}."
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
- logger.error "Error computing flag remotely: #{e}"
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
- response = nil
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
- res = _request_feature_flag_definitions
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.error "Failed to load feature flags: #{res}"
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
- throw e
541
+ raise
533
542
  end
534
543
  end
535
544
  end
@@ -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"] = feature_variants.keys
147
+ parsed[:properties]["$active_feature_flags"] = active_feature_variants.keys
144
148
  end
145
149
  parsed
146
150
  end
@@ -1,3 +1,3 @@
1
1
  class PostHog
2
- VERSION = '2.4.3'
2
+ VERSION = '2.5.0'
3
3
  end
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.3
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-02-29 00:00:00.000000000 Z
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.1.4
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.1.4
138
+ version: 0.6.0
139
139
  description: The PostHog ruby library
140
140
  email: hey@posthog.com
141
141
  executables: