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 )
50
+ features_map = get_features_map ( experiments_id_map )
27
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
}
33
- config [ 'sdkKey' ] = @project_config . sdk_key if @project_config . sdk_key
34
- config [ 'environmentKey' ] = @project_config . environment_key if @project_config . environment_key
35
66
config
36
67
end
37
68
38
69
private
39
70
40
- def experiments_map
41
- feature_variables_map = @project_config . feature_flags . reduce ( { } ) do |result_map , feature |
42
- result_map . update ( feature [ 'id' ] => feature [ 'variables' ] )
43
- end
71
+ def experiments_id_map
72
+ feature_variables_map = feature_variable_map
73
+ audiences_id_map = audiences_map
44
74
@project_config . experiments . reduce ( { } ) do |experiments_map , experiment |
75
+ feature_id = @project_config . experiment_feature_map . fetch ( experiment [ 'id' ] , [ ] ) . first
45
76
experiments_map . update (
46
- experiment [ 'key ' ] => {
77
+ experiment [ 'id ' ] => {
47
78
'id' => experiment [ 'id' ] ,
48
79
'key' => experiment [ 'key' ] ,
49
- 'variationsMap' => experiment [ 'variations' ] . reduce ( { } ) do |variations_map , variation |
50
- variation_object = {
51
- 'id' => variation [ 'id' ] ,
52
- 'key' => variation [ 'key' ] ,
53
- 'variablesMap' => get_merged_variables_map ( variation , experiment [ 'id' ] , feature_variables_map )
54
- }
55
- variation_object [ 'featureEnabled' ] = variation [ 'featureEnabled' ] if @project_config . feature_experiment? ( experiment [ 'id' ] )
56
- variations_map . update ( variation [ 'key' ] => variation_object )
57
- end
80
+ 'variationsMap' => get_variation_map ( feature_id , experiment , feature_variables_map ) ,
81
+ 'audiences' => replace_ids_with_names ( experiment . fetch ( 'audienceConditions' , [ ] ) , audiences_id_map ) || ''
58
82
}
59
83
)
60
84
end
61
85
end
62
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
+
63
117
# Merges feature key and type from feature variables to variation variables.
64
- def get_merged_variables_map ( variation , experiment_id , feature_variables_map )
65
- feature_ids = @project_config . experiment_feature_map [ experiment_id ]
66
- return { } unless feature_ids
118
+ def get_merged_variables_map ( variation , feature_id , feature_variables_map )
119
+ return { } unless feature_id
67
120
68
- experiment_feature_variables = feature_variables_map [ feature_ids [ 0 ] ]
121
+ feature_variables = feature_variables_map [ feature_id ]
69
122
# temporary variation variables map to get values to merge.
70
123
temp_variables_id_map = { }
71
124
if variation [ 'variables' ]
@@ -78,7 +131,7 @@ def get_merged_variables_map(variation, experiment_id, feature_variables_map)
78
131
)
79
132
end
80
133
end
81
- experiment_feature_variables . reduce ( { } ) do |variables_map , feature_variable |
134
+ feature_variables . reduce ( { } ) do |variables_map , feature_variable |
82
135
variation_variable = temp_variables_id_map [ feature_variable [ 'id' ] ]
83
136
variable_value = variation [ 'featureEnabled' ] && variation_variable ? variation_variable [ 'value' ] : feature_variable [ 'defaultValue' ]
84
137
variables_map . update (
@@ -94,13 +147,15 @@ def get_merged_variables_map(variation, experiment_id, feature_variables_map)
94
147
95
148
def get_features_map ( all_experiments_map )
96
149
@project_config . feature_flags . reduce ( { } ) do |features_map , feature |
150
+ delivery_rules = get_delivery_rules ( @rollouts , feature [ 'rolloutId' ] , feature [ 'id' ] )
97
151
features_map . update (
98
152
feature [ 'key' ] => {
99
153
'id' => feature [ 'id' ] ,
100
154
'key' => feature [ 'key' ] ,
155
+ # This experimentsMap is deprecated. Use experimentRules and deliveryRules instead.
101
156
'experimentsMap' => feature [ 'experimentIds' ] . reduce ( { } ) do |experiments_map , experiment_id |
102
157
experiment_key = @project_config . experiment_id_map [ experiment_id ] [ 'key' ]
103
- experiments_map . update ( experiment_key => all_experiments_map [ experiment_key ] )
158
+ experiments_map . update ( experiment_key => experiments_id_map [ experiment_id ] )
104
159
end ,
105
160
'variablesMap' => feature [ 'variables' ] . reduce ( { } ) do |variables , variable |
106
161
variables . update (
@@ -111,10 +166,107 @@ def get_features_map(all_experiments_map)
111
166
'value' => variable [ 'defaultValue' ]
112
167
}
113
168
)
114
- 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
115
174
}
116
175
)
117
176
end
118
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
119
271
end
120
272
end
0 commit comments