posthog-ruby 3.19.0 → 3.21.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 +77 -7
- data/lib/posthog/defaults.rb +1 -0
- data/lib/posthog/feature_flag_evaluations.rb +12 -1
- data/lib/posthog/feature_flags.rb +50 -13
- data/lib/posthog/flag_definition_cache.rb +8 -6
- 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: 4ae3a78450df91e2e4cb88cd69a58e167cdb291dcc02f8ab446e3b0975c270eb
|
|
4
|
+
data.tar.gz: b912b023ed02a37004cfbe22511437ee4df2a5ec63799d6d47e6f6aa9fc4f164
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 42b8b2587ee825e9a5446def45d11940b52a3a2203828b4506b2a4e1c28e2deefd61406df6ab33800bf1055c1475130554d6edef55a1498b478c43a30591f7b0
|
|
7
|
+
data.tar.gz: 4eec6bdd11abbeee85720d2a68872ad366f39b5c9c28ce72fd96aaf54f3098218578ccac954e35fe01099975d6f5a2afcfe5016b4460520a6341e465f026ed99
|
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
|
|
@@ -76,6 +101,13 @@ module PostHog
|
|
|
76
101
|
# @option opts [Integer] :feature_flag_request_max_retries How many times to retry a flag request after a
|
|
77
102
|
# transient network error. Each retry sleeps on the calling thread before retrying, so this adds to
|
|
78
103
|
# worst-case latency. Defaults to 1. Set to 0 to disable retrying.
|
|
104
|
+
# @option opts [Boolean] :feature_flags_async_load +true+ to fetch feature flag definitions for local
|
|
105
|
+
# evaluation only on the poller's background thread instead of the calling thread. The constructor
|
|
106
|
+
# returns without waiting for definitions: the poller fetches immediately on boot and, if that fails,
|
|
107
|
+
# keeps retrying on its regular polling interval until a load succeeds. Until then local evaluation
|
|
108
|
+
# treats definitions as absent (check {#feature_flags_loaded?}), so evaluations return nil with
|
|
109
|
+
# +only_evaluate_locally: true+, or fall back to the remote flags endpoint (which still runs on the
|
|
110
|
+
# calling thread) without it. Defaults to +false+.
|
|
79
111
|
# @option opts [Proc] :before_send A callback that receives the event hash and should return either a modified
|
|
80
112
|
# hash to be sent to PostHog or nil to prevent the event from being sent. e.g. `before_send: ->(event) { event }`.
|
|
81
113
|
# @option opts [Boolean] :disable_singleton_warning +true+ to suppress the warning when multiple clients share
|
|
@@ -162,7 +194,8 @@ module PostHog
|
|
|
162
194
|
opts[:feature_flag_request_timeout_seconds] || Defaults::FeatureFlags::FLAG_REQUEST_TIMEOUT_SECONDS,
|
|
163
195
|
opts[:on_error],
|
|
164
196
|
flag_definition_cache_provider: opts[:flag_definition_cache_provider],
|
|
165
|
-
feature_flag_request_max_retries: opts[:feature_flag_request_max_retries]
|
|
197
|
+
feature_flag_request_max_retries: opts[:feature_flag_request_max_retries],
|
|
198
|
+
async_load: opts[:feature_flags_async_load] == true
|
|
166
199
|
)
|
|
167
200
|
end
|
|
168
201
|
|
|
@@ -245,6 +278,7 @@ module PostHog
|
|
|
245
278
|
return false if @disabled
|
|
246
279
|
|
|
247
280
|
symbolize_keys! attrs
|
|
281
|
+
minimal_flag_called_event = attrs.delete(:_minimal_flag_called_event) == true
|
|
248
282
|
enrich_capture_attrs_with_context(attrs)
|
|
249
283
|
|
|
250
284
|
# Precedence: an explicit `flags` snapshot always wins, regardless of
|
|
@@ -313,7 +347,13 @@ module PostHog
|
|
|
313
347
|
end
|
|
314
348
|
|
|
315
349
|
attrs[:is_server] = @is_server
|
|
316
|
-
|
|
350
|
+
message = FieldParser.parse_for_capture(attrs)
|
|
351
|
+
# Minimal events are built from the allowlist after full assembly so
|
|
352
|
+
# context properties and parser-added metadata can never leak in.
|
|
353
|
+
if minimal_flag_called_event
|
|
354
|
+
message[:properties] = message[:properties].slice(*MINIMAL_FLAG_CALLED_EVENT_PROPERTIES)
|
|
355
|
+
end
|
|
356
|
+
enqueue(message)
|
|
317
357
|
end
|
|
318
358
|
|
|
319
359
|
# Captures an exception as an event
|
|
@@ -619,6 +659,11 @@ module PostHog
|
|
|
619
659
|
evaluated_at = nil
|
|
620
660
|
errors_while_computing = false
|
|
621
661
|
quota_limited = false
|
|
662
|
+
# Server-controlled gate for minimal `$feature_flag_called` events. When
|
|
663
|
+
# the snapshot uses a remote /flags response, the response's top-level
|
|
664
|
+
# `minimalFlagCalledEvents` field governs; a local-only snapshot reads
|
|
665
|
+
# the gate polled with the flag definitions.
|
|
666
|
+
minimal_flag_called_events = @feature_flags_poller.minimal_flag_called_events
|
|
622
667
|
|
|
623
668
|
# Skip the remote `/flags` round-trip when the caller scoped the request
|
|
624
669
|
# to a fixed set of `flag_keys` and we've already resolved every one of
|
|
@@ -627,10 +672,16 @@ module PostHog
|
|
|
627
672
|
all_requested_flags_resolved_locally = flag_keys_set && (flag_keys_set - locally_evaluated_keys).empty?
|
|
628
673
|
|
|
629
674
|
if !only_evaluate_locally && !all_requested_flags_resolved_locally
|
|
675
|
+
# The gate is team-level, so the /flags response gate supersedes the
|
|
676
|
+
# poller gate for mixed snapshots — both signals come from the same
|
|
677
|
+
# server and agree in steady state. When the response omits the gate,
|
|
678
|
+
# the whole snapshot fails safe to full events.
|
|
679
|
+
minimal_flag_called_events = false
|
|
630
680
|
begin
|
|
631
681
|
flags_response = @feature_flags_poller.get_flags(
|
|
632
682
|
distinct_id, groups, person_properties, group_properties, flag_keys, disable_geoip
|
|
633
683
|
)
|
|
684
|
+
minimal_flag_called_events = flags_response[:minimalFlagCalledEvents] == true
|
|
634
685
|
request_id = flags_response[:requestId]
|
|
635
686
|
evaluated_at = flags_response[:evaluatedAt]
|
|
636
687
|
errors_while_computing = flags_response[:errorsWhileComputingFlags] == true
|
|
@@ -669,7 +720,8 @@ module PostHog
|
|
|
669
720
|
evaluated_at: evaluated_at,
|
|
670
721
|
flag_definitions_loaded_at: @feature_flags_poller.flag_definitions_loaded_at,
|
|
671
722
|
errors_while_computing: errors_while_computing,
|
|
672
|
-
quota_limited: quota_limited
|
|
723
|
+
quota_limited: quota_limited,
|
|
724
|
+
minimal_flag_called_events: minimal_flag_called_events
|
|
673
725
|
)
|
|
674
726
|
end
|
|
675
727
|
|
|
@@ -769,10 +821,12 @@ module PostHog
|
|
|
769
821
|
response.delete(:requestId)
|
|
770
822
|
response.delete(:evaluatedAt)
|
|
771
823
|
response.delete(:flagDetails)
|
|
824
|
+
response.delete(:minimalFlagCalledEvents)
|
|
772
825
|
response
|
|
773
826
|
end
|
|
774
827
|
|
|
775
|
-
# Reload locally cached feature flag definitions
|
|
828
|
+
# Reload locally cached feature flag definitions synchronously on the
|
|
829
|
+
# calling thread.
|
|
776
830
|
#
|
|
777
831
|
# @return [void]
|
|
778
832
|
def reload_feature_flags
|
|
@@ -787,6 +841,17 @@ module PostHog
|
|
|
787
841
|
@feature_flags_poller.load_feature_flags(true)
|
|
788
842
|
end
|
|
789
843
|
|
|
844
|
+
# Whether feature flag definitions for local evaluation are currently loaded.
|
|
845
|
+
# False until the first successful load, and false again if a quota-limited
|
|
846
|
+
# (402) response discards the definitions.
|
|
847
|
+
#
|
|
848
|
+
# @return [Boolean]
|
|
849
|
+
def feature_flags_loaded?
|
|
850
|
+
return false if @disabled
|
|
851
|
+
|
|
852
|
+
@feature_flags_poller.definitions_loaded?
|
|
853
|
+
end
|
|
854
|
+
|
|
790
855
|
# Whether the client will actually send events. It is disabled when the
|
|
791
856
|
# api_key is missing or blank, in which case every capture call no-ops.
|
|
792
857
|
#
|
|
@@ -865,7 +930,7 @@ module PostHog
|
|
|
865
930
|
# separate event for each group a user is evaluated under.
|
|
866
931
|
def _capture_feature_flag_called_if_needed(
|
|
867
932
|
distinct_id: nil, key: nil, response: nil, properties: nil,
|
|
868
|
-
groups: nil, disable_geoip: nil
|
|
933
|
+
groups: nil, disable_geoip: nil, minimal: false
|
|
869
934
|
)
|
|
870
935
|
response_repr = response.nil? ? '::null::' : response
|
|
871
936
|
groups_repr =
|
|
@@ -895,6 +960,7 @@ module PostHog
|
|
|
895
960
|
}
|
|
896
961
|
msg[:groups] = groups if groups
|
|
897
962
|
msg[:disable_geoip] = disable_geoip unless disable_geoip.nil?
|
|
963
|
+
msg[:_minimal_flag_called_event] = true if minimal
|
|
898
964
|
|
|
899
965
|
capture(msg)
|
|
900
966
|
end
|
|
@@ -925,7 +991,7 @@ module PostHog
|
|
|
925
991
|
groups, person_properties, group_properties
|
|
926
992
|
)
|
|
927
993
|
feature_flag_response, flag_was_locally_evaluated, request_id, evaluated_at, feature_flag_error, payload,
|
|
928
|
-
has_experiment =
|
|
994
|
+
has_experiment, minimal_flag_called_events =
|
|
929
995
|
@feature_flags_poller.get_feature_flag(
|
|
930
996
|
key, distinct_id, groups, person_properties, group_properties, only_evaluate_locally
|
|
931
997
|
)
|
|
@@ -940,9 +1006,13 @@ module PostHog
|
|
|
940
1006
|
properties['$feature_flag_evaluated_at'] = evaluated_at if evaluated_at
|
|
941
1007
|
properties['$feature_flag_error'] = feature_flag_error if feature_flag_error
|
|
942
1008
|
|
|
1009
|
+
# Emit a minimal event only when the server gate is on and the flag is
|
|
1010
|
+
# known to have no linked experiment. Any missing signal fails safe to
|
|
1011
|
+
# the full event.
|
|
1012
|
+
minimal = minimal_flag_called_events == true && has_experiment == false
|
|
943
1013
|
_capture_feature_flag_called_if_needed(
|
|
944
1014
|
distinct_id: distinct_id, key: key, response: feature_flag_response,
|
|
945
|
-
properties: properties, groups: groups
|
|
1015
|
+
properties: properties, groups: groups, minimal: minimal
|
|
946
1016
|
)
|
|
947
1017
|
end
|
|
948
1018
|
|
data/lib/posthog/defaults.rb
CHANGED
|
@@ -19,6 +19,7 @@ module PostHog
|
|
|
19
19
|
|
|
20
20
|
module FeatureFlags
|
|
21
21
|
FLAG_REQUEST_TIMEOUT_SECONDS = 3
|
|
22
|
+
POLLING_INTERVAL_SECONDS = 30
|
|
22
23
|
# Number of retries for a flag request after a transient network error.
|
|
23
24
|
# Flag requests are stateless and cause no server-side mutation, so
|
|
24
25
|
# retrying is safe.
|
|
@@ -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
|
|
@@ -30,6 +30,7 @@ module PostHog
|
|
|
30
30
|
include PostHog::Utils
|
|
31
31
|
|
|
32
32
|
# @param polling_interval [Integer, nil] Seconds between local feature flag definition polls.
|
|
33
|
+
# Defaults to {Defaults::FeatureFlags::POLLING_INTERVAL_SECONDS}.
|
|
33
34
|
# @param secret_key [String, nil] Credential used to fetch local evaluation definitions. Accepts either a
|
|
34
35
|
# Personal API Key (`phx_...`) or a Project Secret API Key (`phs_...`).
|
|
35
36
|
# @param project_api_key [String] Project API key.
|
|
@@ -40,6 +41,9 @@ module PostHog
|
|
|
40
41
|
# @param feature_flag_request_max_retries [Integer, nil] Retries after a transient network error or retryable
|
|
41
42
|
# HTTP response status on a flag request. Defaults to {Defaults::FeatureFlags::FLAG_REQUEST_MAX_RETRIES}.
|
|
42
43
|
# Set to 0 to disable retrying.
|
|
44
|
+
# @param async_load [Boolean] When true, flag definitions are fetched only on the poller thread: an
|
|
45
|
+
# immediate first tick at construction, then the regular polling cadence, which keeps retrying until a
|
|
46
|
+
# load succeeds.
|
|
43
47
|
def initialize(
|
|
44
48
|
polling_interval,
|
|
45
49
|
secret_key,
|
|
@@ -48,9 +52,10 @@ module PostHog
|
|
|
48
52
|
feature_flag_request_timeout_seconds,
|
|
49
53
|
on_error = nil,
|
|
50
54
|
flag_definition_cache_provider: nil,
|
|
51
|
-
feature_flag_request_max_retries: nil
|
|
55
|
+
feature_flag_request_max_retries: nil,
|
|
56
|
+
async_load: false
|
|
52
57
|
)
|
|
53
|
-
@polling_interval = polling_interval ||
|
|
58
|
+
@polling_interval = polling_interval || Defaults::FeatureFlags::POLLING_INTERVAL_SECONDS
|
|
54
59
|
@secret_key = secret_key
|
|
55
60
|
@project_api_key = project_api_key
|
|
56
61
|
@host = host
|
|
@@ -65,14 +70,19 @@ module PostHog
|
|
|
65
70
|
@on_error = on_error || proc { |status, error| }
|
|
66
71
|
@quota_limited = Concurrent::AtomicBoolean.new(false)
|
|
67
72
|
@flags_etag = Concurrent::AtomicReference.new(nil)
|
|
68
|
-
@flag_definitions_loaded_at = nil
|
|
69
|
-
|
|
73
|
+
@flag_definitions_loaded_at = Concurrent::AtomicReference.new(nil)
|
|
74
|
+
@async_load = async_load
|
|
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
|
|
70
79
|
@flag_definition_cache_provider = flag_definition_cache_provider
|
|
71
80
|
FlagDefinitionCacheProvider.validate!(@flag_definition_cache_provider) if @flag_definition_cache_provider
|
|
72
81
|
|
|
73
82
|
@task =
|
|
74
83
|
Concurrent::TimerTask.new(
|
|
75
|
-
execution_interval: polling_interval
|
|
84
|
+
execution_interval: @polling_interval,
|
|
85
|
+
run_now: @async_load
|
|
76
86
|
) { _load_feature_flags }
|
|
77
87
|
|
|
78
88
|
# If no secret_key, disable local evaluation & thus polling for definitions
|
|
@@ -80,19 +90,31 @@ module PostHog
|
|
|
80
90
|
logger.info 'No secret_key provided, disabling local evaluation'
|
|
81
91
|
@loaded_flags_successfully_once.make_true
|
|
82
92
|
else
|
|
83
|
-
# load once before timer
|
|
84
|
-
load_feature_flags
|
|
93
|
+
# load once synchronously before timer, unless @async_load
|
|
94
|
+
load_feature_flags unless @async_load
|
|
85
95
|
@task.execute
|
|
86
96
|
end
|
|
87
97
|
end
|
|
88
98
|
|
|
89
99
|
def load_feature_flags(force_reload = false)
|
|
90
|
-
return
|
|
100
|
+
return if (@loaded_flags_successfully_once.true? || @async_load) && !force_reload
|
|
91
101
|
|
|
92
102
|
_load_feature_flags
|
|
93
103
|
end
|
|
94
104
|
|
|
95
|
-
|
|
105
|
+
# Whether flag definitions are currently loaded. False until the first
|
|
106
|
+
# successful load, and false again if a quota-limited (402) response
|
|
107
|
+
# discards them.
|
|
108
|
+
def definitions_loaded?
|
|
109
|
+
!@flag_definitions_loaded_at.value.nil?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Epoch milliseconds of the last successful definitions load, or nil.
|
|
113
|
+
def flag_definitions_loaded_at
|
|
114
|
+
@flag_definitions_loaded_at.value
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
attr_reader :feature_flags_by_key, :minimal_flag_called_events
|
|
96
118
|
|
|
97
119
|
def get_feature_variants(
|
|
98
120
|
distinct_id,
|
|
@@ -226,6 +248,11 @@ module PostHog
|
|
|
226
248
|
# evaluated flags carry it in the response metadata. nil when the server
|
|
227
249
|
# (an older deployment) does not report it.
|
|
228
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
|
|
229
256
|
|
|
230
257
|
request_id = nil
|
|
231
258
|
evaluated_at = nil
|
|
@@ -260,6 +287,7 @@ module PostHog
|
|
|
260
287
|
|
|
261
288
|
flag_detail = flags_data[:flagDetails]&.[](key.to_sym)
|
|
262
289
|
has_experiment = flag_detail&.metadata&.has_experiment
|
|
290
|
+
minimal_flag_called_events = flags_data[:minimalFlagCalledEvents]
|
|
263
291
|
|
|
264
292
|
logger.debug "Successfully computed flag remotely: #{key} -> #{response}"
|
|
265
293
|
rescue Timeout::Error => e
|
|
@@ -274,7 +302,8 @@ module PostHog
|
|
|
274
302
|
end
|
|
275
303
|
end
|
|
276
304
|
|
|
277
|
-
[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]
|
|
278
307
|
end
|
|
279
308
|
|
|
280
309
|
def get_all_flags(
|
|
@@ -333,6 +362,7 @@ module PostHog
|
|
|
333
362
|
quota_limited = nil
|
|
334
363
|
status_code = nil
|
|
335
364
|
flag_details = nil
|
|
365
|
+
minimal_flag_called_events = nil
|
|
336
366
|
|
|
337
367
|
if fallback_to_server && !only_evaluate_locally
|
|
338
368
|
begin
|
|
@@ -348,6 +378,7 @@ module PostHog
|
|
|
348
378
|
|
|
349
379
|
request_id = flags_and_payloads[:requestId]
|
|
350
380
|
evaluated_at = flags_and_payloads[:evaluatedAt]
|
|
381
|
+
minimal_flag_called_events = flags_and_payloads[:minimalFlagCalledEvents]
|
|
351
382
|
|
|
352
383
|
# Check if feature_flags are quota limited
|
|
353
384
|
if quota_limited&.include?('feature_flags')
|
|
@@ -383,7 +414,8 @@ module PostHog
|
|
|
383
414
|
evaluatedAt: evaluated_at,
|
|
384
415
|
errorsWhileComputingFlags: errors_while_computing,
|
|
385
416
|
quotaLimited: quota_limited,
|
|
386
|
-
status: status_code
|
|
417
|
+
status: status_code,
|
|
418
|
+
minimalFlagCalledEvents: minimal_flag_called_events
|
|
387
419
|
}
|
|
388
420
|
end
|
|
389
421
|
|
|
@@ -1183,6 +1215,8 @@ module PostHog
|
|
|
1183
1215
|
@feature_flags_by_key = {}
|
|
1184
1216
|
@group_type_mapping = Concurrent::Hash.new
|
|
1185
1217
|
@cohorts = Concurrent::Hash.new
|
|
1218
|
+
@flag_definitions_loaded_at.value = nil
|
|
1219
|
+
@minimal_flag_called_events = false
|
|
1186
1220
|
@loaded_flags_successfully_once.make_false
|
|
1187
1221
|
@quota_limited.make_true
|
|
1188
1222
|
return
|
|
@@ -1206,7 +1240,8 @@ module PostHog
|
|
|
1206
1240
|
data = {
|
|
1207
1241
|
flags: @feature_flags.to_a,
|
|
1208
1242
|
group_type_mapping: @group_type_mapping.to_h,
|
|
1209
|
-
cohorts: @cohorts.to_h
|
|
1243
|
+
cohorts: @cohorts.to_h,
|
|
1244
|
+
minimal_flag_called_events: @minimal_flag_called_events
|
|
1210
1245
|
}
|
|
1211
1246
|
@flag_definition_cache_provider.on_flag_definitions_received(data)
|
|
1212
1247
|
rescue StandardError => e
|
|
@@ -1218,6 +1253,7 @@ module PostHog
|
|
|
1218
1253
|
flags = get_by_symbol_or_string_key(data, 'flags') || []
|
|
1219
1254
|
group_type_mapping = get_by_symbol_or_string_key(data, 'group_type_mapping') || {}
|
|
1220
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')
|
|
1221
1257
|
|
|
1222
1258
|
@feature_flags = Concurrent::Array.new(flags.map { |f| deep_symbolize_keys(f) })
|
|
1223
1259
|
|
|
@@ -1229,9 +1265,10 @@ module PostHog
|
|
|
1229
1265
|
|
|
1230
1266
|
@group_type_mapping = Concurrent::Hash[deep_symbolize_keys(group_type_mapping)]
|
|
1231
1267
|
@cohorts = Concurrent::Hash[deep_symbolize_keys(cohorts)]
|
|
1268
|
+
@minimal_flag_called_events = minimal_flag_called_events == true
|
|
1232
1269
|
|
|
1233
1270
|
logger.debug "Loaded #{@feature_flags.length} feature flags and #{@cohorts.length} cohorts"
|
|
1234
|
-
@flag_definitions_loaded_at = (Time.now.to_f * 1000).to_i
|
|
1271
|
+
@flag_definitions_loaded_at.value = (Time.now.to_f * 1000).to_i
|
|
1235
1272
|
@loaded_flags_successfully_once.make_true if @loaded_flags_successfully_once.false?
|
|
1236
1273
|
end
|
|
1237
1274
|
|
|
@@ -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/version.rb
CHANGED