posthog-ruby 3.3.3 → 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 +65 -21
- data/lib/posthog/feature_flag_error.rb +36 -0
- data/lib/posthog/feature_flag_result.rb +56 -0
- data/lib/posthog/feature_flags.rb +80 -19
- 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
|
@@ -279,38 +279,77 @@ module PostHog
|
|
|
279
279
|
only_evaluate_locally: false,
|
|
280
280
|
send_feature_flag_events: true
|
|
281
281
|
)
|
|
282
|
-
|
|
282
|
+
result = get_feature_flag_result(
|
|
283
|
+
key,
|
|
283
284
|
distinct_id,
|
|
284
|
-
groups,
|
|
285
|
-
person_properties,
|
|
286
|
-
group_properties
|
|
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
|
|
287
290
|
)
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
315
|
+
)
|
|
316
|
+
person_properties, group_properties = add_local_person_and_group_properties(
|
|
290
317
|
distinct_id,
|
|
291
318
|
groups,
|
|
292
319
|
person_properties,
|
|
293
|
-
group_properties
|
|
294
|
-
only_evaluate_locally
|
|
320
|
+
group_properties
|
|
295
321
|
)
|
|
296
|
-
|
|
322
|
+
feature_flag_response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload =
|
|
323
|
+
@feature_flags_poller.get_feature_flag(
|
|
324
|
+
key,
|
|
325
|
+
distinct_id,
|
|
326
|
+
groups,
|
|
327
|
+
person_properties,
|
|
328
|
+
group_properties,
|
|
329
|
+
only_evaluate_locally
|
|
330
|
+
)
|
|
297
331
|
feature_flag_reported_key = "#{key}_#{feature_flag_response}"
|
|
332
|
+
|
|
298
333
|
if !@distinct_id_has_sent_flag_calls[distinct_id].include?(feature_flag_reported_key) && send_feature_flag_events
|
|
334
|
+
properties = {
|
|
335
|
+
'$feature_flag' => key,
|
|
336
|
+
'$feature_flag_response' => feature_flag_response,
|
|
337
|
+
'locally_evaluated' => flag_was_locally_evaluated
|
|
338
|
+
}
|
|
339
|
+
properties['$feature_flag_request_id'] = request_id if request_id
|
|
340
|
+
properties['$feature_flag_evaluated_at'] = evaluated_at if evaluated_at
|
|
341
|
+
properties['$feature_flag_error'] = feature_flag_error if feature_flag_error
|
|
342
|
+
|
|
299
343
|
capture(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
'$feature_flag' => key,
|
|
305
|
-
'$feature_flag_response' => feature_flag_response,
|
|
306
|
-
'locally_evaluated' => flag_was_locally_evaluated
|
|
307
|
-
}.merge(request_id ? { '$feature_flag_request_id' => request_id } : {}),
|
|
308
|
-
groups: groups
|
|
309
|
-
}
|
|
344
|
+
distinct_id: distinct_id,
|
|
345
|
+
event: '$feature_flag_called',
|
|
346
|
+
properties: properties,
|
|
347
|
+
groups: groups
|
|
310
348
|
)
|
|
311
349
|
@distinct_id_has_sent_flag_calls[distinct_id] << feature_flag_reported_key
|
|
312
350
|
end
|
|
313
|
-
|
|
351
|
+
|
|
352
|
+
FeatureFlagResult.from_value_and_payload(key, feature_flag_response, payload)
|
|
314
353
|
end
|
|
315
354
|
|
|
316
355
|
# Returns all flags for a given user
|
|
@@ -336,6 +375,9 @@ module PostHog
|
|
|
336
375
|
|
|
337
376
|
# Returns payload for a given feature flag
|
|
338
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
|
+
#
|
|
339
381
|
# @param [String] key The key of the feature flag
|
|
340
382
|
# @param [String] distinct_id The distinct id of the user
|
|
341
383
|
# @option [String or boolean] match_value The value of the feature flag to be matched
|
|
@@ -385,7 +427,9 @@ module PostHog
|
|
|
385
427
|
distinct_id, groups, person_properties, group_properties, only_evaluate_locally
|
|
386
428
|
)
|
|
387
429
|
|
|
388
|
-
|
|
430
|
+
# Remove internal information
|
|
431
|
+
response.delete(:requestId)
|
|
432
|
+
response.delete(:evaluatedAt)
|
|
389
433
|
response
|
|
390
434
|
end
|
|
391
435
|
|
|
@@ -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
|
|
@@ -43,6 +43,7 @@ module PostHog
|
|
|
43
43
|
@feature_flag_request_timeout_seconds = feature_flag_request_timeout_seconds
|
|
44
44
|
@on_error = on_error || proc { |status, error| }
|
|
45
45
|
@quota_limited = Concurrent::AtomicBoolean.new(false)
|
|
46
|
+
@flags_etag = Concurrent::AtomicReference.new(nil)
|
|
46
47
|
@task =
|
|
47
48
|
Concurrent::TimerTask.new(
|
|
48
49
|
execution_interval: polling_interval
|
|
@@ -140,6 +141,7 @@ module PostHog
|
|
|
140
141
|
[key, FeatureFlag.from_value_and_payload(key, value, flags_response[:featureFlagPayloads][key])]
|
|
141
142
|
end
|
|
142
143
|
end
|
|
144
|
+
|
|
143
145
|
flags_response
|
|
144
146
|
end
|
|
145
147
|
|
|
@@ -167,18 +169,13 @@ module PostHog
|
|
|
167
169
|
end
|
|
168
170
|
|
|
169
171
|
response = nil
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@feature_flags.each do |flag|
|
|
173
|
-
if key == flag[:key]
|
|
174
|
-
feature_flag = flag
|
|
175
|
-
break
|
|
176
|
-
end
|
|
177
|
-
end
|
|
172
|
+
payload = nil
|
|
173
|
+
feature_flag = @feature_flags_by_key&.[](key)
|
|
178
174
|
|
|
179
175
|
unless feature_flag.nil?
|
|
180
176
|
begin
|
|
181
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?
|
|
182
179
|
logger.debug "Successfully computed flag locally: #{key} -> #{response}"
|
|
183
180
|
rescue RequiresServerEvaluation, InconclusiveMatchError => e
|
|
184
181
|
logger.debug "Failed to compute flag #{key} locally: #{e}"
|
|
@@ -190,28 +187,50 @@ module PostHog
|
|
|
190
187
|
flag_was_locally_evaluated = !response.nil?
|
|
191
188
|
|
|
192
189
|
request_id = nil
|
|
190
|
+
evaluated_at = nil
|
|
191
|
+
feature_flag_error = nil
|
|
193
192
|
|
|
194
193
|
if !flag_was_locally_evaluated && !only_evaluate_locally
|
|
195
194
|
begin
|
|
195
|
+
errors = []
|
|
196
196
|
flags_data = get_all_flags_and_payloads(distinct_id, groups, person_properties, group_properties,
|
|
197
197
|
only_evaluate_locally, true)
|
|
198
198
|
if flags_data.key?(:featureFlags)
|
|
199
199
|
flags = stringify_keys(flags_data[:featureFlags] || {})
|
|
200
|
+
payloads = stringify_keys(flags_data[:featureFlagPayloads] || {})
|
|
200
201
|
request_id = flags_data[:requestId]
|
|
202
|
+
evaluated_at = flags_data[:evaluatedAt]
|
|
201
203
|
else
|
|
202
204
|
logger.debug "Missing feature flags key: #{flags_data.to_json}"
|
|
203
205
|
flags = {}
|
|
206
|
+
payloads = {}
|
|
204
207
|
end
|
|
205
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
|
+
|
|
206
215
|
response = flags[key]
|
|
207
216
|
response = false if response.nil?
|
|
217
|
+
payload = payloads[key]
|
|
218
|
+
feature_flag_error = errors.join(',') unless errors.empty?
|
|
219
|
+
|
|
208
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
|
|
209
227
|
rescue StandardError => e
|
|
210
228
|
@on_error.call(-1, "Error computing flag remotely: #{e}. #{e.backtrace.join("\n")}")
|
|
229
|
+
feature_flag_error = FeatureFlagError::UNKNOWN_ERROR
|
|
211
230
|
end
|
|
212
231
|
end
|
|
213
232
|
|
|
214
|
-
[response, flag_was_locally_evaluated, request_id]
|
|
233
|
+
[response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload]
|
|
215
234
|
end
|
|
216
235
|
|
|
217
236
|
def get_all_flags(
|
|
@@ -252,6 +271,7 @@ module PostHog
|
|
|
252
271
|
payloads = {}
|
|
253
272
|
fallback_to_server = @feature_flags.empty?
|
|
254
273
|
request_id = nil # Only for /flags requests
|
|
274
|
+
evaluated_at = nil # Only for /flags requests
|
|
255
275
|
|
|
256
276
|
@feature_flags.each do |flag|
|
|
257
277
|
match_value = _compute_flag_locally(flag, distinct_id, groups, person_properties, group_properties)
|
|
@@ -265,23 +285,32 @@ module PostHog
|
|
|
265
285
|
fallback_to_server = true
|
|
266
286
|
end
|
|
267
287
|
|
|
288
|
+
errors_while_computing = false
|
|
289
|
+
quota_limited = nil
|
|
290
|
+
status_code = nil
|
|
291
|
+
|
|
268
292
|
if fallback_to_server && !only_evaluate_locally
|
|
269
293
|
begin
|
|
270
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]
|
|
271
298
|
|
|
272
299
|
unless flags_and_payloads.key?(:featureFlags)
|
|
273
300
|
raise StandardError, "Error flags response: #{flags_and_payloads}"
|
|
274
301
|
end
|
|
275
302
|
|
|
303
|
+
request_id = flags_and_payloads[:requestId]
|
|
304
|
+
evaluated_at = flags_and_payloads[:evaluatedAt]
|
|
305
|
+
|
|
276
306
|
# Check if feature_flags are quota limited
|
|
277
|
-
if
|
|
307
|
+
if quota_limited&.include?('feature_flags')
|
|
278
308
|
logger.warn '[FEATURE FLAGS] Quota limited for feature flags'
|
|
279
309
|
flags = {}
|
|
280
310
|
payloads = {}
|
|
281
311
|
else
|
|
282
312
|
flags = stringify_keys(flags_and_payloads[:featureFlags] || {})
|
|
283
313
|
payloads = stringify_keys(flags_and_payloads[:featureFlagPayloads] || {})
|
|
284
|
-
request_id = flags_and_payloads[:requestId]
|
|
285
314
|
end
|
|
286
315
|
rescue StandardError => e
|
|
287
316
|
@on_error.call(-1, "Error computing flag remotely: #{e}")
|
|
@@ -292,7 +321,11 @@ module PostHog
|
|
|
292
321
|
{
|
|
293
322
|
featureFlags: flags,
|
|
294
323
|
featureFlagPayloads: payloads,
|
|
295
|
-
requestId: request_id
|
|
324
|
+
requestId: request_id,
|
|
325
|
+
evaluatedAt: evaluated_at,
|
|
326
|
+
errorsWhileComputingFlags: errors_while_computing,
|
|
327
|
+
quotaLimited: quota_limited,
|
|
328
|
+
status: status_code
|
|
296
329
|
}
|
|
297
330
|
end
|
|
298
331
|
|
|
@@ -834,12 +867,20 @@ module PostHog
|
|
|
834
867
|
|
|
835
868
|
def _load_feature_flags
|
|
836
869
|
begin
|
|
837
|
-
res = _request_feature_flag_definitions
|
|
870
|
+
res = _request_feature_flag_definitions(etag: @flags_etag.value)
|
|
838
871
|
rescue StandardError => e
|
|
839
872
|
@on_error.call(-1, e.to_s)
|
|
840
873
|
return
|
|
841
874
|
end
|
|
842
875
|
|
|
876
|
+
# Handle 304 Not Modified - flags haven't changed, skip processing
|
|
877
|
+
# Only update ETag if the 304 response includes one
|
|
878
|
+
if res[:not_modified]
|
|
879
|
+
@flags_etag.value = res[:etag] if res[:etag]
|
|
880
|
+
logger.debug '[FEATURE FLAGS] Flags not modified (304), using cached data'
|
|
881
|
+
return
|
|
882
|
+
end
|
|
883
|
+
|
|
843
884
|
# Handle quota limits with 402 status
|
|
844
885
|
if res.is_a?(Hash) && res[:status] == 402
|
|
845
886
|
logger.warn(
|
|
@@ -856,6 +897,9 @@ module PostHog
|
|
|
856
897
|
end
|
|
857
898
|
|
|
858
899
|
if res.key?(:flags)
|
|
900
|
+
# Only update ETag on successful responses with flag data
|
|
901
|
+
@flags_etag.value = res[:etag]
|
|
902
|
+
|
|
859
903
|
@feature_flags = res[:flags] || []
|
|
860
904
|
@feature_flags_by_key = {}
|
|
861
905
|
@feature_flags.each do |flag|
|
|
@@ -871,13 +915,14 @@ module PostHog
|
|
|
871
915
|
end
|
|
872
916
|
end
|
|
873
917
|
|
|
874
|
-
def _request_feature_flag_definitions
|
|
918
|
+
def _request_feature_flag_definitions(etag: nil)
|
|
875
919
|
uri = URI("#{@host}/api/feature_flag/local_evaluation")
|
|
876
920
|
uri.query = URI.encode_www_form([['token', @project_api_key], %w[send_cohorts true]])
|
|
877
921
|
req = Net::HTTP::Get.new(uri)
|
|
878
922
|
req['Authorization'] = "Bearer #{@personal_api_key}"
|
|
923
|
+
req['If-None-Match'] = etag if etag
|
|
879
924
|
|
|
880
|
-
_request(uri, req)
|
|
925
|
+
_request(uri, req, nil, include_etag: true)
|
|
881
926
|
end
|
|
882
927
|
|
|
883
928
|
def _request_feature_flag_evaluation(data = {})
|
|
@@ -901,7 +946,7 @@ module PostHog
|
|
|
901
946
|
end
|
|
902
947
|
|
|
903
948
|
# rubocop:disable Lint/ShadowedException
|
|
904
|
-
def _request(uri, request_object, timeout = nil)
|
|
949
|
+
def _request(uri, request_object, timeout = nil, include_etag: false)
|
|
905
950
|
request_object['User-Agent'] = "posthog-ruby#{PostHog::VERSION}"
|
|
906
951
|
request_timeout = timeout || 10
|
|
907
952
|
|
|
@@ -913,16 +958,28 @@ module PostHog
|
|
|
913
958
|
read_timeout: request_timeout
|
|
914
959
|
) do |http|
|
|
915
960
|
res = http.request(request_object)
|
|
961
|
+
status_code = res.code.to_i
|
|
962
|
+
etag = include_etag ? res['ETag'] : nil
|
|
963
|
+
|
|
964
|
+
# Handle 304 Not Modified - return special response indicating no change
|
|
965
|
+
if status_code == 304
|
|
966
|
+
logger.debug("#{request_object.method} #{_mask_tokens_in_url(uri.to_s)} returned 304 Not Modified")
|
|
967
|
+
return { not_modified: true, etag: etag, status: status_code }
|
|
968
|
+
end
|
|
916
969
|
|
|
917
970
|
# Parse response body to hash
|
|
918
971
|
begin
|
|
919
972
|
response = JSON.parse(res.body, { symbolize_names: true })
|
|
920
|
-
# Only add status if response is a hash
|
|
921
|
-
|
|
973
|
+
# Only add status (and etag if requested) if response is a hash
|
|
974
|
+
extra_fields = { status: status_code }
|
|
975
|
+
extra_fields[:etag] = etag if include_etag
|
|
976
|
+
response = response.merge(extra_fields) if response.is_a?(Hash)
|
|
922
977
|
return response
|
|
923
978
|
rescue JSON::ParserError
|
|
924
979
|
# Handle case when response isn't valid JSON
|
|
925
|
-
|
|
980
|
+
error_response = { error: 'Invalid JSON response', body: res.body, status: status_code }
|
|
981
|
+
error_response[:etag] = etag if include_etag
|
|
982
|
+
return error_response
|
|
926
983
|
end
|
|
927
984
|
end
|
|
928
985
|
rescue Timeout::Error,
|
|
@@ -939,5 +996,9 @@ module PostHog
|
|
|
939
996
|
end
|
|
940
997
|
end
|
|
941
998
|
# rubocop:enable Lint/ShadowedException
|
|
999
|
+
|
|
1000
|
+
def _mask_tokens_in_url(url)
|
|
1001
|
+
url.gsub(/token=([^&]{10})[^&]*/, 'token=\1...')
|
|
1002
|
+
end
|
|
942
1003
|
end
|
|
943
1004
|
end
|
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: []
|