Skip to content

Commit 1333d60

Browse files
[FSSDK-9781] warn on duplicate experiment key (#343)
1 parent ca43f1e commit 1333d60

File tree

5 files changed

+69
-5
lines changed

5 files changed

+69
-5
lines changed

lib/optimizely.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ def get_optimizely_config
889889
if @config_manager.respond_to?(:optimizely_config)
890890
@config_manager.optimizely_config
891891
else
892-
OptimizelyConfig.new(project_config).config
892+
OptimizelyConfig.new(project_config, @logger).config
893893
end
894894
end
895895

lib/optimizely/config_manager/http_project_config_manager.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def config
141141
end
142142

143143
def optimizely_config
144-
@optimizely_config = OptimizelyConfig.new(@config).config if @optimizely_config.nil?
144+
@optimizely_config = OptimizelyConfig.new(@config, @logger).config if @optimizely_config.nil?
145145

146146
@optimizely_config
147147
end

lib/optimizely/config_manager/static_project_config_manager.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@ def initialize(datafile, logger, error_handler, skip_json_validation)
4141
error_handler,
4242
skip_json_validation
4343
)
44+
@logger = logger
4445
@sdk_key = @config&.sdk_key
4546
@optimizely_config = nil
4647
end
4748

4849
def optimizely_config
49-
@optimizely_config = OptimizelyConfig.new(@config).config if @optimizely_config.nil?
50+
@optimizely_config = OptimizelyConfig.new(@config, @logger).config if @optimizely_config.nil?
5051

5152
@optimizely_config
5253
end

lib/optimizely/optimizely_config.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ module Optimizely
1919
require 'json'
2020
class OptimizelyConfig
2121
include Optimizely::ConditionTreeEvaluator
22-
def initialize(project_config)
22+
def initialize(project_config, logger = nil)
2323
@project_config = project_config
24+
@logger = logger || NoOpLogger.new
2425
@rollouts = @project_config.rollouts
2526
@audiences = []
2627
audience_id_lookup_dict = {}
@@ -91,6 +92,7 @@ def audiences_map
9192

9293
def experiments_map
9394
experiments_id_map.values.reduce({}) do |experiments_key_map, experiment|
95+
@logger.log(Logger::WARN, "Duplicate experiment keys found in datafile: #{experiment['key']}") if experiments_key_map.key? experiment['key']
9496
experiments_key_map.update(experiment['key'] => experiment)
9597
end
9698
end

spec/optimizely_config_spec.rb

+62-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
require 'spec_helper'
2020

2121
describe Optimizely::OptimizelyConfig do
22+
let(:config_body) { OptimizelySpec::VALID_CONFIG_BODY }
2223
let(:config_body_JSON) { OptimizelySpec::VALID_CONFIG_BODY_JSON }
2324
let(:similar_exp_keys_JSON) { OptimizelySpec::SIMILAR_EXP_KEYS_JSON }
2425
let(:typed_audiences_JSON) { OptimizelySpec::CONFIG_DICT_WITH_TYPED_AUDIENCES_JSON }
@@ -768,7 +769,7 @@
768769
'',
769770
'"exactString" OR "999999999"'
770771
]
771-
optimizely_config = Optimizely::OptimizelyConfig.new(project_instance_typed_audiences.send(:project_config))
772+
optimizely_config = Optimizely::OptimizelyConfig.new(project_instance_typed_audiences.send(:project_config), spy_logger)
772773
audiences_map = optimizely_config.send(:audiences_map)
773774
audience_conditions.each_with_index do |audience_condition, index|
774775
result = optimizely_config.send(:replace_ids_with_names, audience_condition, audiences_map)
@@ -796,4 +797,64 @@
796797
expect(optimizely_config_similar_rule_keys['sdkKey']).to eq('')
797798
expect(optimizely_config_similar_rule_keys['environmentKey']).to eq('')
798799
end
800+
801+
it 'should use the newest of duplicate experiment keys' do
802+
duplicate_experiment_key = 'test_experiment'
803+
new_experiment = {
804+
'key': duplicate_experiment_key,
805+
'status': 'Running',
806+
'layerId': '8',
807+
"audienceConditions": %w[
808+
or
809+
11160
810+
],
811+
'audienceIds': ['11160'],
812+
'id': '111137',
813+
'forcedVariations': {},
814+
'trafficAllocation': [
815+
{'entityId': '222242', 'endOfRange': 8000},
816+
{'entityId': '', 'endOfRange': 10_000}
817+
],
818+
'variations': [
819+
{
820+
'id': '222242',
821+
'key': 'control',
822+
'variables': []
823+
}
824+
]
825+
}
826+
827+
new_feature = {
828+
'id': '91117',
829+
'key': 'new_feature',
830+
'experimentIds': ['111137'],
831+
'rolloutId': '',
832+
'variables': [
833+
{'id': '127', 'key': 'is_working', 'defaultValue': 'true', 'type': 'boolean'},
834+
{'id': '128', 'key': 'environment', 'defaultValue': 'devel', 'type': 'string'},
835+
{'id': '129', 'key': 'cost', 'defaultValue': '10.99', 'type': 'double'},
836+
{'id': '130', 'key': 'count', 'defaultValue': '999', 'type': 'integer'},
837+
{'id': '131', 'key': 'variable_without_usage', 'defaultValue': '45', 'type': 'integer'},
838+
{'id': '132', 'key': 'object', 'defaultValue': '{"test": 12}', 'type': 'string', 'subType': 'json'},
839+
{'id': '133', 'key': 'true_object', 'defaultValue': '{"true_test": 23.54}', 'type': 'json'}
840+
]
841+
}
842+
843+
config = OptimizelySpec.deep_clone(config_body)
844+
845+
config['experiments'].push(new_experiment)
846+
config['featureFlags'].push(new_feature)
847+
project_config = Optimizely::DatafileProjectConfig.new(JSON.dump(config), spy_logger, error_handler)
848+
849+
opti_config = Optimizely::OptimizelyConfig.new(project_config, spy_logger)
850+
851+
key_map = opti_config.config['experimentsMap']
852+
id_map = opti_config.send(:experiments_id_map)
853+
854+
expected_warning_message = "Duplicate experiment keys found in datafile: #{duplicate_experiment_key}"
855+
expect(spy_logger).to have_received(:log).once.with(Logger::WARN, expected_warning_message)
856+
857+
expect(key_map[duplicate_experiment_key]['id']).to eq(new_experiment[:id])
858+
expect(id_map.values.count { |exp| exp['key'] == duplicate_experiment_key }).to eq(2)
859+
end
799860
end

0 commit comments

Comments
 (0)