ablaevent-ruby 2.3.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 593ac5ac8af6a3e678b7af74f678f28c1b13bcd90c414c90b853d000f4b3b822
4
+ data.tar.gz: 0b901a391a410da624d1ddd2e2fc570de473174be9afa03c3990265259790d20
5
+ SHA512:
6
+ metadata.gz: 4d77f12b6e30ca51d9b30db45eaec75ae6491e6546b9d93feb1a139bb848fc734c140a10e5f9ffaf40b4c0533b8ee8a9a859b88ea7ba4aa0449c4c0821ee8353
7
+ data.tar.gz: 6eb786e2594776410e06227be296d96f7c0f038f2567de11a2924dd66685befc229303a1417bbad38cea404bf6c443d8543e8b809748ee9dbd8998e92c1794ac
data/bin/posthog ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'posthog'
4
+ require 'rubygems'
5
+ require 'commander/import'
6
+ require 'time'
7
+ require 'json'
8
+
9
+ program :name, 'posthog'
10
+ program :version, '1.0.0'
11
+ program :description, 'PostHog API'
12
+
13
+ def json_hash(str)
14
+ return JSON.parse(str) if str
15
+ end
16
+
17
+ command :capture do |c|
18
+ c.description = 'capture an event'
19
+
20
+ c.option '--api-key=<string>', String, 'The PostHog API Key'
21
+ c.option '--api-host=<url>',
22
+ String,
23
+ 'The PostHog API URL host part (scheme+domain)'
24
+ c.option '--distinct-id=<distinct_id>',
25
+ String,
26
+ 'The distinct id to send the event as'
27
+ c.option '--event=<event>', String, 'The event name to send with the event'
28
+ c.option '--properties=<properties>', 'The properties to send (JSON-encoded)'
29
+
30
+ c.action do |args, options|
31
+ posthog =
32
+ PostHog::Client.new(
33
+ {
34
+ api_key: options.api_key,
35
+ api_host: options.api_host,
36
+ on_error: Proc.new { |status, msg| print msg }
37
+ }
38
+ )
39
+
40
+ posthog.capture(
41
+ {
42
+ distinct_id: options.distinct_id,
43
+ event: options.event,
44
+ properties: json_hash(options.properties)
45
+ }
46
+ )
47
+
48
+ posthog.flush
49
+ end
50
+ end
51
+
52
+ command :identify do |c|
53
+ c.description = 'identify the user'
54
+
55
+ c.option '--api-key=<api_key>', String, 'The PostHog API Key'
56
+ c.option '--api-host=<url>',
57
+ String,
58
+ 'The PostHog API URL host part (scheme+domain)'
59
+ c.option '--distinct-id=<distinct_id>',
60
+ String,
61
+ 'The distinct id to send the event as'
62
+ c.option '--properties=<properties>', 'The properties to send (JSON-encoded)'
63
+
64
+ c.action do |args, options|
65
+ posthog =
66
+ PostHog::Client.new(
67
+ {
68
+ api_key: options.api_key,
69
+ api_host: options.api_host,
70
+ on_error: Proc.new { |status, msg| print msg }
71
+ }
72
+ )
73
+
74
+ posthog.identify(
75
+ {
76
+ distinct_id: options.distinct_id,
77
+ properties: json_hash(options.properties)
78
+ }
79
+ )
80
+
81
+ posthog.flush
82
+ end
83
+ end
84
+
85
+ command :alias do |c|
86
+ c.description = 'set an alias for a distinct id'
87
+
88
+ c.option '--api-key=<api_key>', String, 'The PostHog API Key'
89
+ c.option '--api-host=<url>',
90
+ String,
91
+ 'The PostHog API URL host part (scheme+domain)'
92
+ c.option '--distinct-id=<distinct_id>', String, 'The distinct id'
93
+ c.option '--alias=<alias>', 'The alias to give to the distinct id'
94
+
95
+ c.action do |args, options|
96
+ posthog =
97
+ PostHog::Client.new(
98
+ {
99
+ api_key: options.api_key,
100
+ api_host: options.api_host,
101
+ on_error: Proc.new { |status, msg| print msg }
102
+ }
103
+ )
104
+
105
+ posthog.alias({ distinct_id: options.distinct_id, alias: options.alias })
106
+
107
+ posthog.flush
108
+ end
109
+ end
@@ -0,0 +1,44 @@
1
+ require 'posthog/defaults'
2
+
3
+ class PostHog
4
+ class BackoffPolicy
5
+ include PostHog::Defaults::BackoffPolicy
6
+
7
+ # @param [Hash] opts
8
+ # @option opts [Numeric] :min_timeout_ms The minimum backoff timeout
9
+ # @option opts [Numeric] :max_timeout_ms The maximum backoff timeout
10
+ # @option opts [Numeric] :multiplier The value to multiply the current
11
+ # interval with for each retry attempt
12
+ # @option opts [Numeric] :randomization_factor The randomization factor
13
+ # to use to create a range around the retry interval
14
+ def initialize(opts = {})
15
+ @min_timeout_ms = opts[:min_timeout_ms] || MIN_TIMEOUT_MS
16
+ @max_timeout_ms = opts[:max_timeout_ms] || MAX_TIMEOUT_MS
17
+ @multiplier = opts[:multiplier] || MULTIPLIER
18
+ @randomization_factor =
19
+ opts[:randomization_factor] || RANDOMIZATION_FACTOR
20
+
21
+ @attempts = 0
22
+ end
23
+
24
+ # @return [Numeric] the next backoff interval, in milliseconds.
25
+ def next_interval
26
+ interval = @min_timeout_ms * (@multiplier**@attempts)
27
+ interval = add_jitter(interval, @randomization_factor)
28
+
29
+ @attempts += 1
30
+
31
+ [interval, @max_timeout_ms].min
32
+ end
33
+
34
+ private
35
+
36
+ def add_jitter(base, randomization_factor)
37
+ random_number = rand
38
+ max_deviation = base * randomization_factor
39
+ deviation = random_number * max_deviation
40
+
41
+ random_number < 0.5 ? base - deviation : base + deviation
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,291 @@
1
+ require 'thread'
2
+ require 'time'
3
+
4
+ require 'posthog/defaults'
5
+ require 'posthog/logging'
6
+ require 'posthog/utils'
7
+ require 'posthog/send_worker'
8
+ require 'posthog/noop_worker'
9
+ require 'posthog/feature_flags'
10
+
11
+ class PostHog
12
+ class Client
13
+ include PostHog::Utils
14
+ include PostHog::Logging
15
+
16
+ # @param [Hash] opts
17
+ # @option opts [String] :api_key Your project's api_key
18
+ # @option opts [FixNum] :max_queue_size Maximum number of calls to be
19
+ # remain queued. Defaults to 10_000.
20
+ # @option opts [Bool] :test_mode +true+ if messages should remain
21
+ # queued for testing. Defaults to +false+.
22
+ # @option opts [Proc] :on_error Handles error calls from the API.
23
+ # @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://e.abla.io`
24
+ # @option opts [Integer] :feature_flags_polling_interval How often to poll for feature flag definition changes. Measured in seconds, defaults to 30.
25
+ def initialize(opts = {})
26
+ symbolize_keys!(opts)
27
+
28
+ opts[:host] ||= 'https://e.abla.io'
29
+
30
+ @queue = Queue.new
31
+ @api_key = opts[:api_key]
32
+ @max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE
33
+ @worker_mutex = Mutex.new
34
+ @worker = if opts[:test_mode]
35
+ NoopWorker.new(@queue)
36
+ else
37
+ SendWorker.new(@queue, @api_key, opts)
38
+ end
39
+ @worker_thread = nil
40
+ @feature_flags_poller = nil
41
+ @personal_api_key = opts[:personal_api_key]
42
+
43
+ check_api_key!
44
+
45
+ @feature_flags_poller =
46
+ FeatureFlagsPoller.new(
47
+ opts[:feature_flags_polling_interval],
48
+ opts[:personal_api_key],
49
+ @api_key,
50
+ opts[:host]
51
+ )
52
+
53
+ @distinct_id_has_sent_flag_calls = SizeLimitedHash.new(Defaults::MAX_HASH_SIZE) { |hash, key| hash[key] = Array.new }
54
+
55
+ at_exit { @worker_thread && @worker_thread[:should_exit] = true }
56
+ end
57
+
58
+ # Synchronously waits until the worker has cleared the queue.
59
+ #
60
+ # Use only for scripts which are not long-running, and will specifically
61
+ # exit
62
+ def flush
63
+ while !@queue.empty? || @worker.is_requesting?
64
+ ensure_worker_running
65
+ sleep(0.1)
66
+ end
67
+ end
68
+
69
+ # Clears the queue without waiting.
70
+ #
71
+ # Use only in test mode
72
+ def clear
73
+ @queue.clear
74
+ end
75
+
76
+ # @!macro common_attrs
77
+ # @option attrs [String] :message_id ID that uniquely
78
+ # identifies a message across the API. (optional)
79
+ # @option attrs [Time] :timestamp When the event occurred (optional)
80
+ # @option attrs [String] :distinct_id The ID for this user in your database
81
+
82
+ # Captures an event
83
+ #
84
+ # @param [Hash] attrs
85
+ #
86
+ # @option attrs [String] :event Event name
87
+ # @option attrs [Hash] :properties Event properties (optional)
88
+ # @option attrs [Bool] :send_feature_flags Whether to send feature flags with this event (optional)
89
+ # @macro common_attrs
90
+ def capture(attrs)
91
+ symbolize_keys! attrs
92
+
93
+ if attrs[:send_feature_flags]
94
+ feature_variants = @feature_flags_poller._get_active_feature_variants(attrs[:distinct_id], attrs[:groups])
95
+
96
+ attrs[:feature_variants] = feature_variants
97
+ end
98
+
99
+ enqueue(FieldParser.parse_for_capture(attrs))
100
+ end
101
+
102
+ # Identifies a user
103
+ #
104
+ # @param [Hash] attrs
105
+ #
106
+ # @option attrs [Hash] :properties User properties (optional)
107
+ # @macro common_attrs
108
+ def identify(attrs)
109
+ symbolize_keys! attrs
110
+ enqueue(FieldParser.parse_for_identify(attrs))
111
+ end
112
+
113
+ # Identifies a group
114
+ #
115
+ # @param [Hash] attrs
116
+ #
117
+ # @option attrs [String] :group_type Group type
118
+ # @option attrs [String] :group_key Group key
119
+ # @option attrs [Hash] :properties Group properties (optional)
120
+ # @macro common_attrs
121
+ def group_identify(attrs)
122
+ symbolize_keys! attrs
123
+ enqueue(FieldParser.parse_for_group_identify(attrs))
124
+ end
125
+
126
+ # Aliases a user from one id to another
127
+ #
128
+ # @param [Hash] attrs
129
+ #
130
+ # @option attrs [String] :alias The alias to give the distinct id
131
+ # @macro common_attrs
132
+ def alias(attrs)
133
+ symbolize_keys! attrs
134
+ enqueue(FieldParser.parse_for_alias(attrs))
135
+ end
136
+
137
+ # @return [Hash] pops the last message from the queue
138
+ def dequeue_last_message
139
+ @queue.pop
140
+ end
141
+
142
+ # @return [Fixnum] number of messages in the queue
143
+ def queued_messages
144
+ @queue.length
145
+ end
146
+
147
+ def is_feature_enabled(flag_key, distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false, send_feature_flag_events: true)
148
+ 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)
149
+ if response.nil?
150
+ return nil
151
+ end
152
+ !!response
153
+ end
154
+
155
+ # Returns whether the given feature flag is enabled for the given user or not
156
+ #
157
+ # @param [String] key The key of the feature flag
158
+ # @param [String] distinct_id The distinct id of the user
159
+ # @param [Hash] groups
160
+ # @param [Hash] person_properties key-value pairs of properties to associate with the user.
161
+ # @param [Hash] group_properties
162
+ #
163
+ # @return [String, nil] The value of the feature flag
164
+ #
165
+ # The provided properties are used to calculate feature flags locally, if possible.
166
+ #
167
+ # `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",
168
+ # you would pass groups={"organization": "5"}.
169
+ # `group_properties` take the format: { group_type_name: { group_properties } }
170
+ # So, for example, if you have the group type "organization" and the group key "5", with the properties name, and employee count,
171
+ # you'll send these as:
172
+ # ```ruby
173
+ # group_properties: {"organization": {"name": "PostHog", "employees": 11}}
174
+ # ```
175
+ def get_feature_flag(key, distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false, send_feature_flag_events: true)
176
+ feature_flag_response, flag_was_locally_evaluated = @feature_flags_poller.get_feature_flag(key, distinct_id, groups, person_properties, group_properties, only_evaluate_locally)
177
+
178
+ feature_flag_reported_key = "#{key}_#{feature_flag_response}"
179
+ if !@distinct_id_has_sent_flag_calls[distinct_id].include?(feature_flag_reported_key) && send_feature_flag_events
180
+ capture(
181
+ {
182
+ 'distinct_id': distinct_id,
183
+ 'event': '$feature_flag_called',
184
+ 'properties': {
185
+ '$feature_flag' => key,
186
+ '$feature_flag_response' => feature_flag_response,
187
+ 'locally_evaluated' => flag_was_locally_evaluated
188
+ },
189
+ 'groups': groups,
190
+ }
191
+ )
192
+ @distinct_id_has_sent_flag_calls[distinct_id] << feature_flag_reported_key
193
+ end
194
+ feature_flag_response
195
+ end
196
+
197
+ # Returns all flags for a given user
198
+ #
199
+ # @param [String] distinct_id The distinct id of the user
200
+ # @param [Hash] groups
201
+ # @param [Hash] person_properties key-value pairs of properties to associate with the user.
202
+ # @param [Hash] group_properties
203
+ #
204
+ # @return [Hash] String (not symbol) key value pairs of flag and their values
205
+ def get_all_flags(distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false)
206
+ return @feature_flags_poller.get_all_flags(distinct_id, groups, person_properties, group_properties, only_evaluate_locally)
207
+ end
208
+
209
+ # Returns payload for a given feature flag
210
+ #
211
+ # @param [String] key The key of the feature flag
212
+ # @param [String] distinct_id The distinct id of the user
213
+ # @option [String or boolean] match_value The value of the feature flag to be matched
214
+ # @option [Hash] groups
215
+ # @option [Hash] person_properties key-value pairs of properties to associate with the user.
216
+ # @option [Hash] group_properties
217
+ # @option [Boolean] only_evaluate_locally
218
+ #
219
+ def get_feature_flag_payload(key, distinct_id, match_value: nil, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false)
220
+ @feature_flags_poller.get_feature_flag_payload(key, distinct_id, match_value, groups, person_properties, group_properties, only_evaluate_locally)
221
+ end
222
+
223
+ # Returns all flags and payloads for a given user
224
+ #
225
+ # @param [String] distinct_id The distinct id of the user
226
+ # @option [Hash] groups
227
+ # @option [Hash] person_properties key-value pairs of properties to associate with the user.
228
+ # @option [Hash] group_properties
229
+ # @option [Boolean] only_evaluate_locally
230
+ #
231
+ def get_all_flags_and_payloads(distinct_id, groups: {}, person_properties: {}, group_properties: {}, only_evaluate_locally: false)
232
+ @feature_flags_poller.get_all_flags_and_payloads(distinct_id, groups, person_properties, group_properties, only_evaluate_locally)
233
+ end
234
+
235
+ def reload_feature_flags
236
+ unless @personal_api_key
237
+ logger.error(
238
+ 'You need to specify a personal_api_key to locally evaluate feature flags'
239
+ )
240
+ return
241
+ end
242
+ @feature_flags_poller.load_feature_flags(true)
243
+ end
244
+
245
+ def shutdown
246
+ @feature_flags_poller.shutdown_poller
247
+ flush
248
+ end
249
+
250
+ private
251
+
252
+ # private: Enqueues the action.
253
+ #
254
+ # returns Boolean of whether the item was added to the queue.
255
+ def enqueue(action)
256
+ # add our request id for tracing purposes
257
+ action[:messageId] ||= uid
258
+
259
+ if @queue.length < @max_queue_size
260
+ @queue << action
261
+ ensure_worker_running
262
+
263
+ true
264
+ else
265
+ logger.warn(
266
+ 'Queue is full, dropping events. The :max_queue_size ' \
267
+ 'configuration parameter can be increased to prevent this from ' \
268
+ 'happening.'
269
+ )
270
+ false
271
+ end
272
+ end
273
+
274
+ # private: Checks that the api_key is properly initialized
275
+ def check_api_key!
276
+ raise ArgumentError, 'API key must be initialized' if @api_key.nil?
277
+ end
278
+
279
+ def ensure_worker_running
280
+ return if worker_running?
281
+ @worker_mutex.synchronize do
282
+ return if worker_running?
283
+ @worker_thread = Thread.new { @worker.run }
284
+ end
285
+ end
286
+
287
+ def worker_running?
288
+ @worker_thread && @worker_thread.alive?
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,39 @@
1
+ class PostHog
2
+ module Defaults
3
+
4
+ MAX_HASH_SIZE = 50_000
5
+
6
+ module Request
7
+ HOST = 'e.abla.io'
8
+ PORT = 443
9
+ PATH = '/batch/'
10
+ SSL = true
11
+ HEADERS = {
12
+ 'Accept' => 'application/json',
13
+ 'Content-Type' => 'application/json',
14
+ 'User-Agent' => "posthog-ruby/#{PostHog::VERSION}"
15
+ }
16
+ RETRIES = 10
17
+ end
18
+
19
+ module Queue
20
+ MAX_SIZE = 10_000
21
+ end
22
+
23
+ module Message
24
+ MAX_BYTES = 32_768 # 32Kb
25
+ end
26
+
27
+ module MessageBatch
28
+ MAX_BYTES = 512_000 # 500Kb
29
+ MAX_SIZE = 100
30
+ end
31
+
32
+ module BackoffPolicy
33
+ MIN_TIMEOUT_MS = 100
34
+ MAX_TIMEOUT_MS = 10_000
35
+ MULTIPLIER = 1.5
36
+ RANDOMIZATION_FACTOR = 0.5
37
+ end
38
+ end
39
+ end