posthog-ruby 1.1.0 → 1.2.0

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