hackle-ruby-sdk 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hackle/client.rb +186 -87
  3. data/lib/hackle/config.rb +59 -17
  4. data/lib/hackle/decision.rb +113 -0
  5. data/lib/hackle/event.rb +89 -0
  6. data/lib/hackle/internal/clock/clock.rb +47 -0
  7. data/lib/hackle/internal/concurrent/executors.rb +20 -0
  8. data/lib/hackle/internal/concurrent/schedule/scheduler.rb +12 -0
  9. data/lib/hackle/internal/concurrent/schedule/timer_scheduler.rb +30 -0
  10. data/lib/hackle/internal/config/parameter_config.rb +50 -0
  11. data/lib/hackle/internal/core/hackle_core.rb +182 -0
  12. data/lib/hackle/{decision → internal/evaluation/bucketer}/bucketer.rb +17 -15
  13. data/lib/hackle/internal/evaluation/evaluator/contextual/contextual_evaluator.rb +29 -0
  14. data/lib/hackle/internal/evaluation/evaluator/delegating/delegating_evaluator.rb +26 -0
  15. data/lib/hackle/internal/evaluation/evaluator/evaluator.rb +117 -0
  16. data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_evaluation_flow_factory.rb +67 -0
  17. data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_evaluator.rb +172 -0
  18. data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_flow_evaluator.rb +241 -0
  19. data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_resolver.rb +166 -0
  20. data/lib/hackle/internal/evaluation/evaluator/remoteconfig/remote_config_determiner.rb +48 -0
  21. data/lib/hackle/internal/evaluation/evaluator/remoteconfig/remote_config_evaluator.rb +174 -0
  22. data/lib/hackle/internal/evaluation/flow/evaluation_flow.rb +49 -0
  23. data/lib/hackle/internal/evaluation/flow/flow_evaluator.rb +11 -0
  24. data/lib/hackle/internal/evaluation/match/condition/condition_matcher.rb +11 -0
  25. data/lib/hackle/internal/evaluation/match/condition/condition_matcher_factory.rb +53 -0
  26. data/lib/hackle/internal/evaluation/match/condition/experiment/experiment_condition_matcher.rb +29 -0
  27. data/lib/hackle/internal/evaluation/match/condition/experiment/experiment_evaluator_matcher.rb +135 -0
  28. data/lib/hackle/internal/evaluation/match/condition/segment/segment_condition_matcher.rb +67 -0
  29. data/lib/hackle/internal/evaluation/match/condition/user/user_condition_matcher.rb +44 -0
  30. data/lib/hackle/internal/evaluation/match/operator/operator_matcher.rb +185 -0
  31. data/lib/hackle/internal/evaluation/match/operator/operator_matcher_factory.rb +31 -0
  32. data/lib/hackle/internal/evaluation/match/target/target_matcher.rb +31 -0
  33. data/lib/hackle/internal/evaluation/match/value/value_matcher.rb +96 -0
  34. data/lib/hackle/internal/evaluation/match/value/value_matcher_factory.rb +28 -0
  35. data/lib/hackle/internal/evaluation/match/value/value_operator_matcher.rb +59 -0
  36. data/lib/hackle/internal/event/user_event.rb +187 -0
  37. data/lib/hackle/internal/event/user_event_dispatcher.rb +156 -0
  38. data/lib/hackle/internal/event/user_event_factory.rb +58 -0
  39. data/lib/hackle/internal/event/user_event_processor.rb +181 -0
  40. data/lib/hackle/internal/http/http.rb +28 -0
  41. data/lib/hackle/internal/http/http_client.rb +48 -0
  42. data/lib/hackle/internal/identifiers/identifier_builder.rb +67 -0
  43. data/lib/hackle/internal/logger/logger.rb +31 -0
  44. data/lib/hackle/internal/model/action.rb +57 -0
  45. data/lib/hackle/internal/model/bucket.rb +58 -0
  46. data/lib/hackle/internal/model/container.rb +47 -0
  47. data/lib/hackle/internal/model/decision_reason.rb +31 -0
  48. data/lib/hackle/{models → internal/model}/event_type.rb +5 -8
  49. data/lib/hackle/internal/model/experiment.rb +194 -0
  50. data/lib/hackle/internal/model/parameter_configuration.rb +19 -0
  51. data/lib/hackle/internal/model/remote_config_parameter.rb +76 -0
  52. data/lib/hackle/internal/model/sdk.rb +23 -0
  53. data/lib/hackle/internal/model/segment.rb +61 -0
  54. data/lib/hackle/internal/model/target.rb +203 -0
  55. data/lib/hackle/internal/model/target_rule.rb +19 -0
  56. data/lib/hackle/internal/model/targeting.rb +45 -0
  57. data/lib/hackle/internal/model/value_type.rb +75 -0
  58. data/lib/hackle/internal/model/variation.rb +27 -0
  59. data/lib/hackle/internal/model/version.rb +153 -0
  60. data/lib/hackle/internal/properties/properties_builder.rb +101 -0
  61. data/lib/hackle/internal/user/hackle_user.rb +74 -0
  62. data/lib/hackle/internal/user/hackle_user_resolver.rb +27 -0
  63. data/lib/hackle/internal/workspace/http_workspace_fetcher.rb +50 -0
  64. data/lib/hackle/internal/workspace/polling_workspace_fetcher.rb +62 -0
  65. data/lib/hackle/internal/workspace/workspace.rb +353 -0
  66. data/lib/hackle/internal/workspace/workspace_fetcher.rb +18 -0
  67. data/lib/hackle/remote_config.rb +55 -0
  68. data/lib/hackle/user.rb +124 -0
  69. data/lib/hackle/version.rb +1 -11
  70. data/lib/hackle.rb +4 -69
  71. metadata +123 -53
  72. data/.gitignore +0 -11
  73. data/.rspec +0 -2
  74. data/.travis.yml +0 -7
  75. data/Gemfile +0 -6
  76. data/README.md +0 -33
  77. data/Rakefile +0 -6
  78. data/hackle-ruby-sdk.gemspec +0 -29
  79. data/lib/hackle/decision/decider.rb +0 -69
  80. data/lib/hackle/events/event_dispatcher.rb +0 -96
  81. data/lib/hackle/events/event_processor.rb +0 -126
  82. data/lib/hackle/events/user_event.rb +0 -61
  83. data/lib/hackle/http/http.rb +0 -37
  84. data/lib/hackle/models/bucket.rb +0 -26
  85. data/lib/hackle/models/event.rb +0 -26
  86. data/lib/hackle/models/experiment.rb +0 -69
  87. data/lib/hackle/models/slot.rb +0 -22
  88. data/lib/hackle/models/user.rb +0 -24
  89. data/lib/hackle/models/variation.rb +0 -21
  90. data/lib/hackle/workspaces/http_workspace_fetcher.rb +0 -24
  91. data/lib/hackle/workspaces/polling_workspace_fetcher.rb +0 -47
  92. data/lib/hackle/workspaces/workspace.rb +0 -100
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hackle-ruby-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hackle
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-01 00:00:00.000000000 Z
11
+ date: 2024-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,70 +16,98 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.17'
19
+ version: '2.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.17'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: coveralls
28
+ name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rake
42
+ name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: '3.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '10.0'
54
+ version: '3.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rspec
56
+ name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '3.0'
61
+ version: '1.28'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '3.0'
68
+ version: '1.28'
69
69
  - !ruby/object:Gem::Dependency
70
- name: rubocop
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.21'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.21'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov-cobertura
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - '='
87
+ - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: 0.73.0
89
+ version: '2.1'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - '='
94
+ - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: 0.73.0
96
+ version: '2.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov-lcov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.7.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.7.0
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: concurrent-ruby
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -98,16 +126,16 @@ dependencies:
98
126
  name: json
99
127
  requirement: !ruby/object:Gem::Requirement
100
128
  requirements:
101
- - - ">="
129
+ - - "~>"
102
130
  - !ruby/object:Gem::Version
103
- version: '1.8'
131
+ version: '2.3'
104
132
  type: :runtime
105
133
  prerelease: false
106
134
  version_requirements: !ruby/object:Gem::Requirement
107
135
  requirements:
108
- - - ">="
136
+ - - "~>"
109
137
  - !ruby/object:Gem::Version
110
- version: '1.8'
138
+ version: '2.3'
111
139
  - !ruby/object:Gem::Dependency
112
140
  name: murmurhash3
113
141
  requirement: !ruby/object:Gem::Requirement
@@ -122,46 +150,88 @@ dependencies:
122
150
  - - "~>"
123
151
  - !ruby/object:Gem::Version
124
152
  version: '0.1'
125
- description: Hackle SDK for Ruby
153
+ description: Ruby SDK for Hackle A/B Tests, Feature Flags, Remote Configs, and Analytics.
126
154
  email:
127
155
  - platform@hackle.io
128
156
  executables: []
129
157
  extensions: []
130
158
  extra_rdoc_files: []
131
159
  files:
132
- - ".gitignore"
133
- - ".rspec"
134
- - ".travis.yml"
135
- - Gemfile
136
- - README.md
137
- - Rakefile
138
- - hackle-ruby-sdk.gemspec
139
160
  - lib/hackle-ruby-sdk.rb
140
161
  - lib/hackle.rb
141
162
  - lib/hackle/client.rb
142
163
  - lib/hackle/config.rb
143
- - lib/hackle/decision/bucketer.rb
144
- - lib/hackle/decision/decider.rb
145
- - lib/hackle/events/event_dispatcher.rb
146
- - lib/hackle/events/event_processor.rb
147
- - lib/hackle/events/user_event.rb
148
- - lib/hackle/http/http.rb
149
- - lib/hackle/models/bucket.rb
150
- - lib/hackle/models/event.rb
151
- - lib/hackle/models/event_type.rb
152
- - lib/hackle/models/experiment.rb
153
- - lib/hackle/models/slot.rb
154
- - lib/hackle/models/user.rb
155
- - lib/hackle/models/variation.rb
164
+ - lib/hackle/decision.rb
165
+ - lib/hackle/event.rb
166
+ - lib/hackle/internal/clock/clock.rb
167
+ - lib/hackle/internal/concurrent/executors.rb
168
+ - lib/hackle/internal/concurrent/schedule/scheduler.rb
169
+ - lib/hackle/internal/concurrent/schedule/timer_scheduler.rb
170
+ - lib/hackle/internal/config/parameter_config.rb
171
+ - lib/hackle/internal/core/hackle_core.rb
172
+ - lib/hackle/internal/evaluation/bucketer/bucketer.rb
173
+ - lib/hackle/internal/evaluation/evaluator/contextual/contextual_evaluator.rb
174
+ - lib/hackle/internal/evaluation/evaluator/delegating/delegating_evaluator.rb
175
+ - lib/hackle/internal/evaluation/evaluator/evaluator.rb
176
+ - lib/hackle/internal/evaluation/evaluator/experiment/experiment_evaluation_flow_factory.rb
177
+ - lib/hackle/internal/evaluation/evaluator/experiment/experiment_evaluator.rb
178
+ - lib/hackle/internal/evaluation/evaluator/experiment/experiment_flow_evaluator.rb
179
+ - lib/hackle/internal/evaluation/evaluator/experiment/experiment_resolver.rb
180
+ - lib/hackle/internal/evaluation/evaluator/remoteconfig/remote_config_determiner.rb
181
+ - lib/hackle/internal/evaluation/evaluator/remoteconfig/remote_config_evaluator.rb
182
+ - lib/hackle/internal/evaluation/flow/evaluation_flow.rb
183
+ - lib/hackle/internal/evaluation/flow/flow_evaluator.rb
184
+ - lib/hackle/internal/evaluation/match/condition/condition_matcher.rb
185
+ - lib/hackle/internal/evaluation/match/condition/condition_matcher_factory.rb
186
+ - lib/hackle/internal/evaluation/match/condition/experiment/experiment_condition_matcher.rb
187
+ - lib/hackle/internal/evaluation/match/condition/experiment/experiment_evaluator_matcher.rb
188
+ - lib/hackle/internal/evaluation/match/condition/segment/segment_condition_matcher.rb
189
+ - lib/hackle/internal/evaluation/match/condition/user/user_condition_matcher.rb
190
+ - lib/hackle/internal/evaluation/match/operator/operator_matcher.rb
191
+ - lib/hackle/internal/evaluation/match/operator/operator_matcher_factory.rb
192
+ - lib/hackle/internal/evaluation/match/target/target_matcher.rb
193
+ - lib/hackle/internal/evaluation/match/value/value_matcher.rb
194
+ - lib/hackle/internal/evaluation/match/value/value_matcher_factory.rb
195
+ - lib/hackle/internal/evaluation/match/value/value_operator_matcher.rb
196
+ - lib/hackle/internal/event/user_event.rb
197
+ - lib/hackle/internal/event/user_event_dispatcher.rb
198
+ - lib/hackle/internal/event/user_event_factory.rb
199
+ - lib/hackle/internal/event/user_event_processor.rb
200
+ - lib/hackle/internal/http/http.rb
201
+ - lib/hackle/internal/http/http_client.rb
202
+ - lib/hackle/internal/identifiers/identifier_builder.rb
203
+ - lib/hackle/internal/logger/logger.rb
204
+ - lib/hackle/internal/model/action.rb
205
+ - lib/hackle/internal/model/bucket.rb
206
+ - lib/hackle/internal/model/container.rb
207
+ - lib/hackle/internal/model/decision_reason.rb
208
+ - lib/hackle/internal/model/event_type.rb
209
+ - lib/hackle/internal/model/experiment.rb
210
+ - lib/hackle/internal/model/parameter_configuration.rb
211
+ - lib/hackle/internal/model/remote_config_parameter.rb
212
+ - lib/hackle/internal/model/sdk.rb
213
+ - lib/hackle/internal/model/segment.rb
214
+ - lib/hackle/internal/model/target.rb
215
+ - lib/hackle/internal/model/target_rule.rb
216
+ - lib/hackle/internal/model/targeting.rb
217
+ - lib/hackle/internal/model/value_type.rb
218
+ - lib/hackle/internal/model/variation.rb
219
+ - lib/hackle/internal/model/version.rb
220
+ - lib/hackle/internal/properties/properties_builder.rb
221
+ - lib/hackle/internal/user/hackle_user.rb
222
+ - lib/hackle/internal/user/hackle_user_resolver.rb
223
+ - lib/hackle/internal/workspace/http_workspace_fetcher.rb
224
+ - lib/hackle/internal/workspace/polling_workspace_fetcher.rb
225
+ - lib/hackle/internal/workspace/workspace.rb
226
+ - lib/hackle/internal/workspace/workspace_fetcher.rb
227
+ - lib/hackle/remote_config.rb
228
+ - lib/hackle/user.rb
156
229
  - lib/hackle/version.rb
157
- - lib/hackle/workspaces/http_workspace_fetcher.rb
158
- - lib/hackle/workspaces/polling_workspace_fetcher.rb
159
- - lib/hackle/workspaces/workspace.rb
160
230
  homepage: https://github.com/hackle-io/hackle-ruby-sdk
161
231
  licenses:
162
232
  - Apache-2.0
163
233
  metadata: {}
164
- post_install_message:
234
+ post_install_message:
165
235
  rdoc_options: []
166
236
  require_paths:
167
237
  - lib
@@ -176,8 +246,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
246
  - !ruby/object:Gem::Version
177
247
  version: '0'
178
248
  requirements: []
179
- rubygems_version: 3.0.3
180
- signing_key:
249
+ rubygems_version: 3.0.3.1
250
+ signing_key:
181
251
  specification_version: 4
182
- summary: Hackle SDK for Ruby
252
+ summary: Hackle Ruby SDK
183
253
  test_files: []
data/.gitignore DELETED
@@ -1,11 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --color
2
- --order random
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.6.3
7
- before_install: gem install bundler -v 1.17.3
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- # Specify your gem's dependencies in hackle-ruby-sdk.gemspec
6
- gemspec
data/README.md DELETED
@@ -1,33 +0,0 @@
1
- # Hackle::Ruby::Sdk
2
-
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/hackle`.
4
-
5
- ## Installation
6
-
7
- Add this line to your application's Gemfile:
8
-
9
- ```ruby
10
- gem 'hackle-ruby-sdk'
11
- ```
12
-
13
- And then execute:
14
-
15
- $ bundle
16
-
17
- Or install it yourself as:
18
-
19
- $ gem install hackle-ruby-sdk
20
-
21
- ## Usage
22
-
23
- TODO: Write usage instructions here
24
-
25
- ## Development
26
-
27
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
-
29
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
-
31
- ## Contributing
32
-
33
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/hackle-ruby-sdk.
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- lib = File.expand_path('lib', __dir__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'hackle/version'
6
-
7
- Gem::Specification.new do |spec|
8
- spec.name = 'hackle-ruby-sdk'
9
- spec.version = Hackle::VERSION
10
- spec.authors = ['Hackle']
11
- spec.email = ['platform@hackle.io']
12
- spec.summary = 'Hackle SDK for Ruby'
13
- spec.description = 'Hackle SDK for Ruby'
14
- spec.homepage = 'https://github.com/hackle-io/hackle-ruby-sdk'
15
- spec.license = 'Apache-2.0'
16
-
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
18
- spec.require_paths = ['lib']
19
-
20
- spec.add_development_dependency 'bundler', '~> 1.17'
21
- spec.add_development_dependency 'coveralls'
22
- spec.add_development_dependency 'rake', '~> 10.0'
23
- spec.add_development_dependency 'rspec', '~> 3.0'
24
- spec.add_development_dependency 'rubocop', '0.73.0'
25
-
26
- spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
27
- spec.add_runtime_dependency 'json', '>= 1.8'
28
- spec.add_runtime_dependency 'murmurhash3', '~> 0.1'
29
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hackle
4
- class Decision
5
-
6
- class NotAllocated < Decision
7
- end
8
-
9
- class ForcedAllocated < Decision
10
- # @return [String]
11
- attr_reader :variation_key
12
-
13
- # @param variation_key [String]
14
- def initialize(variation_key:)
15
- @variation_key = variation_key
16
- end
17
- end
18
-
19
- class NaturalAllocated < Decision
20
- # @return [Variation]
21
- attr_reader :variation
22
-
23
- # @param variation [Variation]
24
- def initialize(variation:)
25
- @variation = variation
26
- end
27
- end
28
- end
29
-
30
- class Decider
31
- def initialize
32
- @bucketer = Bucketer.new
33
- end
34
-
35
- # @param experiment [Experiment]
36
- # @param user [User]
37
- #
38
- # @return [Decision]
39
- def decide(experiment:, user:)
40
- case experiment
41
- when Experiment::Completed
42
- Decision::ForcedAllocated.new(variation_key: experiment.winner_variation_key)
43
- when Experiment::Running
44
- decide_running(running_experiment: experiment, user: user)
45
- else
46
- NotAllocated.new
47
- end
48
- end
49
-
50
- # @param running_experiment [Experiment::Running]
51
- # @param user [User]
52
- #
53
- # @return [Decision]
54
- def decide_running(running_experiment:, user:)
55
-
56
- overridden_variation = running_experiment.get_overridden_variation(user: user)
57
- return Decision::ForcedAllocated.new(variation_key: overridden_variation.key) unless overridden_variation.nil?
58
-
59
- allocated_slot = @bucketer.bucketing(bucket: running_experiment.bucket, user: user)
60
- return Decision::NotAllocated.new if allocated_slot.nil?
61
-
62
- allocated_variation = running_experiment.get_variation(variation_id: allocated_slot.variation_id)
63
- return Decision::NotAllocated.new if allocated_variation.nil?
64
- return Decision::NotAllocated.new if allocated_variation.dropped
65
-
66
- Decision::NaturalAllocated.new(variation: allocated_variation)
67
- end
68
- end
69
- end
@@ -1,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hackle
4
- class EventDispatcher
5
-
6
- DEFAULT_DISPATCH_WORKER_SIZE = 2
7
- DEFAULT_DISPATCH_QUEUE_CAPACITY = 50
8
-
9
- def initialize(config:, sdk_info:)
10
- @logger = config.logger
11
- @client = HTTP.client(base_uri: config.event_uri)
12
- @headers = HTTP.sdk_headers(sdk_info: sdk_info)
13
- @dispatcher_executor = Concurrent::ThreadPoolExecutor.new(
14
- min_threads: DEFAULT_DISPATCH_WORKER_SIZE,
15
- max_threads: DEFAULT_DISPATCH_WORKER_SIZE,
16
- max_queue: DEFAULT_DISPATCH_QUEUE_CAPACITY
17
- )
18
- end
19
-
20
- def dispatch(events:)
21
- payload = create_payload(events: events)
22
- begin
23
- @dispatcher_executor.post { dispatch_payload(payload: payload) }
24
- rescue Concurrent::RejectedExecutionError
25
- @logger.warn { 'Dispatcher executor queue is full. Event dispatch rejected' }
26
- end
27
- end
28
-
29
- def shutdown
30
- @dispatcher_executor.shutdown
31
- unless @dispatcher_executor.wait_for_termination(10)
32
- @logger.warn { 'Failed to dispatch previously submitted events' }
33
- end
34
- end
35
-
36
- private
37
-
38
- def dispatch_payload(payload:)
39
- request = Net::HTTP::Post.new('/api/v1/events', @headers)
40
- request.content_type = 'application/json'
41
- request.body = payload.to_json
42
-
43
- response = @client.request(request)
44
-
45
- status_code = response.code.to_i
46
- HTTP.check_successful(status_code: status_code)
47
- rescue => e
48
- @logger.error { "Failed to dispatch events: #{e.inspect}" }
49
- end
50
-
51
- def create_payload(events:)
52
- exposure_events = []
53
- track_events = []
54
- events.each do |event|
55
- case event
56
- when UserEvent::Exposure
57
- exposure_events << create_exposure_event(event)
58
- when UserEvent::Track
59
- track_events << create_track_event(event)
60
- end
61
- end
62
- {
63
- exposureEvents: exposure_events,
64
- trackEvents: track_events
65
- }
66
- end
67
-
68
- #
69
- # @param exposure [UserEvent::Exposure]
70
- #
71
- def create_exposure_event(exposure)
72
- {
73
- timestamp: exposure.timestamp,
74
- userId: exposure.user.id,
75
- experimentId: exposure.experiment.id,
76
- experimentKey: exposure.experiment.key,
77
- variationId: exposure.variation.id,
78
- variationKey: exposure.variation.key
79
- }
80
- end
81
-
82
- #
83
- # @param track [UserEvent::Track]
84
- #
85
- def create_track_event(track)
86
- {
87
- timestamp: track.timestamp,
88
- userId: track.user.id,
89
- eventTypeId: track.event_type.id,
90
- eventTypeKey: track.event_type.key,
91
- value: track.event.value,
92
- properties: track.event.properties
93
- }
94
- end
95
- end
96
- end
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hackle
4
-
5
- class EventProcessor
6
-
7
- DEFAULT_FLUSH_INTERVAL = 10
8
-
9
- # @param config [Config]
10
- # @param event_dispatcher [EventDispatcher]
11
- def initialize(config:, event_dispatcher:)
12
- @logger = config.logger
13
- @event_dispatcher = event_dispatcher
14
- @message_processor = MessageProcessor.new(config: config, event_dispatcher: event_dispatcher)
15
- @flush_task = Concurrent::TimerTask.new(execution_interval: DEFAULT_FLUSH_INTERVAL) { flush }
16
- @consume_task = nil
17
- @running = false
18
- end
19
-
20
- def start!
21
- return if @running
22
-
23
- @consume_task = Thread.new { @message_processor.consuming_loop }
24
- @flush_task.execute
25
- @running = true
26
- end
27
-
28
- def stop!
29
- return unless @running
30
-
31
- @logger.info { 'Shutting down Hackle event_processor' }
32
-
33
- @message_processor.produce(message: Message::Shutdown.new, non_block: false)
34
- @consume_task.join(10)
35
- @flush_task.shutdown
36
- @event_dispatcher.shutdown
37
-
38
- @running = false
39
- end
40
-
41
- # @param event [UserEvent]
42
- def process(event:)
43
- @message_processor.produce(message: Message::Event.new(event))
44
- end
45
-
46
- def flush
47
- @message_processor.produce(message: Message::Flush.new)
48
- end
49
-
50
- class Message
51
- class Event < Message
52
-
53
- # @return [UserEvent]
54
- attr_reader :event
55
-
56
- # @param event [UserEvent]
57
- def initialize(event)
58
- @event = event
59
- end
60
- end
61
-
62
- class Flush < Message
63
- end
64
-
65
- class Shutdown < Message
66
- end
67
- end
68
-
69
- class MessageProcessor
70
-
71
- DEFAULT_MESSAGE_QUEUE_CAPACITY = 1000
72
- DEFAULT_MAX_EVENT_DISPATCH_SIZE = 500
73
-
74
- def initialize(config:, event_dispatcher:)
75
- @logger = config.logger
76
- @event_dispatcher = event_dispatcher
77
- @message_queue = SizedQueue.new(DEFAULT_MESSAGE_QUEUE_CAPACITY)
78
- @random = Random.new
79
- @consumed_events = []
80
- end
81
-
82
- # @param message [Message]
83
- # @param non_block [boolean]
84
- def produce(message:, non_block: true)
85
- @message_queue.push(message, non_block)
86
- rescue ThreadError
87
- if @random.rand(1..100) == 1 # log only 1% of the time
88
- @logger.warn { 'Events are produced faster than can be consumed. Some events will be dropped.' }
89
- end
90
- end
91
-
92
- def consuming_loop
93
- loop do
94
- message = @message_queue.pop
95
- case message
96
- when Message::Event
97
- consume_event(event: message.event)
98
- when Message::Flush
99
- dispatch_events
100
- when Message::Shutdown
101
- break
102
- end
103
- end
104
- rescue => e
105
- @logger.warn { "Uncaught exception in events message processor: #{e.inspect}" }
106
- ensure
107
- dispatch_events
108
- end
109
-
110
- private
111
-
112
- # @param event [UserEvent]
113
- def consume_event(event:)
114
- @consumed_events << event
115
- dispatch_events if @consumed_events.length >= DEFAULT_MAX_EVENT_DISPATCH_SIZE
116
- end
117
-
118
- def dispatch_events
119
- return if @consumed_events.empty?
120
-
121
- @event_dispatcher.dispatch(events: @consumed_events)
122
- @consumed_events = []
123
- end
124
- end
125
- end
126
- end