ablaevent-ruby 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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