posthog-ruby 2.9.0 → 2.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9be467263d4138e0b223e18281a9b0af56351fbf9d807b01bf4ff8f87d004a7
4
- data.tar.gz: 1c4a9990d2958023b7db53511e64aa90ade5364d06e42e23b2b981d6acb59ba2
3
+ metadata.gz: d987590608ff1c705f8f419d43cb6ef439233f8b3dbc5e3ac84bd64b3d552d9a
4
+ data.tar.gz: d4ae4f458a8a9faa654519b958cc20f828638ead4696dbc8a8c03ed198e44606
5
5
  SHA512:
6
- metadata.gz: abde921140981a63fb82677e5024c4fcce79f9e560a870b0fd91114eaff0df1af0a5908742fbab3dee612d1d1a866998e9f1d10a587484e4baaf06b2f7cafd45
7
- data.tar.gz: 7d88779cd59527ba500d8a2dcee0692a7135da88910d8045b276f32b635caee887b3fe2382a85fd6408b0e8cd57c58e0ca84495c99f912763a13f2b62e01dc78
6
+ metadata.gz: a9671f5f0fb37ae15a605b1118f09a526fa383aa129335a78a85f8b54c772e8c28ade9e3fd06bf36e142f7a994eca33400965add6c1e2ceebf3b8fcc32d8df23
7
+ data.tar.gz: 5fd8756e6c1f20bb0df3c40e8e72aa9c9d79dedd2bedb0c53930254d8a58417b75c38867f9e2e673bd0a53968cb7beded76cc6be18760d3fe8755d33cc75cd1b
data/bin/posthog CHANGED
@@ -11,7 +11,7 @@ program :version, '1.0.0'
11
11
  program :description, 'PostHog API'
12
12
 
13
13
  def json_hash(str)
14
- return JSON.parse(str) if str
14
+ JSON.parse(str) if str
15
15
  end
16
16
 
17
17
  command :capture do |c|
@@ -27,13 +27,13 @@ command :capture do |c|
27
27
  c.option '--event=<event>', String, 'The event name to send with the event'
28
28
  c.option '--properties=<properties>', 'The properties to send (JSON-encoded)'
29
29
 
30
- c.action do |args, options|
30
+ c.action do |_args, options|
31
31
  posthog =
32
32
  PostHog::Client.new(
33
33
  {
34
34
  api_key: options.api_key,
35
35
  api_host: options.api_host,
36
- on_error: Proc.new { |status, msg| print msg }
36
+ on_error: proc { |_status, msg| print msg }
37
37
  }
38
38
  )
39
39
 
@@ -61,13 +61,13 @@ command :identify do |c|
61
61
  'The distinct id to send the event as'
62
62
  c.option '--properties=<properties>', 'The properties to send (JSON-encoded)'
63
63
 
64
- c.action do |args, options|
64
+ c.action do |_args, options|
65
65
  posthog =
66
66
  PostHog::Client.new(
67
67
  {
68
68
  api_key: options.api_key,
69
69
  api_host: options.api_host,
70
- on_error: Proc.new { |status, msg| print msg }
70
+ on_error: proc { |_status, msg| print msg }
71
71
  }
72
72
  )
73
73
 
@@ -92,13 +92,13 @@ command :alias do |c|
92
92
  c.option '--distinct-id=<distinct_id>', String, 'The distinct id'
93
93
  c.option '--alias=<alias>', 'The alias to give to the distinct id'
94
94
 
95
- c.action do |args, options|
95
+ c.action do |_args, options|
96
96
  posthog =
97
97
  PostHog::Client.new(
98
98
  {
99
99
  api_key: options.api_key,
100
100
  api_host: options.api_host,
101
- on_error: Proc.new { |status, msg| print msg }
101
+ on_error: proc { |_status, msg| print msg }
102
102
  }
103
103
  )
104
104
 
@@ -22,8 +22,10 @@ class PostHog
22
22
  # queued for testing. Defaults to +false+.
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
- # @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.
25
+ # @option opts [Integer] :feature_flags_polling_interval How often to poll for feature flag definition changes.
26
+ # Measured in seconds, defaults to 30.
27
+ # @option opts [Integer] :feature_flag_request_timeout_seconds How long to wait for feature flag evaluation.
28
+ # Measured in seconds, defaults to 3.
27
29
  def initialize(opts = {})
28
30
  symbolize_keys!(opts)
29
31
 
@@ -34,10 +36,10 @@ class PostHog
34
36
  @max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE
35
37
  @worker_mutex = Mutex.new
36
38
  @worker = if opts[:test_mode]
37
- NoopWorker.new(@queue)
38
- else
39
- SendWorker.new(@queue, @api_key, opts)
40
- end
39
+ NoopWorker.new(@queue)
40
+ else
41
+ SendWorker.new(@queue, @api_key, opts)
42
+ end
41
43
  @worker_thread = nil
42
44
  @feature_flags_poller = nil
43
45
  @personal_api_key = opts[:personal_api_key]
@@ -53,8 +55,10 @@ class PostHog
53
55
  opts[:feature_flag_request_timeout_seconds] || Defaults::FeatureFlags::FLAG_REQUEST_TIMEOUT_SECONDS,
54
56
  opts[:on_error]
55
57
  )
56
-
57
- @distinct_id_has_sent_flag_calls = SizeLimitedHash.new(Defaults::MAX_HASH_SIZE) { |hash, key| hash[key] = Array.new }
58
+
59
+ @distinct_id_has_sent_flag_calls = SizeLimitedHash.new(Defaults::MAX_HASH_SIZE) do |hash, key|
60
+ hash[key] = []
61
+ end
58
62
  end
59
63
 
60
64
  # Synchronously waits until the worker has cleared the queue.
@@ -88,6 +92,10 @@ class PostHog
88
92
  # @option attrs [String] :event Event name
89
93
  # @option attrs [Hash] :properties Event properties (optional)
90
94
  # @option attrs [Bool] :send_feature_flags Whether to send feature flags with this event (optional)
95
+ # @option attrs [String] :uuid ID that uniquely identifies an event;
96
+ # events in PostHog are deduplicated by the
97
+ # combination of teamId, timestamp date,
98
+ # event name, distinct id, and UUID
91
99
  # @macro common_attrs
92
100
  def capture(attrs)
93
101
  symbolize_keys! attrs
@@ -147,18 +155,34 @@ class PostHog
147
155
  @queue.length
148
156
  end
149
157
 
150
- def is_feature_enabled(flag_key, distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false, send_feature_flag_events: true)
151
- response = get_feature_flag(flag_key, distinct_id, groups: groups, person_properties: person_properties, group_properties: group_properties, only_evaluate_locally: only_evaluate_locally, send_feature_flag_events: send_feature_flag_events)
152
- if response.nil?
153
- return nil
154
- end
158
+ # TODO: In future version, rename to `feature_flag_enabled?`
159
+ def is_feature_enabled( # rubocop:disable Naming/PredicateName
160
+ flag_key,
161
+ distinct_id,
162
+ groups: {},
163
+ person_properties: {},
164
+ group_properties: {},
165
+ only_evaluate_locally: false,
166
+ send_feature_flag_events: true
167
+ )
168
+ response = get_feature_flag(
169
+ flag_key,
170
+ distinct_id,
171
+ groups: groups,
172
+ person_properties: person_properties,
173
+ group_properties: group_properties,
174
+ only_evaluate_locally: only_evaluate_locally,
175
+ send_feature_flag_events: send_feature_flag_events
176
+ )
177
+ return nil if response.nil?
178
+
155
179
  !!response
156
180
  end
157
181
 
158
182
  # @param [String] flag_key The unique flag key of the feature flag
159
183
  # @return [String] The decrypted value of the feature flag payload
160
184
  def get_remote_config_payload(flag_key)
161
- return @feature_flags_poller.get_remote_config_payload(flag_key)
185
+ @feature_flags_poller.get_remote_config_payload(flag_key)
162
186
  end
163
187
 
164
188
  # Returns whether the given feature flag is enabled for the given user or not
@@ -168,35 +192,56 @@ class PostHog
168
192
  # @param [Hash] groups
169
193
  # @param [Hash] person_properties key-value pairs of properties to associate with the user.
170
194
  # @param [Hash] group_properties
171
- #
195
+ #
172
196
  # @return [String, nil] The value of the feature flag
173
197
  #
174
198
  # The provided properties are used to calculate feature flags locally, if possible.
175
199
  #
176
- # `groups` are a mapping from group type to group key. So, if you have a group type of "organization" and a group key of "5",
200
+ # `groups` are a mapping from group type to group key. So, if you have a group type of "organization"
201
+ # and a group key of "5",
177
202
  # you would pass groups={"organization": "5"}.
178
203
  # `group_properties` take the format: { group_type_name: { group_properties } }
179
- # So, for example, if you have the group type "organization" and the group key "5", with the properties name, and employee count,
180
- # you'll send these as:
204
+ # So, for example, if you have the group type "organization" and the group key "5", with the properties name,
205
+ # and employee count, you'll send these as:
181
206
  # ```ruby
182
207
  # group_properties: {"organization": {"name": "PostHog", "employees": 11}}
183
208
  # ```
184
- def get_feature_flag(key, distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false, send_feature_flag_events: true)
185
- person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups, person_properties, group_properties)
186
- feature_flag_response, flag_was_locally_evaluated, request_id = @feature_flags_poller.get_feature_flag(key, distinct_id, groups, person_properties, group_properties, only_evaluate_locally)
209
+ def get_feature_flag(
210
+ key,
211
+ distinct_id,
212
+ groups: {},
213
+ person_properties: {},
214
+ group_properties: {},
215
+ only_evaluate_locally: false,
216
+ send_feature_flag_events: true
217
+ )
218
+ person_properties, group_properties = add_local_person_and_group_properties(
219
+ distinct_id,
220
+ groups,
221
+ person_properties,
222
+ group_properties
223
+ )
224
+ feature_flag_response, flag_was_locally_evaluated, request_id = @feature_flags_poller.get_feature_flag(
225
+ key,
226
+ distinct_id,
227
+ groups,
228
+ person_properties,
229
+ group_properties,
230
+ only_evaluate_locally
231
+ )
187
232
 
188
233
  feature_flag_reported_key = "#{key}_#{feature_flag_response}"
189
234
  if !@distinct_id_has_sent_flag_calls[distinct_id].include?(feature_flag_reported_key) && send_feature_flag_events
190
235
  capture(
191
236
  {
192
- 'distinct_id': distinct_id,
193
- 'event': '$feature_flag_called',
194
- 'properties': {
237
+ distinct_id: distinct_id,
238
+ event: '$feature_flag_called',
239
+ properties: {
195
240
  '$feature_flag' => key,
196
241
  '$feature_flag_response' => feature_flag_response,
197
242
  'locally_evaluated' => flag_was_locally_evaluated
198
- }.merge(request_id ? {'$feature_flag_request_id' => request_id} : {}),
199
- 'groups': groups,
243
+ }.merge(request_id ? { '$feature_flag_request_id' => request_id } : {}),
244
+ groups: groups
200
245
  }
201
246
  )
202
247
  @distinct_id_has_sent_flag_calls[distinct_id] << feature_flag_reported_key
@@ -210,11 +255,19 @@ class PostHog
210
255
  # @param [Hash] groups
211
256
  # @param [Hash] person_properties key-value pairs of properties to associate with the user.
212
257
  # @param [Hash] group_properties
213
- #
258
+ #
214
259
  # @return [Hash] String (not symbol) key value pairs of flag and their values
215
- def get_all_flags(distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false)
216
- person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups, person_properties, group_properties)
217
- return @feature_flags_poller.get_all_flags(distinct_id, groups, person_properties, group_properties, only_evaluate_locally)
260
+ def get_all_flags(
261
+ distinct_id,
262
+ groups: {},
263
+ person_properties: {},
264
+ group_properties: {},
265
+ only_evaluate_locally: false
266
+ )
267
+ person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups,
268
+ person_properties, group_properties)
269
+ @feature_flags_poller.get_all_flags(distinct_id, groups, person_properties, group_properties,
270
+ only_evaluate_locally)
218
271
  end
219
272
 
220
273
  # Returns payload for a given feature flag
@@ -227,13 +280,23 @@ class PostHog
227
280
  # @option [Hash] group_properties
228
281
  # @option [Boolean] only_evaluate_locally
229
282
  #
230
- def get_feature_flag_payload(key, distinct_id, match_value: nil, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false)
231
- person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups, person_properties, group_properties)
232
- @feature_flags_poller.get_feature_flag_payload(key, distinct_id, match_value, groups, person_properties, group_properties, only_evaluate_locally)
283
+ def get_feature_flag_payload(
284
+ key,
285
+ distinct_id,
286
+ match_value: nil,
287
+ groups: {},
288
+ person_properties: {},
289
+ group_properties: {},
290
+ only_evaluate_locally: false
291
+ )
292
+ person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups,
293
+ person_properties, group_properties)
294
+ @feature_flags_poller.get_feature_flag_payload(key, distinct_id, match_value, groups, person_properties,
295
+ group_properties, only_evaluate_locally)
233
296
  end
234
297
 
235
298
  # Returns all flags and payloads for a given user
236
- #
299
+ #
237
300
  # @return [Hash] A hash with the following keys:
238
301
  # featureFlags: A hash of feature flags
239
302
  # featureFlagPayloads: A hash of feature flag payloads
@@ -244,9 +307,20 @@ class PostHog
244
307
  # @option [Hash] group_properties
245
308
  # @option [Boolean] only_evaluate_locally
246
309
  #
247
- def get_all_flags_and_payloads(distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false)
248
- person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups, person_properties, group_properties)
249
- response = @feature_flags_poller.get_all_flags_and_payloads(distinct_id, groups, person_properties, group_properties, only_evaluate_locally)
310
+ def get_all_flags_and_payloads(
311
+ distinct_id,
312
+ groups: {},
313
+ person_properties: {},
314
+ group_properties: {},
315
+ only_evaluate_locally: false
316
+ )
317
+ person_properties, group_properties = add_local_person_and_group_properties(
318
+ distinct_id, groups, person_properties, group_properties
319
+ )
320
+ response = @feature_flags_poller.get_all_flags_and_payloads(
321
+ distinct_id, groups, person_properties, group_properties, only_evaluate_locally
322
+ )
323
+
250
324
  response.delete(:requestId) # remove internal information.
251
325
  response
252
326
  end
@@ -283,8 +357,8 @@ class PostHog
283
357
  else
284
358
  logger.warn(
285
359
  'Queue is full, dropping events. The :max_queue_size ' \
286
- 'configuration parameter can be increased to prevent this from ' \
287
- 'happening.'
360
+ 'configuration parameter can be increased to prevent this from ' \
361
+ 'happening.'
288
362
  )
289
363
  false
290
364
  end
@@ -297,8 +371,10 @@ class PostHog
297
371
 
298
372
  def ensure_worker_running
299
373
  return if worker_running?
374
+
300
375
  @worker_mutex.synchronize do
301
376
  return if worker_running?
377
+
302
378
  @worker_thread = Thread.new { @worker.run }
303
379
  end
304
380
  end
@@ -308,7 +384,6 @@ class PostHog
308
384
  end
309
385
 
310
386
  def add_local_person_and_group_properties(distinct_id, groups, person_properties, group_properties)
311
-
312
387
  groups ||= {}
313
388
  person_properties ||= {}
314
389
  group_properties ||= {}
@@ -317,21 +392,22 @@ class PostHog
317
392
  symbolize_keys! person_properties
318
393
  symbolize_keys! group_properties
319
394
 
320
- group_properties.each do |key, value|
395
+ group_properties.each_value do |value|
321
396
  symbolize_keys! value
322
397
  end
323
398
 
324
- all_person_properties = { "distinct_id" => distinct_id }.merge(person_properties)
399
+ all_person_properties = { distinct_id: distinct_id }.merge(person_properties)
325
400
 
326
401
  all_group_properties = {}
327
402
  if groups
328
403
  groups.each do |group_name, group_key|
329
404
  all_group_properties[group_name] = {
330
- "$group_key" => group_key}.merge(group_properties&.dig(group_name) || {})
405
+ :'$group_key' => group_key
406
+ }.merge((group_properties && group_properties[group_name]) || {})
331
407
  end
332
408
  end
333
409
 
334
- return all_person_properties, all_group_properties
410
+ [all_person_properties, all_group_properties]
335
411
  end
336
412
  end
337
413
  end
@@ -1,25 +1,24 @@
1
1
  class PostHog
2
2
  module Defaults
3
-
4
3
  MAX_HASH_SIZE = 50_000
5
4
 
6
5
  module Request
7
- HOST = 'app.posthog.com'
6
+ HOST = 'app.posthog.com'.freeze
8
7
  PORT = 443
9
- PATH = '/batch/'
8
+ PATH = '/batch/'.freeze
10
9
  SSL = true
11
10
  HEADERS = {
12
11
  'Accept' => 'application/json',
13
12
  'Content-Type' => 'application/json',
14
13
  'User-Agent' => "posthog-ruby/#{PostHog::VERSION}"
15
- }
14
+ }.freeze
16
15
  RETRIES = 10
17
16
  end
18
17
 
19
18
  module FeatureFlags
20
19
  FLAG_REQUEST_TIMEOUT_SECONDS = 3
21
20
  end
22
-
21
+
23
22
  module Queue
24
23
  MAX_SIZE = 10_000
25
24
  end
@@ -4,34 +4,35 @@ class FeatureFlag
4
4
 
5
5
  def initialize(json)
6
6
  json.transform_keys!(&:to_s)
7
- @key = json["key"]
8
- @enabled = json["enabled"]
9
- @variant = json["variant"]
10
- @reason = json["reason"] ? EvaluationReason.new(json["reason"]) : nil
11
- @metadata = json["metadata"] ? FeatureFlagMetadata.new(json["metadata"].transform_keys(&:to_s)) : nil
7
+ @key = json['key']
8
+ @enabled = json['enabled']
9
+ @variant = json['variant']
10
+ @reason = json['reason'] ? EvaluationReason.new(json['reason']) : nil
11
+ @metadata = json['metadata'] ? FeatureFlagMetadata.new(json['metadata'].transform_keys(&:to_s)) : nil
12
12
  end
13
13
 
14
- def get_value
14
+ # TODO: Rename to `value` in future version
15
+ def get_value # rubocop:disable Naming/AccessorMethodName
15
16
  @variant || @enabled
16
17
  end
17
18
 
18
19
  def payload
19
- @metadata&.payload
20
+ @metadata.payload if @metadata
20
21
  end
21
22
 
22
23
  def self.from_value_and_payload(key, value, payload)
23
24
  new({
24
- "key" => key,
25
- "enabled" => value.is_a?(String) ? true : value,
26
- "variant" => value.is_a?(String) ? value : nil,
27
- "reason" => nil,
28
- "metadata" => {
29
- "id" => nil,
30
- "version" => nil,
31
- "payload" => payload,
32
- "description" => nil
33
- }
34
- })
25
+ 'key' => key,
26
+ 'enabled' => value.is_a?(String) || value,
27
+ 'variant' => value.is_a?(String) ? value : nil,
28
+ 'reason' => nil,
29
+ 'metadata' => {
30
+ 'id' => nil,
31
+ 'version' => nil,
32
+ 'payload' => payload,
33
+ 'description' => nil
34
+ }
35
+ })
35
36
  end
36
37
  end
37
38
 
@@ -41,9 +42,9 @@ class EvaluationReason
41
42
 
42
43
  def initialize(json)
43
44
  json.transform_keys!(&:to_s)
44
- @code = json["code"]
45
- @description = json["description"]
46
- @condition_index = json["condition_index"]&.to_i if json["condition_index"]
45
+ @code = json['code']
46
+ @description = json['description']
47
+ @condition_index = json['condition_index'].to_i if json['condition_index']
47
48
  end
48
49
  end
49
50
 
@@ -53,9 +54,9 @@ class FeatureFlagMetadata
53
54
 
54
55
  def initialize(json)
55
56
  json.transform_keys!(&:to_s)
56
- @id = json["id"]
57
- @version = json["version"]
58
- @payload = json["payload"]
59
- @description = json["description"]
57
+ @id = json['id']
58
+ @version = json['version']
59
+ @payload = json['payload']
60
+ @description = json['description']
60
61
  end
61
- end
62
+ end