-
Notifications
You must be signed in to change notification settings - Fork 28
feat(OptimizelyConfig): Add new fields to OptimizelyConfig #285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
06ba039
61706dc
f065fa4
2099a46
9fa1c8c
9c25e55
5e89329
7101864
e9bfc9a
f8843dd
d3ac144
c9fd3fe
50ed1fd
f3f9cae
fe21ff7
8938534
a12b39e
94ca43d
e6b1e8d
eb45b1a
4edf298
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,56 +16,109 @@ | |
# | ||
|
||
module Optimizely | ||
require 'json' | ||
class OptimizelyConfig | ||
include Optimizely::ConditionTreeEvaluator | ||
def initialize(project_config) | ||
@project_config = project_config | ||
@rollouts = @project_config.rollouts | ||
@audiences = [] | ||
audience_id_lookup_dict = {} | ||
|
||
@project_config.typed_audiences.each do |typed_audience| | ||
@audiences.push( | ||
'id' => typed_audience['id'], | ||
'name' => typed_audience['name'], | ||
'conditions' => typed_audience['conditions'].to_json | ||
) | ||
audience_id_lookup_dict[typed_audience['id']] = typed_audience['id'] | ||
end | ||
|
||
@project_config.audiences.each do |audience| | ||
next unless !audience_id_lookup_dict.key?(audience['id']) && (audience['id'] != '$opt_dummy_audience') | ||
|
||
@audiences.push( | ||
'id' => audience['id'], | ||
'name' => audience['name'], | ||
'conditions' => audience['conditions'] | ||
) | ||
end | ||
end | ||
|
||
def config | ||
experiments_map_object = experiments_map | ||
features_map = get_features_map(experiments_map_object) | ||
features_map = get_features_map(experiments_id_map) | ||
config = { | ||
'sdkKey' => @project_config.sdk_key, | ||
'datafile' => @project_config.datafile, | ||
# This experimentsMap is for experiments of legacy projects only. | ||
# For flag projects, experiment keys are not guaranteed to be unique | ||
# across multiple flags, so this map may not include all experiments | ||
# when keys conflict. Use experimentRules and deliveryRules instead. | ||
'experimentsMap' => experiments_map_object, | ||
'featuresMap' => features_map, | ||
'revision' => @project_config.revision | ||
'revision' => @project_config.revision, | ||
'attributes' => get_attributes_list(@project_config.attributes), | ||
'audiences' => @audiences, | ||
'events' => get_events_list(@project_config.events), | ||
'environmentKey' => @project_config.environment_key | ||
} | ||
config['sdkKey'] = @project_config.sdk_key if @project_config.sdk_key | ||
config['environmentKey'] = @project_config.environment_key if @project_config.environment_key | ||
config | ||
end | ||
|
||
private | ||
|
||
def experiments_map | ||
feature_variables_map = @project_config.feature_flags.reduce({}) do |result_map, feature| | ||
result_map.update(feature['id'] => feature['variables']) | ||
end | ||
def experiments_id_map | ||
feature_variables_map = feature_variable_map | ||
audiences_id_map = audiences_map | ||
@project_config.experiments.reduce({}) do |experiments_map, experiment| | ||
feature_id = @project_config.experiment_feature_map.fetch(experiment['id'], []).first | ||
experiments_map.update( | ||
experiment['key'] => { | ||
experiment['id'] => { | ||
'id' => experiment['id'], | ||
'key' => experiment['key'], | ||
'variationsMap' => experiment['variations'].reduce({}) do |variations_map, variation| | ||
variation_object = { | ||
'id' => variation['id'], | ||
'key' => variation['key'], | ||
'variablesMap' => get_merged_variables_map(variation, experiment['id'], feature_variables_map) | ||
} | ||
variation_object['featureEnabled'] = variation['featureEnabled'] if @project_config.feature_experiment?(experiment['id']) | ||
variations_map.update(variation['key'] => variation_object) | ||
end | ||
'variationsMap' => get_variation_map(feature_id, experiment, feature_variables_map), | ||
'audiences' => replace_ids_with_names(experiment.fetch('audienceConditions', []), audiences_id_map) || '' | ||
} | ||
) | ||
end | ||
end | ||
|
||
def audiences_map | ||
@audiences.reduce({}) do |audiences_map, optly_audience| | ||
audiences_map.update(optly_audience['id'] => optly_audience['name']) | ||
end | ||
end | ||
|
||
def experiments_map | ||
experiments_id_map.values.reduce({}) do |experiments_key_map, experiment| | ||
experiments_key_map.update(experiment['key'] => experiment) | ||
end | ||
end | ||
|
||
def feature_variable_map | ||
@project_config.feature_flags.reduce({}) do |result_map, feature| | ||
result_map.update(feature['id'] => feature['variables']) | ||
end | ||
end | ||
|
||
def get_variation_map(feature_id, experiment, feature_variables_map) | ||
experiment['variations'].reduce({}) do |variations_map, variation| | ||
variation_object = { | ||
'id' => variation['id'], | ||
'key' => variation['key'], | ||
'featureEnabled' => variation['featureEnabled'], | ||
'variablesMap' => get_merged_variables_map(variation, feature_id, feature_variables_map) | ||
} | ||
variations_map.update(variation['key'] => variation_object) | ||
end | ||
end | ||
|
||
# Merges feature key and type from feature variables to variation variables. | ||
def get_merged_variables_map(variation, experiment_id, feature_variables_map) | ||
feature_ids = @project_config.experiment_feature_map[experiment_id] | ||
return {} unless feature_ids | ||
def get_merged_variables_map(variation, feature_id, feature_variables_map) | ||
return {} unless feature_id | ||
|
||
experiment_feature_variables = feature_variables_map[feature_ids[0]] | ||
feature_variables = feature_variables_map[feature_id] | ||
# temporary variation variables map to get values to merge. | ||
temp_variables_id_map = {} | ||
if variation['variables'] | ||
|
@@ -78,7 +131,7 @@ def get_merged_variables_map(variation, experiment_id, feature_variables_map) | |
) | ||
end | ||
end | ||
experiment_feature_variables.reduce({}) do |variables_map, feature_variable| | ||
feature_variables.reduce({}) do |variables_map, feature_variable| | ||
variation_variable = temp_variables_id_map[feature_variable['id']] | ||
variable_value = variation['featureEnabled'] && variation_variable ? variation_variable['value'] : feature_variable['defaultValue'] | ||
variables_map.update( | ||
|
@@ -94,13 +147,15 @@ def get_merged_variables_map(variation, experiment_id, feature_variables_map) | |
|
||
def get_features_map(all_experiments_map) | ||
@project_config.feature_flags.reduce({}) do |features_map, feature| | ||
delivery_rules = get_delivery_rules(@rollouts, feature['rolloutId'], feature['id']) | ||
features_map.update( | ||
feature['key'] => { | ||
'id' => feature['id'], | ||
'key' => feature['key'], | ||
# This experimentsMap is deprecated. Use experimentRules and deliveryRules instead. | ||
'experimentsMap' => feature['experimentIds'].reduce({}) do |experiments_map, experiment_id| | ||
experiment_key = @project_config.experiment_id_map[experiment_id]['key'] | ||
experiments_map.update(experiment_key => all_experiments_map[experiment_key]) | ||
experiments_map.update(experiment_key => experiments_id_map[experiment_id]) | ||
jaeopt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end, | ||
'variablesMap' => feature['variables'].reduce({}) do |variables, variable| | ||
variables.update( | ||
|
@@ -111,10 +166,107 @@ def get_features_map(all_experiments_map) | |
'value' => variable['defaultValue'] | ||
} | ||
) | ||
end | ||
end, | ||
'experimentRules' => feature['experimentIds'].reduce([]) do |experiments_map, experiment_id| | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
experiments_map.push(all_experiments_map[experiment_id]) | ||
end, | ||
'deliveryRules' => delivery_rules | ||
} | ||
) | ||
end | ||
end | ||
|
||
def get_attributes_list(attributes) | ||
attributes.map do |attribute| | ||
{ | ||
'id' => attribute['id'], | ||
'key' => attribute['key'] | ||
} | ||
end | ||
end | ||
|
||
def get_events_list(events) | ||
events.map do |event| | ||
{ | ||
'id' => event['id'], | ||
'key' => event['key'], | ||
'experimentIds' => event['experimentIds'] | ||
} | ||
end | ||
end | ||
|
||
def lookup_name_from_id(audience_id, audiences_map) | ||
audiences_map[audience_id] || audience_id | ||
end | ||
|
||
def stringify_conditions(conditions, audiences_map) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code looks complex. It will be great if you can add detailed comments inside the method with each branch condition. |
||
operand = 'OR' | ||
conditions_str = '' | ||
length = conditions.length() | ||
return '' if length.zero? | ||
return '"' + lookup_name_from_id(conditions[0], audiences_map) + '"' if length == 1 && !OPERATORS.include?(conditions[0]) | ||
|
||
# Edge cases for lengths 0, 1 or 2 | ||
if length == 2 && OPERATORS.include?(conditions[0]) && !conditions[1].is_a?(Array) && !OPERATORS.include?(conditions[1]) | ||
return '"' + lookup_name_from_id(conditions[1], audiences_map) + '"' if conditions[0] != 'not' | ||
|
||
return conditions[0].upcase + ' "' + lookup_name_from_id(conditions[1], audiences_map) + '"' | ||
|
||
end | ||
if length > 1 | ||
(0..length - 1).each do |n| | ||
# Operand is handled here and made Upper Case | ||
if OPERATORS.include?(conditions[n]) | ||
operand = conditions[n].upcase | ||
# Check if element is a list or not | ||
elsif conditions[n].is_a?(Array) | ||
# Check if at the end or not to determine where to add the operand | ||
# Recursive call to call stringify on embedded list | ||
conditions_str += if n + 1 < length | ||
'(' + stringify_conditions(conditions[n], audiences_map) + ') ' | ||
else | ||
operand + ' (' + stringify_conditions(conditions[n], audiences_map) + ')' | ||
end | ||
# If the item is not a list, we process as an audience ID and retrieve the name | ||
else | ||
audience_name = lookup_name_from_id(conditions[n], audiences_map) | ||
unless audience_name.nil? | ||
# Below handles all cases for one ID or greater | ||
conditions_str += if n + 1 < length - 1 | ||
'"' + audience_name + '" ' + operand + ' ' | ||
elsif n + 1 == length | ||
operand + ' "' + audience_name + '"' | ||
else | ||
'"' + audience_name + '" ' | ||
end | ||
end | ||
end | ||
end | ||
end | ||
conditions_str || '' | ||
end | ||
|
||
def replace_ids_with_names(conditions, audiences_map) | ||
!conditions.empty? ? stringify_conditions(conditions, audiences_map) : '' | ||
end | ||
|
||
def get_delivery_rules(rollouts, rollout_id, feature_id) | ||
audiences_id_map = audiences_map | ||
feature_variables_map = feature_variable_map | ||
rollout = rollouts.select { |selected_rollout| selected_rollout['id'] == rollout_id } | ||
if rollout.any? | ||
rollout = rollout[0] | ||
experiments = rollout['experiments'] | ||
return experiments.map do |experiment| | ||
{ | ||
'id' => experiment['id'], | ||
'key' => experiment['key'], | ||
'variationsMap' => get_variation_map(feature_id, experiment, feature_variables_map), | ||
'audiences' => replace_ids_with_names(experiment.fetch('audienceConditions', []), audiences_id_map) || '' | ||
} | ||
end | ||
end | ||
[] | ||
end | ||
end | ||
end |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a note about key-conflict issue on top of 'experimentsMap':
// This experimentsMap is for experiments of legacy projects only.
// For flag projects, experiment keys are not guaranteed to be unique
// across multiple flags, so this map may not include all experiments
// when keys conflict.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need a period at the end of the first sentence - "This experimentsMap is for experiments of legacy projects only"