optimizely-sdk 3.3.2 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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