Skip to content

Commit b97c41e

Browse files
authored
feat: send flag decisions (#272)
1 parent dc2088d commit b97c41e

16 files changed

+306
-54
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ Gemfile.lock
1111
/tmp/
1212
*.DS_Store
1313
.ruby-version
14-
vendor/bundle
14+
vendor/bundle
15+
*.code-workspace

lib/optimizely.rb

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,10 @@ def activate(experiment_key, user_id, attributes = nil)
140140

141141
# Create and dispatch impression event
142142
experiment = config.get_experiment_from_key(experiment_key)
143-
send_impression(config, experiment, variation_key, user_id, attributes)
143+
send_impression(
144+
config, experiment, variation_key, '', experiment_key,
145+
Optimizely::DecisionService::DECISION_SOURCES['EXPERIMENT'], user_id, attributes
146+
)
144147

145148
variation_key
146149
end
@@ -316,14 +319,23 @@ def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
316319
experiment_key: decision.experiment['key'],
317320
variation_key: variation['key']
318321
}
319-
# Send event if Decision came from an experiment.
320-
send_impression(config, decision.experiment, variation['key'], user_id, attributes)
321-
else
322-
@logger.log(Logger::DEBUG,
323-
"The user '#{user_id}' is not being experimented on in feature '#{feature_flag_key}'.")
322+
# Send event if Decision came from a feature test.
323+
send_impression(
324+
config, decision.experiment, variation['key'], feature_flag_key, decision.experiment['key'], source_string, user_id, attributes
325+
)
326+
elsif decision.source == Optimizely::DecisionService::DECISION_SOURCES['ROLLOUT'] && config.send_flag_decisions
327+
send_impression(
328+
config, decision.experiment, variation['key'], feature_flag_key, decision.experiment['key'], source_string, user_id, attributes
329+
)
324330
end
325331
end
326332

333+
if decision.nil? && config.send_flag_decisions
334+
send_impression(
335+
config, nil, '', feature_flag_key, '', source_string, user_id, attributes
336+
)
337+
end
338+
327339
@notification_center.send_notifications(
328340
NotificationCenter::NOTIFICATION_TYPES[:DECISION],
329341
Helpers::Constants::DECISION_NOTIFICATION_TYPES['FEATURE'],
@@ -867,15 +879,42 @@ def validate_instantiation_options
867879
raise InvalidInputError, 'event_dispatcher'
868880
end
869881

870-
def send_impression(config, experiment, variation_key, user_id, attributes = nil)
882+
def send_impression(config, experiment, variation_key, flag_key, rule_key, rule_type, user_id, attributes = nil)
883+
if experiment.nil?
884+
experiment = {
885+
'id' => '',
886+
'key' => '',
887+
'layerId' => '',
888+
'status' => '',
889+
'variations' => [],
890+
'trafficAllocation' => [],
891+
'audienceIds' => [],
892+
'audienceConditions' => [],
893+
'forcedVariations' => {}
894+
}
895+
end
896+
871897
experiment_key = experiment['key']
872-
variation_id = config.get_variation_id_from_key(experiment_key, variation_key)
873-
user_event = UserEventFactory.create_impression_event(config, experiment, variation_id, user_id, attributes)
898+
899+
variation_id = ''
900+
variation_id = config.get_variation_id_from_key(experiment_key, variation_key) if experiment_key != ''
901+
902+
metadata = {
903+
flag_key: flag_key,
904+
rule_key: rule_key,
905+
rule_type: rule_type,
906+
variation_key: variation_key
907+
}
908+
909+
user_event = UserEventFactory.create_impression_event(config, experiment, variation_id, metadata, user_id, attributes)
874910
@event_processor.process(user_event)
875911
return unless @notification_center.notification_count(NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE]).positive?
876912

877913
@logger.log(Logger::INFO, "Activating user '#{user_id}' in experiment '#{experiment_key}'.")
878-
variation = config.get_variation_from_id(experiment_key, variation_id)
914+
915+
experiment = nil if experiment_key == ''
916+
variation = nil
917+
variation = config.get_variation_from_id(experiment_key, variation_id) unless experiment.nil?
879918
log_event = EventFactory.create_log_event(user_event, @logger)
880919
@notification_center.send_notifications(
881920
NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],

lib/optimizely/config/datafile_project_config.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class DatafileProjectConfig < ProjectConfig
4040
attr_reader :revision
4141
attr_reader :rollouts
4242
attr_reader :version
43+
attr_reader :send_flag_decisions
4344

4445
attr_reader :attribute_key_map
4546
attr_reader :audience_id_map
@@ -83,6 +84,7 @@ def initialize(datafile, logger, error_handler)
8384
@bot_filtering = config['botFiltering']
8485
@revision = config['revision']
8586
@rollouts = config.fetch('rollouts', [])
87+
@send_flag_decisions = config.fetch('sendFlagDecisions', false)
8688

8789
# Json type is represented in datafile as a subtype of string for the sake of backwards compatibility.
8890
# Converting it to a first-class json type while creating Project Config

lib/optimizely/decision_service.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class DecisionService
4040
Decision = Struct.new(:experiment, :variation, :source)
4141

4242
DECISION_SOURCES = {
43+
'EXPERIMENT' => 'experiment',
4344
'FEATURE_TEST' => 'feature-test',
4445
'ROLLOUT' => 'rollout'
4546
}.freeze

lib/optimizely/event/entity/decision.rb

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

33
#
4-
# Copyright 2019, Optimizely and contributors
4+
# Copyright 2019-2020, 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.
@@ -17,19 +17,21 @@
1717
#
1818
module Optimizely
1919
class Decision
20-
attr_reader :campaign_id, :experiment_id, :variation_id
20+
attr_reader :campaign_id, :experiment_id, :variation_id, :metadata
2121

22-
def initialize(campaign_id:, experiment_id:, variation_id:)
22+
def initialize(campaign_id:, experiment_id:, variation_id:, metadata:)
2323
@campaign_id = campaign_id
2424
@experiment_id = experiment_id
2525
@variation_id = variation_id
26+
@metadata = metadata
2627
end
2728

2829
def as_json
2930
{
3031
campaign_id: @campaign_id,
3132
experiment_id: @experiment_id,
32-
variation_id: @variation_id
33+
variation_id: @variation_id,
34+
metadata: @metadata
3335
}
3436
end
3537
end

lib/optimizely/event/entity/impression_event.rb

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

33
#
4-
# Copyright 2019, Optimizely and contributors
4+
# Copyright 2019-2020, 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.
@@ -19,7 +19,7 @@
1919
require 'optimizely/helpers/date_time_utils'
2020
module Optimizely
2121
class ImpressionEvent < UserEvent
22-
attr_reader :user_id, :experiment_layer_id, :experiment_id, :variation_id,
22+
attr_reader :user_id, :experiment_layer_id, :experiment_id, :variation_id, :metadata,
2323
:visitor_attributes, :bot_filtering
2424

2525
def initialize(
@@ -28,6 +28,7 @@ def initialize(
2828
experiment_layer_id:,
2929
experiment_id:,
3030
variation_id:,
31+
metadata:,
3132
visitor_attributes:,
3233
bot_filtering:
3334
)
@@ -38,6 +39,7 @@ def initialize(
3839
@experiment_layer_id = experiment_layer_id
3940
@experiment_id = experiment_id
4041
@variation_id = variation_id
42+
@metadata = metadata
4143
@visitor_attributes = visitor_attributes
4244
@bot_filtering = bot_filtering
4345
end

lib/optimizely/event/event_factory.rb

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

33
#
4-
# Copyright 2019, Optimizely and contributors
4+
# Copyright 2019-2020, 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.
@@ -101,10 +101,11 @@ def build_attribute_list(user_attributes, project_config)
101101
private
102102

103103
def create_impression_event_visitor(impression_event)
104-
decision = Optimizely::Decision.new(
104+
decision = Decision.new(
105105
campaign_id: impression_event.experiment_layer_id,
106106
experiment_id: impression_event.experiment_id,
107-
variation_id: impression_event.variation_id
107+
variation_id: impression_event.variation_id,
108+
metadata: impression_event.metadata
108109
)
109110

110111
snapshot_event = Optimizely::SnapshotEvent.new(

lib/optimizely/event/user_event_factory.rb

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

33
#
4-
# Copyright 2019, Optimizely and contributors
4+
# Copyright 2019-2020, 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.
@@ -22,7 +22,7 @@
2222
module Optimizely
2323
class UserEventFactory
2424
# UserEventFactory builds ImpressionEvent and ConversionEvent objects from a given user_event.
25-
def self.create_impression_event(project_config, experiment, variation_id, user_id, user_attributes)
25+
def self.create_impression_event(project_config, experiment, variation_id, metadata, user_id, user_attributes)
2626
# Create impression Event to be sent to the logging endpoint.
2727
#
2828
# project_config - Instance of ProjectConfig
@@ -42,13 +42,14 @@ def self.create_impression_event(project_config, experiment, variation_id, user_
4242
).as_json
4343

4444
visitor_attributes = Optimizely::EventFactory.build_attribute_list(user_attributes, project_config)
45-
experiment_layer_id = project_config.experiment_key_map[experiment['key']]['layerId']
45+
experiment_layer_id = experiment['layerId']
4646
Optimizely::ImpressionEvent.new(
4747
event_context: event_context,
4848
user_id: user_id,
4949
experiment_layer_id: experiment_layer_id,
5050
experiment_id: experiment['id'],
5151
variation_id: variation_id,
52+
metadata: metadata,
5253
visitor_attributes: visitor_attributes,
5354
bot_filtering: project_config.bot_filtering
5455
)

lib/optimizely/project_config.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def bot_filtering; end
4646

4747
def revision; end
4848

49+
def send_flag_decisions; end
50+
4951
def rollouts; end
5052

5153
def experiment_running?(experiment); end

spec/config/datafile_project_config_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# See the License for the specific language governing permissions and
1616
# limitations under the License.
1717
#
18+
require 'json'
1819
require 'spec_helper'
1920
require 'optimizely/config/datafile_project_config'
2021
require 'optimizely/exceptions'
@@ -50,6 +51,7 @@
5051
expect(project_config.groups).to eq(config_body['groups'])
5152
expect(project_config.project_id).to eq(config_body['projectId'])
5253
expect(project_config.revision).to eq(config_body['revision'])
54+
expect(project_config.send_flag_decisions).to eq(config_body['sendFlagDecisions'])
5355

5456
expected_attribute_key_map = {
5557
'browser_type' => config_body['attributes'][0],
@@ -769,6 +771,15 @@
769771

770772
expect(project_config.audience_id_map).to eq(expected_audience_id_map)
771773
end
774+
775+
it 'should initialize send_flag_decisions to false when not in datafile' do
776+
config_body_without_flag_decision = config_body.dup
777+
config_body_without_flag_decision.delete('sendFlagDecisions')
778+
config_body_json = JSON.dump(config_body_without_flag_decision)
779+
project_config = Optimizely::DatafileProjectConfig.new(config_body_json, logger, error_handler)
780+
781+
expect(project_config.send_flag_decisions).to eq(false)
782+
end
772783
end
773784

774785
describe '@logger' do

0 commit comments

Comments
 (0)