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
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hackle
4
-
5
- class UserEvent
6
-
7
- # @!attribute [r] timestamp
8
- # @return [Integer]
9
- # @!attribute [r] user
10
- # @return [User]
11
- attr_reader :timestamp, :user
12
-
13
- # @param user [User]
14
- def initialize(user:)
15
- @timestamp = UserEvent.generate_timestamp
16
- @user = user
17
- end
18
-
19
- class Exposure < UserEvent
20
-
21
- # @!attribute [r] experiment
22
- # @return [Experiment]
23
- # @!attribute [r] variation
24
- # @return [Variation]
25
- attr_reader :experiment, :variation
26
-
27
- # @param user [User]
28
- # @param experiment [Experiment]
29
- # @param variation [Variation]
30
- def initialize(user:, experiment:, variation:)
31
- super(user: user)
32
- @experiment = experiment
33
- @variation = variation
34
- end
35
- end
36
-
37
-
38
- class Track < UserEvent
39
-
40
- # @!attribute [r] event_type
41
- # @return [EventType]
42
- # @!attribute [r] event
43
- # @return [Event]
44
- attr_reader :event_type, :event
45
-
46
- # @param user [User]
47
- # @param event_type [EventType]
48
- # @param event [Event]
49
- def initialize(user:, event_type:, event:)
50
- super(user: user)
51
- @event_type = event_type
52
- @event = event
53
- end
54
- end
55
-
56
- # @return [Integer]
57
- def self.generate_timestamp
58
- (Time.now.to_f * 1000).to_i
59
- end
60
- end
61
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'net/http'
4
-
5
- module Hackle
6
- class UnexpectedResponseError < StandardError
7
- end
8
-
9
- class HTTP
10
- def self.client(base_uri:)
11
- uri = URI.parse(base_uri)
12
- client = Net::HTTP.new(uri.host, uri.port)
13
- client.use_ssl = uri.scheme == 'https'
14
- client.open_timeout = 5
15
- client.read_timeout = 10
16
- client
17
- end
18
-
19
- def self.sdk_headers(sdk_info:)
20
- {
21
- 'X-HACKLE-SDK-KEY' => sdk_info.key,
22
- 'X-HACKLE-SDK-NAME' => sdk_info.name,
23
- 'X-HACKLE-SDK-VERSION' => sdk_info.version
24
- }
25
- end
26
-
27
- def self.successful?(status_code:)
28
- status_code >= 200 && status_code < 300
29
- end
30
-
31
- def self.check_successful(status_code:)
32
- unless successful?(status_code: status_code)
33
- raise UnexpectedResponseError, "HTTP status code #{status_code}"
34
- end
35
- end
36
- end
37
- end
@@ -1,26 +0,0 @@
1
- module Hackle
2
-
3
- class Bucket
4
-
5
- # @!attribute [r] seed
6
- # @return [Integer]
7
- # @!attribute [r] slot_size
8
- # @return [Integer]
9
- attr_reader :seed, :slot_size
10
-
11
- # @param seed [Integer]
12
- # @param slot_size [Integer]
13
- # @param slots [Array]
14
- def initialize(seed:, slot_size:, slots:)
15
- @seed = seed
16
- @slot_size = slot_size
17
- @slots = slots
18
- end
19
-
20
- # @param slot_number [Integer]
21
- # @return [Slot, nil]
22
- def get_slot(slot_number:)
23
- @slots.find { |slot| slot.contains?(slot_number: slot_number) }
24
- end
25
- end
26
- end
@@ -1,26 +0,0 @@
1
- module Hackle
2
- class Event
3
-
4
- # @!attribute [r] key
5
- # @return [String]
6
- # @!attribute [r] value
7
- # @return [Float, nil]
8
- # @!attribute [r] properties
9
- # @return [Hash]
10
- attr_reader :key, :value, :properties
11
-
12
-
13
- # @param key [String]
14
- # @param value [Float, nil]
15
- # @param properties [Hash{Symbol => String, Number, boolean}]
16
- def initialize(key:, value:, properties:)
17
- @key = key
18
- @value = value
19
- @properties = properties
20
- end
21
-
22
- def valid?
23
- !key.nil? && key.is_a?(String)
24
- end
25
- end
26
- end
@@ -1,69 +0,0 @@
1
- module Hackle
2
- class Experiment
3
-
4
- # @!attribute [r] id
5
- # @return [Integer]
6
- # @!attribute [r] key
7
- # @return [Integer]
8
- attr_reader :id, :key
9
-
10
- # @param id [Integer]
11
- # @param key [Integer]
12
- def initialize(id:, key:)
13
- @id = id
14
- @key = key
15
- end
16
-
17
- class Running < Experiment
18
-
19
- # @!attribute [r] bucket
20
- # @return [Bucket]
21
- attr_reader :bucket
22
-
23
- # @param id [Integer]
24
- # @param key [Integer]
25
- # @param bucket [Bucket]
26
- # @param variations [Hash{String => Variation}]
27
- # @param overrides [Hash{String => Integer}]
28
- def initialize(id:, key:, bucket:, variations:, overrides:)
29
- super(id: id, key: key)
30
- @bucket = bucket
31
-
32
- # @type [Hash{String => Variation}]
33
- @variations = variations
34
-
35
- # @type [Hash{String => Integer}]
36
- @overrides = overrides
37
- end
38
-
39
- # @param variation_id [Integer]
40
- # @return [Variation, nil]
41
- def get_variation(variation_id:)
42
- @variations[variation_id]
43
- end
44
-
45
- # @param user [User]
46
- # @return [Variation, nil]
47
- def get_overridden_variation(user:)
48
- overridden_variation_id = @overrides[user.id]
49
- return nil if overridden_variation_id.nil?
50
- get_variation(variation_id: overridden_variation_id)
51
- end
52
- end
53
-
54
- class Completed < Experiment
55
-
56
- # @!attribute [r] winner_variation_key
57
- # @return [String]
58
- attr_reader :winner_variation_key
59
-
60
- # @param id [Integer]
61
- # @param key [Integer]
62
- # @param winner_variation_key [String]
63
- def initialize(id:, key:, winner_variation_key:)
64
- super(id: id, key: key)
65
- @winner_variation_key = winner_variation_key
66
- end
67
- end
68
- end
69
- end
@@ -1,22 +0,0 @@
1
- module Hackle
2
- class Slot
3
- # @!attribute variation_id
4
- # @return [Integer]
5
- attr_reader :variation_id
6
-
7
- # @param start_inclusive [Integer]
8
- # @param end_exclusive [Integer]
9
- # @param variation_id [Integer]
10
- def initialize(start_inclusive:, end_exclusive:, variation_id:)
11
- @start_inclusive = start_inclusive
12
- @end_exclusive = end_exclusive
13
- @variation_id = variation_id
14
- end
15
-
16
- # @param slot_number [Integer]
17
- # @return [boolean]
18
- def contains?(slot_number:)
19
- @start_inclusive <= slot_number && slot_number < @end_exclusive
20
- end
21
- end
22
- end
@@ -1,24 +0,0 @@
1
- module Hackle
2
-
3
- class User
4
-
5
- # @!attribute [r] id
6
- # @return [String]
7
- # @!attribute [r] properties
8
- # @return [Hash]
9
- attr_reader :id, :properties
10
-
11
- #
12
- # @param id [String]
13
- # @param properties [Hash]
14
- #
15
- def initialize(id:, properties:)
16
- @id = id
17
- @properties = properties
18
- end
19
-
20
- def valid?
21
- !id.nil? && id.is_a?(String)
22
- end
23
- end
24
- end
@@ -1,21 +0,0 @@
1
- module Hackle
2
- class Variation
3
-
4
- # @!attribute id
5
- # @return [Integer]
6
- # @!attribute key
7
- # @return [String]
8
- # @!attribute dropped
9
- # @return [boolean]
10
- attr_reader :id, :key, :dropped
11
-
12
- # @param id [Integer]
13
- # @param key [String]
14
- # @param dropped [boolean]
15
- def initialize(id:, key:, dropped:)
16
- @id = id
17
- @key = key
18
- @dropped = dropped
19
- end
20
- end
21
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module Hackle
6
- class HttpWorkspaceFetcher
7
-
8
- def initialize(config:, sdk_info:)
9
- @client = HTTP.client(base_uri: config.base_uri)
10
- @headers = HTTP.sdk_headers(sdk_info: sdk_info)
11
- end
12
-
13
- def fetch
14
- request = Net::HTTP::Get.new('/api/v1/workspaces', @headers)
15
- response = @client.request(request)
16
-
17
- status_code = response.code.to_i
18
- HTTP.check_successful(status_code: status_code)
19
-
20
- response_body = JSON.parse(response.body, symbolize_names: true)
21
- Workspace.create(data: response_body)
22
- end
23
- end
24
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'concurrent'
4
-
5
- module Hackle
6
- class PollingWorkspaceFetcher
7
-
8
- DEFAULT_POLLING_INTERVAL = 10
9
-
10
- def initialize(config:, http_fetcher:)
11
- @logger = config.logger
12
- @http_fetcher = http_fetcher
13
- @current_workspace = Concurrent::AtomicReference.new
14
- @task = Concurrent::TimerTask.new(execution_interval: DEFAULT_POLLING_INTERVAL) { poll }
15
- @running = false
16
- end
17
-
18
- # @return [Workspace, nil]
19
- def fetch
20
- @current_workspace.get
21
- end
22
-
23
- def start!
24
- return if @running
25
-
26
- poll
27
- @task.execute
28
- @running = true
29
- end
30
-
31
- def stop!
32
- return unless @running
33
-
34
- @logger.info { 'Shutting down Hackle workspace_fetcher' }
35
-
36
- @task.shutdown
37
- @running = false
38
- end
39
-
40
- def poll
41
- workspace = @http_fetcher.fetch
42
- @current_workspace.set(workspace)
43
- rescue => e
44
- @logger.error { "Failed to poll Workspace: #{e.inspect}" }
45
- end
46
- end
47
- end
@@ -1,100 +0,0 @@
1
- module Hackle
2
- class Workspace
3
-
4
- # @param experiments [Hash{Integer => Experiment}]
5
- # @param event_types [Hash{String => EventType}]
6
- def initialize(experiments:, event_types:)
7
-
8
- # @type [Hash{Integer => Experiment}]
9
- @experiments = experiments
10
-
11
- # @type [Hash{String => EventType}]
12
- @event_types = event_types
13
- end
14
-
15
- # @param experiment_key [Integer]
16
- #
17
- # @return [Experiment, nil]
18
- def get_experiment(experiment_key:)
19
- @experiments[experiment_key]
20
- end
21
-
22
- # @param event_type_key [String]
23
- #
24
- # @return [EventType]
25
- def get_event_type(event_type_key:)
26
- event_type = @event_types[event_type_key]
27
-
28
- if event_type.nil?
29
- EventType.undefined(key: event_type_key)
30
- else
31
- event_type
32
- end
33
- end
34
-
35
- class << self
36
- def create(data:)
37
- buckets = Hash[data[:buckets].map { |b| [b[:id], bucket(b)] }]
38
- running_experiments = Hash[data[:experiments].map { |re| [re[:key], running_experiment(re, buckets)] }]
39
- completed_experiment = Hash[data[:completedExperiments].map { |ce| [ce[:experimentKey], completed_experiment(ce)] }]
40
- event_types = Hash[data[:events].map { |e| [e[:key], event_type(e)] }]
41
- experiments = running_experiments.merge(completed_experiment)
42
- Workspace.new(
43
- experiments: experiments,
44
- event_types: event_types
45
- )
46
- end
47
-
48
- private
49
-
50
- def running_experiment(data, buckets)
51
- Experiment::Running.new(
52
- id: data[:id],
53
- key: data[:key],
54
- bucket: buckets[data[:bucketId]],
55
- variations: Hash[data[:variations].map { |v| [v[:id], variation(v)] }],
56
- overrides: Hash[data[:execution][:userOverrides].map { |u| [u[:userId], u[:variationId]] }]
57
- )
58
- end
59
-
60
- def completed_experiment(data)
61
- Experiment::Completed.new(
62
- id: data[:experimentId],
63
- key: data[:experimentKey],
64
- winner_variation_key: data[:winnerVariationKey]
65
- )
66
- end
67
-
68
- def variation(data)
69
- Variation.new(
70
- id: data[:id],
71
- key: data[:key],
72
- dropped: data[:status] == 'DROPPED'
73
- )
74
- end
75
-
76
- def bucket(data)
77
- Bucket.new(
78
- seed: data[:seed],
79
- slot_size: data[:slotSize],
80
- slots: data[:slots].map { |s| slot(s) }
81
- )
82
- end
83
-
84
- def slot(data)
85
- Slot.new(
86
- start_inclusive: data[:startInclusive],
87
- end_exclusive: data[:endExclusive],
88
- variation_id: data[:variationId]
89
- )
90
- end
91
-
92
- def event_type(data)
93
- EventType.new(
94
- id: data[:id],
95
- key: data[:key]
96
- )
97
- end
98
- end
99
- end
100
- end