posthog-ruby 1.1.0 → 1.2.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: 1a418b6727a9730ad159789d9b6fdb7b9df4b680fbb9e6d4a5e4ee3d05fa52e6
4
- data.tar.gz: 978744c42354807116a2a59e8dafe673cb939fea688d388f6090d877bb64ed31
3
+ metadata.gz: b45334d5c62eb513540ccc0b292e2b29b097785a50e53527f33ae0b6a81abc1e
4
+ data.tar.gz: 3bba8055756830eeb52cd8fc8c53c4a994b452b665929d4e487324d0ac94b5a1
5
5
  SHA512:
6
- metadata.gz: 8a44e8cc2f0ce02e3a44f5cf61f5e6b291233a91e79dc89eb6eaa8e07fea4d42174802b694d5a52c76ec81c5825a12728fae7c4020470eb9faf271e7c0b9c4f9
7
- data.tar.gz: 8f5a14c51e7130bac3aa58ea644ae45d2ed44c5ff045413c574af15a5fcbbefee751a840c1b3fcdb61d0eef8af1ce689f6f5e019c18fbb3cea3107a08e3d6b20
6
+ metadata.gz: 94977f7338a9859aed6e556d77aa98ff9d04a0a70edd8104e3917166f7428600fa46c78a19ae21c38fc2522b413342ef0af6801f365cf95e7f1d245fc802fc93
7
+ data.tar.gz: 8edc603d8a4e59339538b9f52d4c73177888393e3a6abd3c6063eb9a9f46d039a3dde88185be325dbbf6e894b2f880de1b047d9706c8e4a3533d567b14fa0fbf
@@ -5,6 +5,8 @@ require 'posthog/defaults'
5
5
  require 'posthog/logging'
6
6
  require 'posthog/utils'
7
7
  require 'posthog/worker'
8
+ require 'posthog/feature_flags'
9
+
8
10
 
9
11
  class PostHog
10
12
  class Client
@@ -25,12 +27,21 @@ class PostHog
25
27
  @worker_mutex = Mutex.new
26
28
  @worker = Worker.new(@queue, @api_key, opts)
27
29
  @worker_thread = nil
30
+ @feature_flags_poller = nil
31
+ @personal_api_key = nil
28
32
 
29
33
  check_api_key!
30
34
 
35
+ if opts[:personal_api_key].present?
36
+ @personal_api_key = opts[:personal_api_key]
37
+ @feature_flags_poller = FeatureFlagsPoller.new(opts[:feature_flags_polling_interval], opts[:personal_api_key], @api_key, opts[:host])
38
+ end
39
+
40
+
31
41
  at_exit { @worker_thread && @worker_thread[:should_exit] = true }
32
42
  end
33
43
 
44
+
34
45
  # Synchronously waits until the worker has flushed the queue.
35
46
  #
36
47
  # Use only for scripts which are not long-running, and will specifically
@@ -87,6 +98,36 @@ class PostHog
87
98
  @queue.length
88
99
  end
89
100
 
101
+ def is_feature_enabled(flag_key, distinct_id, default_value=false)
102
+ unless @personal_api_key
103
+ logger.error('You need to specify a personal_api_key to use feature flags')
104
+ return
105
+ end
106
+ is_enabled = @feature_flags_poller.is_feature_enabled(flag_key, distinct_id, default_value)
107
+ capture({
108
+ 'distinct_id': distinct_id,
109
+ 'event': '$feature_flag_called',
110
+ 'properties': {
111
+ '$feature_flag': flag_key,
112
+ '$feature_flag_response': is_enabled
113
+ }
114
+ })
115
+ return is_enabled
116
+ end
117
+
118
+ def reload_feature_flags
119
+ unless @personal_api_key
120
+ logger.error('You need to specify a personal_api_key to use feature flags')
121
+ return
122
+ end
123
+ @feature_flags_poller.load_feature_flags(true)
124
+ end
125
+
126
+ def shutdown
127
+ @feature_flags_poller.shutdown_poller
128
+ flush
129
+ end
130
+
90
131
  private
91
132
 
92
133
  # private: Enqueues the action.
@@ -0,0 +1,123 @@
1
+ require 'concurrent'
2
+ require 'net/http'
3
+ require 'json'
4
+ require 'posthog/version'
5
+ require 'posthog/logging'
6
+ require 'digest'
7
+ class PostHog
8
+ class FeatureFlagsPoller
9
+ include PostHog::Logging
10
+
11
+ def initialize(polling_interval, personal_api_key, project_api_key, host)
12
+ @polling_interval = polling_interval || 60 * 5
13
+ @personal_api_key = personal_api_key
14
+ @project_api_key = project_api_key
15
+ @host = host || 'app.posthog.com'
16
+ @feature_flags = Concurrent::Array.new
17
+ @loaded_flags_successfully_once = Concurrent::AtomicBoolean.new
18
+
19
+
20
+ @task = Concurrent::TimerTask.new(execution_interval: polling_interval, timeout_interval: 15) do
21
+ _load_feature_flags
22
+ end
23
+
24
+ # load once before timer
25
+ load_feature_flags
26
+ @task.execute
27
+ end
28
+
29
+
30
+ def is_feature_enabled(key, distinct_id, default_result = false)
31
+ # make sure they're loaded on first run
32
+ load_feature_flags
33
+
34
+
35
+ unless @loaded_flags_successfully_once
36
+ return default_result
37
+ end
38
+
39
+ feature_flag = nil
40
+
41
+ # puts @feature_flags
42
+
43
+ @feature_flags.each do |flag|
44
+ if key == flag['key']
45
+ feature_flag = flag
46
+ break
47
+ end
48
+ end
49
+
50
+ if !feature_flag
51
+ return default_result
52
+ end
53
+
54
+ flag_rollout_pctg = feature_flag['rollout_percentage'] ? feature_flag['rollout_percentage'] : 100
55
+ if feature_flag['is_simple_flag']
56
+ return is_simple_flag_enabled(key, distinct_id, flag_rollout_pctg)
57
+ else
58
+ data = { 'distinct_id' => distinct_id }
59
+ res = _request('POST', 'decide', false, data)
60
+ return res['featureFlags'].include? key
61
+ end
62
+
63
+ return false
64
+ end
65
+
66
+ def is_simple_flag_enabled(key, distinct_id, rollout_percentage)
67
+ hash = Digest::SHA1.hexdigest "#{key}.#{distinct_id}"
68
+ return (Integer(hash[0..14], 16).to_f / 0xfffffffffffffff) <= (rollout_percentage / 100)
69
+ end
70
+
71
+ def load_feature_flags(force_reload = false)
72
+ if @loaded_flags_successfully_once.false? || force_reload
73
+ _load_feature_flags
74
+ end
75
+
76
+ end
77
+
78
+ def shutdown_poller()
79
+ @task.shutdown
80
+ end
81
+
82
+ private
83
+
84
+ def _load_feature_flags()
85
+ res = _request('GET', 'api/feature_flag', true)
86
+ @feature_flags.clear
87
+ @feature_flags = res['results'].filter { |flag| flag['active'] }
88
+ if @loaded_flags_successfully_once.false?
89
+ @loaded_flags_successfully_once.make_true
90
+ end
91
+ end
92
+
93
+ def _request(method, endpoint, use_personal_api_key = false, data = {})
94
+ uri = URI("https://#{@host}/#{endpoint}/?token=#{@project_api_key}")
95
+ req = nil
96
+ if use_personal_api_key
97
+ req = Net::HTTP::Get.new(uri)
98
+ req['Authorization'] = "Bearer #{@personal_api_key}"
99
+ else
100
+ req = Net::HTTP::Post.new(uri)
101
+ req['Content-Type'] = 'application/json'
102
+ data['token'] = @project_api_key
103
+ req.body = data.to_json
104
+ end
105
+
106
+ req['User-Agent'] = "posthog-ruby#{PostHog::VERSION}"
107
+
108
+ begin
109
+ res_body = nil
110
+ res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) do |http|
111
+ res = http.request(req)
112
+ res_body = JSON.parse(res.body)
113
+ return res_body
114
+ end
115
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
116
+ logger.debug("Unable to complete request to #{uri}")
117
+ throw e
118
+ end
119
+ end
120
+
121
+ end
122
+ end
123
+
@@ -1,3 +1,3 @@
1
1
  class PostHog
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: posthog-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-15 00:00:00.000000000 Z
11
+ date: 2021-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -135,6 +135,7 @@ files:
135
135
  - lib/posthog/backoff_policy.rb
136
136
  - lib/posthog/client.rb
137
137
  - lib/posthog/defaults.rb
138
+ - lib/posthog/feature_flags.rb
138
139
  - lib/posthog/field_parser.rb
139
140
  - lib/posthog/logging.rb
140
141
  - lib/posthog/message_batch.rb