hackle-ruby-sdk 1.0.0 → 2.0.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 +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
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'hackle/internal/model/action'
|
|
4
|
+
require 'hackle/internal/model/bucket'
|
|
5
|
+
require 'hackle/internal/model/container'
|
|
6
|
+
require 'hackle/internal/model/event_type'
|
|
7
|
+
require 'hackle/internal/model/experiment'
|
|
8
|
+
require 'hackle/internal/model/parameter_configuration'
|
|
9
|
+
require 'hackle/internal/model/remote_config_parameter'
|
|
10
|
+
require 'hackle/internal/model/segment'
|
|
11
|
+
require 'hackle/internal/model/target'
|
|
12
|
+
require 'hackle/internal/model/target_rule'
|
|
13
|
+
require 'hackle/internal/model/targeting'
|
|
14
|
+
require 'hackle/internal/model/value_type'
|
|
15
|
+
require 'hackle/internal/model/variation'
|
|
16
|
+
|
|
17
|
+
module Hackle
|
|
18
|
+
class Workspace
|
|
19
|
+
# @param experiments [Hash{Integer => Experiment}]
|
|
20
|
+
# @param feature_flags [Hash{Integer => Experiment}]
|
|
21
|
+
# @param buckets [Hash{Integer => Bucket}]
|
|
22
|
+
# @param event_types [Hash{String => EventType}]
|
|
23
|
+
# @param segments [Hash{String => Segment}]
|
|
24
|
+
# @param containers [Hash{Integer => Container}]
|
|
25
|
+
# @param parameter_configurations [Hash{Integer => ParameterConfiguration}]
|
|
26
|
+
# @param remote_config_parameters [Hash{String => RemoteConfigParameter}]
|
|
27
|
+
def initialize(
|
|
28
|
+
experiments:,
|
|
29
|
+
feature_flags:,
|
|
30
|
+
buckets:,
|
|
31
|
+
event_types:,
|
|
32
|
+
segments:,
|
|
33
|
+
containers:,
|
|
34
|
+
parameter_configurations:,
|
|
35
|
+
remote_config_parameters:
|
|
36
|
+
)
|
|
37
|
+
# @type [Hash{Integer => Experiment}]
|
|
38
|
+
@experiments = experiments
|
|
39
|
+
|
|
40
|
+
# @type [Hash{Integer => Experiment}]
|
|
41
|
+
@feature_flags = feature_flags
|
|
42
|
+
|
|
43
|
+
# @type [Hash{Integer => Bucket}]
|
|
44
|
+
@buckets = buckets
|
|
45
|
+
|
|
46
|
+
# @type [Hash{String => EventType}]
|
|
47
|
+
@event_types = event_types
|
|
48
|
+
|
|
49
|
+
# @type [Hash{String => Segment}]
|
|
50
|
+
@segments = segments
|
|
51
|
+
|
|
52
|
+
# @type [Hash{Integer => Container}]
|
|
53
|
+
@containers = containers
|
|
54
|
+
|
|
55
|
+
# @type [Hash{Integer => ParameterConfiguration}]
|
|
56
|
+
@parameter_configurations = parameter_configurations
|
|
57
|
+
|
|
58
|
+
# @type [Hash{String => RemoteConfigParameter}]
|
|
59
|
+
@remote_config_parameters = remote_config_parameters
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @param experiment_key [Integer]
|
|
63
|
+
# @return [Hackle::Experiment, nil]
|
|
64
|
+
def get_experiment_or_nil(experiment_key)
|
|
65
|
+
@experiments[experiment_key]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @param feature_key [Integer]
|
|
69
|
+
# @return [Hackle::Experiment, nil]
|
|
70
|
+
def get_feature_flag_or_nil(feature_key)
|
|
71
|
+
@feature_flags[feature_key]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @param event_type_key [String]
|
|
75
|
+
# @return [Hackle::EventType, nil]
|
|
76
|
+
def get_event_type_or_nil(event_type_key)
|
|
77
|
+
@event_types[event_type_key]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @param bucket_id [Integer]
|
|
81
|
+
# @return [Hackle::Bucket, nil]
|
|
82
|
+
def get_bucket_or_nil(bucket_id)
|
|
83
|
+
@buckets[bucket_id]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @param segment_key [String]
|
|
87
|
+
# @return [Hackle::Segment, nil]
|
|
88
|
+
def get_segment_or_nil(segment_key)
|
|
89
|
+
@segments[segment_key]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @param container_id [Integer]
|
|
93
|
+
# @return [Hackle::Container, nil]
|
|
94
|
+
def get_container_or_nil(container_id)
|
|
95
|
+
@containers[container_id]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @param parameter_configuration_id [Integer]
|
|
99
|
+
# @return [Hackle::ParameterConfiguration, nil]
|
|
100
|
+
def get_parameter_configuration_or_nil(parameter_configuration_id)
|
|
101
|
+
@parameter_configurations[parameter_configuration_id]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @param parameter_key [String]
|
|
105
|
+
# @return [Hackle::RemoteConfigParameter, nil]
|
|
106
|
+
def get_remote_config_parameter_or_nil(parameter_key)
|
|
107
|
+
@remote_config_parameters[parameter_key]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class << self
|
|
111
|
+
# @param experiments [Array<Experiment>]
|
|
112
|
+
# @param feature_flags [Array<Experiment>]
|
|
113
|
+
# @param buckets [Array<Bucket>]
|
|
114
|
+
# @param event_types [Array<EventType>]
|
|
115
|
+
# @param segments [Array<Segment>]
|
|
116
|
+
# @param containers [Array<Container>]
|
|
117
|
+
# @param parameter_configurations [Array<ParameterConfiguration>]
|
|
118
|
+
# @param remote_config_parameters [Array<RemoteConfigParameter>]
|
|
119
|
+
# @return [Workspace]
|
|
120
|
+
def create(
|
|
121
|
+
experiments: [],
|
|
122
|
+
feature_flags: [],
|
|
123
|
+
buckets: [],
|
|
124
|
+
event_types: [],
|
|
125
|
+
segments: [],
|
|
126
|
+
containers: [],
|
|
127
|
+
parameter_configurations: [],
|
|
128
|
+
remote_config_parameters: []
|
|
129
|
+
)
|
|
130
|
+
Workspace.new(
|
|
131
|
+
experiments: experiments.each_with_object({}) { |item, hash| hash[item.key] = item },
|
|
132
|
+
feature_flags: feature_flags.each_with_object({}) { |item, hash| hash[item.key] = item },
|
|
133
|
+
buckets: buckets.each_with_object({}) { |item, hash| hash[item.id] = item },
|
|
134
|
+
event_types: event_types.each_with_object({}) { |item, hash| hash[item.key] = item },
|
|
135
|
+
segments: segments.each_with_object({}) { |item, hash| hash[item.key] = item },
|
|
136
|
+
containers: containers.each_with_object({}) { |item, hash| hash[item.id] = item },
|
|
137
|
+
parameter_configurations: parameter_configurations.each_with_object({}) { |item, hash| hash[item.id] = item },
|
|
138
|
+
remote_config_parameters: remote_config_parameters.each_with_object({}) { |item, hash| hash[item.key] = item }
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @param hash [Hash]
|
|
143
|
+
# @return [Hackle::Workspace]
|
|
144
|
+
def from_hash(hash)
|
|
145
|
+
Workspace.create(
|
|
146
|
+
experiments: hash[:experiments].map { |it| experiment(it, ExperimentType::AB_TEST) },
|
|
147
|
+
feature_flags: hash[:featureFlags].map { |it| experiment(it, ExperimentType::FEATURE_FLAG) },
|
|
148
|
+
buckets: hash[:buckets].map { |it| bucket(it) },
|
|
149
|
+
event_types: hash[:events].map { |it| event_type(it) },
|
|
150
|
+
segments: hash[:segments].map { |it| segment_or_nil(it) }.compact,
|
|
151
|
+
containers: hash[:containers].map { |it| container(it) },
|
|
152
|
+
parameter_configurations: hash[:parameterConfigurations].map { |it| parameter_configuration(it) },
|
|
153
|
+
remote_config_parameters: hash[:remoteConfigParameters].map { |it| remote_config_parameter_or_nil(it) }.compact
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def experiment(hash, experiment_type)
|
|
160
|
+
status = ExperimentStatus.from_or_nil(hash[:execution][:status])
|
|
161
|
+
return nil if status.nil?
|
|
162
|
+
|
|
163
|
+
default_rule = action_or_nil(hash[:execution][:defaultRule])
|
|
164
|
+
return nil if default_rule.nil?
|
|
165
|
+
|
|
166
|
+
Experiment.new(
|
|
167
|
+
id: hash[:id],
|
|
168
|
+
key: hash[:key],
|
|
169
|
+
name: hash[:name],
|
|
170
|
+
type: experiment_type,
|
|
171
|
+
identifier_type: hash[:identifierType],
|
|
172
|
+
status: status,
|
|
173
|
+
version: hash[:version],
|
|
174
|
+
execution_version: hash[:execution][:version],
|
|
175
|
+
variations: hash[:variations].map { |it| variation(it) },
|
|
176
|
+
user_overrides: Hash[hash[:execution][:userOverrides].map { |it| [it[:userId], it[:variationId]] }],
|
|
177
|
+
segment_overrides: hash[:execution][:segmentOverrides].map { |it| target_rule_or_nil(it, TargetingType::IDENTIFIER) }.compact,
|
|
178
|
+
target_audiences: hash[:execution][:targetAudiences].map { |it| target_or_nil(it, TargetingType::PROPERTY) }.compact,
|
|
179
|
+
target_rules: hash[:execution][:targetRules].map { |it| target_rule_or_nil(it, TargetingType::PROPERTY) }.compact,
|
|
180
|
+
default_rule: default_rule,
|
|
181
|
+
container_id: hash[:containerId],
|
|
182
|
+
winner_variation_id: hash[:winnerVariationId]
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def variation(hash)
|
|
187
|
+
Variation.new(
|
|
188
|
+
id: hash[:id],
|
|
189
|
+
key: hash[:key],
|
|
190
|
+
is_dropped: hash[:status] == 'DROPPED',
|
|
191
|
+
parameter_configuration_id: hash[:parameterConfigurationId]
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def target_or_nil(hash, targeting_type)
|
|
196
|
+
conditions = hash[:conditions].map { |it| condition_or_nil(it, targeting_type) }.compact
|
|
197
|
+
return nil if conditions.empty?
|
|
198
|
+
|
|
199
|
+
Target.new(conditions: conditions)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def condition_or_nil(hash, targeting_type)
|
|
203
|
+
key = target_key_or_nil(hash[:key])
|
|
204
|
+
return nil if key.nil?
|
|
205
|
+
return nil unless targeting_type.supports?(key.type)
|
|
206
|
+
|
|
207
|
+
match = target_match_or_nil(hash[:match])
|
|
208
|
+
return nil if match.nil?
|
|
209
|
+
|
|
210
|
+
TargetCondition.new(
|
|
211
|
+
key: key,
|
|
212
|
+
match: match
|
|
213
|
+
)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def target_key_or_nil(hash)
|
|
217
|
+
type = TargetKeyType.from_or_nil(hash[:type])
|
|
218
|
+
return nil if type.nil?
|
|
219
|
+
|
|
220
|
+
TargetKey.new(
|
|
221
|
+
type: type,
|
|
222
|
+
name: hash[:name]
|
|
223
|
+
)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def target_match_or_nil(hash)
|
|
227
|
+
type = TargetMatchType.from_or_nil(hash[:type])
|
|
228
|
+
return nil if type.nil?
|
|
229
|
+
|
|
230
|
+
operator = TargetOperator.from_or_nil(hash[:operator])
|
|
231
|
+
return nil if operator.nil?
|
|
232
|
+
|
|
233
|
+
value_type = ValueType.from_or_nil(hash[:valueType])
|
|
234
|
+
return nil if value_type.nil?
|
|
235
|
+
|
|
236
|
+
TargetMatch.new(
|
|
237
|
+
type: type,
|
|
238
|
+
operator: operator,
|
|
239
|
+
value_type: value_type,
|
|
240
|
+
values: hash[:values]
|
|
241
|
+
)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def action_or_nil(hash)
|
|
245
|
+
type = ActionType.from_or_nil(hash[:type])
|
|
246
|
+
return nil if type.nil?
|
|
247
|
+
|
|
248
|
+
Action.new(
|
|
249
|
+
type: type,
|
|
250
|
+
variation_id: hash[:variationId],
|
|
251
|
+
bucket_id: hash[:bucketId]
|
|
252
|
+
)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def target_rule_or_nil(hash, targeting_type)
|
|
256
|
+
target = target_or_nil(hash[:target], targeting_type)
|
|
257
|
+
return nil if target.nil?
|
|
258
|
+
|
|
259
|
+
action = action_or_nil(hash[:action])
|
|
260
|
+
return nil if action.nil?
|
|
261
|
+
|
|
262
|
+
TargetRule.new(
|
|
263
|
+
target: target,
|
|
264
|
+
action: action
|
|
265
|
+
)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def bucket(hash)
|
|
269
|
+
Bucket.new(
|
|
270
|
+
id: hash[:id],
|
|
271
|
+
seed: hash[:seed],
|
|
272
|
+
slot_size: hash[:slotSize],
|
|
273
|
+
slots: hash[:slots].map { |it| slot(it) }
|
|
274
|
+
)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def slot(hash)
|
|
278
|
+
Slot.new(
|
|
279
|
+
start_inclusive: hash[:startInclusive],
|
|
280
|
+
end_exclusive: hash[:endExclusive],
|
|
281
|
+
variation_id: hash[:variationId]
|
|
282
|
+
)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def event_type(hash)
|
|
286
|
+
EventType.new(
|
|
287
|
+
id: hash[:id],
|
|
288
|
+
key: hash[:key]
|
|
289
|
+
)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def segment_or_nil(hash)
|
|
293
|
+
type = SegmentType.from_or_nil(hash[:type])
|
|
294
|
+
return nil if type.nil?
|
|
295
|
+
|
|
296
|
+
Segment.new(
|
|
297
|
+
id: hash[:id],
|
|
298
|
+
key: hash[:key],
|
|
299
|
+
type: type,
|
|
300
|
+
targets: hash[:targets].map { |it| target_or_nil(it, TargetingType::SEGMENT) }.compact
|
|
301
|
+
)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def container(hash)
|
|
305
|
+
Container.new(
|
|
306
|
+
id: hash[:id],
|
|
307
|
+
bucket_id: hash[:bucketId],
|
|
308
|
+
groups: hash[:groups].map { |it| ContainerGroup.new(id: it[:id], experiments: it[:experiments]) }
|
|
309
|
+
)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def parameter_configuration(hash)
|
|
313
|
+
ParameterConfiguration.new(
|
|
314
|
+
id: hash[:id],
|
|
315
|
+
parameters: Hash[hash[:parameters].map { |it| [it[:key], it[:value]] }]
|
|
316
|
+
)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def remote_config_parameter_or_nil(hash)
|
|
320
|
+
type = ValueType.from_or_nil(hash[:type])
|
|
321
|
+
return nil if type.nil?
|
|
322
|
+
|
|
323
|
+
RemoteConfigParameter.new(
|
|
324
|
+
id: hash[:id],
|
|
325
|
+
key: hash[:key],
|
|
326
|
+
type: type,
|
|
327
|
+
identifier_type: hash[:identifierType],
|
|
328
|
+
target_rules: hash[:targetRules].map { |it| remote_config_target_rule_or_nil(it) }.compact,
|
|
329
|
+
default_value: RemoteConfigValue.new(
|
|
330
|
+
id: hash[:defaultValue][:id],
|
|
331
|
+
raw_value: hash[:defaultValue][:value],
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def remote_config_target_rule_or_nil(hash)
|
|
337
|
+
target = target_or_nil(hash[:target], TargetingType::PROPERTY)
|
|
338
|
+
return nil if target.nil?
|
|
339
|
+
|
|
340
|
+
RemoteConfigTargetRule.new(
|
|
341
|
+
key: hash[:key],
|
|
342
|
+
name: hash[:name],
|
|
343
|
+
target: target,
|
|
344
|
+
bucket_id: hash[:bucketId],
|
|
345
|
+
value: RemoteConfigValue.new(
|
|
346
|
+
id: hash[:value][:id],
|
|
347
|
+
raw_value: hash[:value][:value]
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hackle
|
|
4
|
+
class RemoteConfig
|
|
5
|
+
# @param user [Hackle::User]
|
|
6
|
+
# @param user_resolver [Hackle::HackleUserResolver]
|
|
7
|
+
# @param core [Hackle::Core]
|
|
8
|
+
def initialize(user:, user_resolver:, core:)
|
|
9
|
+
# @type [Hackle::User]
|
|
10
|
+
@user = user
|
|
11
|
+
|
|
12
|
+
# @type [Hackle::HackleUserResolver]
|
|
13
|
+
@user_resolver = user_resolver
|
|
14
|
+
|
|
15
|
+
# @type [Hackle::Core]
|
|
16
|
+
@core = core
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param key [String]
|
|
20
|
+
# @param default_value [Object, nil]
|
|
21
|
+
# @return [Object, nil]
|
|
22
|
+
def get(key, default_value = nil)
|
|
23
|
+
case default_value
|
|
24
|
+
when nil
|
|
25
|
+
decision(key, default_value, ValueType::NULL).value
|
|
26
|
+
when String
|
|
27
|
+
decision(key, default_value, ValueType::STRING).value
|
|
28
|
+
when Numeric
|
|
29
|
+
decision(key, default_value, ValueType::NUMBER).value
|
|
30
|
+
when TrueClass, FalseClass
|
|
31
|
+
decision(key, default_value, ValueType::BOOLEAN).value
|
|
32
|
+
else
|
|
33
|
+
decision(key, default_value, ValueType::UNKNOWN).value
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# @param key [String]
|
|
41
|
+
# @param default_value [Object, nil]
|
|
42
|
+
# @param required_type [Hackle::ValueType]
|
|
43
|
+
# @return [Hackle::RemoteConfigDecision]
|
|
44
|
+
def decision(key, default_value, required_type)
|
|
45
|
+
hackle_user = @user_resolver.resolve_or_nil(@user)
|
|
46
|
+
return RemoteConfigDecision.new(default_value, DecisionReason::INVALID_INPUT) if hackle_user.nil?
|
|
47
|
+
return RemoteConfigDecision.new(default_value, DecisionReason::INVALID_INPUT) if key.nil?
|
|
48
|
+
|
|
49
|
+
@core.remote_config(key, hackle_user, required_type, default_value)
|
|
50
|
+
rescue => e
|
|
51
|
+
Log.get.error { "Unexpected error while deciding remote config parameter[#{key}]. Returning default value: #{e.inspect}" }
|
|
52
|
+
RemoteConfigDecision.new(default_value, DecisionReason::EXCEPTION)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/hackle/user.rb
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'hackle/internal/identifiers/identifier_builder'
|
|
4
|
+
require 'hackle/internal/properties/properties_builder'
|
|
5
|
+
|
|
6
|
+
module Hackle
|
|
7
|
+
class User
|
|
8
|
+
# @return [String, nil]
|
|
9
|
+
attr_reader :id
|
|
10
|
+
|
|
11
|
+
# @return [String, nil]
|
|
12
|
+
attr_reader :user_id
|
|
13
|
+
|
|
14
|
+
# @return [String, nil]
|
|
15
|
+
attr_reader :device_id
|
|
16
|
+
|
|
17
|
+
# @return [Hash{String => String}]
|
|
18
|
+
attr_reader :identifiers
|
|
19
|
+
|
|
20
|
+
# @return [Hash{String => Object}]
|
|
21
|
+
attr_reader :properties
|
|
22
|
+
|
|
23
|
+
# @param id [String, nil]
|
|
24
|
+
# @param user_id [String, nil]
|
|
25
|
+
# @param device_id [String, nil]
|
|
26
|
+
# @param identifiers [Hash{String => String}]
|
|
27
|
+
# @param properties [Hash{String => Object}]
|
|
28
|
+
def initialize(id:, user_id:, device_id:, identifiers:, properties:)
|
|
29
|
+
@id = id
|
|
30
|
+
@user_id = user_id
|
|
31
|
+
@device_id = device_id
|
|
32
|
+
@identifiers = identifiers
|
|
33
|
+
@properties = properties
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ==(other)
|
|
37
|
+
return false unless other.is_a?(User)
|
|
38
|
+
|
|
39
|
+
id == other.id &&
|
|
40
|
+
user_id == other.user_id &&
|
|
41
|
+
device_id == other.device_id &&
|
|
42
|
+
identifiers == other.identifiers &&
|
|
43
|
+
properties == other.properties
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s
|
|
47
|
+
"Hackle::User(id: #{id}, user_id: #{user_id}, device_id: #{device_id}, identifiers: #{identifiers}, properties: #{properties})"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [User::Builder]
|
|
51
|
+
def self.builder
|
|
52
|
+
Builder.new
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Builder
|
|
56
|
+
def initialize
|
|
57
|
+
@identifiers = IdentifiersBuilder.new
|
|
58
|
+
@properties = PropertiesBuilder.new
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param id [String, Numeric]
|
|
62
|
+
# @return [User::Builder]
|
|
63
|
+
def id(id)
|
|
64
|
+
@id = IdentifiersBuilder.sanitize_value_or_nil(id)
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @param user_id [String, Numeric]
|
|
69
|
+
# @return [User::Builder]
|
|
70
|
+
def user_id(user_id)
|
|
71
|
+
@user_id = IdentifiersBuilder.sanitize_value_or_nil(user_id)
|
|
72
|
+
self
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @param device_id [String, Numeric]
|
|
76
|
+
# @return [User::Builder]
|
|
77
|
+
def device_id(device_id)
|
|
78
|
+
@device_id = IdentifiersBuilder.sanitize_value_or_nil(device_id)
|
|
79
|
+
self
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @param identifier_type [String]
|
|
83
|
+
# @param identifier_value [String, nil]
|
|
84
|
+
# @return [User::Builder]
|
|
85
|
+
def identifier(identifier_type, identifier_value)
|
|
86
|
+
@identifiers.add(identifier_type, identifier_value)
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @param identifiers [Hash{String => String}]
|
|
91
|
+
# @return [User::Builder]
|
|
92
|
+
def identifiers(identifiers)
|
|
93
|
+
@identifiers.add_all(identifiers)
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @param key [String]
|
|
98
|
+
# @param value [Object, nil]
|
|
99
|
+
# @return [User::Builder]
|
|
100
|
+
def property(key, value)
|
|
101
|
+
@properties.add(key, value)
|
|
102
|
+
self
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @param properties [Hash{String => Object}]
|
|
106
|
+
# @return [User::Builder]
|
|
107
|
+
def properties(properties)
|
|
108
|
+
@properties.add_all(properties)
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @return [User]
|
|
113
|
+
def build
|
|
114
|
+
User.new(
|
|
115
|
+
id: @id,
|
|
116
|
+
user_id: @user_id,
|
|
117
|
+
device_id: @device_id,
|
|
118
|
+
identifiers: @identifiers.build,
|
|
119
|
+
properties: @properties.build
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/hackle/version.rb
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Hackle
|
|
4
|
-
VERSION = '
|
|
5
|
-
SDK_NAME = 'ruby-sdk'
|
|
6
|
-
|
|
7
|
-
class SdkInfo
|
|
8
|
-
attr_reader :key, :name, :version
|
|
9
|
-
def initialize(key:)
|
|
10
|
-
@key = key
|
|
11
|
-
@name = SDK_NAME
|
|
12
|
-
@version = VERSION
|
|
13
|
-
end
|
|
14
|
-
end
|
|
4
|
+
VERSION = '2.0.0'
|
|
15
5
|
end
|
data/lib/hackle.rb
CHANGED
|
@@ -2,75 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require 'hackle/client'
|
|
4
4
|
require 'hackle/config'
|
|
5
|
-
require 'hackle/
|
|
5
|
+
require 'hackle/decision'
|
|
6
|
+
require 'hackle/user'
|
|
7
|
+
require 'hackle/event'
|
|
8
|
+
require 'hackle/remote_config'
|
|
6
9
|
|
|
7
10
|
module Hackle
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
# Instantiates a Hackle client.
|
|
11
|
-
#
|
|
12
|
-
# @see Client#initialize
|
|
13
|
-
#
|
|
14
|
-
# @param sdk_key [String] The SDK key of your Hackle environment
|
|
15
|
-
# @param options Optional parameters of configuration options
|
|
16
|
-
#
|
|
17
|
-
# @return [Client] The Hackle client instance.
|
|
18
|
-
#
|
|
19
|
-
def self.client(sdk_key:, **options)
|
|
20
|
-
config = Config.new(options)
|
|
21
|
-
sdk_info = SdkInfo.new(key: sdk_key)
|
|
22
|
-
|
|
23
|
-
http_workspace_fetcher = HttpWorkspaceFetcher.new(config: config, sdk_info: sdk_info)
|
|
24
|
-
polling_workspace_fetcher = PollingWorkspaceFetcher.new(config: config, http_fetcher: http_workspace_fetcher)
|
|
25
|
-
|
|
26
|
-
event_dispatcher = EventDispatcher.new(config: config, sdk_info: sdk_info)
|
|
27
|
-
event_processor = EventProcessor.new(config: config, event_dispatcher: event_dispatcher)
|
|
28
|
-
|
|
29
|
-
polling_workspace_fetcher.start!
|
|
30
|
-
event_processor.start!
|
|
31
|
-
|
|
32
|
-
Client.new(
|
|
33
|
-
config: config,
|
|
34
|
-
workspace_fetcher: polling_workspace_fetcher,
|
|
35
|
-
event_processor: event_processor,
|
|
36
|
-
decider: Decider.new
|
|
37
|
-
)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
# Instantiate a user to be used for the hackle sdk.
|
|
42
|
-
#
|
|
43
|
-
# The only required parameter is `id`, which must uniquely identify each user.
|
|
44
|
-
#
|
|
45
|
-
# @example
|
|
46
|
-
# Hackle.user(id: 'ae2182e0')
|
|
47
|
-
# Hackle.user(id: 'ae2182e0', app_version: '1.0.1', paying_customer: false)
|
|
48
|
-
#
|
|
49
|
-
# @param id [String] The identifier of the user. (e.g. device_id, account_id etc.)
|
|
50
|
-
# @param properties Additional properties of the user. (e.g. app_version, membership_grade, etc.)
|
|
51
|
-
#
|
|
52
|
-
# @return [User] The configured user object.
|
|
53
|
-
#
|
|
54
|
-
def self.user(id:, **properties)
|
|
55
|
-
User.new(id: id, properties: properties)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
#
|
|
59
|
-
# Instantiate an event to be used for the hackle sdk.
|
|
60
|
-
#
|
|
61
|
-
# The only required parameter is `key`, which must uniquely identify each event.
|
|
62
|
-
#
|
|
63
|
-
# @example
|
|
64
|
-
# Hackle.event(key: 'purchase')
|
|
65
|
-
# Hackle.event(key: 'purchase', value: 42000.0, app_version: '1.0.1', payment_method: 'CARD' )
|
|
66
|
-
#
|
|
67
|
-
# @param key [String] The unique key of the events.
|
|
68
|
-
# @param value [Float] Optional numeric value of the events (e.g. purchase_amount, quantity, etc.)
|
|
69
|
-
# @param properties Additional properties of the events (e.g. app_version, os_type, etc.)
|
|
70
|
-
#
|
|
71
|
-
# @return [Event] The configured event object.
|
|
72
|
-
#
|
|
73
|
-
def self.event(key:, value: nil, **properties)
|
|
74
|
-
Event.new(key: key, value: value, properties: properties)
|
|
75
|
-
end
|
|
76
11
|
end
|