posthog-ruby 2.10.0 → 3.0.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: d987590608ff1c705f8f419d43cb6ef439233f8b3dbc5e3ac84bd64b3d552d9a
4
- data.tar.gz: d4ae4f458a8a9faa654519b958cc20f828638ead4696dbc8a8c03ed198e44606
3
+ metadata.gz: 253c223f00642a02c72d074b6fbf7a523d5799b2a03eed42794e3ab03d6d9c82
4
+ data.tar.gz: e6c509257a8b09588a3cfd4081ef79184150d28e1d9d9b41a189364c9758e99a
5
5
  SHA512:
6
- metadata.gz: a9671f5f0fb37ae15a605b1118f09a526fa383aa129335a78a85f8b54c772e8c28ade9e3fd06bf36e142f7a994eca33400965add6c1e2ceebf3b8fcc32d8df23
7
- data.tar.gz: 5fd8756e6c1f20bb0df3c40e8e72aa9c9d79dedd2bedb0c53930254d8a58417b75c38867f9e2e673bd0a53968cb7beded76cc6be18760d3fe8755d33cc75cd1b
6
+ metadata.gz: 824adb5f3f57fbc404876cf8a3a05736ee1b9d5e42114f5a51cfd6d833a35ddff0a45eb9010aaa2744d1c3ea5e150807a2b05d4e88ed7883d579d9debd71a53f
7
+ data.tar.gz: d48f1843633e2a8ff16a5f17a9ada51d431a1219ca91b273e72872e8e6acae2f0d409252247c1ec0b7c165ea11fe844313dbad57f16322de20c0bf53651cf6fb
data/bin/posthog CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'posthog'
4
5
  require 'rubygems'
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'posthog/defaults'
2
4
 
3
- class PostHog
5
+ module PostHog
4
6
  class BackoffPolicy
5
7
  include PostHog::Defaults::BackoffPolicy
6
8
 
@@ -1,4 +1,5 @@
1
- require 'thread'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'time'
3
4
 
4
5
  require 'posthog/defaults'
@@ -8,7 +9,7 @@ require 'posthog/send_worker'
8
9
  require 'posthog/noop_worker'
9
10
  require 'posthog/feature_flags'
10
11
 
11
- class PostHog
12
+ module PostHog
12
13
  class Client
13
14
  include PostHog::Utils
14
15
  include PostHog::Logging
@@ -26,6 +27,8 @@ class PostHog
26
27
  # Measured in seconds, defaults to 30.
27
28
  # @option opts [Integer] :feature_flag_request_timeout_seconds How long to wait for feature flag evaluation.
28
29
  # Measured in seconds, defaults to 3.
30
+ # @option opts [Proc] :before_send A block that receives the event hash and should return either a modified hash
31
+ # to be sent to PostHog or nil to prevent the event from being sent. e.g. `before_send: ->(event) { event }`
29
32
  def initialize(opts = {})
30
33
  symbolize_keys!(opts)
31
34
 
@@ -59,6 +62,8 @@ class PostHog
59
62
  @distinct_id_has_sent_flag_calls = SizeLimitedHash.new(Defaults::MAX_HASH_SIZE) do |hash, key|
60
63
  hash[key] = []
61
64
  end
65
+
66
+ @before_send = opts[:before_send]
62
67
  end
63
68
 
64
69
  # Synchronously waits until the worker has cleared the queue.
@@ -342,10 +347,36 @@ class PostHog
342
347
 
343
348
  private
344
349
 
350
+ # before_send should run immediately before the event is sent to the queue.
351
+ # @param [Object] action The event to be sent to PostHog
352
+ # @return [null, Object, nil] The processed event or nil if the event should not be sent
353
+ def process_before_send(action)
354
+ return action if action.nil? || action.empty?
355
+ return action unless @before_send
356
+
357
+ begin
358
+ processed_action = @before_send.call(action)
359
+
360
+ if processed_action.nil?
361
+ logger.warn("Event #{action[:event]} was rejected in beforeSend function")
362
+ elsif processed_action.empty?
363
+ logger.warn("Event #{action[:event]} has no properties after beforeSend function, this is likely an error")
364
+ end
365
+
366
+ processed_action
367
+ rescue StandardError => e
368
+ logger.error("Error in beforeSend function - using original event: #{e.message}")
369
+ action
370
+ end
371
+ end
372
+
345
373
  # private: Enqueues the action.
346
374
  #
347
375
  # returns Boolean of whether the item was added to the queue.
348
376
  def enqueue(action)
377
+ action = process_before_send(action)
378
+ return false if action.nil? || action.empty?
379
+
349
380
  # add our request id for tracing purposes
350
381
  action[:messageId] ||= uid
351
382
 
@@ -380,7 +411,7 @@ class PostHog
380
411
  end
381
412
 
382
413
  def worker_running?
383
- @worker_thread && @worker_thread.alive?
414
+ @worker_thread&.alive?
384
415
  end
385
416
 
386
417
  def add_local_person_and_group_properties(distinct_id, groups, person_properties, group_properties)
@@ -399,12 +430,10 @@ class PostHog
399
430
  all_person_properties = { distinct_id: distinct_id }.merge(person_properties)
400
431
 
401
432
  all_group_properties = {}
402
- if groups
403
- groups.each do |group_name, group_key|
404
- all_group_properties[group_name] = {
405
- :'$group_key' => group_key
406
- }.merge((group_properties && group_properties[group_name]) || {})
407
- end
433
+ groups&.each do |group_name, group_key|
434
+ all_group_properties[group_name] = {
435
+ '$group_key': group_key
436
+ }.merge((group_properties && group_properties[group_name]) || {})
408
437
  end
409
438
 
410
439
  [all_person_properties, all_group_properties]
@@ -1,11 +1,13 @@
1
- class PostHog
1
+ # frozen_string_literal: true
2
+
3
+ module PostHog
2
4
  module Defaults
3
5
  MAX_HASH_SIZE = 50_000
4
6
 
5
7
  module Request
6
- HOST = 'app.posthog.com'.freeze
8
+ HOST = 'app.posthog.com'
7
9
  PORT = 443
8
- PATH = '/batch/'.freeze
10
+ PATH = '/batch/'
9
11
  SSL = true
10
12
  HEADERS = {
11
13
  'Accept' => 'application/json',
@@ -1,62 +1,66 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Represents a feature flag returned by /flags v2
2
- class FeatureFlag
3
- attr_reader :key, :enabled, :variant, :reason, :metadata
4
-
5
- def initialize(json)
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
12
- end
4
+ module PostHog
5
+ class FeatureFlag
6
+ attr_reader :key, :enabled, :variant, :reason, :metadata
13
7
 
14
- # TODO: Rename to `value` in future version
15
- def get_value # rubocop:disable Naming/AccessorMethodName
16
- @variant || @enabled
17
- end
8
+ def initialize(json)
9
+ json.transform_keys!(&:to_s)
10
+ @key = json['key']
11
+ @enabled = json['enabled']
12
+ @variant = json['variant']
13
+ @reason = json['reason'] ? EvaluationReason.new(json['reason']) : nil
14
+ @metadata = json['metadata'] ? FeatureFlagMetadata.new(json['metadata'].transform_keys(&:to_s)) : nil
15
+ end
18
16
 
19
- def payload
20
- @metadata.payload if @metadata
21
- end
17
+ # TODO: Rename to `value` in future version
18
+ def get_value # rubocop:disable Naming/AccessorMethodName
19
+ @variant || @enabled
20
+ end
22
21
 
23
- def self.from_value_and_payload(key, value, payload)
24
- new({
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
- })
22
+ def payload
23
+ @metadata&.payload
24
+ end
25
+
26
+ def self.from_value_and_payload(key, value, payload)
27
+ new({
28
+ 'key' => key,
29
+ 'enabled' => value.is_a?(String) || value,
30
+ 'variant' => value.is_a?(String) ? value : nil,
31
+ 'reason' => nil,
32
+ 'metadata' => {
33
+ 'id' => nil,
34
+ 'version' => nil,
35
+ 'payload' => payload,
36
+ 'description' => nil
37
+ }
38
+ })
39
+ end
36
40
  end
37
- end
38
41
 
39
- # Represents the reason why a flag was enabled/disabled
40
- class EvaluationReason
41
- attr_reader :code, :description, :condition_index
42
+ # Represents the reason why a flag was enabled/disabled
43
+ class EvaluationReason
44
+ attr_reader :code, :description, :condition_index
42
45
 
43
- def initialize(json)
44
- json.transform_keys!(&:to_s)
45
- @code = json['code']
46
- @description = json['description']
47
- @condition_index = json['condition_index'].to_i if json['condition_index']
46
+ def initialize(json)
47
+ json.transform_keys!(&:to_s)
48
+ @code = json['code']
49
+ @description = json['description']
50
+ @condition_index = json['condition_index'].to_i if json['condition_index']
51
+ end
48
52
  end
49
- end
50
53
 
51
- # Represents metadata about a feature flag
52
- class FeatureFlagMetadata
53
- attr_reader :id, :version, :payload, :description
54
+ # Represents metadata about a feature flag
55
+ class FeatureFlagMetadata
56
+ attr_reader :id, :version, :payload, :description
54
57
 
55
- def initialize(json)
56
- json.transform_keys!(&:to_s)
57
- @id = json['id']
58
- @version = json['version']
59
- @payload = json['payload']
60
- @description = json['description']
58
+ def initialize(json)
59
+ json.transform_keys!(&:to_s)
60
+ @id = json['id']
61
+ @version = json['version']
62
+ @payload = json['payload']
63
+ @description = json['description']
64
+ end
61
65
  end
62
66
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'concurrent'
2
4
  require 'net/http'
3
5
  require 'json'
@@ -6,7 +8,7 @@ require 'posthog/logging'
6
8
  require 'posthog/feature_flag'
7
9
  require 'digest'
8
10
 
9
- class PostHog
11
+ module PostHog
10
12
  class InconclusiveMatchError < StandardError
11
13
  end
12
14
 
@@ -125,9 +127,9 @@ class PostHog
125
127
  # v3 format
126
128
  flags_response[:featureFlags] = flags_response[:featureFlags] || {}
127
129
  flags_response[:featureFlagPayloads] = flags_response[:featureFlagPayloads] || {}
128
- flags_response[:flags] = flags_response[:featureFlags].map do |key, value|
130
+ flags_response[:flags] = flags_response[:featureFlags].to_h do |key, value|
129
131
  [key, FeatureFlag.from_value_and_payload(key, value, flags_response[:featureFlagPayloads][key])]
130
- end.to_h
132
+ end
131
133
  end
132
134
  flags_response
133
135
  end
@@ -242,17 +244,15 @@ class PostHog
242
244
  request_id = nil # Only for /flags requests
243
245
 
244
246
  @feature_flags.each do |flag|
245
- begin
246
- match_value = _compute_flag_locally(flag, distinct_id, groups, person_properties, group_properties)
247
- flags[flag[:key]] = match_value
248
- match_payload = _compute_flag_payload_locally(flag[:key], match_value)
249
- payloads[flag[:key]] = match_payload if match_payload
250
- rescue InconclusiveMatchError
251
- fallback_to_server = true
252
- rescue StandardError => e
253
- @on_error.call(-1, "Error computing flag locally: #{e}. #{e.backtrace.join("\n")} ")
254
- fallback_to_server = true
255
- end
247
+ match_value = _compute_flag_locally(flag, distinct_id, groups, person_properties, group_properties)
248
+ flags[flag[:key]] = match_value
249
+ match_payload = _compute_flag_payload_locally(flag[:key], match_value)
250
+ payloads[flag[:key]] = match_payload if match_payload
251
+ rescue InconclusiveMatchError
252
+ fallback_to_server = true
253
+ rescue StandardError => e
254
+ @on_error.call(-1, "Error computing flag locally: #{e}. #{e.backtrace.join("\n")} ")
255
+ fallback_to_server = true
256
256
  end
257
257
 
258
258
  if fallback_to_server && !only_evaluate_locally
@@ -264,7 +264,7 @@ class PostHog
264
264
  end
265
265
 
266
266
  # Check if feature_flags are quota limited
267
- if flags_and_payloads[:quotaLimited] && flags_and_payloads[:quotaLimited].include?('feature_flags')
267
+ if flags_and_payloads[:quotaLimited]&.include?('feature_flags')
268
268
  logger.warn '[FEATURE FLAGS] Quota limited for feature flags'
269
269
  flags = {}
270
270
  payloads = {}
@@ -505,22 +505,20 @@ class PostHog
505
505
  # NOTE: This NEEDS to be `each` because `each_key` breaks
506
506
  # This is not a hash, it's just an array with 2 entries
507
507
  sorted_flag_conditions.each do |condition, _idx| # rubocop:disable Style/HashEachMethods
508
- begin
509
- if is_condition_match(flag, distinct_id, condition, properties)
510
- variant_override = condition[:variant]
511
- flag_multivariate = flag_filters[:multivariate] || {}
512
- flag_variants = flag_multivariate[:variants] || []
513
- variant = if flag_variants.map { |variant| variant[:key] }.include?(condition[:variant])
514
- variant_override
515
- else
516
- get_matching_variant(flag, distinct_id)
517
- end
518
- result = variant || true
519
- break
520
- end
521
- rescue InconclusiveMatchError
522
- is_inconclusive = true
508
+ if is_condition_match(flag, distinct_id, condition, properties)
509
+ variant_override = condition[:variant]
510
+ flag_multivariate = flag_filters[:multivariate] || {}
511
+ flag_variants = flag_multivariate[:variants] || []
512
+ variant = if flag_variants.map { |variant| variant[:key] }.include?(condition[:variant])
513
+ variant_override
514
+ else
515
+ get_matching_variant(flag, distinct_id)
516
+ end
517
+ result = variant || true
518
+ break
523
519
  end
520
+ rescue InconclusiveMatchError
521
+ is_inconclusive = true
524
522
  end
525
523
 
526
524
  if !result.nil?
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'posthog/logging'
2
4
 
3
- class PostHog
5
+ module PostHog
4
6
  class FieldParser
5
7
  class << self
6
8
  include PostHog::Utils
@@ -55,7 +57,7 @@ class PostHog
55
57
  {
56
58
  type: 'identify',
57
59
  event: '$identify',
58
- :'$set' => properties,
60
+ '$set': properties,
59
61
  properties: properties.merge(common[:properties] || {})
60
62
  }
61
63
  )
@@ -79,9 +81,9 @@ class PostHog
79
81
  {
80
82
  event: '$groupidentify',
81
83
  properties: {
82
- :'$group_type' => group_type,
83
- :'$group_key' => group_key,
84
- :'$group_set' => properties.merge(common[:properties] || {})
84
+ '$group_type': group_type,
85
+ '$group_key': group_key,
86
+ '$group_set': properties.merge(common[:properties] || {})
85
87
  }
86
88
  }
87
89
  )
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
2
4
 
3
- class PostHog
5
+ module PostHog
4
6
  # Wraps an existing logger and adds a prefix to all messages
5
7
  class PrefixedLogger
6
8
  def initialize(logger, prefix)
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
  require 'posthog/logging'
3
5
 
4
- class PostHog
6
+ module PostHog
5
7
  # A batch of `Message`s to be sent to the API
6
8
  class MessageBatch
7
9
  class JSONGenerationError < StandardError
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # A worker that doesn't consume jobs
2
- class PostHog
4
+ module PostHog
3
5
  class NoopWorker
4
6
  def initialize(queue)
5
7
  @queue = queue
@@ -1,4 +1,6 @@
1
- class PostHog
1
+ # frozen_string_literal: true
2
+
3
+ module PostHog
2
4
  class Response
3
5
  attr_reader :status, :error
4
6
 
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'posthog/defaults'
2
4
  require 'posthog/message_batch'
3
5
  require 'posthog/transport'
4
6
  require 'posthog/utils'
5
7
 
6
- class PostHog
8
+ module PostHog
7
9
  class SendWorker
8
10
  include PostHog::Utils
9
11
  include PostHog::Defaults
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'posthog/defaults'
2
4
  require 'posthog/utils'
3
5
  require 'posthog/response'
@@ -7,7 +9,7 @@ require 'net/http'
7
9
  require 'net/https'
8
10
  require 'json'
9
11
 
10
- class PostHog
12
+ module PostHog
11
13
  class Transport
12
14
  include PostHog::Defaults::Request
13
15
  include PostHog::Utils
data/lib/posthog/utils.rb CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'securerandom'
2
4
 
3
- class PostHog
5
+ module PostHog
4
6
  class InconclusiveMatchError < StandardError
5
7
  end
6
8
 
@@ -10,7 +12,7 @@ class PostHog
10
12
  # public: Return a new hash with keys converted from strings to symbols
11
13
  #
12
14
  def symbolize_keys(hash)
13
- hash.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v }
15
+ hash.transform_keys(&:to_sym)
14
16
  end
15
17
 
16
18
  # public: Convert hash keys from strings to symbols in place
@@ -22,15 +24,15 @@ class PostHog
22
24
  # public: Return a new hash with keys as strings
23
25
  #
24
26
  def stringify_keys(hash)
25
- hash.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v }
27
+ hash.transform_keys(&:to_s)
26
28
  end
27
29
 
28
30
  # public: Returns a new hash with all the date values in the into iso8601
29
31
  # strings
30
32
  #
31
33
  def isoify_dates(hash)
32
- hash.each_with_object({}) do |(k, v), memo|
33
- memo[k] = datetime_in_iso8601(v)
34
+ hash.transform_values do |v|
35
+ datetime_in_iso8601(v)
34
36
  end
35
37
  end
36
38
 
@@ -65,7 +67,7 @@ class PostHog
65
67
 
66
68
  def time_in_iso8601(time, fraction_digits = 3)
67
69
  fraction =
68
- (('.%06i' % time.usec)[0, fraction_digits + 1] if fraction_digits > 0) # rubocop:disable Style/FormatString
70
+ (('.%06i' % time.usec)[0, fraction_digits + 1] if fraction_digits.positive?) # rubocop:disable Style/FormatString
69
71
 
70
72
  "#{time.strftime('%Y-%m-%dT%H:%M:%S')}#{fraction}#{formatted_offset(time, true, 'Z')}"
71
73
  end
@@ -80,7 +82,7 @@ class PostHog
80
82
  end
81
83
 
82
84
  def seconds_to_utc_offset(seconds, colon = true)
83
- format((colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON), (seconds < 0 ? '-' : '+'),
85
+ format((colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON), (seconds.negative? ? '-' : '+'),
84
86
  seconds.abs / 3600, (seconds.abs % 3600) / 60)
85
87
  end
86
88
 
@@ -99,7 +101,7 @@ class PostHog
99
101
  end
100
102
  end
101
103
 
102
- UTC_OFFSET_WITH_COLON = '%s%02d:%02d'.freeze
104
+ UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
103
105
  UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
104
106
 
105
107
  # TODO: Rename to `valid_regex?` in future version
@@ -111,8 +113,8 @@ class PostHog
111
113
  end
112
114
 
113
115
  class SizeLimitedHash < Hash
114
- def initialize(max_length, *args, &block)
115
- super(*args, &block)
116
+ def initialize(max_length, ...)
117
+ super(...)
116
118
  @max_length = max_length
117
119
  end
118
120
 
@@ -1,3 +1,5 @@
1
- class PostHog
2
- VERSION = '2.10.0'.freeze
1
+ # frozen_string_literal: true
2
+
3
+ module PostHog
4
+ VERSION = '3.0.0'
3
5
  end
data/lib/posthog.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'posthog/version'
2
4
  require 'posthog/defaults'
3
5
  require 'posthog/utils'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: posthog-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.10.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''
@@ -33,7 +33,6 @@ files:
33
33
  - bin/helpers/_utils.sh
34
34
  - bin/posthog
35
35
  - bin/test
36
- - lib/posthog-ruby.rb
37
36
  - lib/posthog.rb
38
37
  - lib/posthog/backoff_policy.rb
39
38
  - lib/posthog/client.rb
@@ -61,7 +60,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
60
  requirements:
62
61
  - - ">="
63
62
  - !ruby/object:Gem::Version
64
- version: '2.0'
63
+ version: '3.0'
65
64
  required_rubygems_version: !ruby/object:Gem::Requirement
66
65
  requirements:
67
66
  - - ">="
data/lib/posthog-ruby.rb DELETED
@@ -1,3 +0,0 @@
1
- # rubocop:disable Naming/FileName
2
- require 'posthog'
3
- # rubocop:enable Naming/FileName