posthog-ruby 2.4.2 → 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: 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: