hackle-ruby-sdk 0.1.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7ece6c3cc0981ab69f50731472d68c3f4903ccf30a5ffd5fb069210e076cc55
4
- data.tar.gz: f9f19bb60c2a4d34afe50203cc4637d92939e9c1a94e8ff86f1334e38d60a515
3
+ metadata.gz: 2eb9518d11c4980c9a6abf036b8400fad5de381db6c60d2fc98b68e37cda8851
4
+ data.tar.gz: faf6536122c01e9a55f95eef4e6497313a7e09e2ff16ea1123ca6c7fdc56470a
5
5
  SHA512:
6
- metadata.gz: e63f1afec1b82a27376f69db48721be968d72b7f959df243155d952159e615aa629dc1e98c473d72155769b7fca567c712e37c11efbbf3ccdee48726e1d76108
7
- data.tar.gz: f89a85d084ebb33b2c788674e4fb4869bae3c5d9952126faa75374a4d07b7db382af3a93b7f3c52acbf64525bdc3e0bd652d11ab64db0637f98f8b0a593541fb
6
+ metadata.gz: cbf193163d0b5926f052e76b7b01e0ca472297ef5da32b2317e90a1f35450cefa6496a72bcd0e9d6c7faf83947b6a9379e07be7111f5fa0a5030b891013c6c1c
7
+ data.tar.gz: e9d8ed3a4439684fb0b3308edb0a2e48c36ba041fa8f081e33ddad237ee8a5b546f0c98a1ec05b5c4123f76fe9289bc2ea3e84d9b02b068a3833472eec77691e
@@ -12,7 +12,7 @@ module Hackle
12
12
  # @see Client#initialize
13
13
  #
14
14
  # @param sdk_key [String] The SDK key of your Hackle environment
15
- # @param options An optional client configuration
15
+ # @param options Optional parameters of configuration options
16
16
  #
17
17
  # @return [Client] The Hackle client instance.
18
18
  #
@@ -36,4 +36,41 @@ module Hackle
36
36
  decider: Decider.new
37
37
  )
38
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
39
76
  end
@@ -3,16 +3,18 @@
3
3
  require 'hackle/decision/bucketer'
4
4
  require 'hackle/decision/decider'
5
5
 
6
- require 'hackle/events/event'
6
+ require 'hackle/events/user_event'
7
7
  require 'hackle/events/event_dispatcher'
8
8
  require 'hackle/events/event_processor'
9
9
 
10
10
  require 'hackle/http/http'
11
11
 
12
12
  require 'hackle/models/bucket'
13
+ require 'hackle/models/event'
13
14
  require 'hackle/models/event_type'
14
15
  require 'hackle/models/experiment'
15
16
  require 'hackle/models/slot'
17
+ require 'hackle/models/user'
16
18
  require 'hackle/models/variation'
17
19
 
18
20
  require 'hackle/workspaces/http_workspace_fetcher'
@@ -36,8 +38,14 @@ module Hackle
36
38
  #
37
39
  def initialize(config:, workspace_fetcher:, event_processor:, decider:)
38
40
  @logger = config.logger
41
+
42
+ # @type [PollingWorkspaceFetcher]
39
43
  @workspace_fetcher = workspace_fetcher
44
+
45
+ # @type [EventProcessor]
40
46
  @event_processor = event_processor
47
+
48
+ # @type [Decider]
41
49
  @decider = decider
42
50
  end
43
51
 
@@ -50,16 +58,16 @@ module Hackle
50
58
  # - The user is not allocated to the experiment
51
59
  # - The decided variation has been dropped
52
60
  #
53
- # @param experiment_key [Integer] The unique key of the experiment.
54
- # @param user_id [String] The identifier of your customer. (e.g. user_email, account_id, decide_id, etc.)
61
+ # @param experiment_key [Integer] The unique key of the experiment. MUST NOT be nil.
62
+ # @param user [User] the user to participate in the experiment. MUST NOT be nil.
55
63
  # @param default_variation [String] The default variation of the experiment.
56
64
  #
57
65
  # @return [String] The decided variation for the user, or default variation
58
66
  #
59
- def variation(experiment_key:, user_id:, default_variation: 'A')
67
+ def variation(experiment_key:, user:, default_variation: 'A')
60
68
 
61
- return default_variation if experiment_key.nil?
62
- return default_variation if user_id.nil?
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?
63
71
 
64
72
  workspace = @workspace_fetcher.fetch
65
73
  return default_variation if workspace.nil?
@@ -67,40 +75,45 @@ module Hackle
67
75
  experiment = workspace.get_experiment(experiment_key: experiment_key)
68
76
  return default_variation if experiment.nil?
69
77
 
70
- decision = @decider.decide(experiment: experiment, user_id: user_id)
78
+ decision = @decider.decide(experiment: experiment, user: user)
71
79
  case decision
72
80
  when Decision::NotAllocated
73
81
  default_variation
74
82
  when Decision::ForcedAllocated
75
83
  decision.variation_key
76
84
  when Decision::NaturalAllocated
77
- exposure_event = Event::Exposure.new(user_id: user_id, experiment: experiment, variation: decision.variation)
85
+ exposure_event = UserEvent::Exposure.new(user: user, experiment: experiment, variation: decision.variation)
78
86
  @event_processor.process(event: exposure_event)
79
87
  decision.variation.key
80
88
  else
81
89
  default_variation
82
90
  end
91
+
92
+ rescue => e
93
+ @logger.error { "Unexpected error while deciding variation for experiment[#{experiment_key}]. Returning default variation[#{default_variation}]: #{e.inspect}" }
94
+ default_variation
83
95
  end
84
96
 
85
97
  #
86
- # Records the events performed by the user.
98
+ # Records the event that occurred by the user.
87
99
  #
88
- # @param event_key [String] The unique key of the events.
89
- # @param user_id [String] The identifier of user that performed the vent.
90
- # @param value [Float] Additional numeric value of the events (e.g. purchase_amount, api_latency, etc.)
100
+ # @param event [Event] the event that occurred.
101
+ # @param user [User] the user that occurred the event.
91
102
  #
92
- def track(event_key:, user_id:, value: nil)
103
+ def track(event:, user:)
93
104
 
94
- return if event_key.nil?
95
- return if user_id.nil?
105
+ return if event.nil? || !event.is_a?(Event) || !event.valid?
106
+ return if user.nil? || !user.is_a?(User) || !user.valid?
96
107
 
97
108
  workspace = @workspace_fetcher.fetch
98
109
  return if workspace.nil?
99
110
 
100
- event_type = workspace.get_event_type(event_type_key: event_key)
101
-
102
- track_event = Event::Track.new(user_id: user_id, event_type: event_type, value: value)
111
+ event_type = workspace.get_event_type(event_type_key: event.key)
112
+ track_event = UserEvent::Track.new(user: user, event_type: event_type, event: event)
103
113
  @event_processor.process(event: track_event)
114
+
115
+ rescue => e
116
+ @logger.error { "Unexpected error while tracking event: #{e.inspect}" }
104
117
  end
105
118
 
106
119
  #
@@ -4,20 +4,34 @@ require 'murmurhash3'
4
4
 
5
5
  module Hackle
6
6
  class Bucketer
7
- def bucketing(bucket:, user_id:)
7
+
8
+ # @param bucket [Bucket]
9
+ # @param user [User]
10
+ #
11
+ # @return [Slot, nil]
12
+ def bucketing(bucket:, user:)
8
13
  slot_number = calculate_slot_number(
9
14
  seed: bucket.seed,
10
15
  slot_size: bucket.slot_size,
11
- user_id: user_id
16
+ user_id: user.id
12
17
  )
13
18
  bucket.get_slot(slot_number: slot_number)
14
19
  end
15
20
 
21
+ # @param seed [Integer]
22
+ # @param slot_size [Integer]
23
+ # @param user_id [String]
24
+ #
25
+ # @return [Integer]
16
26
  def calculate_slot_number(seed:, slot_size:, user_id:)
17
27
  hash_value = hash(data: user_id, seed: seed)
18
28
  hash_value.abs % slot_size
19
29
  end
20
30
 
31
+ # @param data [String]
32
+ # @param seed [Integer]
33
+ #
34
+ # @return [Integer]
21
35
  def hash(data:, seed:)
22
36
  unsigned_value = MurmurHash3::V32.str_hash(data, seed)
23
37
  if (unsigned_value & 0x80000000).zero?
@@ -2,20 +2,25 @@
2
2
 
3
3
  module Hackle
4
4
  class Decision
5
+
5
6
  class NotAllocated < Decision
6
7
  end
7
8
 
8
9
  class ForcedAllocated < Decision
10
+ # @return [String]
9
11
  attr_reader :variation_key
10
12
 
13
+ # @param variation_key [String]
11
14
  def initialize(variation_key:)
12
15
  @variation_key = variation_key
13
16
  end
14
17
  end
15
18
 
16
19
  class NaturalAllocated < Decision
20
+ # @return [Variation]
17
21
  attr_reader :variation
18
22
 
23
+ # @param variation [Variation]
19
24
  def initialize(variation:)
20
25
  @variation = variation
21
26
  end
@@ -27,21 +32,31 @@ module Hackle
27
32
  @bucketer = Bucketer.new
28
33
  end
29
34
 
30
- def decide(experiment:, user_id:)
35
+ # @param experiment [Experiment]
36
+ # @param user [User]
37
+ #
38
+ # @return [Decision]
39
+ def decide(experiment:, user:)
31
40
  case experiment
32
41
  when Experiment::Completed
33
42
  Decision::ForcedAllocated.new(variation_key: experiment.winner_variation_key)
34
43
  when Experiment::Running
35
- decide_running(running_experiment: experiment, user_id: user_id)
44
+ decide_running(running_experiment: experiment, user: user)
45
+ else
46
+ NotAllocated.new
36
47
  end
37
48
  end
38
49
 
39
- def decide_running(running_experiment:, user_id:)
50
+ # @param running_experiment [Experiment::Running]
51
+ # @param user [User]
52
+ #
53
+ # @return [Decision]
54
+ def decide_running(running_experiment:, user:)
40
55
 
41
- overridden_variation = running_experiment.get_overridden_variation(user_id: user_id)
56
+ overridden_variation = running_experiment.get_overridden_variation(user: user)
42
57
  return Decision::ForcedAllocated.new(variation_key: overridden_variation.key) unless overridden_variation.nil?
43
58
 
44
- allocated_slot = @bucketer.bucketing(bucket: running_experiment.bucket, user_id: user_id)
59
+ allocated_slot = @bucketer.bucketing(bucket: running_experiment.bucket, user: user)
45
60
  return Decision::NotAllocated.new if allocated_slot.nil?
46
61
 
47
62
  allocated_variation = running_experiment.get_variation(variation_id: allocated_slot.variation_id)
@@ -53,9 +53,9 @@ module Hackle
53
53
  track_events = []
54
54
  events.each do |event|
55
55
  case event
56
- when Event::Exposure
56
+ when UserEvent::Exposure
57
57
  exposure_events << create_exposure_event(event)
58
- when Event::Track
58
+ when UserEvent::Track
59
59
  track_events << create_track_event(event)
60
60
  end
61
61
  end
@@ -65,24 +65,31 @@ module Hackle
65
65
  }
66
66
  end
67
67
 
68
- def create_exposure_event(event)
68
+ #
69
+ # @param exposure [UserEvent::Exposure]
70
+ #
71
+ def create_exposure_event(exposure)
69
72
  {
70
- timestamp: event.timestamp,
71
- userId: event.user_id,
72
- experimentId: event.experiment.id,
73
- experimentKey: event.experiment.key,
74
- variationId: event.variation.id,
75
- variationKey: event.variation.key
73
+ timestamp: exposure.timestamp,
74
+ userId: exposure.user.id,
75
+ experimentId: exposure.experiment.id,
76
+ experimentKey: exposure.experiment.key,
77
+ variationId: exposure.variation.id,
78
+ variationKey: exposure.variation.key
76
79
  }
77
80
  end
78
81
 
79
- def create_track_event(event)
82
+ #
83
+ # @param track [UserEvent::Track]
84
+ #
85
+ def create_track_event(track)
80
86
  {
81
- timestamp: event.timestamp,
82
- userId: event.user_id,
83
- eventTypeId: event.event_type.id,
84
- eventTypeKey: event.event_type.key,
85
- value: event.value
87
+ timestamp: track.timestamp,
88
+ userId: track.user.id,
89
+ eventTypeId: track.event_type.id,
90
+ eventTypeKey: track.event_type.key,
91
+ value: track.event.value,
92
+ properties: track.event.properties
86
93
  }
87
94
  end
88
95
  end
@@ -6,6 +6,8 @@ module Hackle
6
6
 
7
7
  DEFAULT_FLUSH_INTERVAL = 10
8
8
 
9
+ # @param config [Config]
10
+ # @param event_dispatcher [EventDispatcher]
9
11
  def initialize(config:, event_dispatcher:)
10
12
  @logger = config.logger
11
13
  @event_dispatcher = event_dispatcher
@@ -26,6 +28,8 @@ module Hackle
26
28
  def stop!
27
29
  return unless @running
28
30
 
31
+ @logger.info { 'Shutting down Hackle event_processor' }
32
+
29
33
  @message_processor.produce(message: Message::Shutdown.new, non_block: false)
30
34
  @consume_task.join(10)
31
35
  @flush_task.shutdown
@@ -34,6 +38,7 @@ module Hackle
34
38
  @running = false
35
39
  end
36
40
 
41
+ # @param event [UserEvent]
37
42
  def process(event:)
38
43
  @message_processor.produce(message: Message::Event.new(event))
39
44
  end
@@ -44,8 +49,11 @@ module Hackle
44
49
 
45
50
  class Message
46
51
  class Event < Message
52
+
53
+ # @return [UserEvent]
47
54
  attr_reader :event
48
55
 
56
+ # @param event [UserEvent]
49
57
  def initialize(event)
50
58
  @event = event
51
59
  end
@@ -71,6 +79,8 @@ module Hackle
71
79
  @consumed_events = []
72
80
  end
73
81
 
82
+ # @param message [Message]
83
+ # @param non_block [boolean]
74
84
  def produce(message:, non_block: true)
75
85
  @message_queue.push(message, non_block)
76
86
  rescue ThreadError
@@ -99,6 +109,7 @@ module Hackle
99
109
 
100
110
  private
101
111
 
112
+ # @param event [UserEvent]
102
113
  def consume_event(event:)
103
114
  @consumed_events << event
104
115
  dispatch_events if @consumed_events.length >= DEFAULT_MAX_EVENT_DISPATCH_SIZE
@@ -0,0 +1,61 @@
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,13 +1,24 @@
1
1
  module Hackle
2
+
2
3
  class Bucket
4
+
5
+ # @!attribute [r] seed
6
+ # @return [Integer]
7
+ # @!attribute [r] slot_size
8
+ # @return [Integer]
3
9
  attr_reader :seed, :slot_size
4
10
 
11
+ # @param seed [Integer]
12
+ # @param slot_size [Integer]
13
+ # @param slots [Array]
5
14
  def initialize(seed:, slot_size:, slots:)
6
15
  @seed = seed
7
16
  @slot_size = slot_size
8
17
  @slots = slots
9
18
  end
10
19
 
20
+ # @param slot_number [Integer]
21
+ # @return [Slot, nil]
11
22
  def get_slot(slot_number:)
12
23
  @slots.find { |slot| slot.contains?(slot_number: slot_number) }
13
24
  end
@@ -0,0 +1,26 @@
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,12 +1,20 @@
1
1
  module Hackle
2
2
  class EventType
3
+
4
+ # @!attribute [r] id
5
+ # @return [Integer]
6
+ # @!attribute [r] key
7
+ # @return [String]
3
8
  attr_reader :id, :key
4
9
 
10
+ # @param id [Integer]
11
+ # @param key [String]
5
12
  def initialize(id:, key:)
6
13
  @id = id
7
14
  @key = key
8
15
  end
9
16
 
17
+ # @param key [String]
10
18
  def self.undefined(key:)
11
19
  EventType.new(id: 0, key: key)
12
20
  end
@@ -1,34 +1,67 @@
1
1
  module Hackle
2
2
  class Experiment
3
+
4
+ # @!attribute [r] id
5
+ # @return [Integer]
6
+ # @!attribute [r] key
7
+ # @return [Integer]
3
8
  attr_reader :id, :key
4
9
 
10
+ # @param id [Integer]
11
+ # @param key [Integer]
12
+ def initialize(id:, key:)
13
+ @id = id
14
+ @key = key
15
+ end
16
+
5
17
  class Running < Experiment
18
+
19
+ # @!attribute [r] bucket
20
+ # @return [Bucket]
6
21
  attr_reader :bucket
7
22
 
8
- def initialize(id:, key:, bucket:, variations:, user_overrides:)
9
- @id = id
10
- @key = key
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)
11
30
  @bucket = bucket
31
+
32
+ # @type [Hash{String => Variation}]
12
33
  @variations = variations
13
- @user_overrides = user_overrides
34
+
35
+ # @type [Hash{String => Integer}]
36
+ @overrides = overrides
14
37
  end
15
38
 
39
+ # @param variation_id [Integer]
40
+ # @return [Variation, nil]
16
41
  def get_variation(variation_id:)
17
42
  @variations[variation_id]
18
43
  end
19
44
 
20
- def get_overridden_variation(user_id:)
21
- variation_id = @user_overrides[user_id]
22
- get_variation(variation_id: variation_id)
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)
23
51
  end
24
52
  end
25
53
 
26
54
  class Completed < Experiment
55
+
56
+ # @!attribute [r] winner_variation_key
57
+ # @return [String]
27
58
  attr_reader :winner_variation_key
28
59
 
60
+ # @param id [Integer]
61
+ # @param key [Integer]
62
+ # @param winner_variation_key [String]
29
63
  def initialize(id:, key:, winner_variation_key:)
30
- @id = id
31
- @key = key
64
+ super(id: id, key: key)
32
65
  @winner_variation_key = winner_variation_key
33
66
  end
34
67
  end
@@ -1,13 +1,20 @@
1
1
  module Hackle
2
2
  class Slot
3
+ # @!attribute variation_id
4
+ # @return [Integer]
3
5
  attr_reader :variation_id
4
6
 
7
+ # @param start_inclusive [Integer]
8
+ # @param end_exclusive [Integer]
9
+ # @param variation_id [Integer]
5
10
  def initialize(start_inclusive:, end_exclusive:, variation_id:)
6
11
  @start_inclusive = start_inclusive
7
12
  @end_exclusive = end_exclusive
8
13
  @variation_id = variation_id
9
14
  end
10
15
 
16
+ # @param slot_number [Integer]
17
+ # @return [boolean]
11
18
  def contains?(slot_number:)
12
19
  @start_inclusive <= slot_number && slot_number < @end_exclusive
13
20
  end
@@ -0,0 +1,24 @@
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,7 +1,17 @@
1
1
  module Hackle
2
2
  class Variation
3
+
4
+ # @!attribute id
5
+ # @return [Integer]
6
+ # @!attribute key
7
+ # @return [String]
8
+ # @!attribute dropped
9
+ # @return [boolean]
3
10
  attr_reader :id, :key, :dropped
4
11
 
12
+ # @param id [Integer]
13
+ # @param key [String]
14
+ # @param dropped [boolean]
5
15
  def initialize(id:, key:, dropped:)
6
16
  @id = id
7
17
  @key = key
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hackle
4
- VERSION = '0.1.0'
4
+ VERSION = '1.0.0'
5
5
  SDK_NAME = 'ruby-sdk'
6
6
 
7
7
  class SdkInfo
@@ -15,6 +15,7 @@ module Hackle
15
15
  @running = false
16
16
  end
17
17
 
18
+ # @return [Workspace, nil]
18
19
  def fetch
19
20
  @current_workspace.get
20
21
  end
@@ -30,6 +31,8 @@ module Hackle
30
31
  def stop!
31
32
  return unless @running
32
33
 
34
+ @logger.info { 'Shutting down Hackle workspace_fetcher' }
35
+
33
36
  @task.shutdown
34
37
  @running = false
35
38
  end
@@ -1,14 +1,27 @@
1
1
  module Hackle
2
2
  class Workspace
3
+
4
+ # @param experiments [Hash{Integer => Experiment}]
5
+ # @param event_types [Hash{String => EventType}]
3
6
  def initialize(experiments:, event_types:)
7
+
8
+ # @type [Hash{Integer => Experiment}]
4
9
  @experiments = experiments
10
+
11
+ # @type [Hash{String => EventType}]
5
12
  @event_types = event_types
6
13
  end
7
14
 
15
+ # @param experiment_key [Integer]
16
+ #
17
+ # @return [Experiment, nil]
8
18
  def get_experiment(experiment_key:)
9
19
  @experiments[experiment_key]
10
20
  end
11
21
 
22
+ # @param event_type_key [String]
23
+ #
24
+ # @return [EventType]
12
25
  def get_event_type(event_type_key:)
13
26
  event_type = @event_types[event_type_key]
14
27
 
@@ -40,7 +53,7 @@ module Hackle
40
53
  key: data[:key],
41
54
  bucket: buckets[data[:bucketId]],
42
55
  variations: Hash[data[:variations].map { |v| [v[:id], variation(v)] }],
43
- user_overrides: Hash[data[:execution][:userOverrides].map { |u| [u[:userId], u[:variationId]] }]
56
+ overrides: Hash[data[:execution][:userOverrides].map { |u| [u[:userId], u[:variationId]] }]
44
57
  )
45
58
  end
46
59
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hackle-ruby-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hackle
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-03 00:00:00.000000000 Z
11
+ date: 2020-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -142,14 +142,16 @@ files:
142
142
  - lib/hackle/config.rb
143
143
  - lib/hackle/decision/bucketer.rb
144
144
  - lib/hackle/decision/decider.rb
145
- - lib/hackle/events/event.rb
146
145
  - lib/hackle/events/event_dispatcher.rb
147
146
  - lib/hackle/events/event_processor.rb
147
+ - lib/hackle/events/user_event.rb
148
148
  - lib/hackle/http/http.rb
149
149
  - lib/hackle/models/bucket.rb
150
+ - lib/hackle/models/event.rb
150
151
  - lib/hackle/models/event_type.rb
151
152
  - lib/hackle/models/experiment.rb
152
153
  - lib/hackle/models/slot.rb
154
+ - lib/hackle/models/user.rb
153
155
  - lib/hackle/models/variation.rb
154
156
  - lib/hackle/version.rb
155
157
  - lib/hackle/workspaces/http_workspace_fetcher.rb
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hackle
4
- class Event
5
- attr_reader :timestamp, :user_id
6
-
7
- class Exposure < Event
8
- attr_reader :experiment, :variation
9
-
10
- def initialize(user_id:, experiment:, variation:)
11
- @timestamp = Event.generate_timestamp
12
- @user_id = user_id
13
- @experiment = experiment
14
- @variation = variation
15
- end
16
- end
17
-
18
- class Track < Event
19
- attr_reader :event_type, :value
20
-
21
- def initialize(user_id:, event_type:, value: nil)
22
- @timestamp = Event.generate_timestamp
23
- @user_id = user_id
24
- @event_type = event_type
25
- @value = value
26
- end
27
- end
28
-
29
- def self.generate_timestamp
30
- (Time.now.to_f * 1000).to_i
31
- end
32
- end
33
- end