optimizely-sdk 3.10.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +202 -202
  3. data/lib/optimizely/audience.rb +127 -97
  4. data/lib/optimizely/bucketer.rb +156 -156
  5. data/lib/optimizely/condition_tree_evaluator.rb +123 -123
  6. data/lib/optimizely/config/datafile_project_config.rb +539 -552
  7. data/lib/optimizely/config/proxy_config.rb +34 -34
  8. data/lib/optimizely/config_manager/async_scheduler.rb +95 -95
  9. data/lib/optimizely/config_manager/http_project_config_manager.rb +330 -329
  10. data/lib/optimizely/config_manager/project_config_manager.rb +24 -24
  11. data/lib/optimizely/config_manager/static_project_config_manager.rb +53 -52
  12. data/lib/optimizely/decide/optimizely_decide_option.rb +28 -28
  13. data/lib/optimizely/decide/optimizely_decision.rb +60 -60
  14. data/lib/optimizely/decide/optimizely_decision_message.rb +26 -26
  15. data/lib/optimizely/decision_service.rb +563 -563
  16. data/lib/optimizely/error_handler.rb +39 -39
  17. data/lib/optimizely/event/batch_event_processor.rb +235 -234
  18. data/lib/optimizely/event/entity/conversion_event.rb +44 -43
  19. data/lib/optimizely/event/entity/decision.rb +38 -38
  20. data/lib/optimizely/event/entity/event_batch.rb +86 -86
  21. data/lib/optimizely/event/entity/event_context.rb +50 -50
  22. data/lib/optimizely/event/entity/impression_event.rb +48 -47
  23. data/lib/optimizely/event/entity/snapshot.rb +33 -33
  24. data/lib/optimizely/event/entity/snapshot_event.rb +48 -48
  25. data/lib/optimizely/event/entity/user_event.rb +22 -22
  26. data/lib/optimizely/event/entity/visitor.rb +36 -35
  27. data/lib/optimizely/event/entity/visitor_attribute.rb +38 -37
  28. data/lib/optimizely/event/event_factory.rb +156 -155
  29. data/lib/optimizely/event/event_processor.rb +25 -25
  30. data/lib/optimizely/event/forwarding_event_processor.rb +44 -43
  31. data/lib/optimizely/event/user_event_factory.rb +88 -88
  32. data/lib/optimizely/event_builder.rb +221 -228
  33. data/lib/optimizely/event_dispatcher.rb +71 -71
  34. data/lib/optimizely/exceptions.rb +135 -139
  35. data/lib/optimizely/helpers/constants.rb +415 -397
  36. data/lib/optimizely/helpers/date_time_utils.rb +30 -30
  37. data/lib/optimizely/helpers/event_tag_utils.rb +132 -132
  38. data/lib/optimizely/helpers/group.rb +31 -31
  39. data/lib/optimizely/helpers/http_utils.rb +65 -64
  40. data/lib/optimizely/helpers/validator.rb +183 -183
  41. data/lib/optimizely/helpers/variable_type.rb +67 -67
  42. data/lib/optimizely/logger.rb +46 -45
  43. data/lib/optimizely/notification_center.rb +174 -176
  44. data/lib/optimizely/optimizely_config.rb +271 -272
  45. data/lib/optimizely/optimizely_factory.rb +181 -181
  46. data/lib/optimizely/optimizely_user_context.rb +204 -179
  47. data/lib/optimizely/params.rb +31 -31
  48. data/lib/optimizely/project_config.rb +99 -91
  49. data/lib/optimizely/semantic_version.rb +166 -166
  50. data/lib/optimizely/{custom_attribute_condition_evaluator.rb → user_condition_evaluator.rb} +391 -369
  51. data/lib/optimizely/user_profile_service.rb +35 -35
  52. data/lib/optimizely/version.rb +21 -21
  53. data/lib/optimizely.rb +1130 -1145
  54. metadata +13 -13
@@ -1,272 +1,271 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2019-2021, 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
- require 'json'
20
- class OptimizelyConfig
21
- include Optimizely::ConditionTreeEvaluator
22
- def initialize(project_config)
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
46
- end
47
-
48
- def config
49
- experiments_map_object = experiments_map
50
- features_map = get_features_map(experiments_id_map)
51
- config = {
52
- 'sdkKey' => @project_config.sdk_key,
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.
58
- 'experimentsMap' => experiments_map_object,
59
- 'featuresMap' => features_map,
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
65
- }
66
- config
67
- end
68
-
69
- private
70
-
71
- def experiments_id_map
72
- feature_variables_map = feature_variable_map
73
- audiences_id_map = audiences_map
74
- @project_config.experiments.reduce({}) do |experiments_map, experiment|
75
- feature_id = @project_config.experiment_feature_map.fetch(experiment['id'], []).first
76
- experiments_map.update(
77
- experiment['id'] => {
78
- 'id' => experiment['id'],
79
- 'key' => experiment['key'],
80
- 'variationsMap' => get_variation_map(feature_id, experiment, feature_variables_map),
81
- 'audiences' => replace_ids_with_names(experiment.fetch('audienceConditions', []), audiences_id_map) || ''
82
- }
83
- )
84
- end
85
- end
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
-
117
- # Merges feature key and type from feature variables to variation variables.
118
- def get_merged_variables_map(variation, feature_id, feature_variables_map)
119
- return {} unless feature_id
120
-
121
- feature_variables = feature_variables_map[feature_id]
122
- # temporary variation variables map to get values to merge.
123
- temp_variables_id_map = {}
124
- if variation['variables']
125
- temp_variables_id_map = variation['variables'].reduce({}) do |variables_map, variable|
126
- variables_map.update(
127
- variable['id'] => {
128
- 'id' => variable['id'],
129
- 'value' => variable['value']
130
- }
131
- )
132
- end
133
- end
134
- feature_variables.reduce({}) do |variables_map, feature_variable|
135
- variation_variable = temp_variables_id_map[feature_variable['id']]
136
- variable_value = variation['featureEnabled'] && variation_variable ? variation_variable['value'] : feature_variable['defaultValue']
137
- variables_map.update(
138
- feature_variable['key'] => {
139
- 'id' => feature_variable['id'],
140
- 'key' => feature_variable['key'],
141
- 'type' => feature_variable['type'],
142
- 'value' => variable_value
143
- }
144
- )
145
- end
146
- end
147
-
148
- def get_features_map(all_experiments_map)
149
- @project_config.feature_flags.reduce({}) do |features_map, feature|
150
- delivery_rules = get_delivery_rules(@rollouts, feature['rolloutId'], feature['id'])
151
- features_map.update(
152
- feature['key'] => {
153
- 'id' => feature['id'],
154
- 'key' => feature['key'],
155
- # This experimentsMap is deprecated. Use experimentRules and deliveryRules instead.
156
- 'experimentsMap' => feature['experimentIds'].reduce({}) do |experiments_map, experiment_id|
157
- experiment_key = @project_config.experiment_id_map[experiment_id]['key']
158
- experiments_map.update(experiment_key => experiments_id_map[experiment_id])
159
- end,
160
- 'variablesMap' => feature['variables'].reduce({}) do |variables, variable|
161
- variables.update(
162
- variable['key'] => {
163
- 'id' => variable['id'],
164
- 'key' => variable['key'],
165
- 'type' => variable['type'],
166
- 'value' => variable['defaultValue']
167
- }
168
- )
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
174
- }
175
- )
176
- end
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
271
- end
272
- end
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019-2022, 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
+ require 'json'
20
+ class OptimizelyConfig
21
+ include Optimizely::ConditionTreeEvaluator
22
+ def initialize(project_config)
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
46
+ end
47
+
48
+ def config
49
+ experiments_map_object = experiments_map
50
+ features_map = get_features_map(experiments_id_map)
51
+ {
52
+ 'sdkKey' => @project_config.sdk_key,
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.
58
+ 'experimentsMap' => experiments_map_object,
59
+ 'featuresMap' => features_map,
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
65
+ }
66
+ end
67
+
68
+ private
69
+
70
+ def experiments_id_map
71
+ feature_variables_map = feature_variable_map
72
+ audiences_id_map = audiences_map
73
+ @project_config.experiments.reduce({}) do |experiments_map, experiment|
74
+ feature_id = @project_config.experiment_feature_map.fetch(experiment['id'], []).first
75
+ experiments_map.update(
76
+ experiment['id'] => {
77
+ 'id' => experiment['id'],
78
+ 'key' => experiment['key'],
79
+ 'variationsMap' => get_variation_map(feature_id, experiment, feature_variables_map),
80
+ 'audiences' => replace_ids_with_names(experiment.fetch('audienceConditions', []), audiences_id_map) || ''
81
+ }
82
+ )
83
+ end
84
+ end
85
+
86
+ def audiences_map
87
+ @audiences.reduce({}) do |audiences_map, optly_audience|
88
+ audiences_map.update(optly_audience['id'] => optly_audience['name'])
89
+ end
90
+ end
91
+
92
+ def experiments_map
93
+ experiments_id_map.values.reduce({}) do |experiments_key_map, experiment|
94
+ experiments_key_map.update(experiment['key'] => experiment)
95
+ end
96
+ end
97
+
98
+ def feature_variable_map
99
+ @project_config.feature_flags.reduce({}) do |result_map, feature|
100
+ result_map.update(feature['id'] => feature['variables'])
101
+ end
102
+ end
103
+
104
+ def get_variation_map(feature_id, experiment, feature_variables_map)
105
+ experiment['variations'].reduce({}) do |variations_map, variation|
106
+ variation_object = {
107
+ 'id' => variation['id'],
108
+ 'key' => variation['key'],
109
+ 'featureEnabled' => variation['featureEnabled'],
110
+ 'variablesMap' => get_merged_variables_map(variation, feature_id, feature_variables_map)
111
+ }
112
+ variations_map.update(variation['key'] => variation_object)
113
+ end
114
+ end
115
+
116
+ # Merges feature key and type from feature variables to variation variables.
117
+ def get_merged_variables_map(variation, feature_id, feature_variables_map)
118
+ return {} unless feature_id
119
+
120
+ feature_variables = feature_variables_map[feature_id]
121
+ # temporary variation variables map to get values to merge.
122
+ temp_variables_id_map = {}
123
+ if variation['variables']
124
+ temp_variables_id_map = variation['variables'].reduce({}) do |variables_map, variable|
125
+ variables_map.update(
126
+ variable['id'] => {
127
+ 'id' => variable['id'],
128
+ 'value' => variable['value']
129
+ }
130
+ )
131
+ end
132
+ end
133
+ feature_variables.reduce({}) do |variables_map, feature_variable|
134
+ variation_variable = temp_variables_id_map[feature_variable['id']]
135
+ variable_value = variation['featureEnabled'] && variation_variable ? variation_variable['value'] : feature_variable['defaultValue']
136
+ variables_map.update(
137
+ feature_variable['key'] => {
138
+ 'id' => feature_variable['id'],
139
+ 'key' => feature_variable['key'],
140
+ 'type' => feature_variable['type'],
141
+ 'value' => variable_value
142
+ }
143
+ )
144
+ end
145
+ end
146
+
147
+ def get_features_map(all_experiments_map)
148
+ @project_config.feature_flags.reduce({}) do |features_map, feature|
149
+ delivery_rules = get_delivery_rules(@rollouts, feature['rolloutId'], feature['id'])
150
+ features_map.update(
151
+ feature['key'] => {
152
+ 'id' => feature['id'],
153
+ 'key' => feature['key'],
154
+ # This experimentsMap is deprecated. Use experimentRules and deliveryRules instead.
155
+ 'experimentsMap' => feature['experimentIds'].reduce({}) do |experiments_map, experiment_id|
156
+ experiment_key = @project_config.experiment_id_map[experiment_id]['key']
157
+ experiments_map.update(experiment_key => experiments_id_map[experiment_id])
158
+ end,
159
+ 'variablesMap' => feature['variables'].reduce({}) do |variables, variable|
160
+ variables.update(
161
+ variable['key'] => {
162
+ 'id' => variable['id'],
163
+ 'key' => variable['key'],
164
+ 'type' => variable['type'],
165
+ 'value' => variable['defaultValue']
166
+ }
167
+ )
168
+ end,
169
+ 'experimentRules' => feature['experimentIds'].reduce([]) do |experiments_map, experiment_id|
170
+ experiments_map.push(all_experiments_map[experiment_id])
171
+ end,
172
+ 'deliveryRules' => delivery_rules
173
+ }
174
+ )
175
+ end
176
+ end
177
+
178
+ def get_attributes_list(attributes)
179
+ attributes.map do |attribute|
180
+ {
181
+ 'id' => attribute['id'],
182
+ 'key' => attribute['key']
183
+ }
184
+ end
185
+ end
186
+
187
+ def get_events_list(events)
188
+ events.map do |event|
189
+ {
190
+ 'id' => event['id'],
191
+ 'key' => event['key'],
192
+ 'experimentIds' => event['experimentIds']
193
+ }
194
+ end
195
+ end
196
+
197
+ def lookup_name_from_id(audience_id, audiences_map)
198
+ audiences_map[audience_id] || audience_id
199
+ end
200
+
201
+ def stringify_conditions(conditions, audiences_map)
202
+ operand = 'OR'
203
+ conditions_str = ''
204
+ length = conditions.length()
205
+ return '' if length.zero?
206
+ return "\"#{lookup_name_from_id(conditions[0], audiences_map)}\"" if length == 1 && !OPERATORS.include?(conditions[0])
207
+
208
+ # Edge cases for lengths 0, 1 or 2
209
+ if length == 2 && OPERATORS.include?(conditions[0]) && !conditions[1].is_a?(Array) && !OPERATORS.include?(conditions[1])
210
+ return "\"#{lookup_name_from_id(conditions[1], audiences_map)}\"" if conditions[0] != 'not'
211
+
212
+ return "#{conditions[0].upcase} \"#{lookup_name_from_id(conditions[1], audiences_map)}\""
213
+
214
+ end
215
+ if length > 1
216
+ (0..length - 1).each do |n|
217
+ # Operand is handled here and made Upper Case
218
+ if OPERATORS.include?(conditions[n])
219
+ operand = conditions[n].upcase
220
+ # Check if element is a list or not
221
+ elsif conditions[n].is_a?(Array)
222
+ # Check if at the end or not to determine where to add the operand
223
+ # Recursive call to call stringify on embedded list
224
+ conditions_str += if n + 1 < length
225
+ "(#{stringify_conditions(conditions[n], audiences_map)}) "
226
+ else
227
+ "#{operand} (#{stringify_conditions(conditions[n], audiences_map)})"
228
+ end
229
+ # If the item is not a list, we process as an audience ID and retrieve the name
230
+ else
231
+ audience_name = lookup_name_from_id(conditions[n], audiences_map)
232
+ unless audience_name.nil?
233
+ # Below handles all cases for one ID or greater
234
+ conditions_str += if n + 1 < length - 1
235
+ "\"#{audience_name}\" #{operand} "
236
+ elsif n + 1 == length
237
+ "#{operand} \"#{audience_name}\""
238
+ else
239
+ "\"#{audience_name}\" "
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
245
+ conditions_str || ''
246
+ end
247
+
248
+ def replace_ids_with_names(conditions, audiences_map)
249
+ !conditions.empty? ? stringify_conditions(conditions, audiences_map) : ''
250
+ end
251
+
252
+ def get_delivery_rules(rollouts, rollout_id, feature_id)
253
+ audiences_id_map = audiences_map
254
+ feature_variables_map = feature_variable_map
255
+ rollout = rollouts.select { |selected_rollout| selected_rollout['id'] == rollout_id }
256
+ if rollout.any?
257
+ rollout = rollout[0]
258
+ experiments = rollout['experiments']
259
+ return experiments.map do |experiment|
260
+ {
261
+ 'id' => experiment['id'],
262
+ 'key' => experiment['key'],
263
+ 'variationsMap' => get_variation_map(feature_id, experiment, feature_variables_map),
264
+ 'audiences' => replace_ids_with_names(experiment.fetch('audienceConditions', []), audiences_id_map) || ''
265
+ }
266
+ end
267
+ end
268
+ []
269
+ end
270
+ end
271
+ end