hackle-ruby-sdk 0.1.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 +191 -79
  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/internal/evaluation/bucketer/bucketer.rb +46 -0
  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/internal/model/event_type.rb +19 -0
  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 -32
  71. metadata +123 -51
  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/bucketer.rb +0 -30
  80. data/lib/hackle/decision/decider.rb +0 -54
  81. data/lib/hackle/events/event.rb +0 -33
  82. data/lib/hackle/events/event_dispatcher.rb +0 -89
  83. data/lib/hackle/events/event_processor.rb +0 -115
  84. data/lib/hackle/http/http.rb +0 -37
  85. data/lib/hackle/models/bucket.rb +0 -15
  86. data/lib/hackle/models/event_type.rb +0 -14
  87. data/lib/hackle/models/experiment.rb +0 -36
  88. data/lib/hackle/models/slot.rb +0 -15
  89. data/lib/hackle/models/variation.rb +0 -11
  90. data/lib/hackle/workspaces/http_workspace_fetcher.rb +0 -24
  91. data/lib/hackle/workspaces/polling_workspace_fetcher.rb +0 -44
  92. data/lib/hackle/workspaces/workspace.rb +0 -87
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hackle
4
+ module Scheduler
5
+ def schedule_periodically(interval_seconds, task) end
6
+ end
7
+
8
+ module ScheduledJob
9
+ def cancel
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+ require 'hackle/internal/concurrent/schedule/scheduler'
5
+
6
+ module Hackle
7
+ class TimerScheduler
8
+ include Scheduler
9
+
10
+ def schedule_periodically(interval_seconds, task)
11
+ timer_task = Concurrent::TimerTask.new(execution_interval: interval_seconds, interval_type: :fixed_rate) do
12
+ task.call
13
+ end
14
+ timer_task.execute
15
+ TimerScheduledJob.new(timer_task)
16
+ end
17
+ end
18
+
19
+ class TimerScheduledJob
20
+ include ScheduledJob
21
+
22
+ def initialize(task)
23
+ @task = task
24
+ end
25
+
26
+ def cancel
27
+ @task.shutdown
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hackle
4
+ class ParameterConfig
5
+
6
+ # @return [Hash{String => Object}]
7
+ attr_reader :parameters
8
+
9
+ # @param parameters [Hash{String => Object}]
10
+ def initialize(parameters)
11
+ @parameters = parameters
12
+ end
13
+
14
+ @empty = new({})
15
+
16
+ # @return [Hackle::ParameterConfig]
17
+ def self.empty
18
+ @empty
19
+ end
20
+
21
+ def ==(other)
22
+ other.is_a?(self.class) && other.parameters == parameters
23
+ end
24
+
25
+ def to_s
26
+ "Hackle::ParameterConfig(#{parameters})"
27
+ end
28
+
29
+ # @param key [String]
30
+ # @param default_value [Object, nil]
31
+ # @return [Object, nil]
32
+ def get(key, default_value = nil)
33
+ parameter_value = parameters.fetch(key, default_value)
34
+
35
+ return default_value if parameter_value.nil?
36
+ return parameter_value if default_value.nil?
37
+
38
+ case default_value
39
+ when String
40
+ parameter_value.is_a?(String) ? parameter_value : default_value
41
+ when Numeric
42
+ parameter_value.is_a?(Numeric) ? parameter_value : default_value
43
+ when TrueClass, FalseClass
44
+ parameter_value.is_a?(TrueClass) || parameter_value.is_a?(FalseClass) ? parameter_value : default_value
45
+ else
46
+ default_value
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hackle/internal/event/user_event'
4
+ require 'hackle/internal/model/decision_reason'
5
+ require 'hackle/internal/evaluation/evaluator/delegating/delegating_evaluator'
6
+ require 'hackle/internal/evaluation/match/condition/condition_matcher_factory'
7
+ require 'hackle/internal/evaluation/match/target/target_matcher'
8
+ require 'hackle/internal/evaluation/bucketer/bucketer'
9
+ require 'hackle/internal/evaluation/evaluator/experiment/experiment_evaluator'
10
+ require 'hackle/internal/evaluation/evaluator/experiment/experiment_evaluation_flow_factory'
11
+ require 'hackle/internal/evaluation/evaluator/remoteconfig/remote_config_evaluator'
12
+ require 'hackle/internal/evaluation/evaluator/remoteconfig/remote_config_determiner'
13
+ require 'hackle/internal/event/user_event_factory'
14
+ require 'hackle/internal/clock/clock'
15
+
16
+ module Hackle
17
+ class Core
18
+
19
+ # @param experiment_evaluator [ExperimentEvaluator]
20
+ # @param remote_config_evaluator [RemoteConfigEvaluator]
21
+ # @param workspace_fetcher [WorkspaceFetcher]
22
+ # @param event_factory [UserEventFactory]
23
+ # @param event_processor [UserEventProcessor]
24
+ # @param clock [Clock]
25
+ def initialize(
26
+ experiment_evaluator:,
27
+ remote_config_evaluator:,
28
+ workspace_fetcher:,
29
+ event_factory:,
30
+ event_processor:,
31
+ clock:
32
+ )
33
+ # @type experiment_evaluator [ExperimentEvaluator]
34
+ @experiment_evaluator = experiment_evaluator
35
+ # @type remote_config_evaluator [RemoteConfigEvaluator]
36
+ @remote_config_evaluator = remote_config_evaluator
37
+ # @type workspace_fetcher [WorkspaceFetcher]
38
+ @workspace_fetcher = workspace_fetcher
39
+ # @type event_factory [UserEventFactory]
40
+ @event_factory = event_factory
41
+ # @type event_processor [UserEventProcessor]
42
+ @event_processor = event_processor
43
+ # @type clock [Clock]
44
+ @clock = clock
45
+ end
46
+
47
+ # @param workspace_fetcher [WorkspaceFetcher]
48
+ # @param event_processor [UserEventProcessor]
49
+ # @return [Core]
50
+ def self.create(workspace_fetcher:, event_processor:)
51
+ delegating_evaluator = DelegatingEvaluator.new
52
+
53
+ condition_matcher_factory = ConditionMatcherFactory.new(evaluator: delegating_evaluator)
54
+ target_matcher = TargetMatcher.new(condition_matcher_factory: condition_matcher_factory)
55
+ bucketer = Bucketer.new(hasher: Hasher.new)
56
+
57
+ experiment_evaluator = ExperimentEvaluator.new(
58
+ flow_factory: ExperimentEvaluationFlowFactory.new(
59
+ target_matcher: target_matcher,
60
+ bucketer: bucketer
61
+ )
62
+ )
63
+ delegating_evaluator.add(experiment_evaluator)
64
+
65
+ remote_config_evaluator = RemoteConfigEvaluator.new(
66
+ target_rule_determiner: RemoteConfigTargetRuleDeterminer.new(
67
+ matcher: RemoteConfigTargetRuleMatcher.new(
68
+ target_matcher: target_matcher,
69
+ bucketer: bucketer
70
+ )
71
+ )
72
+ )
73
+ delegating_evaluator.add(remote_config_evaluator)
74
+
75
+ Core.new(
76
+ experiment_evaluator: experiment_evaluator,
77
+ remote_config_evaluator: remote_config_evaluator,
78
+ workspace_fetcher: workspace_fetcher,
79
+ event_factory: UserEventFactory.new(clock: SystemClock.instance),
80
+ event_processor: event_processor,
81
+ clock: SystemClock.instance
82
+ )
83
+ end
84
+
85
+ # @param experiment_key [Integer]
86
+ # @param user [HackleUser]
87
+ # @param default_variation [String]
88
+ # @return [ExperimentDecision]
89
+ def experiment(experiment_key, user, default_variation)
90
+ workspace = @workspace_fetcher.fetch
91
+ return ExperimentDecision.new(default_variation, DecisionReason::SDK_NOT_READY, ParameterConfig.empty) if workspace.nil?
92
+
93
+ experiment = workspace.get_experiment_or_nil(experiment_key)
94
+ return ExperimentDecision.new(default_variation, DecisionReason::EXPERIMENT_NOT_FOUND, ParameterConfig.empty) if experiment.nil?
95
+
96
+ request = ExperimentRequest.create(workspace, user, experiment, default_variation)
97
+ evaluation = @experiment_evaluator.evaluate(request, Evaluator.context)
98
+
99
+ events = @event_factory.create(request, evaluation)
100
+ events.each do |event|
101
+ @event_processor.process(event)
102
+ end
103
+
104
+ ExperimentDecision.new(evaluation.variation_key, evaluation.reason, evaluation.parameter_config)
105
+ end
106
+
107
+ # @param feature_key [Integer]
108
+ # @param user [HackleUser]
109
+ # @return [FeatureFlagDecision]
110
+ def feature_flag(feature_key, user)
111
+ workspace = @workspace_fetcher.fetch
112
+ return FeatureFlagDecision.new(false, DecisionReason::SDK_NOT_READY, ParameterConfig.empty) if workspace.nil?
113
+
114
+ feature_flag = workspace.get_feature_flag_or_nil(feature_key)
115
+ return FeatureFlagDecision.new(false, DecisionReason::FEATURE_FLAG_NOT_FOUND, ParameterConfig.empty) if feature_flag.nil?
116
+
117
+ request = ExperimentRequest.create(workspace, user, feature_flag, 'A')
118
+ evaluation = @experiment_evaluator.evaluate(request, Evaluator.context)
119
+
120
+ events = @event_factory.create(request, evaluation)
121
+ events.each do |event|
122
+ @event_processor.process(event)
123
+ end
124
+
125
+ is_on = evaluation.variation_key != 'A'
126
+ FeatureFlagDecision.new(is_on, evaluation.reason, evaluation.parameter_config)
127
+ end
128
+
129
+ # @param parameter_key [String]
130
+ # @param user [HackleUser]
131
+ # @param required_type [ValueType]
132
+ # @param default_value [Object, nil]
133
+ def remote_config(parameter_key, user, required_type, default_value)
134
+ workspace = @workspace_fetcher.fetch
135
+ return RemoteConfigDecision.new(default_value, DecisionReason::SDK_NOT_READY) if workspace.nil?
136
+
137
+ parameter = workspace.get_remote_config_parameter_or_nil(parameter_key)
138
+ return RemoteConfigDecision.new(default_value, DecisionReason::REMOTE_CONFIG_PARAMETER_NOT_FOUND) if parameter.nil?
139
+
140
+ request = RemoteConfigRequest.create(workspace, user, parameter, required_type, default_value)
141
+ evaluation = @remote_config_evaluator.evaluate(request, Evaluator.context)
142
+
143
+ events = @event_factory.create(request, evaluation)
144
+ events.each do |event|
145
+ @event_processor.process(event)
146
+ end
147
+
148
+ RemoteConfigDecision.new(evaluation.value, evaluation.reason)
149
+ end
150
+
151
+ # @param event [Event]
152
+ # @param user [HackleUser]
153
+ def track(event, user)
154
+ event_type = event_type(event)
155
+ @event_processor.process(UserEvent.track(event_type, event, user, @clock.current_millis))
156
+ end
157
+
158
+ def close
159
+ @workspace_fetcher.stop
160
+ @event_processor.stop
161
+ end
162
+
163
+ def resume
164
+ @workspace_fetcher.resume
165
+ @event_processor.resume
166
+ end
167
+
168
+ private
169
+
170
+ # @param event [Event]
171
+ # @return [EventType]
172
+ def event_type(event)
173
+ workspace = @workspace_fetcher.fetch
174
+ return EventType.new(id: 0, key: event.key) if workspace.nil?
175
+
176
+ event_type = workspace.get_event_type_or_nil(event.key)
177
+ return EventType.new(id: 0, key: event.key) if event_type.nil?
178
+
179
+ event_type
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'murmurhash3'
4
+
5
+ module Hackle
6
+ class Bucketer
7
+
8
+ # @param hasher [Hasher]
9
+ def initialize(hasher:)
10
+ # @type [Hasher]
11
+ @hasher = hasher
12
+ end
13
+
14
+ # @param bucket [Bucket]
15
+ # @param identifier [String]
16
+ # @return [Slot, nil]
17
+ def bucketing(bucket, identifier)
18
+ slot_number = calculate_slot_number(seed: bucket.seed, slot_size: bucket.slot_size, value: identifier)
19
+ bucket.get_slot_or_nil(slot_number)
20
+ end
21
+
22
+ # @param seed [Integer]
23
+ # @param slot_size [Integer]
24
+ # @param value [String]
25
+ # @return [Integer]
26
+ def calculate_slot_number(seed:, slot_size:, value:)
27
+ hash_value = @hasher.hash(value, seed)
28
+ hash_value.abs % slot_size
29
+ end
30
+
31
+ end
32
+
33
+ class Hasher
34
+ # @param data [String]
35
+ # @param seed [Integer]
36
+ # @return [Integer]
37
+ def hash(data, seed)
38
+ unsigned_value = MurmurHash3::V32.str_hash(data, seed)
39
+ if (unsigned_value & 0x80000000).zero?
40
+ unsigned_value
41
+ else
42
+ -((unsigned_value ^ 0xFFFFFFFF) + 1)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hackle/internal/evaluation/evaluator/evaluator'
4
+
5
+ module Hackle
6
+ module ContextualEvaluator
7
+ include Evaluator
8
+
9
+ # @param request [EvaluatorRequest]
10
+ # @return [boolean]
11
+ def supports?(request) end
12
+
13
+ # @param request [EvaluatorRequest]
14
+ # @param context [EvaluatorContext]
15
+ # @return [EvaluatorEvaluation]
16
+ def evaluate_internal(request, context) end
17
+
18
+ def evaluate(request, context)
19
+ raise ArgumentError, 'circular evaluation has occurred' if context.request_include?(request)
20
+
21
+ context.add_request(request)
22
+ begin
23
+ evaluate_internal(request, context)
24
+ ensure
25
+ context.remove_request(request)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hackle/internal/evaluation/evaluator/evaluator'
4
+
5
+ module Hackle
6
+ class DelegatingEvaluator
7
+ include Evaluator
8
+
9
+ def initialize
10
+ # @type [Array<ContextualEvaluator>]
11
+ @evaluators = []
12
+ end
13
+
14
+ # @param evaluator [ContextualEvaluator]
15
+ def add(evaluator)
16
+ @evaluators << evaluator
17
+ end
18
+
19
+ def evaluate(request, context)
20
+ evaluator = @evaluators.find { |it| it.supports?(request) }
21
+ raise ArgumentError, "unsupported EvaluatorRequest [#{request.class}]" if evaluator.nil?
22
+
23
+ evaluator.evaluate(request, context)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hackle
4
+
5
+ module Evaluator
6
+ # @param request [EvaluatorRequest]
7
+ # @param context [EvaluatorContext]
8
+ # @return [EvaluatorEvaluation]
9
+ def evaluate(request, context) end
10
+
11
+ # @return [EvaluatorContext]
12
+ def self.context
13
+ EvaluatorContext.new
14
+ end
15
+ end
16
+
17
+ class EvaluatorRequest
18
+
19
+ # @return [EvaluatorKey]
20
+ attr_reader :key
21
+
22
+ # @return [Workspace]
23
+ attr_reader :workspace
24
+
25
+ # @return [HackleUser]
26
+ attr_reader :user
27
+
28
+ # @param key [EvaluatorKey]
29
+ # @param workspace [Workspace]
30
+ # @param user [HackleUser]
31
+ def initialize(key:, workspace:, user:)
32
+ @key = key
33
+ @workspace = workspace
34
+ @user = user
35
+ end
36
+
37
+ def ==(other)
38
+ other.is_a?(EvaluatorRequest) && key == other.key
39
+ end
40
+ end
41
+
42
+ class EvaluatorEvaluation
43
+
44
+ # @return [String]
45
+ attr_reader :reason
46
+
47
+ # @return [Array<EvaluatorEvaluation>]
48
+ attr_reader :target_evaluations
49
+
50
+ # @param reason [String]
51
+ # @param target_evaluations [Array<EvaluatorEvaluation>]
52
+ def initialize(reason:, target_evaluations:)
53
+ @reason = reason
54
+ @target_evaluations = target_evaluations
55
+ end
56
+ end
57
+
58
+ class EvaluatorContext
59
+
60
+ def initialize
61
+ # @type [Array<EvaluatorRequest>]
62
+ @requests = []
63
+ # @type [Array<EvaluatorEvaluation>]
64
+ @evaluations = []
65
+ end
66
+
67
+ # @return [Array<EvaluatorRequest>]
68
+ def requests
69
+ @requests.dup
70
+ end
71
+
72
+ # @param request [EvaluatorRequest]
73
+ # @return [boolean]
74
+ def request_include?(request)
75
+ @requests.include?(request)
76
+ end
77
+
78
+ # @param request [EvaluatorRequest]
79
+ def add_request(request)
80
+ @requests << request
81
+ end
82
+
83
+ # @param request [EvaluatorRequest]
84
+ def remove_request(request)
85
+ @requests.delete(request)
86
+ end
87
+
88
+ # @return [Array<EvaluatorEvaluation>]
89
+ def evaluations
90
+ @evaluations.dup
91
+ end
92
+
93
+ # @param evaluation [EvaluatorEvaluation]
94
+ def add_evaluation(evaluation)
95
+ @evaluations << evaluation
96
+ end
97
+ end
98
+
99
+ class EvaluatorKey
100
+ # @return [String]
101
+ attr_reader :type
102
+
103
+ # @return [Integer]
104
+ attr_reader :id
105
+
106
+ # @param type [String]
107
+ # @param id [Integer]
108
+ def initialize(type:, id:)
109
+ @type = type
110
+ @id = id
111
+ end
112
+
113
+ def ==(other)
114
+ other.is_a?(EvaluatorKey) && type == other.type && id == other.id
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hackle/internal/model/experiment'
4
+ require 'hackle/internal/evaluation/flow/evaluation_flow'
5
+ require 'hackle/internal/evaluation/evaluator/experiment/experiment_resolver'
6
+ require 'hackle/internal/evaluation/evaluator/experiment/experiment_flow_evaluator'
7
+
8
+ module Hackle
9
+ class ExperimentEvaluationFlowFactory
10
+ # @param target_matcher [TargetMatcher]
11
+ # @param bucketer [Bucketer]
12
+ def initialize(target_matcher:, bucketer:)
13
+
14
+ action_resolver = ExperimentActionResolver.new(bucketer: bucketer)
15
+ override_resolver = ExperimentOverrideResolver.new(
16
+ target_matcher: target_matcher,
17
+ action_resolver: action_resolver
18
+ )
19
+ container_resolver = ExperimentContainerResolver.new(bucketer: bucketer)
20
+ target_determiner = ExperimentTargetDeterminer.new(target_matcher: target_matcher)
21
+ target_rule_determiner = ExperimentTargetRuleDeterminer.new(target_matcher: target_matcher)
22
+
23
+ # @type [ExperimentFlow]
24
+ @ab_test_flow = EvaluationFlow.create(
25
+ [
26
+ OverrideExperimentFlowEvaluator.new(override_resolver: override_resolver),
27
+ IdentifierExperimentFlowEvaluator.new,
28
+ ContainerExperimentFlowEvaluator.new(container_resolver: container_resolver),
29
+ TargetExperimentFlowEvaluator.new(target_determiner: target_determiner),
30
+ DraftExperimentFlowEvaluator.new,
31
+ PausedExperimentFlowEvaluator.new,
32
+ CompletedExperimentFlowEvaluator.new,
33
+ TrafficAllocateExperimentFlowEvaluator.new(action_resolver: action_resolver)
34
+ ]
35
+ )
36
+
37
+ # @type [ExperimentFlow]
38
+ @feature_flag_flow = EvaluationFlow.create(
39
+ [
40
+ DraftExperimentFlowEvaluator.new,
41
+ PausedExperimentFlowEvaluator.new,
42
+ CompletedExperimentFlowEvaluator.new,
43
+ OverrideExperimentFlowEvaluator.new(override_resolver: override_resolver),
44
+ IdentifierExperimentFlowEvaluator.new,
45
+ TargetRuleExperimentFlowEvaluator.new(
46
+ target_rule_determiner: target_rule_determiner,
47
+ action_resolver: action_resolver
48
+ ),
49
+ DefaultRuleExperimentFlowEvaluator.new(action_resolver: action_resolver)
50
+ ]
51
+ )
52
+ end
53
+
54
+ # @param experiment_type [ExperimentType]
55
+ # @return [ExperimentFlow]
56
+ def get(experiment_type)
57
+ case experiment_type
58
+ when ExperimentType::AB_TEST
59
+ @ab_test_flow
60
+ when ExperimentType::FEATURE_FLAG
61
+ @feature_flag_flow
62
+ else
63
+ raise ArgumentError, "unsupported ExperimentType [#{experiment_type}]"
64
+ end
65
+ end
66
+ end
67
+ end