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 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: