Skip to content

Commit 9d0de5f

Browse files
rashidspaliabbasrizvi
authored andcommitted
feat(decision-listener): Incorporated new decision notification listener changes. (#166)
1 parent 9250847 commit 9d0de5f

8 files changed

+186
-124
lines changed

lib/optimizely.rb

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ def get_variation(experiment_key, user_id, attributes = nil)
137137
}, @logger, Logger::ERROR
138138
)
139139

140+
experiment = @config.get_experiment_from_key(experiment_key)
141+
return nil if experiment.nil?
142+
140143
unless user_inputs_valid?(attributes)
141144
@logger.log(Logger::INFO, "Not activating user '#{user_id}.")
142145
return nil
@@ -145,10 +148,14 @@ def get_variation(experiment_key, user_id, attributes = nil)
145148
variation_id = @decision_service.get_variation(experiment_key, user_id, attributes)
146149
variation = @config.get_variation_from_id(experiment_key, variation_id) unless variation_id.nil?
147150
variation_key = variation['key'] if variation
148-
151+
decision_notification_type = if @config.feature_experiment?(experiment['id'])
152+
Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE_TEST']
153+
else
154+
Helpers::Constants::DECISION_NOTIFICATION_TYPES['AB_TEST']
155+
end
149156
@notification_center.send_notifications(
150157
NotificationCenter::NOTIFICATION_TYPES[:DECISION],
151-
Helpers::Constants::DECISION_INFO_TYPES['EXPERIMENT'], user_id, (attributes || {}),
158+
decision_notification_type, user_id, (attributes || {}),
152159
experiment_key: experiment_key,
153160
variation_key: variation_key
154161
)
@@ -264,14 +271,16 @@ def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
264271
decision = @decision_service.get_variation_for_feature(feature_flag, user_id, attributes)
265272

266273
feature_enabled = false
267-
source_string = Optimizely::DecisionService::DECISION_SOURCE_ROLLOUT
274+
source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
268275
if decision.is_a?(Optimizely::DecisionService::Decision)
269276
variation = decision['variation']
270277
feature_enabled = variation['featureEnabled']
271-
if decision.source == Optimizely::DecisionService::DECISION_SOURCE_EXPERIMENT
272-
source_string = Optimizely::DecisionService::DECISION_SOURCE_EXPERIMENT
273-
experiment_key = decision.experiment['key']
274-
variation_key = variation['key']
278+
if decision.source == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
279+
source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
280+
source_info = {
281+
experiment_key: decision.experiment['key'],
282+
variation_key: variation['key']
283+
}
275284
# Send event if Decision came from an experiment.
276285
send_impression(decision.experiment, variation['key'], user_id, attributes)
277286
else
@@ -282,13 +291,12 @@ def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
282291

283292
@notification_center.send_notifications(
284293
NotificationCenter::NOTIFICATION_TYPES[:DECISION],
285-
Helpers::Constants::DECISION_INFO_TYPES['FEATURE'],
294+
Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE'],
286295
user_id, (attributes || {}),
287296
feature_key: feature_flag_key,
288297
feature_enabled: feature_enabled,
289-
source: source_string.upcase,
290-
source_experiment_key: experiment_key,
291-
source_variation_key: variation_key
298+
source: source_string,
299+
source_info: source_info || {}
292300
)
293301

294302
if feature_enabled == true
@@ -481,22 +489,23 @@ def get_feature_variable_for_type(feature_flag_key, variable_key, variable_type,
481489
return nil if variable.nil?
482490

483491
feature_enabled = false
484-
485492
# Returns nil if type differs
486493
if variable['type'] != variable_type
487494
@logger.log(Logger::WARN,
488495
"Requested variable as type '#{variable_type}' but variable '#{variable_key}' is of type '#{variable['type']}'.")
489496
return nil
490497
else
491-
source_string = Optimizely::DecisionService::DECISION_SOURCE_ROLLOUT
498+
source_string = Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
492499
decision = @decision_service.get_variation_for_feature(feature_flag, user_id, attributes)
493500
variable_value = variable['defaultValue']
494501
if decision
495502
variation = decision['variation']
496-
if decision['source'] == Optimizely::DecisionService::DECISION_SOURCE_EXPERIMENT
497-
experiment_key = decision.experiment['key']
498-
variation_key = variation['key']
499-
source_string = Optimizely::DecisionService::DECISION_SOURCE_EXPERIMENT
503+
if decision['source'] == Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
504+
source_info = {
505+
experiment_key: decision.experiment['key'],
506+
variation_key: variation['key']
507+
}
508+
source_string = Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
500509
end
501510
feature_enabled = variation['featureEnabled']
502511
if feature_enabled == true
@@ -524,15 +533,14 @@ def get_feature_variable_for_type(feature_flag_key, variable_key, variable_type,
524533

525534
@notification_center.send_notifications(
526535
NotificationCenter::NOTIFICATION_TYPES[:DECISION],
527-
Helpers::Constants::DECISION_INFO_TYPES['FEATURE_VARIABLE'], user_id, (attributes || {}),
536+
Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE_VARIABLE'], user_id, (attributes || {}),
528537
feature_key: feature_flag_key,
529538
feature_enabled: feature_enabled,
539+
source: source_string,
530540
variable_key: variable_key,
531541
variable_type: variable_type,
532542
variable_value: variable_value,
533-
source: source_string.upcase,
534-
source_experiment_key: experiment_key,
535-
source_variation_key: variation_key
543+
source_info: source_info || {}
536544
)
537545

538546
variable_value

lib/optimizely/decision_service.rb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2017-2018, Optimizely and contributors
4+
# Copyright 2017-2019, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -35,8 +35,11 @@ class DecisionService
3535
attr_reader :config
3636

3737
Decision = Struct.new(:experiment, :variation, :source)
38-
DECISION_SOURCE_EXPERIMENT = 'experiment'
39-
DECISION_SOURCE_ROLLOUT = 'rollout'
38+
39+
DECISION_SOURCES = {
40+
'FEATURE_TEST' => 'feature-test',
41+
'ROLLOUT' => 'rollout'
42+
}.freeze
4043

4144
def initialize(config, user_profile_service = nil)
4245
@config = config
@@ -172,7 +175,7 @@ def get_variation_for_feature_experiment(feature_flag, user_id, attributes = nil
172175
Logger::INFO,
173176
"The user '#{user_id}' is bucketed into experiment '#{experiment_key}' of feature '#{feature_flag_key}'."
174177
)
175-
return Decision.new(experiment, variation, DECISION_SOURCE_EXPERIMENT)
178+
return Decision.new(experiment, variation, DECISION_SOURCES['FEATURE_TEST'])
176179
end
177180

178181
@config.logger.log(
@@ -236,7 +239,7 @@ def get_variation_for_feature_rollout(feature_flag, user_id, attributes = nil)
236239

237240
# Evaluate if user satisfies the traffic allocation for this rollout rule
238241
variation = @bucketer.bucket(rollout_rule, bucketing_id, user_id)
239-
return Decision.new(rollout_rule, variation, DECISION_SOURCE_ROLLOUT) unless variation.nil?
242+
return Decision.new(rollout_rule, variation, DECISION_SOURCES['ROLLOUT']) unless variation.nil?
240243

241244
break
242245
end
@@ -255,7 +258,7 @@ def get_variation_for_feature_rollout(feature_flag, user_id, attributes = nil)
255258
return nil
256259
end
257260
variation = @bucketer.bucket(everyone_else_experiment, bucketing_id, user_id)
258-
return Decision.new(everyone_else_experiment, variation, DECISION_SOURCE_ROLLOUT) unless variation.nil?
261+
return Decision.new(everyone_else_experiment, variation, DECISION_SOURCES['ROLLOUT']) unless variation.nil?
259262

260263
nil
261264
end

lib/optimizely/helpers/constants.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,10 +353,11 @@ module Constants
353353
'to upgrade to a newer release of the Optimizely SDK.'
354354
}.freeze
355355

356-
DECISION_INFO_TYPES = {
357-
'EXPERIMENT' => 'experiment',
356+
DECISION_NOTIFICATION_TYPES = {
357+
'AB_TEST' => 'ab-test',
358358
'FEATURE' => 'feature',
359-
'FEATURE_VARIABLE' => 'feature_variable'
359+
'FEATURE_TEST' => 'feature-test',
360+
'FEATURE_VARIABLE' => 'feature-variable'
360361
}.freeze
361362
end
362363
end

lib/optimizely/project_config.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class ProjectConfig
4747
attr_reader :attribute_key_map
4848
attr_reader :audience_id_map
4949
attr_reader :event_key_map
50+
attr_reader :experiment_feature_map
5051
attr_reader :experiment_id_map
5152
attr_reader :experiment_key_map
5253
attr_reader :feature_flag_key_map
@@ -140,9 +141,13 @@ def initialize(datafile, logger, error_handler)
140141
@variation_key_map[key] = generate_key_map(variations, 'key')
141142
end
142143
@feature_flag_key_map = generate_key_map(@feature_flags, 'key')
144+
@experiment_feature_map = {}
143145
@feature_variable_key_map = {}
144146
@feature_flag_key_map.each do |key, feature_flag|
145147
@feature_variable_key_map[key] = generate_key_map(feature_flag['variables'], 'key')
148+
feature_flag['experimentIds'].each do |experiment_id|
149+
@experiment_feature_map[experiment_id] = [feature_flag['id']]
150+
end
146151
end
147152
end
148153

@@ -451,6 +456,16 @@ def get_rollout_from_id(rollout_id)
451456
nil
452457
end
453458

459+
def feature_experiment?(experiment_id)
460+
# Determines if given experiment is a feature test.
461+
#
462+
# experiment_id - String experiment ID
463+
#
464+
# Returns true if experiment belongs to any feature,
465+
# false otherwise.
466+
@experiment_feature_map.key?(experiment_id)
467+
end
468+
454469
private
455470

456471
def generate_key_map(array, key)

spec/decision_service_spec.rb

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2017-2018, Optimizely and contributors
4+
# Copyright 2017-2019, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -388,7 +388,7 @@
388388
expected_decision = Optimizely::DecisionService::Decision.new(
389389
config.experiment_key_map['test_experiment_multivariate'],
390390
config.variation_id_map['test_experiment_multivariate']['122231'],
391-
Optimizely::DecisionService::DECISION_SOURCE_EXPERIMENT
391+
Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
392392
)
393393
expect(decision_service.get_variation_for_feature_experiment(feature_flag, 'user_1', user_attributes)).to eq(expected_decision)
394394

@@ -408,18 +408,18 @@
408408
expected_decision = Optimizely::DecisionService::Decision.new(
409409
mutex_exp,
410410
variation,
411-
Optimizely::DecisionService::DECISION_SOURCE_EXPERIMENT
411+
Optimizely::DecisionService::DECISION_SOURCES['FEATURE_TEST']
412412
)
413413
allow(decision_service).to receive(:get_variation)
414414
.and_return(variation['id'])
415415
end
416416

417417
it 'should return the variation the user is bucketed into' do
418-
feature_flag = config.feature_flag_key_map['boolean_feature']
418+
feature_flag = config.feature_flag_key_map['mutex_group_feature']
419419
expect(decision_service.get_variation_for_feature_experiment(feature_flag, user_id, user_attributes)).to eq(expected_decision)
420420

421421
expect(spy_logger).to have_received(:log).once
422-
.with(Logger::INFO, "The user 'user_1' is bucketed into experiment 'group1_exp1' of feature 'boolean_feature'.")
422+
.with(Logger::INFO, "The user 'user_1' is bucketed into experiment 'group1_exp1' of feature 'mutex_group_feature'.")
423423
end
424424
end
425425

@@ -436,11 +436,11 @@
436436
end
437437

438438
it 'should return nil and log a message' do
439-
feature_flag = config.feature_flag_key_map['boolean_feature']
439+
feature_flag = config.feature_flag_key_map['mutex_group_feature']
440440
expect(decision_service.get_variation_for_feature_experiment(feature_flag, user_id, user_attributes)).to eq(nil)
441441

442442
expect(spy_logger).to have_received(:log).once
443-
.with(Logger::INFO, "The user 'user_1' is not bucketed into any of the experiments on the feature 'boolean_feature'.")
443+
.with(Logger::INFO, "The user 'user_1' is not bucketed into any of the experiments on the feature 'mutex_group_feature'.")
444444
end
445445
end
446446
end
@@ -487,7 +487,7 @@
487487
feature_flag = config.feature_flag_key_map['boolean_single_variable_feature']
488488
rollout_experiment = config.rollout_id_map[feature_flag['rolloutId']]['experiments'][0]
489489
variation = rollout_experiment['variations'][0]
490-
expected_decision = Optimizely::DecisionService::Decision.new(rollout_experiment, variation, Optimizely::DecisionService::DECISION_SOURCE_ROLLOUT)
490+
expected_decision = Optimizely::DecisionService::Decision.new(rollout_experiment, variation, Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT'])
491491
allow(Optimizely::Audience).to receive(:user_in_experiment?).and_return(true)
492492
allow(decision_service.bucketer).to receive(:bucket)
493493
.with(rollout_experiment, user_id, user_id)
@@ -527,7 +527,7 @@
527527
rollout = config.rollout_id_map[feature_flag['rolloutId']]
528528
everyone_else_experiment = rollout['experiments'][2]
529529
variation = everyone_else_experiment['variations'][0]
530-
expected_decision = Optimizely::DecisionService::Decision.new(everyone_else_experiment, variation, Optimizely::DecisionService::DECISION_SOURCE_ROLLOUT)
530+
expected_decision = Optimizely::DecisionService::Decision.new(everyone_else_experiment, variation, Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT'])
531531
allow(Optimizely::Audience).to receive(:user_in_experiment?).and_return(true)
532532
allow(decision_service.bucketer).to receive(:bucket)
533533
.with(rollout['experiments'][0], user_id, user_id)
@@ -554,7 +554,7 @@
554554
rollout = config.rollout_id_map[feature_flag['rolloutId']]
555555
everyone_else_experiment = rollout['experiments'][2]
556556
variation = everyone_else_experiment['variations'][0]
557-
expected_decision = Optimizely::DecisionService::Decision.new(everyone_else_experiment, variation, Optimizely::DecisionService::DECISION_SOURCE_ROLLOUT)
557+
expected_decision = Optimizely::DecisionService::Decision.new(everyone_else_experiment, variation, Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT'])
558558
allow(Optimizely::Audience).to receive(:user_in_experiment?).and_return(false)
559559

560560
allow(Optimizely::Audience).to receive(:user_in_experiment?)
@@ -652,7 +652,7 @@
652652
expected_decision = Optimizely::DecisionService::Decision.new(
653653
nil,
654654
variation,
655-
Optimizely::DecisionService::DECISION_SOURCE_ROLLOUT
655+
Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT']
656656
)
657657
allow(decision_service).to receive(:get_variation_for_feature_experiment).and_return(nil)
658658
allow(decision_service).to receive(:get_variation_for_feature_rollout).and_return(expected_decision)

spec/project_config_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@
5959
'test_event_not_running' => config_body['events'][3]
6060
}
6161

62+
expected_experiment_feature_map = {
63+
'122227' => [config_body['featureFlags'][0]['id']],
64+
'133331' => [config_body['featureFlags'][6]['id']],
65+
'133332' => [config_body['featureFlags'][6]['id']],
66+
'122238' => [config_body['featureFlags'][1]['id']],
67+
'122241' => [config_body['featureFlags'][2]['id']],
68+
'122235' => [config_body['featureFlags'][4]['id']],
69+
'122230' => [config_body['featureFlags'][5]['id']]
70+
}
71+
6272
expected_experiment_key_map = {
6373
'test_experiment' => config_body['experiments'][0],
6474
'test_experiment_not_started' => config_body['experiments'][1],
@@ -650,6 +660,7 @@
650660
expect(project_config.attribute_key_map).to eq(expected_attribute_key_map)
651661
expect(project_config.audience_id_map).to eq(expected_audience_id_map)
652662
expect(project_config.event_key_map).to eq(expected_event_key_map)
663+
expect(project_config.experiment_feature_map).to eq(expected_experiment_feature_map)
653664
expect(project_config.experiment_key_map).to eq(expected_experiment_key_map)
654665
expect(project_config.feature_flag_key_map).to eq(expected_feature_flag_key_map)
655666
expect(project_config.feature_variable_key_map).to eq(expected_feature_variable_key_map)
@@ -1069,4 +1080,18 @@
10691080
expect(config.get_attribute_id('$opt_user_agent')).to eq('$opt_user_agent')
10701081
end
10711082
end
1083+
1084+
describe '#feature_experiment' do
1085+
let(:config) { Optimizely::ProjectConfig.new(config_body_JSON, logger, error_handler) }
1086+
1087+
it 'should return true if the experiment is a feature test' do
1088+
experiment = config.get_experiment_from_key('test_experiment_double_feature')
1089+
expect(config.feature_experiment?(experiment['id'])).to eq(true)
1090+
end
1091+
1092+
it 'should return false if the experiment is not a feature test' do
1093+
experiment = config.get_experiment_from_key('test_experiment')
1094+
expect(config.feature_experiment?(experiment['id'])).to eq(false)
1095+
end
1096+
end
10721097
end

0 commit comments

Comments
 (0)