optimizely-sdk 3.10.1 → 4.0.1

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.
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 +15 -14
@@ -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