posthog-ruby 2.4.2 → 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: f17b77ed0648665e282dc6f9483695eebcb955c08f4873350d5bbd01277c6534
4
- data.tar.gz: e4e638cd17a013f3b3c1e012a5fd08f825fe6e43ce741050c31f7b084a718b96
3
+ metadata.gz: 654b547b68b6d0ebcfd4ef3523fa46d32a825d01485138e80443cf494d295f4a
4
+ data.tar.gz: 9ae165ba631600e2ee0f59ce6d80fe3defe5b2c42f8d99a7c6b30393b73d2760
5
5
  SHA512:
6
- metadata.gz: 9b22dfa3f6765ffbfeb912d775ce5eee8ee4db21be2236dc13679d816c43fff161a707f33ff280ccc4d101e729d593fd909df8ff38c4d8ac8187da48071a506d
7
- data.tar.gz: df159e86bc400f53d3d85b4068fc158f600fc32bc5015e022305d27d8cf65487ae946a90934fc7737efdc8086b387a9988883490d7298d261c9520c68c9e1078
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,12 +49,12 @@ 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 }
55
-
56
- at_exit { @worker_thread && @worker_thread[:should_exit] = true }
57
58
  end
58
59
 
59
60
  # Synchronously waits until the worker has cleared the queue.
@@ -92,7 +93,7 @@ class PostHog
92
93
  symbolize_keys! attrs
93
94
 
94
95
  if attrs[:send_feature_flags]
95
- 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] || {})
96
97
 
97
98
  attrs[:feature_variants] = feature_variants
98
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}
@@ -229,7 +228,7 @@ class PostHog
229
228
  # Guard against overflow, disallow numbers greater than 10_000
230
229
  return nil
231
230
  end
232
-
231
+
233
232
  interval = match[2]
234
233
  if interval == "h"
235
234
  parsed_dt = parsed_dt - (number/24r)
@@ -254,7 +253,7 @@ class PostHog
254
253
  def self.match_property(property, property_values)
255
254
  # only looks for matches where key exists in property_values
256
255
  # doesn't support operator is_not_set
257
-
256
+
258
257
  PostHog::Utils.symbolize_keys! property
259
258
  PostHog::Utils.symbolize_keys! property_values
260
259
 
@@ -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
@@ -436,7 +436,7 @@ class PostHog
436
436
  if !rollout_percentage.nil? and _hash(flag[:key], distinct_id) > (rollout_percentage.to_f/100)
437
437
  return false
438
438
  end
439
-
439
+
440
440
  return true
441
441
  end
442
442
 
@@ -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,10 +534,12 @@ 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
- end
543
+ end
535
544
  end
536
545
  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.2'
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.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-01-26 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
@@ -44,70 +44,70 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.3'
47
+ version: '13.1'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '10.3'
54
+ version: '13.1'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '3.0'
61
+ version: '3.13'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '3.0'
68
+ version: '3.13'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: tzinfo
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '='
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 1.2.1
75
+ version: '2.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '='
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 1.2.1
82
+ version: '2.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: activesupport
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 4.1.11
89
+ version: '7.1'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 4.1.11
96
+ version: '7.1'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: oj
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 3.6.2
103
+ version: 3.16.3
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 3.6.2
110
+ version: 3.16.3
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rubocop
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -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: