posthog-ruby 3.4.0 → 3.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 +46 -9
- data/lib/posthog/feature_flag_error.rb +36 -0
- data/lib/posthog/feature_flag_result.rb +56 -0
- data/lib/posthog/feature_flags.rb +39 -13
- data/lib/posthog/logging.rb +2 -2
- data/lib/posthog/version.rb +1 -1
- data/lib/posthog.rb +2 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d0dcf1963eb64bad7469885f038127408c5815a5cad8b5d338f220b2fac5263
|
|
4
|
+
data.tar.gz: 970b55467f0594423d57c36c9c546bb65ddcb68041ab11b4bea7ded9e25928bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5cfebda482c0b67e1c12fce9cae0081e182eb1d92577a7f07405ebff817635c31f70a780f4416b5de0d4bdecff7393852c9a8f8d44e34f5d8edf80f0db4434b3
|
|
7
|
+
data.tar.gz: 1ec51e9f018c98cdc8d00b86a0c11613f48544d6ebbeaebe5d98c5084861dcea0ba63be12f506d01a3493db87a9c9fe0f9c1c339d6ed286c7845f5da8bc4db26
|
data/lib/posthog/client.rb
CHANGED
|
@@ -278,6 +278,40 @@ module PostHog
|
|
|
278
278
|
group_properties: {},
|
|
279
279
|
only_evaluate_locally: false,
|
|
280
280
|
send_feature_flag_events: true
|
|
281
|
+
)
|
|
282
|
+
result = get_feature_flag_result(
|
|
283
|
+
key,
|
|
284
|
+
distinct_id,
|
|
285
|
+
groups: groups,
|
|
286
|
+
person_properties: person_properties,
|
|
287
|
+
group_properties: group_properties,
|
|
288
|
+
only_evaluate_locally: only_evaluate_locally,
|
|
289
|
+
send_feature_flag_events: send_feature_flag_events
|
|
290
|
+
)
|
|
291
|
+
result&.value
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Returns both the feature flag value and payload in a single call.
|
|
295
|
+
# This method raises the $feature_flag_called event with the payload included.
|
|
296
|
+
#
|
|
297
|
+
# @param [String] key The key of the feature flag
|
|
298
|
+
# @param [String] distinct_id The distinct id of the user
|
|
299
|
+
# @param [Hash] groups
|
|
300
|
+
# @param [Hash] person_properties key-value pairs of properties to associate with the user.
|
|
301
|
+
# @param [Hash] group_properties
|
|
302
|
+
# @param [Boolean] only_evaluate_locally
|
|
303
|
+
# @param [Boolean] send_feature_flag_events
|
|
304
|
+
#
|
|
305
|
+
# @return [FeatureFlagResult, nil] A FeatureFlagResult object containing the flag value and payload,
|
|
306
|
+
# or nil if the flag evaluation returned nil
|
|
307
|
+
def get_feature_flag_result(
|
|
308
|
+
key,
|
|
309
|
+
distinct_id,
|
|
310
|
+
groups: {},
|
|
311
|
+
person_properties: {},
|
|
312
|
+
group_properties: {},
|
|
313
|
+
only_evaluate_locally: false,
|
|
314
|
+
send_feature_flag_events: true
|
|
281
315
|
)
|
|
282
316
|
person_properties, group_properties = add_local_person_and_group_properties(
|
|
283
317
|
distinct_id,
|
|
@@ -285,7 +319,7 @@ module PostHog
|
|
|
285
319
|
person_properties,
|
|
286
320
|
group_properties
|
|
287
321
|
)
|
|
288
|
-
feature_flag_response, flag_was_locally_evaluated, request_id, evaluated_at =
|
|
322
|
+
feature_flag_response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload =
|
|
289
323
|
@feature_flags_poller.get_feature_flag(
|
|
290
324
|
key,
|
|
291
325
|
distinct_id,
|
|
@@ -294,8 +328,8 @@ module PostHog
|
|
|
294
328
|
group_properties,
|
|
295
329
|
only_evaluate_locally
|
|
296
330
|
)
|
|
297
|
-
|
|
298
331
|
feature_flag_reported_key = "#{key}_#{feature_flag_response}"
|
|
332
|
+
|
|
299
333
|
if !@distinct_id_has_sent_flag_calls[distinct_id].include?(feature_flag_reported_key) && send_feature_flag_events
|
|
300
334
|
properties = {
|
|
301
335
|
'$feature_flag' => key,
|
|
@@ -304,18 +338,18 @@ module PostHog
|
|
|
304
338
|
}
|
|
305
339
|
properties['$feature_flag_request_id'] = request_id if request_id
|
|
306
340
|
properties['$feature_flag_evaluated_at'] = evaluated_at if evaluated_at
|
|
341
|
+
properties['$feature_flag_error'] = feature_flag_error if feature_flag_error
|
|
307
342
|
|
|
308
343
|
capture(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
groups: groups
|
|
314
|
-
}
|
|
344
|
+
distinct_id: distinct_id,
|
|
345
|
+
event: '$feature_flag_called',
|
|
346
|
+
properties: properties,
|
|
347
|
+
groups: groups
|
|
315
348
|
)
|
|
316
349
|
@distinct_id_has_sent_flag_calls[distinct_id] << feature_flag_reported_key
|
|
317
350
|
end
|
|
318
|
-
|
|
351
|
+
|
|
352
|
+
FeatureFlagResult.from_value_and_payload(key, feature_flag_response, payload)
|
|
319
353
|
end
|
|
320
354
|
|
|
321
355
|
# Returns all flags for a given user
|
|
@@ -341,6 +375,9 @@ module PostHog
|
|
|
341
375
|
|
|
342
376
|
# Returns payload for a given feature flag
|
|
343
377
|
#
|
|
378
|
+
# @deprecated Use {#get_feature_flag_result} instead, which returns both the flag value and payload
|
|
379
|
+
# and properly raises the $feature_flag_called event.
|
|
380
|
+
#
|
|
344
381
|
# @param [String] key The key of the feature flag
|
|
345
382
|
# @param [String] distinct_id The distinct id of the user
|
|
346
383
|
# @option [String or boolean] match_value The value of the feature flag to be matched
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PostHog
|
|
4
|
+
# Error type constants for the $feature_flag_error property.
|
|
5
|
+
#
|
|
6
|
+
# These values are sent in analytics events to track flag evaluation failures.
|
|
7
|
+
# They should not be changed without considering impact on existing dashboards
|
|
8
|
+
# and queries that filter on these values.
|
|
9
|
+
#
|
|
10
|
+
# Error values:
|
|
11
|
+
# ERRORS_WHILE_COMPUTING: Server returned errorsWhileComputingFlags=true
|
|
12
|
+
# FLAG_MISSING: Requested flag not in API response
|
|
13
|
+
# QUOTA_LIMITED: Rate/quota limit exceeded
|
|
14
|
+
# TIMEOUT: Request timed out
|
|
15
|
+
# CONNECTION_ERROR: Network connectivity issue
|
|
16
|
+
# UNKNOWN_ERROR: Unexpected exceptions
|
|
17
|
+
#
|
|
18
|
+
# For API errors with status codes, use the api_error() method which returns
|
|
19
|
+
# a string like "api_error_500".
|
|
20
|
+
class FeatureFlagError
|
|
21
|
+
ERRORS_WHILE_COMPUTING = 'errors_while_computing_flags'
|
|
22
|
+
FLAG_MISSING = 'flag_missing'
|
|
23
|
+
QUOTA_LIMITED = 'quota_limited'
|
|
24
|
+
TIMEOUT = 'timeout'
|
|
25
|
+
CONNECTION_ERROR = 'connection_error'
|
|
26
|
+
UNKNOWN_ERROR = 'unknown_error'
|
|
27
|
+
|
|
28
|
+
# Generate API error string with status code.
|
|
29
|
+
#
|
|
30
|
+
# @param status [Integer, String] The HTTP status code
|
|
31
|
+
# @return [String] Error string in format "api_error_STATUS"
|
|
32
|
+
def self.api_error(status)
|
|
33
|
+
"api_error_#{status.to_s.downcase}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module PostHog
|
|
6
|
+
# Represents the result of a feature flag evaluation
|
|
7
|
+
# containing both the flag value and payload
|
|
8
|
+
class FeatureFlagResult
|
|
9
|
+
attr_reader :key, :variant, :payload
|
|
10
|
+
|
|
11
|
+
def initialize(key:, enabled:, variant: nil, payload: nil)
|
|
12
|
+
@key = key
|
|
13
|
+
@enabled = enabled
|
|
14
|
+
@variant = variant
|
|
15
|
+
@payload = payload
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns the effective value of the feature flag
|
|
19
|
+
# variant if present, otherwise enabled status
|
|
20
|
+
def value
|
|
21
|
+
@variant || @enabled
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns whether or not the feature flag evaluated as enabled
|
|
25
|
+
def enabled?
|
|
26
|
+
@enabled
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Factory method to create from flag value and payload
|
|
30
|
+
def self.from_value_and_payload(key, value, payload)
|
|
31
|
+
return nil if value.nil?
|
|
32
|
+
|
|
33
|
+
parsed_payload = parse_payload(payload)
|
|
34
|
+
|
|
35
|
+
if value.is_a?(String)
|
|
36
|
+
new(key: key, enabled: true, variant: value, payload: parsed_payload)
|
|
37
|
+
else
|
|
38
|
+
new(key: key, enabled: value, payload: parsed_payload)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.parse_payload(payload)
|
|
43
|
+
return nil if payload.nil?
|
|
44
|
+
return payload unless payload.is_a?(String)
|
|
45
|
+
return nil if payload.empty?
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
JSON.parse(payload)
|
|
49
|
+
rescue JSON::ParserError
|
|
50
|
+
payload
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private_class_method :parse_payload
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -169,18 +169,13 @@ module PostHog
|
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
response = nil
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
@feature_flags.each do |flag|
|
|
175
|
-
if key == flag[:key]
|
|
176
|
-
feature_flag = flag
|
|
177
|
-
break
|
|
178
|
-
end
|
|
179
|
-
end
|
|
172
|
+
payload = nil
|
|
173
|
+
feature_flag = @feature_flags_by_key&.[](key)
|
|
180
174
|
|
|
181
175
|
unless feature_flag.nil?
|
|
182
176
|
begin
|
|
183
177
|
response = _compute_flag_locally(feature_flag, distinct_id, groups, person_properties, group_properties)
|
|
178
|
+
payload = _compute_flag_payload_locally(key, response) unless response.nil?
|
|
184
179
|
logger.debug "Successfully computed flag locally: #{key} -> #{response}"
|
|
185
180
|
rescue RequiresServerEvaluation, InconclusiveMatchError => e
|
|
186
181
|
logger.debug "Failed to compute flag #{key} locally: #{e}"
|
|
@@ -193,29 +188,49 @@ module PostHog
|
|
|
193
188
|
|
|
194
189
|
request_id = nil
|
|
195
190
|
evaluated_at = nil
|
|
191
|
+
feature_flag_error = nil
|
|
196
192
|
|
|
197
193
|
if !flag_was_locally_evaluated && !only_evaluate_locally
|
|
198
194
|
begin
|
|
195
|
+
errors = []
|
|
199
196
|
flags_data = get_all_flags_and_payloads(distinct_id, groups, person_properties, group_properties,
|
|
200
197
|
only_evaluate_locally, true)
|
|
201
198
|
if flags_data.key?(:featureFlags)
|
|
202
199
|
flags = stringify_keys(flags_data[:featureFlags] || {})
|
|
200
|
+
payloads = stringify_keys(flags_data[:featureFlagPayloads] || {})
|
|
203
201
|
request_id = flags_data[:requestId]
|
|
204
202
|
evaluated_at = flags_data[:evaluatedAt]
|
|
205
203
|
else
|
|
206
204
|
logger.debug "Missing feature flags key: #{flags_data.to_json}"
|
|
207
205
|
flags = {}
|
|
206
|
+
payloads = {}
|
|
208
207
|
end
|
|
209
208
|
|
|
209
|
+
status = flags_data[:status]
|
|
210
|
+
errors << FeatureFlagError.api_error(status) if status && status >= 400
|
|
211
|
+
errors << FeatureFlagError::ERRORS_WHILE_COMPUTING if flags_data[:errorsWhileComputingFlags]
|
|
212
|
+
errors << FeatureFlagError::QUOTA_LIMITED if flags_data[:quotaLimited]&.include?('feature_flags')
|
|
213
|
+
errors << FeatureFlagError::FLAG_MISSING unless flags.key?(key.to_s)
|
|
214
|
+
|
|
210
215
|
response = flags[key]
|
|
211
216
|
response = false if response.nil?
|
|
217
|
+
payload = payloads[key]
|
|
218
|
+
feature_flag_error = errors.join(',') unless errors.empty?
|
|
219
|
+
|
|
212
220
|
logger.debug "Successfully computed flag remotely: #{key} -> #{response}"
|
|
221
|
+
rescue Timeout::Error => e
|
|
222
|
+
@on_error.call(-1, "Timeout while fetching flags remotely: #{e}")
|
|
223
|
+
feature_flag_error = FeatureFlagError::TIMEOUT
|
|
224
|
+
rescue Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError, SocketError => e
|
|
225
|
+
@on_error.call(-1, "Connection error while fetching flags remotely: #{e}")
|
|
226
|
+
feature_flag_error = FeatureFlagError::CONNECTION_ERROR
|
|
213
227
|
rescue StandardError => e
|
|
214
228
|
@on_error.call(-1, "Error computing flag remotely: #{e}. #{e.backtrace.join("\n")}")
|
|
229
|
+
feature_flag_error = FeatureFlagError::UNKNOWN_ERROR
|
|
215
230
|
end
|
|
216
231
|
end
|
|
217
232
|
|
|
218
|
-
[response, flag_was_locally_evaluated, request_id, evaluated_at]
|
|
233
|
+
[response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload]
|
|
219
234
|
end
|
|
220
235
|
|
|
221
236
|
def get_all_flags(
|
|
@@ -270,24 +285,32 @@ module PostHog
|
|
|
270
285
|
fallback_to_server = true
|
|
271
286
|
end
|
|
272
287
|
|
|
288
|
+
errors_while_computing = false
|
|
289
|
+
quota_limited = nil
|
|
290
|
+
status_code = nil
|
|
291
|
+
|
|
273
292
|
if fallback_to_server && !only_evaluate_locally
|
|
274
293
|
begin
|
|
275
294
|
flags_and_payloads = get_flags(distinct_id, groups, person_properties, group_properties)
|
|
295
|
+
errors_while_computing = flags_and_payloads[:errorsWhileComputingFlags] || false
|
|
296
|
+
quota_limited = flags_and_payloads[:quotaLimited]
|
|
297
|
+
status_code = flags_and_payloads[:status]
|
|
276
298
|
|
|
277
299
|
unless flags_and_payloads.key?(:featureFlags)
|
|
278
300
|
raise StandardError, "Error flags response: #{flags_and_payloads}"
|
|
279
301
|
end
|
|
280
302
|
|
|
303
|
+
request_id = flags_and_payloads[:requestId]
|
|
304
|
+
evaluated_at = flags_and_payloads[:evaluatedAt]
|
|
305
|
+
|
|
281
306
|
# Check if feature_flags are quota limited
|
|
282
|
-
if
|
|
307
|
+
if quota_limited&.include?('feature_flags')
|
|
283
308
|
logger.warn '[FEATURE FLAGS] Quota limited for feature flags'
|
|
284
309
|
flags = {}
|
|
285
310
|
payloads = {}
|
|
286
311
|
else
|
|
287
312
|
flags = stringify_keys(flags_and_payloads[:featureFlags] || {})
|
|
288
313
|
payloads = stringify_keys(flags_and_payloads[:featureFlagPayloads] || {})
|
|
289
|
-
request_id = flags_and_payloads[:requestId]
|
|
290
|
-
evaluated_at = flags_and_payloads[:evaluatedAt]
|
|
291
314
|
end
|
|
292
315
|
rescue StandardError => e
|
|
293
316
|
@on_error.call(-1, "Error computing flag remotely: #{e}")
|
|
@@ -299,7 +322,10 @@ module PostHog
|
|
|
299
322
|
featureFlags: flags,
|
|
300
323
|
featureFlagPayloads: payloads,
|
|
301
324
|
requestId: request_id,
|
|
302
|
-
evaluatedAt: evaluated_at
|
|
325
|
+
evaluatedAt: evaluated_at,
|
|
326
|
+
errorsWhileComputingFlags: errors_while_computing,
|
|
327
|
+
quotaLimited: quota_limited,
|
|
328
|
+
status: status_code
|
|
303
329
|
}
|
|
304
330
|
end
|
|
305
331
|
|
data/lib/posthog/logging.rb
CHANGED
data/lib/posthog/version.rb
CHANGED
data/lib/posthog.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: posthog-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: concurrent-ruby
|
|
@@ -40,6 +40,8 @@ files:
|
|
|
40
40
|
- lib/posthog/defaults.rb
|
|
41
41
|
- lib/posthog/exception_capture.rb
|
|
42
42
|
- lib/posthog/feature_flag.rb
|
|
43
|
+
- lib/posthog/feature_flag_error.rb
|
|
44
|
+
- lib/posthog/feature_flag_result.rb
|
|
43
45
|
- lib/posthog/feature_flags.rb
|
|
44
46
|
- lib/posthog/field_parser.rb
|
|
45
47
|
- lib/posthog/logging.rb
|
|
@@ -70,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
70
72
|
- !ruby/object:Gem::Version
|
|
71
73
|
version: '0'
|
|
72
74
|
requirements: []
|
|
73
|
-
rubygems_version:
|
|
75
|
+
rubygems_version: 4.0.3
|
|
74
76
|
specification_version: 4
|
|
75
77
|
summary: PostHog library
|
|
76
78
|
test_files: []
|