posthog-ruby 3.20.0 → 3.22.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 +130 -30
- data/lib/posthog/feature_flag_evaluations.rb +12 -1
- data/lib/posthog/feature_flags.rb +22 -5
- data/lib/posthog/flag_definition_cache.rb +8 -6
- data/lib/posthog/send_worker.rb +0 -4
- data/lib/posthog/utils.rb +6 -0
- data/lib/posthog/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b47ba497bca9801a2d3a530351c021a9441368b63c16cebb2e04dcfe7d0d3b21
|
|
4
|
+
data.tar.gz: ab1cb2e8238e5f19e3d9c33d96386c98478e357d7bdb96121d8332910f61477a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc57a456ab1f6ecdce0508ccea5a5896e42901938e8eb8d29b2f8ec07bd1a16c069030feed87aa196a604fffd2e3d0e40999f12434dca839e3aa507f008afdb7
|
|
7
|
+
data.tar.gz: d4c2693b6a8c60706707599d2958cdf24c54aa21f4ee8d067fbdae1cf9624fad08c923d95e67c5a629cd69aaee420e221a1d7edc51422d644bef8e4218d60b66
|
data/lib/posthog/client.rb
CHANGED
|
@@ -22,6 +22,31 @@ module PostHog
|
|
|
22
22
|
include PostHog::Utils
|
|
23
23
|
include PostHog::Logging
|
|
24
24
|
|
|
25
|
+
# Strict allowlist of properties kept on minimal `$feature_flag_called`
|
|
26
|
+
# events (server-gated, non-experiment flags only). Everything else —
|
|
27
|
+
# context properties, `$feature/<key>`, payloads, system metadata — is
|
|
28
|
+
# stripped. Includes symbol forms so allowlisted properties supplied
|
|
29
|
+
# through context with symbol keys survive.
|
|
30
|
+
MINIMAL_FLAG_CALLED_EVENT_PROPERTIES = %w[
|
|
31
|
+
$feature_flag
|
|
32
|
+
$feature_flag_response
|
|
33
|
+
$feature_flag_has_experiment
|
|
34
|
+
$feature_flag_id
|
|
35
|
+
$feature_flag_version
|
|
36
|
+
$feature_flag_reason
|
|
37
|
+
$feature_flag_request_id
|
|
38
|
+
$feature_flag_evaluated_at
|
|
39
|
+
$feature_flag_error
|
|
40
|
+
locally_evaluated
|
|
41
|
+
$groups
|
|
42
|
+
$process_person_profile
|
|
43
|
+
$session_id
|
|
44
|
+
$is_server
|
|
45
|
+
$lib
|
|
46
|
+
$lib_version
|
|
47
|
+
].flat_map { |key| [key, key.to_sym] }.freeze
|
|
48
|
+
private_constant :MINIMAL_FLAG_CALLED_EVENT_PROPERTIES
|
|
49
|
+
|
|
25
50
|
# Thread-safe tracking of client instances per API key for singleton warnings
|
|
26
51
|
@instances_by_api_key = {}
|
|
27
52
|
@instances_mutex = Mutex.new
|
|
@@ -118,7 +143,10 @@ module PostHog
|
|
|
118
143
|
@max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE
|
|
119
144
|
@worker_mutex = Mutex.new
|
|
120
145
|
@shutdown_mutex = Mutex.new
|
|
146
|
+
@shutdown_condition = ConditionVariable.new
|
|
121
147
|
@shutdown = false
|
|
148
|
+
@shutdown_complete = false
|
|
149
|
+
@shutdown_result = false
|
|
122
150
|
@sync_mode = opts[:sync_mode] == true && !opts[:test_mode] && !@disabled
|
|
123
151
|
@on_error = opts[:on_error] || proc { |status, error| }
|
|
124
152
|
@worker = if opts[:test_mode] || @disabled
|
|
@@ -184,29 +212,40 @@ module PostHog
|
|
|
184
212
|
@deprecation_emitted_for = Concurrent::Set.new
|
|
185
213
|
end
|
|
186
214
|
|
|
187
|
-
#
|
|
215
|
+
# When sync_mode is enabled, blocks until in-flight requests are complete
|
|
216
|
+
# and returns true. Timeout parameter has no effect.
|
|
188
217
|
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
218
|
+
# Otherwise, waits until the worker has cleared the queue or timeout is hit.
|
|
219
|
+
# Note: The asynchronous wait polls and can be starved if another thread is
|
|
220
|
+
# actively continuing to enqueue new events.
|
|
191
221
|
#
|
|
192
|
-
# @
|
|
193
|
-
|
|
222
|
+
# @param timeout [Numeric, nil] Maximum seconds to wait for pending events
|
|
223
|
+
# to be sent, or +nil+ to wait indefinitely.
|
|
224
|
+
# @return [Boolean] +true+ if all pending events were sent, +false+ if the
|
|
225
|
+
# timeout elapsed first.
|
|
226
|
+
def flush(timeout: nil)
|
|
194
227
|
if @sync_mode
|
|
195
228
|
# Wait for any in-flight sync send to complete
|
|
196
229
|
@sync_lock.synchronize {} # rubocop:disable Lint/EmptyBlock
|
|
197
|
-
return
|
|
230
|
+
return true
|
|
198
231
|
end
|
|
199
232
|
|
|
200
233
|
if @worker.is_a?(NoopWorker)
|
|
201
234
|
clear
|
|
202
|
-
return
|
|
235
|
+
return true
|
|
203
236
|
end
|
|
204
237
|
|
|
238
|
+
deadline = timeout && (monotonic_time + timeout)
|
|
239
|
+
|
|
205
240
|
while !@queue.empty? || @worker.is_requesting?
|
|
206
241
|
ensure_worker_running
|
|
207
242
|
@worker.request_flush
|
|
208
|
-
|
|
243
|
+
remaining = deadline && (deadline - monotonic_time)
|
|
244
|
+
return false if remaining && remaining <= 0
|
|
245
|
+
|
|
246
|
+
sleep(remaining ? remaining.clamp(0, 0.1) : 0.1)
|
|
209
247
|
end
|
|
248
|
+
true
|
|
210
249
|
end
|
|
211
250
|
|
|
212
251
|
# Clears the queue without waiting.
|
|
@@ -253,6 +292,7 @@ module PostHog
|
|
|
253
292
|
return false if @disabled
|
|
254
293
|
|
|
255
294
|
symbolize_keys! attrs
|
|
295
|
+
minimal_flag_called_event = attrs.delete(:_minimal_flag_called_event) == true
|
|
256
296
|
enrich_capture_attrs_with_context(attrs)
|
|
257
297
|
|
|
258
298
|
# Precedence: an explicit `flags` snapshot always wins, regardless of
|
|
@@ -321,7 +361,13 @@ module PostHog
|
|
|
321
361
|
end
|
|
322
362
|
|
|
323
363
|
attrs[:is_server] = @is_server
|
|
324
|
-
|
|
364
|
+
message = FieldParser.parse_for_capture(attrs)
|
|
365
|
+
# Minimal events are built from the allowlist after full assembly so
|
|
366
|
+
# context properties and parser-added metadata can never leak in.
|
|
367
|
+
if minimal_flag_called_event
|
|
368
|
+
message[:properties] = message[:properties].slice(*MINIMAL_FLAG_CALLED_EVENT_PROPERTIES)
|
|
369
|
+
end
|
|
370
|
+
enqueue(message)
|
|
325
371
|
end
|
|
326
372
|
|
|
327
373
|
# Captures an exception as an event
|
|
@@ -627,6 +673,11 @@ module PostHog
|
|
|
627
673
|
evaluated_at = nil
|
|
628
674
|
errors_while_computing = false
|
|
629
675
|
quota_limited = false
|
|
676
|
+
# Server-controlled gate for minimal `$feature_flag_called` events. When
|
|
677
|
+
# the snapshot uses a remote /flags response, the response's top-level
|
|
678
|
+
# `minimalFlagCalledEvents` field governs; a local-only snapshot reads
|
|
679
|
+
# the gate polled with the flag definitions.
|
|
680
|
+
minimal_flag_called_events = @feature_flags_poller.minimal_flag_called_events
|
|
630
681
|
|
|
631
682
|
# Skip the remote `/flags` round-trip when the caller scoped the request
|
|
632
683
|
# to a fixed set of `flag_keys` and we've already resolved every one of
|
|
@@ -635,10 +686,16 @@ module PostHog
|
|
|
635
686
|
all_requested_flags_resolved_locally = flag_keys_set && (flag_keys_set - locally_evaluated_keys).empty?
|
|
636
687
|
|
|
637
688
|
if !only_evaluate_locally && !all_requested_flags_resolved_locally
|
|
689
|
+
# The gate is team-level, so the /flags response gate supersedes the
|
|
690
|
+
# poller gate for mixed snapshots — both signals come from the same
|
|
691
|
+
# server and agree in steady state. When the response omits the gate,
|
|
692
|
+
# the whole snapshot fails safe to full events.
|
|
693
|
+
minimal_flag_called_events = false
|
|
638
694
|
begin
|
|
639
695
|
flags_response = @feature_flags_poller.get_flags(
|
|
640
696
|
distinct_id, groups, person_properties, group_properties, flag_keys, disable_geoip
|
|
641
697
|
)
|
|
698
|
+
minimal_flag_called_events = flags_response[:minimalFlagCalledEvents] == true
|
|
642
699
|
request_id = flags_response[:requestId]
|
|
643
700
|
evaluated_at = flags_response[:evaluatedAt]
|
|
644
701
|
errors_while_computing = flags_response[:errorsWhileComputingFlags] == true
|
|
@@ -677,7 +734,8 @@ module PostHog
|
|
|
677
734
|
evaluated_at: evaluated_at,
|
|
678
735
|
flag_definitions_loaded_at: @feature_flags_poller.flag_definitions_loaded_at,
|
|
679
736
|
errors_while_computing: errors_while_computing,
|
|
680
|
-
quota_limited: quota_limited
|
|
737
|
+
quota_limited: quota_limited,
|
|
738
|
+
minimal_flag_called_events: minimal_flag_called_events
|
|
681
739
|
)
|
|
682
740
|
end
|
|
683
741
|
|
|
@@ -777,6 +835,7 @@ module PostHog
|
|
|
777
835
|
response.delete(:requestId)
|
|
778
836
|
response.delete(:evaluatedAt)
|
|
779
837
|
response.delete(:flagDetails)
|
|
838
|
+
response.delete(:minimalFlagCalledEvents)
|
|
780
839
|
response
|
|
781
840
|
end
|
|
782
841
|
|
|
@@ -817,29 +876,65 @@ module PostHog
|
|
|
817
876
|
|
|
818
877
|
# Flush pending events and stop background resources.
|
|
819
878
|
#
|
|
820
|
-
#
|
|
821
|
-
|
|
822
|
-
|
|
879
|
+
# When sync_mode is not set, this method calls the flush method; see flush
|
|
880
|
+
# method documentation for timeout semantics. Unlike flush, this method
|
|
881
|
+
# stops the worker. So any events still queued after the timeout will not be
|
|
882
|
+
# sent. After the timeout, this method may wait up to one additional second
|
|
883
|
+
# for worker and transport cleanup, which may continue after it returns.
|
|
884
|
+
#
|
|
885
|
+
# @param timeout [Numeric, nil] Maximum seconds to wait for pending events
|
|
886
|
+
# to be sent, or +nil+ to wait indefinitely. Has no effect when sync_mode
|
|
887
|
+
# is true.
|
|
888
|
+
# @return [Boolean] +true+ if all pending events were sent, +false+ if the
|
|
889
|
+
# timeout elapsed first.
|
|
890
|
+
def shutdown(timeout: nil)
|
|
891
|
+
deadline = timeout && (monotonic_time + timeout)
|
|
892
|
+
shutdown_result = @shutdown_mutex.synchronize do
|
|
823
893
|
if @shutdown
|
|
824
|
-
|
|
894
|
+
until @shutdown_complete
|
|
895
|
+
# Avoid deadlocking when an in-flight send's on_error callback
|
|
896
|
+
# re-enters shutdown while the original caller is waiting for it.
|
|
897
|
+
break if Thread.current == @worker_thread || (@sync_mode && @sync_lock.owned?)
|
|
898
|
+
|
|
899
|
+
remaining = deadline && (deadline - monotonic_time)
|
|
900
|
+
break if remaining && remaining <= 0
|
|
901
|
+
|
|
902
|
+
@shutdown_condition.wait(@shutdown_mutex, remaining)
|
|
903
|
+
end
|
|
904
|
+
@shutdown_complete ? @shutdown_result : false
|
|
825
905
|
else
|
|
826
906
|
@shutdown = true
|
|
827
|
-
|
|
907
|
+
nil
|
|
828
908
|
end
|
|
829
909
|
end
|
|
830
|
-
return
|
|
910
|
+
return shutdown_result unless shutdown_result.nil?
|
|
831
911
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
912
|
+
flushed = false
|
|
913
|
+
begin
|
|
914
|
+
self.class._decrement_instance_count(@api_key) unless @disabled
|
|
915
|
+
@feature_flags_poller&.shutdown_poller
|
|
916
|
+
flushed =
|
|
917
|
+
if @sync_mode
|
|
918
|
+
# Waiting for @sync_lock lets any in-flight sync send finish before
|
|
919
|
+
# the connection is closed.
|
|
920
|
+
@sync_lock.synchronize { @transport&.shutdown }
|
|
921
|
+
true
|
|
922
|
+
else
|
|
923
|
+
drained = flush(timeout: timeout)
|
|
924
|
+
@worker&.shutdown
|
|
925
|
+
@worker_thread&.join(1)
|
|
926
|
+
drained
|
|
927
|
+
end
|
|
928
|
+
@distinct_id_has_sent_flag_calls_mutex.synchronize do
|
|
929
|
+
@distinct_id_has_sent_flag_calls.clear
|
|
930
|
+
end
|
|
931
|
+
flushed
|
|
932
|
+
ensure
|
|
933
|
+
@shutdown_mutex.synchronize do
|
|
934
|
+
@shutdown_result = flushed
|
|
935
|
+
@shutdown_complete = true
|
|
936
|
+
@shutdown_condition.broadcast
|
|
937
|
+
end
|
|
843
938
|
end
|
|
844
939
|
end
|
|
845
940
|
|
|
@@ -885,7 +980,7 @@ module PostHog
|
|
|
885
980
|
# separate event for each group a user is evaluated under.
|
|
886
981
|
def _capture_feature_flag_called_if_needed(
|
|
887
982
|
distinct_id: nil, key: nil, response: nil, properties: nil,
|
|
888
|
-
groups: nil, disable_geoip: nil
|
|
983
|
+
groups: nil, disable_geoip: nil, minimal: false
|
|
889
984
|
)
|
|
890
985
|
response_repr = response.nil? ? '::null::' : response
|
|
891
986
|
groups_repr =
|
|
@@ -915,6 +1010,7 @@ module PostHog
|
|
|
915
1010
|
}
|
|
916
1011
|
msg[:groups] = groups if groups
|
|
917
1012
|
msg[:disable_geoip] = disable_geoip unless disable_geoip.nil?
|
|
1013
|
+
msg[:_minimal_flag_called_event] = true if minimal
|
|
918
1014
|
|
|
919
1015
|
capture(msg)
|
|
920
1016
|
end
|
|
@@ -945,7 +1041,7 @@ module PostHog
|
|
|
945
1041
|
groups, person_properties, group_properties
|
|
946
1042
|
)
|
|
947
1043
|
feature_flag_response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload,
|
|
948
|
-
has_experiment =
|
|
1044
|
+
has_experiment, minimal_flag_called_events =
|
|
949
1045
|
@feature_flags_poller.get_feature_flag(
|
|
950
1046
|
key, distinct_id, groups, person_properties, group_properties, only_evaluate_locally
|
|
951
1047
|
)
|
|
@@ -960,9 +1056,13 @@ module PostHog
|
|
|
960
1056
|
properties['$feature_flag_evaluated_at'] = evaluated_at if evaluated_at
|
|
961
1057
|
properties['$feature_flag_error'] = feature_flag_error if feature_flag_error
|
|
962
1058
|
|
|
1059
|
+
# Emit a minimal event only when the server gate is on and the flag is
|
|
1060
|
+
# known to have no linked experiment. Any missing signal fails safe to
|
|
1061
|
+
# the full event.
|
|
1062
|
+
minimal = minimal_flag_called_events == true && has_experiment == false
|
|
963
1063
|
_capture_feature_flag_called_if_needed(
|
|
964
1064
|
distinct_id: distinct_id, key: key, response: feature_flag_response,
|
|
965
|
-
properties: properties, groups: groups
|
|
1065
|
+
properties: properties, groups: groups, minimal: minimal
|
|
966
1066
|
)
|
|
967
1067
|
end
|
|
968
1068
|
|
|
@@ -44,6 +44,8 @@ module PostHog
|
|
|
44
44
|
# @param flag_definitions_loaded_at [Time, nil] When local flag definitions were loaded.
|
|
45
45
|
# @param errors_while_computing [Boolean] Whether the server reported errors while computing flags.
|
|
46
46
|
# @param quota_limited [Boolean] Whether feature flag evaluation was quota limited.
|
|
47
|
+
# @param minimal_flag_called_events [Boolean] Server-controlled gate for minimal
|
|
48
|
+
# `$feature_flag_called` events.
|
|
47
49
|
# @param accessed [Array<String>, Set<String>, nil] Flag keys already accessed by this snapshot.
|
|
48
50
|
def initialize(
|
|
49
51
|
host: nil,
|
|
@@ -56,6 +58,7 @@ module PostHog
|
|
|
56
58
|
flag_definitions_loaded_at: nil,
|
|
57
59
|
errors_while_computing: false,
|
|
58
60
|
quota_limited: false,
|
|
61
|
+
minimal_flag_called_events: false,
|
|
59
62
|
accessed: nil
|
|
60
63
|
)
|
|
61
64
|
@host = host
|
|
@@ -68,6 +71,7 @@ module PostHog
|
|
|
68
71
|
@flag_definitions_loaded_at = flag_definitions_loaded_at
|
|
69
72
|
@errors_while_computing = errors_while_computing
|
|
70
73
|
@quota_limited = quota_limited
|
|
74
|
+
@minimal_flag_called_events = minimal_flag_called_events == true
|
|
71
75
|
@accessed = Set.new(accessed || [])
|
|
72
76
|
end
|
|
73
77
|
|
|
@@ -188,13 +192,19 @@ module PostHog
|
|
|
188
192
|
errors << 'flag_missing' if flag.nil?
|
|
189
193
|
properties['$feature_flag_error'] = errors.join(',') unless errors.empty?
|
|
190
194
|
|
|
195
|
+
# Emit a minimal event only when the server gate is on and the flag is
|
|
196
|
+
# known to have no linked experiment. Any missing signal (unknown flag,
|
|
197
|
+
# has_experiment absent) fails safe to the full event.
|
|
198
|
+
minimal = @minimal_flag_called_events && !flag.nil? && flag.has_experiment == false
|
|
199
|
+
|
|
191
200
|
@host.capture_flag_called_event_if_needed.call(
|
|
192
201
|
distinct_id: @distinct_id,
|
|
193
202
|
key: key,
|
|
194
203
|
response: response,
|
|
195
204
|
properties: properties,
|
|
196
205
|
groups: @groups,
|
|
197
|
-
disable_geoip: @disable_geoip
|
|
206
|
+
disable_geoip: @disable_geoip,
|
|
207
|
+
minimal: minimal
|
|
198
208
|
)
|
|
199
209
|
end
|
|
200
210
|
|
|
@@ -210,6 +220,7 @@ module PostHog
|
|
|
210
220
|
flag_definitions_loaded_at: @flag_definitions_loaded_at,
|
|
211
221
|
errors_while_computing: @errors_while_computing,
|
|
212
222
|
quota_limited: @quota_limited,
|
|
223
|
+
minimal_flag_called_events: @minimal_flag_called_events,
|
|
213
224
|
accessed: @accessed.dup
|
|
214
225
|
)
|
|
215
226
|
end
|
|
@@ -72,7 +72,10 @@ module PostHog
|
|
|
72
72
|
@flags_etag = Concurrent::AtomicReference.new(nil)
|
|
73
73
|
@flag_definitions_loaded_at = Concurrent::AtomicReference.new(nil)
|
|
74
74
|
@async_load = async_load
|
|
75
|
-
|
|
75
|
+
# Server-controlled gate for minimal `$feature_flag_called` events, read
|
|
76
|
+
# from the top-level `minimal_flag_called_events` key of the local
|
|
77
|
+
# evaluation definitions payload. false when the server does not send it.
|
|
78
|
+
@minimal_flag_called_events = false
|
|
76
79
|
@flag_definition_cache_provider = flag_definition_cache_provider
|
|
77
80
|
FlagDefinitionCacheProvider.validate!(@flag_definition_cache_provider) if @flag_definition_cache_provider
|
|
78
81
|
|
|
@@ -111,7 +114,7 @@ module PostHog
|
|
|
111
114
|
@flag_definitions_loaded_at.value
|
|
112
115
|
end
|
|
113
116
|
|
|
114
|
-
attr_reader :feature_flags_by_key
|
|
117
|
+
attr_reader :feature_flags_by_key, :minimal_flag_called_events
|
|
115
118
|
|
|
116
119
|
def get_feature_variants(
|
|
117
120
|
distinct_id,
|
|
@@ -245,6 +248,11 @@ module PostHog
|
|
|
245
248
|
# evaluated flags carry it in the response metadata. nil when the server
|
|
246
249
|
# (an older deployment) does not report it.
|
|
247
250
|
has_experiment = feature_flag[:has_experiment] if flag_was_locally_evaluated
|
|
251
|
+
# Server-controlled gate for minimal `$feature_flag_called` events.
|
|
252
|
+
# Locally-evaluated flags read it from the definitions payload; remotely
|
|
253
|
+
# evaluated flags read it from the /flags response. nil when the signal
|
|
254
|
+
# is unavailable, which fails safe to the full event.
|
|
255
|
+
minimal_flag_called_events = @minimal_flag_called_events if flag_was_locally_evaluated
|
|
248
256
|
|
|
249
257
|
request_id = nil
|
|
250
258
|
evaluated_at = nil
|
|
@@ -279,6 +287,7 @@ module PostHog
|
|
|
279
287
|
|
|
280
288
|
flag_detail = flags_data[:flagDetails]&.[](key.to_sym)
|
|
281
289
|
has_experiment = flag_detail&.metadata&.has_experiment
|
|
290
|
+
minimal_flag_called_events = flags_data[:minimalFlagCalledEvents]
|
|
282
291
|
|
|
283
292
|
logger.debug "Successfully computed flag remotely: #{key} -> #{response}"
|
|
284
293
|
rescue Timeout::Error => e
|
|
@@ -293,7 +302,8 @@ module PostHog
|
|
|
293
302
|
end
|
|
294
303
|
end
|
|
295
304
|
|
|
296
|
-
[response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload, has_experiment
|
|
305
|
+
[response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload, has_experiment,
|
|
306
|
+
minimal_flag_called_events]
|
|
297
307
|
end
|
|
298
308
|
|
|
299
309
|
def get_all_flags(
|
|
@@ -352,6 +362,7 @@ module PostHog
|
|
|
352
362
|
quota_limited = nil
|
|
353
363
|
status_code = nil
|
|
354
364
|
flag_details = nil
|
|
365
|
+
minimal_flag_called_events = nil
|
|
355
366
|
|
|
356
367
|
if fallback_to_server && !only_evaluate_locally
|
|
357
368
|
begin
|
|
@@ -367,6 +378,7 @@ module PostHog
|
|
|
367
378
|
|
|
368
379
|
request_id = flags_and_payloads[:requestId]
|
|
369
380
|
evaluated_at = flags_and_payloads[:evaluatedAt]
|
|
381
|
+
minimal_flag_called_events = flags_and_payloads[:minimalFlagCalledEvents]
|
|
370
382
|
|
|
371
383
|
# Check if feature_flags are quota limited
|
|
372
384
|
if quota_limited&.include?('feature_flags')
|
|
@@ -402,7 +414,8 @@ module PostHog
|
|
|
402
414
|
evaluatedAt: evaluated_at,
|
|
403
415
|
errorsWhileComputingFlags: errors_while_computing,
|
|
404
416
|
quotaLimited: quota_limited,
|
|
405
|
-
status: status_code
|
|
417
|
+
status: status_code,
|
|
418
|
+
minimalFlagCalledEvents: minimal_flag_called_events
|
|
406
419
|
}
|
|
407
420
|
end
|
|
408
421
|
|
|
@@ -1203,6 +1216,7 @@ module PostHog
|
|
|
1203
1216
|
@group_type_mapping = Concurrent::Hash.new
|
|
1204
1217
|
@cohorts = Concurrent::Hash.new
|
|
1205
1218
|
@flag_definitions_loaded_at.value = nil
|
|
1219
|
+
@minimal_flag_called_events = false
|
|
1206
1220
|
@loaded_flags_successfully_once.make_false
|
|
1207
1221
|
@quota_limited.make_true
|
|
1208
1222
|
return
|
|
@@ -1226,7 +1240,8 @@ module PostHog
|
|
|
1226
1240
|
data = {
|
|
1227
1241
|
flags: @feature_flags.to_a,
|
|
1228
1242
|
group_type_mapping: @group_type_mapping.to_h,
|
|
1229
|
-
cohorts: @cohorts.to_h
|
|
1243
|
+
cohorts: @cohorts.to_h,
|
|
1244
|
+
minimal_flag_called_events: @minimal_flag_called_events
|
|
1230
1245
|
}
|
|
1231
1246
|
@flag_definition_cache_provider.on_flag_definitions_received(data)
|
|
1232
1247
|
rescue StandardError => e
|
|
@@ -1238,6 +1253,7 @@ module PostHog
|
|
|
1238
1253
|
flags = get_by_symbol_or_string_key(data, 'flags') || []
|
|
1239
1254
|
group_type_mapping = get_by_symbol_or_string_key(data, 'group_type_mapping') || {}
|
|
1240
1255
|
cohorts = get_by_symbol_or_string_key(data, 'cohorts') || {}
|
|
1256
|
+
minimal_flag_called_events = get_by_symbol_or_string_key(data, 'minimal_flag_called_events')
|
|
1241
1257
|
|
|
1242
1258
|
@feature_flags = Concurrent::Array.new(flags.map { |f| deep_symbolize_keys(f) })
|
|
1243
1259
|
|
|
@@ -1249,6 +1265,7 @@ module PostHog
|
|
|
1249
1265
|
|
|
1250
1266
|
@group_type_mapping = Concurrent::Hash[deep_symbolize_keys(group_type_mapping)]
|
|
1251
1267
|
@cohorts = Concurrent::Hash[deep_symbolize_keys(cohorts)]
|
|
1268
|
+
@minimal_flag_called_events = minimal_flag_called_events == true
|
|
1252
1269
|
|
|
1253
1270
|
logger.debug "Loaded #{@feature_flags.length} feature flags and #{@cohorts.length} cohorts"
|
|
1254
1271
|
@flag_definitions_loaded_at.value = (Time.now.to_f * 1000).to_i
|
|
@@ -14,9 +14,11 @@ module PostHog
|
|
|
14
14
|
#
|
|
15
15
|
# @!method flag_definitions
|
|
16
16
|
# Retrieve cached flag definitions. Return a Hash with +:flags+,
|
|
17
|
-
# +:group_type_mapping+,
|
|
18
|
-
# is empty. Returning +nil+ triggers an API
|
|
19
|
-
# loaded yet (emergency fallback).
|
|
17
|
+
# +:group_type_mapping+, +:cohorts+, and +:minimal_flag_called_events+
|
|
18
|
+
# keys, or +nil+ if the cache is empty. Returning +nil+ triggers an API
|
|
19
|
+
# fetch when no flags are loaded yet (emergency fallback). Providers
|
|
20
|
+
# written before +:minimal_flag_called_events+ existed continue to work;
|
|
21
|
+
# a missing key is treated as +false+.
|
|
20
22
|
# @return [Hash, nil]
|
|
21
23
|
#
|
|
22
24
|
# @!method should_fetch_flag_definitions?
|
|
@@ -27,9 +29,9 @@ module PostHog
|
|
|
27
29
|
#
|
|
28
30
|
# @!method on_flag_definitions_received(data)
|
|
29
31
|
# Called after successfully fetching new definitions from the API.
|
|
30
|
-
# +data+ is a Hash with +:flags+, +:group_type_mapping+,
|
|
31
|
-
# keys (plain Ruby types, not Concurrent::
|
|
32
|
-
# external cache.
|
|
32
|
+
# +data+ is a Hash with +:flags+, +:group_type_mapping+, +:cohorts+, and
|
|
33
|
+
# +:minimal_flag_called_events+ keys (plain Ruby types, not Concurrent::
|
|
34
|
+
# wrappers). Store it in your external cache.
|
|
33
35
|
# @param data [Hash]
|
|
34
36
|
# @return [void]
|
|
35
37
|
#
|
data/lib/posthog/send_worker.rb
CHANGED
data/lib/posthog/utils.rb
CHANGED
|
@@ -136,6 +136,12 @@ module PostHog
|
|
|
136
136
|
end
|
|
137
137
|
end
|
|
138
138
|
|
|
139
|
+
# public: Current monotonic clock time in seconds, for measuring durations
|
|
140
|
+
#
|
|
141
|
+
def monotonic_time
|
|
142
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
143
|
+
end
|
|
144
|
+
|
|
139
145
|
# Hash that clears itself when it reaches a maximum length.
|
|
140
146
|
#
|
|
141
147
|
# @api private
|
data/lib/posthog/version.rb
CHANGED