optimizely-sdk 3.3.2 → 3.4.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
- SHA1:
3
- metadata.gz: 7a2fc40c97158de70c4ec1d0eee4db872cac0b5a
4
- data.tar.gz: f754bd8cd02ef852bf16cc555ceca5a03ebd39f3
2
+ SHA256:
3
+ metadata.gz: 00fc9a6599ce3b83c1696f788f5f4ad5e627c0063da459eb11353a7ae2872033
4
+ data.tar.gz: 36a84eb5593ce921103a7c1829e4c0677d95f82a287f37347ec8de3484fad082
5
5
  SHA512:
6
- metadata.gz: dfafb0abdfa7df11a1e2bd57423b1a543158ecbc3de564f1500274070c00071b1e87edf79f94f4e95b39cedd913e554865e51a783542c8a111ddaca1afd4641b
7
- data.tar.gz: 6bb7cba088585ecb5caa429e159b609653563001c90a185c8c22f8b187edce064b8c291a360072facf203118f40b9c3e45cf27d02603d65a444918dbbc5db44e
6
+ metadata.gz: 26f809dd552d54670e9d2ffc00bacbbb91ba509b7e5ce98a2cc720dacf495e5fb232f50286d0373f18144bac8ed2c5a540a3435423b03cea347b2eb449dbd053
7
+ data.tar.gz: 67a123c84a058e5d398b39a3349266d9a2e484022c313ae63afc4f1ca97988a99e0285a9a4d6b42beb5df5c514e66cc4b54663f258bd602a80afbe8c28b175a2
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright 2016-2019, Optimizely and contributors
4
+ # Copyright 2016-2020, Optimizely and contributors
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@ require_relative 'optimizely/helpers/validator'
33
33
  require_relative 'optimizely/helpers/variable_type'
34
34
  require_relative 'optimizely/logger'
35
35
  require_relative 'optimizely/notification_center'
36
+ require_relative 'optimizely/optimizely_config'
36
37
 
37
38
  module Optimizely
38
39
  class Project
@@ -523,6 +524,57 @@ module Optimizely
523
524
  @event_processor.stop! if @event_processor.respond_to?(:stop!)
524
525
  end
525
526
 
527
+ def get_optimizely_config
528
+ # Get OptimizelyConfig object containing experiments and features data
529
+ # Returns Object
530
+ #
531
+ # OptimizelyConfig Object Schema
532
+ # {
533
+ # 'experimentsMap' => {
534
+ # 'my-fist-experiment' => {
535
+ # 'id' => '111111',
536
+ # 'key' => 'my-fist-experiment'
537
+ # 'variationsMap' => {
538
+ # 'variation_1' => {
539
+ # 'id' => '121212',
540
+ # 'key' => 'variation_1',
541
+ # 'variablesMap' => {
542
+ # 'age' => {
543
+ # 'id' => '222222',
544
+ # 'key' => 'age',
545
+ # 'type' => 'integer',
546
+ # 'value' => '0',
547
+ # }
548
+ # }
549
+ # }
550
+ # }
551
+ # }
552
+ # },
553
+ # 'featuresMap' => {
554
+ # 'awesome-feature' => {
555
+ # 'id' => '333333',
556
+ # 'key' => 'awesome-feature',
557
+ # 'experimentsMap' => Object,
558
+ # 'variablesMap' => Object,
559
+ # }
560
+ # },
561
+ # 'revision' => '13',
562
+ # }
563
+ #
564
+ unless is_valid
565
+ @logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_optimizely_config').message)
566
+ return nil
567
+ end
568
+
569
+ # config_manager might not contain optimizely_config if its supplied by the consumer
570
+ # Generating a new OptimizelyConfig object in this case as a fallback
571
+ if @config_manager.respond_to?(:optimizely_config)
572
+ @config_manager.optimizely_config
573
+ else
574
+ OptimizelyConfig.new(project_config).config
575
+ end
576
+ end
577
+
526
578
  private
527
579
 
528
580
  def get_variation_with_config(experiment_key, user_id, attributes, config)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright 2019, Optimizely and contributors
4
+ # Copyright 2019-2020, Optimizely and contributors
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ require_relative '../helpers/constants'
22
22
  require_relative '../logger'
23
23
  require_relative '../notification_center'
24
24
  require_relative '../project_config'
25
+ require_relative '../optimizely_config'
25
26
  require_relative 'project_config_manager'
26
27
  require_relative 'async_scheduler'
27
28
  require 'httparty'
@@ -30,7 +31,7 @@ module Optimizely
30
31
  class HTTPProjectConfigManager < ProjectConfigManager
31
32
  # Config manager that polls for the datafile and updated ProjectConfig based on an update interval.
32
33
 
33
- attr_reader :stopped
34
+ attr_reader :stopped, :optimizely_config
34
35
 
35
36
  # Initialize config manager. One of sdk_key or url has to be set to be able to use.
36
37
  #
@@ -73,6 +74,7 @@ module Optimizely
73
74
  @skip_json_validation = skip_json_validation
74
75
  @notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
75
76
  @config = datafile.nil? ? nil : DatafileProjectConfig.create(datafile, @logger, @error_handler, @skip_json_validation)
77
+ @optimizely_config = @config.nil? ? nil : OptimizelyConfig.new(@config).config
76
78
  @mutex = Mutex.new
77
79
  @resource = ConditionVariable.new
78
80
  @async_scheduler = AsyncScheduler.new(method(:fetch_datafile_config), @polling_interval, auto_update, @logger)
@@ -192,6 +194,7 @@ module Optimizely
192
194
  end
193
195
 
194
196
  @config = config
197
+ @optimizely_config = OptimizelyConfig.new(config).config
195
198
 
196
199
  @notification_center.send_notifications(NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE])
197
200
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright 2019, Optimizely and contributors
4
+ # Copyright 2019-2020, Optimizely and contributors
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -17,11 +17,13 @@
17
17
  #
18
18
 
19
19
  require_relative '../config/datafile_project_config'
20
+ require_relative '../optimizely_config'
20
21
  require_relative 'project_config_manager'
22
+
21
23
  module Optimizely
22
24
  class StaticProjectConfigManager < ProjectConfigManager
23
25
  # Implementation of ProjectConfigManager interface.
24
- attr_reader :config
26
+ attr_reader :config, :optimizely_config
25
27
 
26
28
  def initialize(datafile, logger, error_handler, skip_json_validation)
27
29
  # Looks up and sets datafile and config based on response body.
@@ -38,6 +40,8 @@ module Optimizely
38
40
  error_handler,
39
41
  skip_json_validation
40
42
  )
43
+
44
+ @optimizely_config = @config.nil? ? nil : OptimizelyConfig.new(@config).config
41
45
  end
42
46
  end
43
47
  end
@@ -31,7 +31,6 @@ module Optimizely
31
31
  DEFAULT_BATCH_INTERVAL = 30_000 # interval in milliseconds
32
32
  DEFAULT_QUEUE_CAPACITY = 1000
33
33
  DEFAULT_TIMEOUT_INTERVAL = 5 # interval in seconds
34
- MAX_NIL_COUNT = 3
35
34
 
36
35
  FLUSH_SIGNAL = 'FLUSH_SIGNAL'
37
36
  SHUTDOWN_SIGNAL = 'SHUTDOWN_SIGNAL'
@@ -62,7 +61,7 @@ module Optimizely
62
61
  @notification_center = notification_center
63
62
  @current_batch = []
64
63
  @started = false
65
- start!
64
+ @stopped = false
66
65
  end
67
66
 
68
67
  def start!
@@ -72,26 +71,37 @@ module Optimizely
72
71
  end
73
72
  @flushing_interval_deadline = Helpers::DateTimeUtils.create_timestamp + @flush_interval
74
73
  @logger.log(Logger::INFO, 'Starting scheduler.')
75
- @thread = Thread.new { run }
74
+ if @wait_mutex.nil?
75
+ @wait_mutex = Mutex.new
76
+ @resource = ConditionVariable.new
77
+ end
78
+ @thread = Thread.new { run_queue }
76
79
  @started = true
80
+ @stopped = false
77
81
  end
78
82
 
79
83
  def flush
80
84
  @event_queue << FLUSH_SIGNAL
85
+ @wait_mutex.synchronize { @resource.signal }
81
86
  end
82
87
 
83
88
  def process(user_event)
84
89
  @logger.log(Logger::DEBUG, "Received userEvent: #{user_event}")
85
90
 
86
- if !@started || !@thread.alive?
91
+ # if the processor has been explicitly stopped. Don't accept tasks
92
+ if @stopped
87
93
  @logger.log(Logger::WARN, 'Executor shutdown, not accepting tasks.')
88
94
  return
89
95
  end
90
96
 
97
+ # start if the processor hasn't been started
98
+ start! unless @started
99
+
91
100
  begin
92
101
  @event_queue.push(user_event, true)
93
- rescue Exception
94
- @logger.log(Logger::WARN, 'Payload not accepted by the queue.')
102
+ @wait_mutex.synchronize { @resource.signal }
103
+ rescue => e
104
+ @logger.log(Logger::WARN, 'Payload not accepted by the queue: ' + e.message)
95
105
  return
96
106
  end
97
107
  end
@@ -101,42 +111,20 @@ module Optimizely
101
111
 
102
112
  @logger.log(Logger::INFO, 'Stopping scheduler.')
103
113
  @event_queue << SHUTDOWN_SIGNAL
114
+ @wait_mutex.synchronize { @resource.signal }
104
115
  @thread.join(DEFAULT_TIMEOUT_INTERVAL)
105
116
  @started = false
117
+ @stopped = true
106
118
  end
107
119
 
108
120
  private
109
121
 
110
- def run
111
- # if we receive a number of item nils that reach MAX_NIL_COUNT,
112
- # then we hang on the pop via setting use_pop to false
113
- @nil_count = 0
114
- # hang on pop if true
115
- @use_pop = false
116
- loop do
117
- if Helpers::DateTimeUtils.create_timestamp >= @flushing_interval_deadline
118
- @logger.log(Logger::DEBUG, 'Deadline exceeded flushing current batch.')
119
- flush_queue!
120
- @flushing_interval_deadline = Helpers::DateTimeUtils.create_timestamp + @flush_interval
121
- @use_pop = true if @nil_count > MAX_NIL_COUNT
122
- end
123
-
124
- item = @event_queue.pop if @event_queue.length.positive? || @use_pop
125
-
126
- if item.nil?
127
- # when nil count is greater than MAX_NIL_COUNT, we hang on the pop until there is an item available.
128
- # this avoids to much spinning of the loop.
129
- @nil_count += 1
130
- next
131
- end
132
-
133
- # reset nil_count and use_pop if we have received an item.
134
- @nil_count = 0
135
- @use_pop = false
136
-
122
+ def process_queue
123
+ while @event_queue.length.positive?
124
+ item = @event_queue.pop
137
125
  if item == SHUTDOWN_SIGNAL
138
126
  @logger.log(Logger::DEBUG, 'Received shutdown signal.')
139
- break
127
+ return false
140
128
  end
141
129
 
142
130
  if item == FLUSH_SIGNAL
@@ -147,15 +135,35 @@ module Optimizely
147
135
 
148
136
  add_to_batch(item) if item.is_a? Optimizely::UserEvent
149
137
  end
138
+ true
139
+ end
140
+
141
+ def run_queue
142
+ loop do
143
+ if Helpers::DateTimeUtils.create_timestamp >= @flushing_interval_deadline
144
+ @logger.log(Logger::DEBUG, 'Deadline exceeded flushing current batch.')
145
+
146
+ break unless process_queue
147
+
148
+ flush_queue!
149
+ @flushing_interval_deadline = Helpers::DateTimeUtils.create_timestamp + @flush_interval
150
+ end
151
+
152
+ break unless process_queue
153
+
154
+ # what is the current interval to flush in seconds
155
+ interval = (@flushing_interval_deadline - Helpers::DateTimeUtils.create_timestamp) * 0.001
156
+
157
+ next unless interval.positive?
158
+
159
+ @wait_mutex.synchronize { @resource.wait(@wait_mutex, interval) }
160
+ end
150
161
  rescue SignalException
151
162
  @logger.log(Logger::ERROR, 'Interrupted while processing buffer.')
152
- rescue Exception => e
163
+ rescue => e
153
164
  @logger.log(Logger::ERROR, "Uncaught exception processing buffer. #{e.message}")
154
165
  ensure
155
- @logger.log(
156
- Logger::INFO,
157
- 'Exiting processing loop. Attempting to flush pending events.'
158
- )
166
+ @logger.log(Logger::INFO, 'Exiting processing loop. Attempting to flush pending events.')
159
167
  flush_queue!
160
168
  end
161
169
 
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019, Optimizely and contributors
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module Optimizely
19
+ class OptimizelyConfig
20
+ def initialize(project_config)
21
+ @project_config = project_config
22
+ end
23
+
24
+ def config
25
+ experiments_map_object = experiments_map
26
+ features_map = get_features_map(experiments_map_object)
27
+ {
28
+ 'experimentsMap' => experiments_map_object,
29
+ 'featuresMap' => features_map,
30
+ 'revision' => @project_config.revision
31
+ }
32
+ end
33
+
34
+ private
35
+
36
+ def experiments_map
37
+ feature_variables_map = @project_config.feature_flags.reduce({}) do |result_map, feature|
38
+ result_map.update(feature['id'] => feature['variables'])
39
+ end
40
+ @project_config.experiments.reduce({}) do |experiments_map, experiment|
41
+ experiments_map.update(
42
+ experiment['key'] => {
43
+ 'id' => experiment['id'],
44
+ 'key' => experiment['key'],
45
+ 'variationsMap' => experiment['variations'].reduce({}) do |variations_map, variation|
46
+ variation_object = {
47
+ 'id' => variation['id'],
48
+ 'key' => variation['key'],
49
+ 'variablesMap' => get_merged_variables_map(variation, experiment['id'], feature_variables_map)
50
+ }
51
+ variation_object['featureEnabled'] = variation['featureEnabled'] if @project_config.feature_experiment?(experiment['id'])
52
+ variations_map.update(variation['key'] => variation_object)
53
+ end
54
+ }
55
+ )
56
+ end
57
+ end
58
+
59
+ # Merges feature key and type from feature variables to variation variables.
60
+ def get_merged_variables_map(variation, experiment_id, feature_variables_map)
61
+ feature_ids = @project_config.experiment_feature_map[experiment_id]
62
+ return {} unless feature_ids
63
+
64
+ experiment_feature_variables = feature_variables_map[feature_ids[0]]
65
+ # temporary variation variables map to get values to merge.
66
+ temp_variables_id_map = {}
67
+ if variation['variables']
68
+ temp_variables_id_map = variation['variables'].reduce({}) do |variables_map, variable|
69
+ variables_map.update(
70
+ variable['id'] => {
71
+ 'id' => variable['id'],
72
+ 'value' => variable['value']
73
+ }
74
+ )
75
+ end
76
+ end
77
+ experiment_feature_variables.reduce({}) do |variables_map, feature_variable|
78
+ variation_variable = temp_variables_id_map[feature_variable['id']]
79
+ variable_value = variation['featureEnabled'] && variation_variable ? variation_variable['value'] : feature_variable['defaultValue']
80
+ variables_map.update(
81
+ feature_variable['key'] => {
82
+ 'id' => feature_variable['id'],
83
+ 'key' => feature_variable['key'],
84
+ 'type' => feature_variable['type'],
85
+ 'value' => variable_value
86
+ }
87
+ )
88
+ end
89
+ end
90
+
91
+ def get_features_map(all_experiments_map)
92
+ @project_config.feature_flags.reduce({}) do |features_map, feature|
93
+ features_map.update(
94
+ feature['key'] => {
95
+ 'id' => feature['id'],
96
+ 'key' => feature['key'],
97
+ 'experimentsMap' => feature['experimentIds'].reduce({}) do |experiments_map, experiment_id|
98
+ experiment_key = @project_config.experiment_id_map[experiment_id]['key']
99
+ experiments_map.update(experiment_key => all_experiments_map[experiment_key])
100
+ end,
101
+ 'variablesMap' => feature['variables'].reduce({}) do |variables, variable|
102
+ variables.update(
103
+ variable['key'] => {
104
+ 'id' => variable['id'],
105
+ 'key' => variable['key'],
106
+ 'type' => variable['type'],
107
+ 'value' => variable['defaultValue']
108
+ }
109
+ )
110
+ end
111
+ }
112
+ )
113
+ end
114
+ end
115
+ end
116
+ end
@@ -17,5 +17,5 @@
17
17
  #
18
18
  module Optimizely
19
19
  CLIENT_ENGINE = 'ruby-sdk'
20
- VERSION = '3.3.2'
20
+ VERSION = '3.4.0'
21
21
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: optimizely-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.2
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Optimizely
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-13 00:00:00.000000000 Z
11
+ date: 2020-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -182,6 +182,7 @@ files:
182
182
  - lib/optimizely/helpers/variable_type.rb
183
183
  - lib/optimizely/logger.rb
184
184
  - lib/optimizely/notification_center.rb
185
+ - lib/optimizely/optimizely_config.rb
185
186
  - lib/optimizely/optimizely_factory.rb
186
187
  - lib/optimizely/params.rb
187
188
  - lib/optimizely/project_config.rb
@@ -206,8 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
207
  - !ruby/object:Gem::Version
207
208
  version: '0'
208
209
  requirements: []
209
- rubyforge_project:
210
- rubygems_version: 2.5.2.3
210
+ rubygems_version: 3.0.3
211
211
  signing_key:
212
212
  specification_version: 4
213
213
  summary: Ruby SDK for Optimizely's testing framework