optimizely-sdk 3.3.2.rc1 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright 2017, Optimizely and contributors
4
+ # Copyright 2017, 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.
@@ -48,6 +48,13 @@ module Optimizely
48
48
  logger.log(Logger::ERROR, "Unable to cast variable value '#{value}' to type "\
49
49
  "'#{variable_type}': #{e.message}.")
50
50
  end
51
+ when 'json'
52
+ begin
53
+ return_value = JSON.parse(value)
54
+ rescue => e
55
+ logger.log(Logger::ERROR, "Unable to cast variable value '#{value}' to type "\
56
+ "'#{variable_type}': #{e.message}.")
57
+ end
51
58
  else
52
59
  # default case is string
53
60
  return_value = value
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019-2020, 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
+ 'datafile' => @project_config.datafile,
29
+ 'experimentsMap' => experiments_map_object,
30
+ 'featuresMap' => features_map,
31
+ 'revision' => @project_config.revision
32
+ }
33
+ end
34
+
35
+ private
36
+
37
+ def experiments_map
38
+ feature_variables_map = @project_config.feature_flags.reduce({}) do |result_map, feature|
39
+ result_map.update(feature['id'] => feature['variables'])
40
+ end
41
+ @project_config.experiments.reduce({}) do |experiments_map, experiment|
42
+ experiments_map.update(
43
+ experiment['key'] => {
44
+ 'id' => experiment['id'],
45
+ 'key' => experiment['key'],
46
+ 'variationsMap' => experiment['variations'].reduce({}) do |variations_map, variation|
47
+ variation_object = {
48
+ 'id' => variation['id'],
49
+ 'key' => variation['key'],
50
+ 'variablesMap' => get_merged_variables_map(variation, experiment['id'], feature_variables_map)
51
+ }
52
+ variation_object['featureEnabled'] = variation['featureEnabled'] if @project_config.feature_experiment?(experiment['id'])
53
+ variations_map.update(variation['key'] => variation_object)
54
+ end
55
+ }
56
+ )
57
+ end
58
+ end
59
+
60
+ # Merges feature key and type from feature variables to variation variables.
61
+ def get_merged_variables_map(variation, experiment_id, feature_variables_map)
62
+ feature_ids = @project_config.experiment_feature_map[experiment_id]
63
+ return {} unless feature_ids
64
+
65
+ experiment_feature_variables = feature_variables_map[feature_ids[0]]
66
+ # temporary variation variables map to get values to merge.
67
+ temp_variables_id_map = {}
68
+ if variation['variables']
69
+ temp_variables_id_map = variation['variables'].reduce({}) do |variables_map, variable|
70
+ variables_map.update(
71
+ variable['id'] => {
72
+ 'id' => variable['id'],
73
+ 'value' => variable['value']
74
+ }
75
+ )
76
+ end
77
+ end
78
+ experiment_feature_variables.reduce({}) do |variables_map, feature_variable|
79
+ variation_variable = temp_variables_id_map[feature_variable['id']]
80
+ variable_value = variation['featureEnabled'] && variation_variable ? variation_variable['value'] : feature_variable['defaultValue']
81
+ variables_map.update(
82
+ feature_variable['key'] => {
83
+ 'id' => feature_variable['id'],
84
+ 'key' => feature_variable['key'],
85
+ 'type' => feature_variable['type'],
86
+ 'value' => variable_value
87
+ }
88
+ )
89
+ end
90
+ end
91
+
92
+ def get_features_map(all_experiments_map)
93
+ @project_config.feature_flags.reduce({}) do |features_map, feature|
94
+ features_map.update(
95
+ feature['key'] => {
96
+ 'id' => feature['id'],
97
+ 'key' => feature['key'],
98
+ 'experimentsMap' => feature['experimentIds'].reduce({}) do |experiments_map, experiment_id|
99
+ experiment_key = @project_config.experiment_id_map[experiment_id]['key']
100
+ experiments_map.update(experiment_key => all_experiments_map[experiment_key])
101
+ end,
102
+ 'variablesMap' => feature['variables'].reduce({}) do |variables, variable|
103
+ variables.update(
104
+ variable['key'] => {
105
+ 'id' => variable['id'],
106
+ 'key' => variable['key'],
107
+ 'type' => variable['type'],
108
+ 'value' => variable['defaultValue']
109
+ }
110
+ )
111
+ end
112
+ }
113
+ )
114
+ end
115
+ end
116
+ end
117
+ end
@@ -17,16 +17,18 @@
17
17
  #
18
18
 
19
19
  require 'optimizely'
20
+ require 'optimizely/error_handler'
20
21
  require 'optimizely/event_dispatcher'
21
22
  require 'optimizely/event/batch_event_processor'
23
+ require 'optimizely/logger'
24
+ require 'optimizely/notification_center'
25
+
22
26
  module Optimizely
23
27
  class OptimizelyFactory
24
- attr_reader :max_event_batch_size, :max_event_flush_interval
25
-
26
28
  # Convenience method for setting the maximum number of events contained within a batch.
27
29
  # @param batch_size Integer - Sets size of EventQueue.
28
30
  # @param logger - Optional LoggerInterface Provides a log method to log messages.
29
- def self.max_event_batch_size(batch_size, logger)
31
+ def self.max_event_batch_size(batch_size, logger = NoOpLogger.new)
30
32
  unless batch_size.is_a? Integer
31
33
  logger.log(
32
34
  Logger::ERROR,
@@ -48,7 +50,7 @@ module Optimizely
48
50
  # Convenience method for setting the maximum time interval in milliseconds between event dispatches.
49
51
  # @param flush_interval Numeric - Time interval between event dispatches.
50
52
  # @param logger - Optional LoggerInterface Provides a log method to log messages.
51
- def self.max_event_flush_interval(flush_interval, logger)
53
+ def self.max_event_flush_interval(flush_interval, logger = NoOpLogger.new)
52
54
  unless flush_interval.is_a? Numeric
53
55
  logger.log(
54
56
  Logger::ERROR,
@@ -67,12 +69,42 @@ module Optimizely
67
69
  @max_event_flush_interval = flush_interval
68
70
  end
69
71
 
72
+ # Convenience method for setting frequency at which datafile has to be polled and ProjectConfig updated.
73
+ #
74
+ # @param polling_interval Numeric - Time in seconds after which to update datafile.
75
+ def self.polling_interval(polling_interval)
76
+ @polling_interval = polling_interval
77
+ end
78
+
79
+ # Convenience method for setting timeout to block the config call until config has been initialized.
80
+ #
81
+ # @param blocking_timeout Numeric - Time in seconds.
82
+ def self.blocking_timeout(blocking_timeout)
83
+ @blocking_timeout = blocking_timeout
84
+ end
85
+
70
86
  # Returns a new optimizely instance.
71
87
  #
72
88
  # @params sdk_key - Required String uniquely identifying the fallback datafile corresponding to project.
73
89
  # @param fallback datafile - Optional JSON string datafile.
74
90
  def self.default_instance(sdk_key, datafile = nil)
75
- Optimizely::Project.new(datafile, nil, nil, nil, nil, nil, sdk_key)
91
+ error_handler = NoOpErrorHandler.new
92
+ logger = NoOpLogger.new
93
+ notification_center = NotificationCenter.new(logger, error_handler)
94
+
95
+ config_manager = Optimizely::HTTPProjectConfigManager.new(
96
+ sdk_key: sdk_key,
97
+ polling_interval: @polling_interval,
98
+ blocking_timeout: @blocking_timeout,
99
+ datafile: datafile,
100
+ logger: logger,
101
+ error_handler: error_handler,
102
+ notification_center: notification_center
103
+ )
104
+
105
+ Optimizely::Project.new(
106
+ datafile, nil, logger, error_handler, nil, nil, sdk_key, config_manager, notification_center
107
+ )
76
108
  end
77
109
 
78
110
  # Returns a new optimizely instance.
@@ -108,10 +140,27 @@ module Optimizely
108
140
  config_manager = nil,
109
141
  notification_center = nil
110
142
  )
143
+
144
+ error_handler ||= NoOpErrorHandler.new
145
+ logger ||= NoOpLogger.new
146
+ notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(logger, error_handler)
147
+
111
148
  event_processor = BatchEventProcessor.new(
112
149
  event_dispatcher: event_dispatcher || EventDispatcher.new,
113
150
  batch_size: @max_event_batch_size,
114
151
  flush_interval: @max_event_flush_interval,
152
+ logger: logger,
153
+ notification_center: notification_center
154
+ )
155
+
156
+ config_manager ||= Optimizely::HTTPProjectConfigManager.new(
157
+ sdk_key: sdk_key,
158
+ polling_interval: @polling_interval,
159
+ blocking_timeout: @blocking_timeout,
160
+ datafile: datafile,
161
+ logger: logger,
162
+ error_handler: error_handler,
163
+ skip_json_validation: skip_json_validation,
115
164
  notification_center: notification_center
116
165
  )
117
166
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2016-2019, Optimizely and contributors
3
+ # Copyright 2016-2020, Optimizely and contributors
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@ module Optimizely
20
20
  # ProjectConfig is an interface capturing the experiment, variation and feature definitions.
21
21
  # The default implementation of ProjectConfig can be found in DatafileProjectConfig.
22
22
 
23
+ def datafile; end
24
+
23
25
  def account_id; end
24
26
 
25
27
  def attributes; end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright 2020, Optimizely and contributors
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require_relative 'exceptions'
20
+
21
+ module Optimizely
22
+ module SemanticVersion
23
+ # Semantic Version Operators
24
+ SEMVER_PRE_RELEASE = '-'
25
+ SEMVER_BUILD = '+'
26
+
27
+ module_function
28
+
29
+ def pre_release?(target)
30
+ # Method to check if the given version is a prerelease
31
+ #
32
+ # target - String representing semantic version
33
+ #
34
+ # Returns true if the given version is a prerelease
35
+ # false if it doesn't
36
+
37
+ raise unless target.is_a? String
38
+
39
+ prerelease_index = target.index(SEMVER_PRE_RELEASE)
40
+ build_index = target.index(SEMVER_BUILD)
41
+
42
+ return false if prerelease_index.nil?
43
+ return true if build_index.nil?
44
+
45
+ # when both operators are present prerelease should precede the build operator
46
+ prerelease_index < build_index
47
+ end
48
+
49
+ def build?(target)
50
+ # Method to check if the given version is a build
51
+ #
52
+ # target - String representing semantic version
53
+ #
54
+ # Returns true if the given version is a build
55
+ # false if it doesn't
56
+
57
+ raise unless target.is_a? String
58
+
59
+ prerelease_index = target.index(SEMVER_PRE_RELEASE)
60
+ build_index = target.index(SEMVER_BUILD)
61
+
62
+ return false if build_index.nil?
63
+ return true if prerelease_index.nil?
64
+
65
+ # when both operators are present build should precede the prerelease operator
66
+ build_index < prerelease_index
67
+ end
68
+
69
+ def split_semantic_version(target)
70
+ # Method to split the given version.
71
+ #
72
+ # target - String representing semantic version
73
+ #
74
+ # Returns List The array of version split into smaller parts i.e major, minor, patch etc,
75
+ # Exception if the given version is invalid.
76
+
77
+ target_prefix = target
78
+ target_suffix = ''
79
+ target_parts = []
80
+
81
+ raise InvalidSemanticVersion if target.include? ' '
82
+
83
+ if pre_release?(target)
84
+ target_parts = target.split(SEMVER_PRE_RELEASE, 2)
85
+ elsif build? target
86
+ target_parts = target.split(SEMVER_BUILD, 2)
87
+ end
88
+
89
+ unless target_parts.empty?
90
+ target_prefix = target_parts[0].to_s
91
+ target_suffix = target_parts[1..-1]
92
+ end
93
+
94
+ # expect a version string of the form x.y.z
95
+ dot_count = target_prefix.count('.')
96
+ raise InvalidSemanticVersion if dot_count > 2
97
+
98
+ target_version_parts = target_prefix.split('.')
99
+ raise InvalidSemanticVersion if target_version_parts.length != dot_count + 1
100
+
101
+ target_version_parts.each do |part|
102
+ raise InvalidSemanticVersion unless Helpers::Validator.string_numeric? part
103
+ end
104
+
105
+ target_version_parts.concat(target_suffix) if target_suffix.is_a?(Array)
106
+
107
+ target_version_parts
108
+ end
109
+
110
+ def compare_user_version_with_target_version(target_version, user_version)
111
+ # Compares target and user versions
112
+ #
113
+ # target_version - String representing target version
114
+ # user_version - String representing user version
115
+
116
+ # Returns boolean 0 if user version is equal to target version,
117
+ # 1 if user version is greater than target version,
118
+ # -1 if user version is less than target version.
119
+
120
+ raise InvalidAttributeType unless target_version.is_a? String
121
+ raise InvalidAttributeType unless user_version.is_a? String
122
+
123
+ is_target_version_prerelease = pre_release?(target_version)
124
+ is_user_version_prerelease = pre_release?(user_version)
125
+
126
+ target_version_parts = split_semantic_version(target_version)
127
+ user_version_parts = split_semantic_version(user_version)
128
+ user_version_parts_len = user_version_parts.length if user_version_parts
129
+
130
+ # Up to the precision of targetedVersion, expect version to match exactly.
131
+ target_version_parts.each_with_index do |_item, idx|
132
+ if user_version_parts_len <= idx
133
+ # even if they are equal at this point. if the target is a prerelease
134
+ # then user version must be greater than the pre release.
135
+ return 1 if is_target_version_prerelease
136
+
137
+ return -1
138
+
139
+ elsif !Helpers::Validator.string_numeric? user_version_parts[idx]
140
+ # compare strings
141
+ if user_version_parts[idx] < target_version_parts[idx]
142
+ return 1 if is_target_version_prerelease && !is_user_version_prerelease
143
+
144
+ return -1
145
+
146
+ elsif user_version_parts[idx] > target_version_parts[idx]
147
+ return -1 if is_user_version_prerelease && !is_target_version_prerelease
148
+
149
+ return 1
150
+ end
151
+
152
+ else
153
+ user_version_part = user_version_parts[idx].to_i
154
+ target_version_part = target_version_parts[idx].to_i
155
+
156
+ return 1 if user_version_part > target_version_part
157
+ return -1 if user_version_part < target_version_part
158
+ end
159
+ end
160
+
161
+ return -1 if is_user_version_prerelease && !is_target_version_prerelease
162
+
163
+ 0
164
+ end
165
+ end
166
+ end
@@ -17,5 +17,5 @@
17
17
  #
18
18
  module Optimizely
19
19
  CLIENT_ENGINE = 'ruby-sdk'
20
- VERSION = '3.3.2.rc1'
20
+ VERSION = '3.6.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.rc1
4
+ version: 3.6.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-05 00:00:00.000000000 Z
11
+ date: 2020-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,20 +94,6 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: httparty
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '0.11'
104
- type: :runtime
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '0.11'
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: json-schema
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -149,6 +135,7 @@ files:
149
135
  - lib/optimizely/bucketer.rb
150
136
  - lib/optimizely/condition_tree_evaluator.rb
151
137
  - lib/optimizely/config/datafile_project_config.rb
138
+ - lib/optimizely/config/proxy_config.rb
152
139
  - lib/optimizely/config_manager/async_scheduler.rb
153
140
  - lib/optimizely/config_manager/http_project_config_manager.rb
154
141
  - lib/optimizely/config_manager/project_config_manager.rb
@@ -178,13 +165,16 @@ files:
178
165
  - lib/optimizely/helpers/date_time_utils.rb
179
166
  - lib/optimizely/helpers/event_tag_utils.rb
180
167
  - lib/optimizely/helpers/group.rb
168
+ - lib/optimizely/helpers/http_utils.rb
181
169
  - lib/optimizely/helpers/validator.rb
182
170
  - lib/optimizely/helpers/variable_type.rb
183
171
  - lib/optimizely/logger.rb
184
172
  - lib/optimizely/notification_center.rb
173
+ - lib/optimizely/optimizely_config.rb
185
174
  - lib/optimizely/optimizely_factory.rb
186
175
  - lib/optimizely/params.rb
187
176
  - lib/optimizely/project_config.rb
177
+ - lib/optimizely/semantic_version.rb
188
178
  - lib/optimizely/user_profile_service.rb
189
179
  - lib/optimizely/version.rb
190
180
  homepage: https://www.optimizely.com/
@@ -202,12 +192,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
202
192
  version: '0'
203
193
  required_rubygems_version: !ruby/object:Gem::Requirement
204
194
  requirements:
205
- - - ">"
195
+ - - ">="
206
196
  - !ruby/object:Gem::Version
207
- version: 1.3.1
197
+ version: '0'
208
198
  requirements: []
209
- rubyforge_project:
210
- rubygems_version: 2.7.6.2
199
+ rubygems_version: 3.0.3
211
200
  signing_key:
212
201
  specification_version: 4
213
202
  summary: Ruby SDK for Optimizely's testing framework