optimizely-sdk 3.8.1 → 3.9.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: 5751701cecb265d27a62c72b268fcb01bb5a37f8696f97a6f56ad29776b226cc
4
- data.tar.gz: 32533f70bdd2861e2250b3c1b8e274d61f04395dd4ff5cf7ff0776d5f49c2a90
3
+ metadata.gz: 3502b43c8fae064fda3e1b237786a0285103d4ce9383a9d64cfc7b34f3f8c849
4
+ data.tar.gz: 9b843fc3a7999e640f329812aeeb8e26474b5b85f29d27c0647ba7c3685cb73f
5
5
  SHA512:
6
- metadata.gz: f7258d91de18c854b91f1d938c4624bf227307a2f01aec82aa23d1f2978ca8510209171f4dc62a3d2dcc606784a741dd9f6572d7c6a81475e2b791e70b7e45be
7
- data.tar.gz: 1471cf812fbebc51fe2207e599f44b60597c3e6e15209af400d4ecabe0333bcd285269b3b421ad80f043c33012aecc54d3d41e59c53dffe64b09b93039b36254
6
+ metadata.gz: a244307de60cf4a7e9b4e90bd0282e071e310701c09b4314a5f011f9f97a1f638205dc9e3de59611c247315314bfd58a0521690bbc3fa876575f3ef8fa917306
7
+ data.tar.gz: b68df42eef86c3a4e1fd22f2b1b4cd3060df4ade0c9125862443164735778a16415af958a4002fba7adcdf078d1552a5ffef446bac22e7a269a5984445d1fd50
@@ -28,6 +28,8 @@ module Optimizely
28
28
  NOT_CONDITION => :not_evaluator
29
29
  }.freeze
30
30
 
31
+ OPERATORS = [AND_CONDITION, OR_CONDITION, NOT_CONDITION].freeze
32
+
31
33
  module_function
32
34
 
33
35
  def evaluate(conditions, leaf_evaluator)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019-2020, Optimizely and contributors
3
+ # Copyright 2019-2021, 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.
@@ -38,6 +38,8 @@ module Optimizely
38
38
  attr_reader :anonymize_ip
39
39
  attr_reader :bot_filtering
40
40
  attr_reader :revision
41
+ attr_reader :sdk_key
42
+ attr_reader :environment_key
41
43
  attr_reader :rollouts
42
44
  attr_reader :version
43
45
  attr_reader :send_flag_decisions
@@ -85,6 +87,8 @@ module Optimizely
85
87
  @anonymize_ip = config.key?('anonymizeIP') ? config['anonymizeIP'] : false
86
88
  @bot_filtering = config['botFiltering']
87
89
  @revision = config['revision']
90
+ @sdk_key = config.fetch('sdkKey', '')
91
+ @environment_key = config.fetch('environmentKey', '')
88
92
  @rollouts = config.fetch('rollouts', [])
89
93
  @send_flag_decisions = config.fetch('sendFlagDecisions', false)
90
94
 
@@ -478,6 +482,16 @@ module Optimizely
478
482
  @experiment_feature_map.key?(experiment_id)
479
483
  end
480
484
 
485
+ def rollout_experiment?(experiment_id)
486
+ # Determines if given experiment is a rollout test.
487
+ #
488
+ # experiment_id - String experiment ID
489
+ #
490
+ # Returns true if experiment belongs to any rollout,
491
+ # false otherwise.
492
+ @rollout_experiment_id_map.key?(experiment_id)
493
+ end
494
+
481
495
  private
482
496
 
483
497
  def generate_key_map(array, key)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019-2020, Optimizely and contributors
3
+ # Copyright 2019-2021, 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.
@@ -16,53 +16,109 @@
16
16
  #
17
17
 
18
18
  module Optimizely
19
+ require 'json'
19
20
  class OptimizelyConfig
21
+ include Optimizely::ConditionTreeEvaluator
20
22
  def initialize(project_config)
21
23
  @project_config = project_config
24
+ @rollouts = @project_config.rollouts
25
+ @audiences = []
26
+ audience_id_lookup_dict = {}
27
+
28
+ @project_config.typed_audiences.each do |typed_audience|
29
+ @audiences.push(
30
+ 'id' => typed_audience['id'],
31
+ 'name' => typed_audience['name'],
32
+ 'conditions' => typed_audience['conditions'].to_json
33
+ )
34
+ audience_id_lookup_dict[typed_audience['id']] = typed_audience['id']
35
+ end
36
+
37
+ @project_config.audiences.each do |audience|
38
+ next unless !audience_id_lookup_dict.key?(audience['id']) && (audience['id'] != '$opt_dummy_audience')
39
+
40
+ @audiences.push(
41
+ 'id' => audience['id'],
42
+ 'name' => audience['name'],
43
+ 'conditions' => audience['conditions']
44
+ )
45
+ end
22
46
  end
23
47
 
24
48
  def config
25
49
  experiments_map_object = experiments_map
26
- features_map = get_features_map(experiments_map_object)
27
- {
50
+ features_map = get_features_map(experiments_id_map)
51
+ config = {
52
+ 'sdkKey' => @project_config.sdk_key,
28
53
  'datafile' => @project_config.datafile,
54
+ # This experimentsMap is for experiments of legacy projects only.
55
+ # For flag projects, experiment keys are not guaranteed to be unique
56
+ # across multiple flags, so this map may not include all experiments
57
+ # when keys conflict. Use experimentRules and deliveryRules instead.
29
58
  'experimentsMap' => experiments_map_object,
30
59
  'featuresMap' => features_map,
31
- 'revision' => @project_config.revision
60
+ 'revision' => @project_config.revision,
61
+ 'attributes' => get_attributes_list(@project_config.attributes),
62
+ 'audiences' => @audiences,
63
+ 'events' => get_events_list(@project_config.events),
64
+ 'environmentKey' => @project_config.environment_key
32
65
  }
66
+ config
33
67
  end
34
68
 
35
69
  private
36
70
 
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
71
+ def experiments_id_map
72
+ feature_variables_map = feature_variable_map
73
+ audiences_id_map = audiences_map
41
74
  @project_config.experiments.reduce({}) do |experiments_map, experiment|
75
+ feature_id = @project_config.experiment_feature_map.fetch(experiment['id'], []).first
42
76
  experiments_map.update(
43
- experiment['key'] => {
77
+ experiment['id'] => {
44
78
  'id' => experiment['id'],
45
79
  '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
80
+ 'variationsMap' => get_variation_map(feature_id, experiment, feature_variables_map),
81
+ 'audiences' => replace_ids_with_names(experiment.fetch('audienceConditions', []), audiences_id_map) || ''
55
82
  }
56
83
  )
57
84
  end
58
85
  end
59
86
 
87
+ def audiences_map
88
+ @audiences.reduce({}) do |audiences_map, optly_audience|
89
+ audiences_map.update(optly_audience['id'] => optly_audience['name'])
90
+ end
91
+ end
92
+
93
+ def experiments_map
94
+ experiments_id_map.values.reduce({}) do |experiments_key_map, experiment|
95
+ experiments_key_map.update(experiment['key'] => experiment)
96
+ end
97
+ end
98
+
99
+ def feature_variable_map
100
+ @project_config.feature_flags.reduce({}) do |result_map, feature|
101
+ result_map.update(feature['id'] => feature['variables'])
102
+ end
103
+ end
104
+
105
+ def get_variation_map(feature_id, experiment, feature_variables_map)
106
+ experiment['variations'].reduce({}) do |variations_map, variation|
107
+ variation_object = {
108
+ 'id' => variation['id'],
109
+ 'key' => variation['key'],
110
+ 'featureEnabled' => variation['featureEnabled'],
111
+ 'variablesMap' => get_merged_variables_map(variation, feature_id, feature_variables_map)
112
+ }
113
+ variations_map.update(variation['key'] => variation_object)
114
+ end
115
+ end
116
+
60
117
  # 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
118
+ def get_merged_variables_map(variation, feature_id, feature_variables_map)
119
+ return {} unless feature_id
64
120
 
65
- experiment_feature_variables = feature_variables_map[feature_ids[0]]
121
+ feature_variables = feature_variables_map[feature_id]
66
122
  # temporary variation variables map to get values to merge.
67
123
  temp_variables_id_map = {}
68
124
  if variation['variables']
@@ -75,7 +131,7 @@ module Optimizely
75
131
  )
76
132
  end
77
133
  end
78
- experiment_feature_variables.reduce({}) do |variables_map, feature_variable|
134
+ feature_variables.reduce({}) do |variables_map, feature_variable|
79
135
  variation_variable = temp_variables_id_map[feature_variable['id']]
80
136
  variable_value = variation['featureEnabled'] && variation_variable ? variation_variable['value'] : feature_variable['defaultValue']
81
137
  variables_map.update(
@@ -91,13 +147,15 @@ module Optimizely
91
147
 
92
148
  def get_features_map(all_experiments_map)
93
149
  @project_config.feature_flags.reduce({}) do |features_map, feature|
150
+ delivery_rules = get_delivery_rules(@rollouts, feature['rolloutId'], feature['id'])
94
151
  features_map.update(
95
152
  feature['key'] => {
96
153
  'id' => feature['id'],
97
154
  'key' => feature['key'],
155
+ # This experimentsMap is deprecated. Use experimentRules and deliveryRules instead.
98
156
  'experimentsMap' => feature['experimentIds'].reduce({}) do |experiments_map, experiment_id|
99
157
  experiment_key = @project_config.experiment_id_map[experiment_id]['key']
100
- experiments_map.update(experiment_key => all_experiments_map[experiment_key])
158
+ experiments_map.update(experiment_key => experiments_id_map[experiment_id])
101
159
  end,
102
160
  'variablesMap' => feature['variables'].reduce({}) do |variables, variable|
103
161
  variables.update(
@@ -108,10 +166,107 @@ module Optimizely
108
166
  'value' => variable['defaultValue']
109
167
  }
110
168
  )
111
- end
169
+ end,
170
+ 'experimentRules' => feature['experimentIds'].reduce([]) do |experiments_map, experiment_id|
171
+ experiments_map.push(all_experiments_map[experiment_id])
172
+ end,
173
+ 'deliveryRules' => delivery_rules
112
174
  }
113
175
  )
114
176
  end
115
177
  end
178
+
179
+ def get_attributes_list(attributes)
180
+ attributes.map do |attribute|
181
+ {
182
+ 'id' => attribute['id'],
183
+ 'key' => attribute['key']
184
+ }
185
+ end
186
+ end
187
+
188
+ def get_events_list(events)
189
+ events.map do |event|
190
+ {
191
+ 'id' => event['id'],
192
+ 'key' => event['key'],
193
+ 'experimentIds' => event['experimentIds']
194
+ }
195
+ end
196
+ end
197
+
198
+ def lookup_name_from_id(audience_id, audiences_map)
199
+ audiences_map[audience_id] || audience_id
200
+ end
201
+
202
+ def stringify_conditions(conditions, audiences_map)
203
+ operand = 'OR'
204
+ conditions_str = ''
205
+ length = conditions.length()
206
+ return '' if length.zero?
207
+ return '"' + lookup_name_from_id(conditions[0], audiences_map) + '"' if length == 1 && !OPERATORS.include?(conditions[0])
208
+
209
+ # Edge cases for lengths 0, 1 or 2
210
+ if length == 2 && OPERATORS.include?(conditions[0]) && !conditions[1].is_a?(Array) && !OPERATORS.include?(conditions[1])
211
+ return '"' + lookup_name_from_id(conditions[1], audiences_map) + '"' if conditions[0] != 'not'
212
+
213
+ return conditions[0].upcase + ' "' + lookup_name_from_id(conditions[1], audiences_map) + '"'
214
+
215
+ end
216
+ if length > 1
217
+ (0..length - 1).each do |n|
218
+ # Operand is handled here and made Upper Case
219
+ if OPERATORS.include?(conditions[n])
220
+ operand = conditions[n].upcase
221
+ # Check if element is a list or not
222
+ elsif conditions[n].is_a?(Array)
223
+ # Check if at the end or not to determine where to add the operand
224
+ # Recursive call to call stringify on embedded list
225
+ conditions_str += if n + 1 < length
226
+ '(' + stringify_conditions(conditions[n], audiences_map) + ') '
227
+ else
228
+ operand + ' (' + stringify_conditions(conditions[n], audiences_map) + ')'
229
+ end
230
+ # If the item is not a list, we process as an audience ID and retrieve the name
231
+ else
232
+ audience_name = lookup_name_from_id(conditions[n], audiences_map)
233
+ unless audience_name.nil?
234
+ # Below handles all cases for one ID or greater
235
+ conditions_str += if n + 1 < length - 1
236
+ '"' + audience_name + '" ' + operand + ' '
237
+ elsif n + 1 == length
238
+ operand + ' "' + audience_name + '"'
239
+ else
240
+ '"' + audience_name + '" '
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ conditions_str || ''
247
+ end
248
+
249
+ def replace_ids_with_names(conditions, audiences_map)
250
+ !conditions.empty? ? stringify_conditions(conditions, audiences_map) : ''
251
+ end
252
+
253
+ def get_delivery_rules(rollouts, rollout_id, feature_id)
254
+ audiences_id_map = audiences_map
255
+ feature_variables_map = feature_variable_map
256
+ rollout = rollouts.select { |selected_rollout| selected_rollout['id'] == rollout_id }
257
+ if rollout.any?
258
+ rollout = rollout[0]
259
+ experiments = rollout['experiments']
260
+ return experiments.map do |experiment|
261
+ {
262
+ 'id' => experiment['id'],
263
+ 'key' => experiment['key'],
264
+ 'variationsMap' => get_variation_map(feature_id, experiment, feature_variables_map),
265
+ 'audiences' => replace_ids_with_names(experiment.fetch('audienceConditions', []), audiences_id_map) || ''
266
+ }
267
+ end
268
+ end
269
+ []
270
+ end
116
271
  end
117
272
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2016-2020, Optimizely and contributors
3
+ # Copyright 2016-2021, 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.
@@ -46,6 +46,10 @@ module Optimizely
46
46
 
47
47
  def revision; end
48
48
 
49
+ def sdk_key; end
50
+
51
+ def environment_key; end
52
+
49
53
  def send_flag_decisions; end
50
54
 
51
55
  def rollouts; end
@@ -17,5 +17,5 @@
17
17
  #
18
18
  module Optimizely
19
19
  CLIENT_ENGINE = 'ruby-sdk'
20
- VERSION = '3.8.1'
20
+ VERSION = '3.9.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.8.1
4
+ version: 3.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Optimizely
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-02 00:00:00.000000000 Z
11
+ date: 2021-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler