1616#
1717
1818module Optimizely
19+ require 'json'
1920 class OptimizelyConfig
21+ include Optimizely ::ConditionTreeEvaluator
2022 def initialize ( project_config )
2123 @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
2246 end
2347
2448 def config
2549 experiments_map_object = experiments_map
26- features_map = get_features_map ( experiments_map_object )
50+ features_map = get_features_map ( experiments_id_map )
2751 config = {
52+ 'sdkKey' => @project_config . sdk_key ,
2853 '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.
2958 'experimentsMap' => experiments_map_object ,
3059 '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
3265 }
33- config [ 'sdkKey' ] = @project_config . sdk_key if @project_config . sdk_key
34- config [ 'environmentKey' ] = @project_config . environment_key if @project_config . environment_key
3566 config
3667 end
3768
3869 private
3970
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
4474 @project_config . experiments . reduce ( { } ) do |experiments_map , experiment |
75+ feature_id = @project_config . experiment_feature_map . fetch ( experiment [ 'id' ] , [ ] ) . first
4576 experiments_map . update (
46- experiment [ 'key ' ] => {
77+ experiment [ 'id ' ] => {
4778 'id' => experiment [ 'id' ] ,
4879 '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 ) || ''
5882 }
5983 )
6084 end
6185 end
6286
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+
63117 # 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
67120
68- experiment_feature_variables = feature_variables_map [ feature_ids [ 0 ] ]
121+ feature_variables = feature_variables_map [ feature_id ]
69122 # temporary variation variables map to get values to merge.
70123 temp_variables_id_map = { }
71124 if variation [ 'variables' ]
@@ -78,7 +131,7 @@ def get_merged_variables_map(variation, experiment_id, feature_variables_map)
78131 )
79132 end
80133 end
81- experiment_feature_variables . reduce ( { } ) do |variables_map , feature_variable |
134+ feature_variables . reduce ( { } ) do |variables_map , feature_variable |
82135 variation_variable = temp_variables_id_map [ feature_variable [ 'id' ] ]
83136 variable_value = variation [ 'featureEnabled' ] && variation_variable ? variation_variable [ 'value' ] : feature_variable [ 'defaultValue' ]
84137 variables_map . update (
@@ -94,13 +147,15 @@ def get_merged_variables_map(variation, experiment_id, feature_variables_map)
94147
95148 def get_features_map ( all_experiments_map )
96149 @project_config . feature_flags . reduce ( { } ) do |features_map , feature |
150+ delivery_rules = get_delivery_rules ( @rollouts , feature [ 'rolloutId' ] , feature [ 'id' ] )
97151 features_map . update (
98152 feature [ 'key' ] => {
99153 'id' => feature [ 'id' ] ,
100154 'key' => feature [ 'key' ] ,
155+ # This experimentsMap is deprecated. Use experimentRules and deliveryRules instead.
101156 'experimentsMap' => feature [ 'experimentIds' ] . reduce ( { } ) do |experiments_map , experiment_id |
102157 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 ] )
104159 end ,
105160 'variablesMap' => feature [ 'variables' ] . reduce ( { } ) do |variables , variable |
106161 variables . update (
@@ -111,10 +166,107 @@ def get_features_map(all_experiments_map)
111166 'value' => variable [ 'defaultValue' ]
112167 }
113168 )
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
115174 }
116175 )
117176 end
118177 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
119271 end
120272end
0 commit comments