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.
- checksums.yaml +4 -4
- data/lib/hackle/client.rb +186 -87
- data/lib/hackle/config.rb +59 -17
- data/lib/hackle/decision.rb +113 -0
- data/lib/hackle/event.rb +89 -0
- data/lib/hackle/internal/clock/clock.rb +47 -0
- data/lib/hackle/internal/concurrent/executors.rb +20 -0
- data/lib/hackle/internal/concurrent/schedule/scheduler.rb +12 -0
- data/lib/hackle/internal/concurrent/schedule/timer_scheduler.rb +30 -0
- data/lib/hackle/internal/config/parameter_config.rb +50 -0
- data/lib/hackle/internal/core/hackle_core.rb +182 -0
- data/lib/hackle/{decision → internal/evaluation/bucketer}/bucketer.rb +17 -15
- data/lib/hackle/internal/evaluation/evaluator/contextual/contextual_evaluator.rb +29 -0
- data/lib/hackle/internal/evaluation/evaluator/delegating/delegating_evaluator.rb +26 -0
- data/lib/hackle/internal/evaluation/evaluator/evaluator.rb +117 -0
- data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_evaluation_flow_factory.rb +67 -0
- data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_evaluator.rb +172 -0
- data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_flow_evaluator.rb +241 -0
- data/lib/hackle/internal/evaluation/evaluator/experiment/experiment_resolver.rb +166 -0
- data/lib/hackle/internal/evaluation/evaluator/remoteconfig/remote_config_determiner.rb +48 -0
- data/lib/hackle/internal/evaluation/evaluator/remoteconfig/remote_config_evaluator.rb +174 -0
- data/lib/hackle/internal/evaluation/flow/evaluation_flow.rb +49 -0
- data/lib/hackle/internal/evaluation/flow/flow_evaluator.rb +11 -0
- data/lib/hackle/internal/evaluation/match/condition/condition_matcher.rb +11 -0
- data/lib/hackle/internal/evaluation/match/condition/condition_matcher_factory.rb +53 -0
- data/lib/hackle/internal/evaluation/match/condition/experiment/experiment_condition_matcher.rb +29 -0
- data/lib/hackle/internal/evaluation/match/condition/experiment/experiment_evaluator_matcher.rb +135 -0
- data/lib/hackle/internal/evaluation/match/condition/segment/segment_condition_matcher.rb +67 -0
- data/lib/hackle/internal/evaluation/match/condition/user/user_condition_matcher.rb +44 -0
- data/lib/hackle/internal/evaluation/match/operator/operator_matcher.rb +185 -0
- data/lib/hackle/internal/evaluation/match/operator/operator_matcher_factory.rb +31 -0
- data/lib/hackle/internal/evaluation/match/target/target_matcher.rb +31 -0
- data/lib/hackle/internal/evaluation/match/value/value_matcher.rb +96 -0
- data/lib/hackle/internal/evaluation/match/value/value_matcher_factory.rb +28 -0
- data/lib/hackle/internal/evaluation/match/value/value_operator_matcher.rb +59 -0
- data/lib/hackle/internal/event/user_event.rb +187 -0
- data/lib/hackle/internal/event/user_event_dispatcher.rb +156 -0
- data/lib/hackle/internal/event/user_event_factory.rb +58 -0
- data/lib/hackle/internal/event/user_event_processor.rb +181 -0
- data/lib/hackle/internal/http/http.rb +28 -0
- data/lib/hackle/internal/http/http_client.rb +48 -0
- data/lib/hackle/internal/identifiers/identifier_builder.rb +67 -0
- data/lib/hackle/internal/logger/logger.rb +31 -0
- data/lib/hackle/internal/model/action.rb +57 -0
- data/lib/hackle/internal/model/bucket.rb +58 -0
- data/lib/hackle/internal/model/container.rb +47 -0
- data/lib/hackle/internal/model/decision_reason.rb +31 -0
- data/lib/hackle/{models → internal/model}/event_type.rb +5 -8
- data/lib/hackle/internal/model/experiment.rb +194 -0
- data/lib/hackle/internal/model/parameter_configuration.rb +19 -0
- data/lib/hackle/internal/model/remote_config_parameter.rb +76 -0
- data/lib/hackle/internal/model/sdk.rb +23 -0
- data/lib/hackle/internal/model/segment.rb +61 -0
- data/lib/hackle/internal/model/target.rb +203 -0
- data/lib/hackle/internal/model/target_rule.rb +19 -0
- data/lib/hackle/internal/model/targeting.rb +45 -0
- data/lib/hackle/internal/model/value_type.rb +75 -0
- data/lib/hackle/internal/model/variation.rb +27 -0
- data/lib/hackle/internal/model/version.rb +153 -0
- data/lib/hackle/internal/properties/properties_builder.rb +101 -0
- data/lib/hackle/internal/user/hackle_user.rb +74 -0
- data/lib/hackle/internal/user/hackle_user_resolver.rb +27 -0
- data/lib/hackle/internal/workspace/http_workspace_fetcher.rb +50 -0
- data/lib/hackle/internal/workspace/polling_workspace_fetcher.rb +62 -0
- data/lib/hackle/internal/workspace/workspace.rb +353 -0
- data/lib/hackle/internal/workspace/workspace_fetcher.rb +18 -0
- data/lib/hackle/remote_config.rb +55 -0
- data/lib/hackle/user.rb +124 -0
- data/lib/hackle/version.rb +1 -11
- data/lib/hackle.rb +4 -69
- metadata +123 -53
- data/.gitignore +0 -11
- data/.rspec +0 -2
- data/.travis.yml +0 -7
- data/Gemfile +0 -6
- data/README.md +0 -33
- data/Rakefile +0 -6
- data/hackle-ruby-sdk.gemspec +0 -29
- data/lib/hackle/decision/decider.rb +0 -69
- data/lib/hackle/events/event_dispatcher.rb +0 -96
- data/lib/hackle/events/event_processor.rb +0 -126
- data/lib/hackle/events/user_event.rb +0 -61
- data/lib/hackle/http/http.rb +0 -37
- data/lib/hackle/models/bucket.rb +0 -26
- data/lib/hackle/models/event.rb +0 -26
- data/lib/hackle/models/experiment.rb +0 -69
- data/lib/hackle/models/slot.rb +0 -22
- data/lib/hackle/models/user.rb +0 -24
- data/lib/hackle/models/variation.rb +0 -21
- data/lib/hackle/workspaces/http_workspace_fetcher.rb +0 -24
- data/lib/hackle/workspaces/polling_workspace_fetcher.rb +0 -47
- data/lib/hackle/workspaces/workspace.rb +0 -100
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 572cfbd5a794b4516be482e888dffdbfbf98485ea566ceef44187b8555f754d8
|
4
|
+
data.tar.gz: 80c1c73b16728ee50eab4a613c0810f42883db15154a0b697d796e003a222274
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f68e02af4c482fba3d10f8598ffc832240524791ad66e29db644996d128236e594c79602ec37e040ba800e6a77f3bab8eb226e5f336aa852c0b9e60eedbc723
|
7
|
+
data.tar.gz: 11eaa326223fd384bc0c0261c1ea3ef6ab7fbff6885570cf62ce3c124cdd0104d63f6525420c5a976c2e14cac932320f07e7aef80f1e0b751be6d1702e11b26d
|
data/lib/hackle/client.rb
CHANGED
@@ -1,127 +1,226 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'hackle/
|
4
|
-
require 'hackle/decision
|
5
|
-
|
6
|
-
require 'hackle/
|
7
|
-
require 'hackle/
|
8
|
-
require 'hackle/
|
9
|
-
|
10
|
-
require 'hackle/
|
11
|
-
|
12
|
-
require 'hackle/
|
13
|
-
require 'hackle/
|
14
|
-
require 'hackle/
|
15
|
-
require 'hackle/
|
16
|
-
require 'hackle/
|
17
|
-
require 'hackle/
|
18
|
-
require 'hackle/
|
19
|
-
|
20
|
-
require 'hackle/workspaces/http_workspace_fetcher'
|
21
|
-
require 'hackle/workspaces/polling_workspace_fetcher'
|
22
|
-
require 'hackle/workspaces/workspace'
|
3
|
+
require 'hackle/event'
|
4
|
+
require 'hackle/decision'
|
5
|
+
require 'hackle/config'
|
6
|
+
require 'hackle/remote_config'
|
7
|
+
require 'hackle/version'
|
8
|
+
require 'hackle/internal/concurrent/executors'
|
9
|
+
require 'hackle/internal/logger/logger'
|
10
|
+
require 'hackle/internal/model/sdk'
|
11
|
+
require 'hackle/internal/model/decision_reason'
|
12
|
+
require 'hackle/internal/config/parameter_config'
|
13
|
+
require 'hackle/internal/workspace/http_workspace_fetcher'
|
14
|
+
require 'hackle/internal/workspace/polling_workspace_fetcher'
|
15
|
+
require 'hackle/internal/event/user_event_dispatcher'
|
16
|
+
require 'hackle/internal/event/user_event_processor'
|
17
|
+
require 'hackle/internal/http/http_client'
|
18
|
+
require 'hackle/internal/core/hackle_core'
|
19
|
+
require 'hackle/internal/user/hackle_user_resolver'
|
23
20
|
|
24
21
|
module Hackle
|
25
|
-
|
26
22
|
#
|
27
|
-
#
|
23
|
+
# The entry point of Hackle SDKs.
|
28
24
|
#
|
29
25
|
class Client
|
26
|
+
# @param core [Hackle::Core]
|
27
|
+
# @param user_resolver [Hackle::HackleUserResolver]
|
28
|
+
def initialize(core:, user_resolver:)
|
29
|
+
# @type [Hackle::Core]
|
30
|
+
@core = core
|
31
|
+
|
32
|
+
# @type [Hackle::HackleUserResolver]
|
33
|
+
@user_resolver = user_resolver
|
34
|
+
end
|
35
|
+
|
36
|
+
@instance = {}
|
37
|
+
|
38
|
+
#
|
39
|
+
# Instantiates a Hackle client.
|
40
|
+
#
|
41
|
+
# @param sdk_key [String]
|
42
|
+
# @param config [Hackle::Config]
|
43
|
+
# @return [Hackle::Client]
|
44
|
+
#
|
45
|
+
def self.create(sdk_key:, config: Config.builder.build)
|
46
|
+
client = @instance[sdk_key]
|
47
|
+
unless client.nil?
|
48
|
+
client.__resume
|
49
|
+
return client
|
50
|
+
end
|
30
51
|
|
52
|
+
Log.init(config.logger)
|
53
|
+
|
54
|
+
sdk = Sdk.new(name: 'ruby-sdk', version: Hackle::VERSION, key: sdk_key)
|
55
|
+
|
56
|
+
http_workspace_fetcher = HttpWorkspaceFetcher.new(
|
57
|
+
http_client: HttpClient.create(base_url: config.sdk_url, sdk: sdk),
|
58
|
+
sdk: sdk
|
59
|
+
)
|
60
|
+
workspace_fetcher = PollingWorkspaceFetcher.new(
|
61
|
+
http_workspace_fetcher: http_workspace_fetcher,
|
62
|
+
scheduler: Executors.scheduler,
|
63
|
+
polling_interval_seconds: 10.0
|
64
|
+
)
|
65
|
+
|
66
|
+
event_dispatcher = UserEventDispatcher.create(
|
67
|
+
http_client: HttpClient.create(base_url: config.event_url, sdk: sdk),
|
68
|
+
executor: Executors.thread_pool(pool_size: 2, queue_capacity: 64)
|
69
|
+
)
|
70
|
+
|
71
|
+
event_processor = DefaultUserEventProcessor.new(
|
72
|
+
queue: SizedQueue.new(10_000),
|
73
|
+
event_dispatcher: event_dispatcher,
|
74
|
+
event_dispatch_size: 100,
|
75
|
+
flush_scheduler: Executors.scheduler,
|
76
|
+
flush_interval_seconds: 10.0,
|
77
|
+
shutdown_timeout_seconds: 10.0
|
78
|
+
)
|
79
|
+
|
80
|
+
workspace_fetcher.start
|
81
|
+
event_processor.start
|
82
|
+
|
83
|
+
core = Core.create(
|
84
|
+
workspace_fetcher: workspace_fetcher,
|
85
|
+
event_processor: event_processor
|
86
|
+
)
|
87
|
+
|
88
|
+
new_client = Client.new(
|
89
|
+
core: core,
|
90
|
+
user_resolver: HackleUserResolver.new
|
91
|
+
)
|
92
|
+
@instance[sdk_key] = new_client
|
93
|
+
new_client
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Decide the variation to expose to the user for experiment.
|
31
98
|
#
|
32
|
-
#
|
99
|
+
# @param experiment_key [Integer]
|
100
|
+
# @param user [Hackle::User]
|
33
101
|
#
|
34
|
-
# @
|
35
|
-
# @param workspace_fetcher [PollingWorkspaceFetcher]
|
36
|
-
# @param event_processor [EventProcessor]
|
37
|
-
# @param decider [Decider]
|
102
|
+
# @return [String] the decided variation for the user
|
38
103
|
#
|
39
|
-
def
|
40
|
-
|
104
|
+
def variation(experiment_key, user)
|
105
|
+
variation_detail(experiment_key, user).variation
|
106
|
+
end
|
41
107
|
|
42
|
-
|
43
|
-
|
108
|
+
#
|
109
|
+
# Decide the variation to expose to the user for experiment, and returns an object that
|
110
|
+
# describes the way the variation was decided.
|
111
|
+
#
|
112
|
+
# @param experiment_key [Integer] the unique key of the experiment. MUST NOT be nil.
|
113
|
+
# @param user [Hackle::User] the user to participate in the experiment. MUST NOT be nil.
|
114
|
+
#
|
115
|
+
# @return [Hackle::ExperimentDecision] an object describing the result
|
116
|
+
#
|
117
|
+
def variation_detail(experiment_key, user)
|
118
|
+
unless experiment_key.is_a?(Integer)
|
119
|
+
Log.get.warn { "Invalid experiment key: #{experiment_key} (expected: integer)" }
|
120
|
+
return ExperimentDecision.new('A', DecisionReason::INVALID_INPUT, ParameterConfig.empty)
|
121
|
+
end
|
44
122
|
|
45
|
-
|
46
|
-
|
123
|
+
hackle_user = @user_resolver.resolve_or_nil(user)
|
124
|
+
if hackle_user.nil?
|
125
|
+
Log.get.warn { "Invalid hackle user: #{user}" }
|
126
|
+
return ExperimentDecision.new('A', DecisionReason::INVALID_INPUT, ParameterConfig.empty)
|
127
|
+
end
|
47
128
|
|
48
|
-
|
49
|
-
|
129
|
+
@core.experiment(experiment_key, hackle_user, 'A')
|
130
|
+
rescue => e
|
131
|
+
Log.get.error { "Unexpected error while deciding variation of experiment[#{experiment_key}]: #{e.inspect}]" }
|
132
|
+
ExperimentDecision.new('A', DecisionReason::EXCEPTION, ParameterConfig.empty)
|
50
133
|
end
|
51
134
|
|
52
135
|
#
|
53
|
-
# Decide the
|
136
|
+
# Decide whether the feature is turned on to the user.
|
137
|
+
#
|
138
|
+
# @param feature_key [Integer] the unique key of the feature.
|
139
|
+
# @param user [Hackle::User] the user requesting the feature.
|
140
|
+
#
|
141
|
+
# @return [TrueClass] of the feature is on
|
142
|
+
# @return [FalseClass] of the feature is off
|
143
|
+
#
|
144
|
+
def is_feature_on(feature_key, user)
|
145
|
+
feature_flag_detail(feature_key, user).is_on
|
146
|
+
end
|
147
|
+
|
54
148
|
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# @
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
def variation(experiment_key:, user:, default_variation: 'A')
|
68
|
-
|
69
|
-
return default_variation if experiment_key.nil? || !experiment_key.is_a?(Integer)
|
70
|
-
return default_variation if user.nil? || !user.is_a?(User) || !user.valid?
|
71
|
-
|
72
|
-
workspace = @workspace_fetcher.fetch
|
73
|
-
return default_variation if workspace.nil?
|
74
|
-
|
75
|
-
experiment = workspace.get_experiment(experiment_key: experiment_key)
|
76
|
-
return default_variation if experiment.nil?
|
77
|
-
|
78
|
-
decision = @decider.decide(experiment: experiment, user: user)
|
79
|
-
case decision
|
80
|
-
when Decision::NotAllocated
|
81
|
-
default_variation
|
82
|
-
when Decision::ForcedAllocated
|
83
|
-
decision.variation_key
|
84
|
-
when Decision::NaturalAllocated
|
85
|
-
exposure_event = UserEvent::Exposure.new(user: user, experiment: experiment, variation: decision.variation)
|
86
|
-
@event_processor.process(event: exposure_event)
|
87
|
-
decision.variation.key
|
88
|
-
else
|
89
|
-
default_variation
|
149
|
+
# Decide whether the feature is turned on to the user, and returns an object that
|
150
|
+
# describes the way the value was decided.
|
151
|
+
#
|
152
|
+
# @param feature_key [Integer] the unique key of the feature.
|
153
|
+
# @param user [Hackle::User] the user requesting the feature.
|
154
|
+
#
|
155
|
+
# @return [Hackle::FeatureFlagDecision] an object describing the result
|
156
|
+
#
|
157
|
+
def feature_flag_detail(feature_key, user)
|
158
|
+
unless feature_key.is_a?(Integer)
|
159
|
+
Log.get.warn { "Invalid feature key: #{feature_key} (expected: integer)" }
|
160
|
+
return FeatureFlagDecision.new(false, DecisionReason::INVALID_INPUT, ParameterConfig.empty)
|
90
161
|
end
|
91
162
|
|
163
|
+
hackle_user = @user_resolver.resolve_or_nil(user)
|
164
|
+
if hackle_user.nil?
|
165
|
+
Log.get.warn { "Invalid hackle user: #{user}" }
|
166
|
+
return FeatureFlagDecision.new(false, DecisionReason::INVALID_INPUT, ParameterConfig.empty)
|
167
|
+
end
|
168
|
+
|
169
|
+
@core.feature_flag(feature_key, hackle_user)
|
92
170
|
rescue => e
|
93
|
-
|
94
|
-
|
171
|
+
Log.get.error { "Unexpected error while deciding feature flag[#{feature_key}]: #{e.inspect}]" }
|
172
|
+
FeatureFlagDecision.new(false, DecisionReason::EXCEPTION, ParameterConfig.empty)
|
95
173
|
end
|
96
174
|
|
97
175
|
#
|
98
|
-
#
|
176
|
+
# Returns a instance of Hackle::RemoteConfig.
|
99
177
|
#
|
100
|
-
# @param
|
101
|
-
# @param user [User] the user that occurred the event.
|
178
|
+
# @param user [Hackle::User] the user requesting the remote config.
|
102
179
|
#
|
103
|
-
|
180
|
+
# @return [Hackle::RemoteConfig]
|
181
|
+
#
|
182
|
+
def remote_config(user)
|
183
|
+
RemoteConfig.new(user: user, user_resolver: @user_resolver, core: @core)
|
184
|
+
end
|
104
185
|
|
105
|
-
|
106
|
-
|
186
|
+
#
|
187
|
+
# Records the event that occurred by the user.
|
188
|
+
#
|
189
|
+
# @param event [Hackle::Event] the event that occurred.
|
190
|
+
# @param user [Hackle::User] the user that occurred the event.
|
191
|
+
#
|
192
|
+
def track(event, user)
|
193
|
+
unless event.is_a?(Event)
|
194
|
+
Log.get.warn { "Invalid event: #{event} (expected: Hackle::Event)" }
|
195
|
+
return
|
196
|
+
end
|
107
197
|
|
108
|
-
|
109
|
-
|
198
|
+
unless event.valid?
|
199
|
+
Log.get.error { "Invalid event: #{event.error_or_nil}" }
|
200
|
+
return
|
201
|
+
end
|
110
202
|
|
111
|
-
|
112
|
-
|
113
|
-
|
203
|
+
hackle_user = @user_resolver.resolve_or_nil(user)
|
204
|
+
if hackle_user.nil?
|
205
|
+
Log.get.warn { "Invalid hackle user: #{user}" }
|
206
|
+
return FeatureFlagDecision.new(false, DecisionReason::INVALID_INPUT, ParameterConfig.empty)
|
207
|
+
end
|
114
208
|
|
209
|
+
@core.track(event, hackle_user)
|
115
210
|
rescue => e
|
116
|
-
|
211
|
+
Log.get.error { "Unexpected error while tracking event: #{e.inspect}]" }
|
117
212
|
end
|
118
213
|
|
119
214
|
#
|
120
215
|
# Shutdown the background task and release the resources used for the background task.
|
216
|
+
# This should only be called when the application shutdown.
|
121
217
|
#
|
122
218
|
def close
|
123
|
-
@
|
124
|
-
|
219
|
+
@core.close
|
220
|
+
end
|
221
|
+
|
222
|
+
def __resume
|
223
|
+
@core.resume
|
125
224
|
end
|
126
225
|
end
|
127
226
|
end
|
data/lib/hackle/config.rb
CHANGED
@@ -4,30 +4,72 @@ require 'logger'
|
|
4
4
|
|
5
5
|
module Hackle
|
6
6
|
class Config
|
7
|
+
# @return [Logger]
|
8
|
+
attr_reader :logger
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
@base_uri = options[:base_uri] || Config.default_base_uri
|
11
|
-
@event_uri = options[:event_uri] || Config.default_event_uri
|
12
|
-
end
|
10
|
+
# @return [String]
|
11
|
+
attr_reader :sdk_url
|
13
12
|
|
14
|
-
|
15
|
-
attr_reader :
|
16
|
-
attr_reader :event_uri
|
13
|
+
# @return [String]
|
14
|
+
attr_reader :event_url
|
17
15
|
|
18
|
-
|
19
|
-
|
16
|
+
# @param logger [Logger]
|
17
|
+
# @param sdk_url [String]
|
18
|
+
# @param event_url [String]
|
19
|
+
def initialize(
|
20
|
+
logger:,
|
21
|
+
sdk_url:,
|
22
|
+
event_url:
|
23
|
+
)
|
24
|
+
@logger = logger
|
25
|
+
@sdk_url = sdk_url
|
26
|
+
@event_url = event_url
|
20
27
|
end
|
21
28
|
|
22
|
-
def self.
|
23
|
-
|
29
|
+
def self.builder
|
30
|
+
Builder.new
|
24
31
|
end
|
25
32
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
33
|
+
class Builder
|
34
|
+
def initialize
|
35
|
+
# noinspection RubyResolve
|
36
|
+
@logger = if defined?(Rails) && Rails.logger
|
37
|
+
Rails.logger
|
38
|
+
else
|
39
|
+
Logger.new($stdout)
|
40
|
+
end
|
41
|
+
@sdk_url = 'https://sdk.hackle.io'
|
42
|
+
@event_url = 'https://event.hackle.io'
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param logger [Logger]
|
46
|
+
# @return [Hackle::Config::Builder]
|
47
|
+
def logger(logger)
|
48
|
+
@logger = logger
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param sdk_url [String]
|
53
|
+
# @return [Hackle::Config::Builder]
|
54
|
+
def sdk_url(sdk_url)
|
55
|
+
@sdk_url = sdk_url
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param event_url [String]
|
60
|
+
# @return [Hackle::Config::Builder]
|
61
|
+
def event_url(event_url)
|
62
|
+
@event_url = event_url
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Hackle::Config]
|
67
|
+
def build
|
68
|
+
Config.new(
|
69
|
+
logger: @logger,
|
70
|
+
sdk_url: @sdk_url,
|
71
|
+
event_url: @event_url
|
72
|
+
)
|
31
73
|
end
|
32
74
|
end
|
33
75
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hackle
|
4
|
+
class ExperimentDecision
|
5
|
+
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :variation
|
8
|
+
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :reason
|
11
|
+
|
12
|
+
# @return [ParameterConfig]
|
13
|
+
attr_reader :config
|
14
|
+
|
15
|
+
# @param variation [String]
|
16
|
+
# @param reason [String]
|
17
|
+
# @param config [ParameterConfig]
|
18
|
+
def initialize(variation, reason, config)
|
19
|
+
@config = config
|
20
|
+
@variation = variation
|
21
|
+
@reason = reason
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param key [String]
|
25
|
+
# @param default_value [Object, nil]
|
26
|
+
# @return [Object, nil]
|
27
|
+
def get(key, default_value = nil)
|
28
|
+
config.get(key, default_value)
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
other.is_a?(self.class) &&
|
33
|
+
variation == other.variation &&
|
34
|
+
reason == other.reason &&
|
35
|
+
config == other.config
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
"ExperimentDecision(variation=#{variation}, reason=#{reason}, config=#{config})"
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class FeatureFlagDecision
|
45
|
+
|
46
|
+
# @return [boolean]
|
47
|
+
attr_accessor :is_on
|
48
|
+
|
49
|
+
# @return [String]
|
50
|
+
attr_accessor :reason
|
51
|
+
|
52
|
+
# @return [ParameterConfig]
|
53
|
+
attr_reader :config
|
54
|
+
|
55
|
+
# @param is_on [boolean]
|
56
|
+
# @param reason [String]
|
57
|
+
# @param config [ParameterConfig]
|
58
|
+
def initialize(is_on, reason, config)
|
59
|
+
@is_on = is_on
|
60
|
+
@reason = reason
|
61
|
+
@config = config
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [boolean]
|
65
|
+
def on?
|
66
|
+
@is_on
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param key [String]
|
70
|
+
# @param default_value [Object, nil]
|
71
|
+
# @return [Object, nil]
|
72
|
+
def get(key, default_value = nil)
|
73
|
+
config.get(key, default_value)
|
74
|
+
end
|
75
|
+
|
76
|
+
def ==(other)
|
77
|
+
other.is_a?(FeatureFlagDecision) &&
|
78
|
+
is_on == other.is_on &&
|
79
|
+
reason == other.reason &&
|
80
|
+
config == other.config
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
"FeatureFlagDecision(is_on=#{is_on}, reason=#{reason}, config=#{config})"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class RemoteConfigDecision
|
89
|
+
|
90
|
+
# @return [Object, nil]
|
91
|
+
attr_reader :value
|
92
|
+
|
93
|
+
# @return [String]
|
94
|
+
attr_reader :reason
|
95
|
+
|
96
|
+
# @param value [Object, nil]
|
97
|
+
# @param reason [String]
|
98
|
+
def initialize(value, reason)
|
99
|
+
@value = value
|
100
|
+
@reason = reason
|
101
|
+
end
|
102
|
+
|
103
|
+
def ==(other)
|
104
|
+
other.is_a?(RemoteConfigDecision) &&
|
105
|
+
value == other.value &&
|
106
|
+
reason == other.reason
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
"RemoteConfigDecision(value=#{value}, reason=#{reason})"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/hackle/event.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hackle/internal/properties/properties_builder'
|
4
|
+
|
5
|
+
module Hackle
|
6
|
+
class Event
|
7
|
+
# @return [String]
|
8
|
+
attr_reader :key
|
9
|
+
|
10
|
+
# @return [Float, nil]
|
11
|
+
attr_reader :value
|
12
|
+
|
13
|
+
# @return [Hash{String => Object}]
|
14
|
+
attr_reader :properties
|
15
|
+
|
16
|
+
# @param key [String]
|
17
|
+
# @param value [Numeric, nil]
|
18
|
+
# @param properties [Hash{String => Object}]
|
19
|
+
def initialize(key:, value:, properties:)
|
20
|
+
@key = key
|
21
|
+
@value = value
|
22
|
+
@properties = properties
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [boolean]
|
26
|
+
def valid?
|
27
|
+
error_or_nil.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String, nil]
|
31
|
+
def error_or_nil
|
32
|
+
return "Invalid event key: #{key} (expected: not empty string)" unless ValueType.not_empty_string?(key)
|
33
|
+
return "Invalid event value: #{value} (expected: number)" if !value.nil? && !ValueType.number?(value)
|
34
|
+
return "Invalid event properties: #{properties} (expected: Hash)" if !properties.nil? && !properties.is_a?(Hash)
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(other)
|
40
|
+
other.is_a?(Event) && other.key == key && other.value == value && other.properties == properties
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
"Hackle::Event(key: #{key}, value: #{value}, properties: #{properties})"
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param key [String]
|
48
|
+
# @return [Hackle::Event::Builder]
|
49
|
+
def self.builder(key)
|
50
|
+
Builder.new(key)
|
51
|
+
end
|
52
|
+
|
53
|
+
class Builder
|
54
|
+
# @param key [String]
|
55
|
+
def initialize(key)
|
56
|
+
@key = key
|
57
|
+
@value = nil
|
58
|
+
@properties = PropertiesBuilder.new
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param value [Float, nil]
|
62
|
+
# @return [Hackle::Event::Builder]
|
63
|
+
def value(value)
|
64
|
+
@value = value
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param key [String]
|
69
|
+
# @param value [Object, nil]
|
70
|
+
# @return [Hackle::Event::Builder]
|
71
|
+
def property(key, value)
|
72
|
+
@properties.add(key, value)
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param properties [Hash{String => Object}]
|
77
|
+
# @return [Hackle::Event::Builder]
|
78
|
+
def properties(properties)
|
79
|
+
@properties.add_all(properties)
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [Hackle::Event]
|
84
|
+
def build
|
85
|
+
Event.new(key: @key, value: @value, properties: @properties.build)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hackle
|
4
|
+
module Clock
|
5
|
+
# @return [Integer]
|
6
|
+
def current_millis; end
|
7
|
+
|
8
|
+
# @return [Integer]
|
9
|
+
def tick; end
|
10
|
+
end
|
11
|
+
|
12
|
+
class SystemClock
|
13
|
+
include Clock
|
14
|
+
|
15
|
+
@instance = new
|
16
|
+
|
17
|
+
# @return [SystemClock]
|
18
|
+
def self.instance
|
19
|
+
@instance
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_millis
|
23
|
+
(Time.now.to_f * 1000).to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
def tick
|
27
|
+
(Time.now.to_f * 1000 * 1000 * 1000).to_i
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class FixedClock
|
32
|
+
include Clock
|
33
|
+
|
34
|
+
# @param time [Integer]
|
35
|
+
def initialize(time)
|
36
|
+
@time = time
|
37
|
+
end
|
38
|
+
|
39
|
+
def current_millis
|
40
|
+
@time
|
41
|
+
end
|
42
|
+
|
43
|
+
def tick
|
44
|
+
@time
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent'
|
4
|
+
require 'hackle/internal/concurrent/schedule/timer_scheduler'
|
5
|
+
|
6
|
+
module Hackle
|
7
|
+
class Executors
|
8
|
+
# @param pool_size [Integer]
|
9
|
+
# @param queue_capacity [Integer]
|
10
|
+
# @return [Concurrent::ThreadPoolExecutor]
|
11
|
+
def self.thread_pool(pool_size:, queue_capacity:)
|
12
|
+
Concurrent::ThreadPoolExecutor.new(min_threads: pool_size, max_threads: pool_size, max_queue: queue_capacity)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [TimerScheduler]
|
16
|
+
def self.scheduler
|
17
|
+
TimerScheduler.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|