diff --git a/lib/optimizely.rb b/lib/optimizely.rb index 3d787c88..f524d822 100644 --- a/lib/optimizely.rb +++ b/lib/optimizely.rb @@ -1018,13 +1018,13 @@ def send_odp_event(action:, identifiers:, type: Helpers::Constants::ODP_MANAGER_ @odp_manager.send_event(type: type, action: action, identifiers: identifiers, data: data) end - def identify_user(user_id:) + def identify_user(identifiers:) unless is_valid @logger.log(Logger::ERROR, InvalidProjectConfigError.new('identify_user').message) return end - @odp_manager.identify_user(user_id: user_id) + @odp_manager.identify_user(identifiers: identifiers) end def fetch_qualified_segments(user_id:, options: []) diff --git a/lib/optimizely/odp/odp_manager.rb b/lib/optimizely/odp/odp_manager.rb index 77e35035..25b53b1d 100644 --- a/lib/optimizely/odp/odp_manager.rb +++ b/lib/optimizely/odp/odp_manager.rb @@ -87,7 +87,7 @@ def fetch_qualified_segments(user_id:, options:) @segment_manager.fetch_qualified_segments(ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID], user_id, options) end - def identify_user(user_id:) + def identify_user(identifiers:) unless @enabled @logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (ODP disabled).') return @@ -102,10 +102,18 @@ def identify_user(user_id:) return end + valid_identifiers = identifiers.select { |_k, v| v && !v.to_s.empty? } + # Identify requires 2+ identifiers to link (e.g., vuid + fs_user_id). + # A single identifier has no cross-reference value and generates unnecessary traffic. + if valid_identifiers.length < 2 + @logger.log(Logger::DEBUG, 'ODP identify event is not dispatched (fewer than 2 valid identifiers).') + return + end + @event_manager.send_event( type: ODP_MANAGER_CONFIG[:EVENT_TYPE], action: 'identified', - identifiers: {ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID] => user_id}, + identifiers: valid_identifiers, data: {} ) end diff --git a/lib/optimizely/optimizely_user_context.rb b/lib/optimizely/optimizely_user_context.rb index 9594c591..a069a738 100644 --- a/lib/optimizely/optimizely_user_context.rb +++ b/lib/optimizely/optimizely_user_context.rb @@ -17,6 +17,7 @@ # require 'json' +require_relative 'helpers/constants' module Optimizely class OptimizelyUserContext @@ -36,7 +37,10 @@ def initialize(optimizely_client, user_id, user_attributes, identify: true) @forced_decisions = {} @qualified_segments = nil - @optimizely_client&.identify_user(user_id: user_id) if identify + if identify + identifiers = {Optimizely::Helpers::Constants::ODP_MANAGER_CONFIG[:KEY_FOR_USER_ID] => user_id} + @optimizely_client&.identify_user(identifiers: identifiers) + end end def clone diff --git a/spec/odp/odp_manager_spec.rb b/spec/odp/odp_manager_spec.rb index be9ff06f..7cc42b73 100644 --- a/spec/odp/odp_manager_spec.rb +++ b/spec/odp/odp_manager_spec.rb @@ -218,10 +218,52 @@ end describe '#identify_user' do - it 'should send event' do + it 'should send event when multiple identifiers provided' do + allow(SecureRandom).to receive(:uuid).and_return(test_uuid) + event_manager = Optimizely::OdpEventManager.new + identifiers = {user_key => user_value, 'email' => 'test@example.com'} + event = Optimizely::OdpEvent.new(type: 'fullstack', action: 'identified', identifiers: identifiers, data: {}) + expect(spy_logger).not_to receive(:log).with(Logger::ERROR, anything) + + expect(event_manager.api_manager) + .to receive(:send_odp_events) + .once + .with(api_key, api_host, [event]) + .and_return(false) + + manager = Optimizely::OdpManager.new(disable: false, event_manager: event_manager, logger: spy_logger) + manager.update_odp_config(api_key, api_host, segments_to_check) + + manager.identify_user(identifiers: identifiers) + + manager.stop! + end + + it 'should not send event when fewer than 2 valid identifiers' do + expect(spy_logger).to receive(:log).with(Logger::DEBUG, 'ODP identify event is not dispatched (fewer than 2 valid identifiers).') + + manager = Optimizely::OdpManager.new(disable: false, logger: spy_logger) + manager.update_odp_config(api_key, api_host, segments_to_check) + manager.identify_user(identifiers: {user_key => user_value}) + + manager.stop! + end + + it 'should not count empty or nil identifier values' do + expect(spy_logger).to receive(:log).with(Logger::DEBUG, 'ODP identify event is not dispatched (fewer than 2 valid identifiers).') + + manager = Optimizely::OdpManager.new(disable: false, logger: spy_logger) + manager.update_odp_config(api_key, api_host, segments_to_check) + manager.identify_user(identifiers: {user_key => user_value, 'email' => '', 'phone' => nil}) + + manager.stop! + end + + it 'should send event with all valid identifiers when some are empty' do allow(SecureRandom).to receive(:uuid).and_return(test_uuid) event_manager = Optimizely::OdpEventManager.new - event = Optimizely::OdpEvent.new(type: 'fullstack', action: 'identified', identifiers: {user_key => user_value}, data: {}) + valid_identifiers = {user_key => user_value, 'email' => 'test@example.com'} + event = Optimizely::OdpEvent.new(type: 'fullstack', action: 'identified', identifiers: valid_identifiers, data: {}) expect(spy_logger).not_to receive(:log).with(Logger::ERROR, anything) expect(event_manager.api_manager) @@ -233,7 +275,7 @@ manager = Optimizely::OdpManager.new(disable: false, event_manager: event_manager, logger: spy_logger) manager.update_odp_config(api_key, api_host, segments_to_check) - manager.identify_user(user_id: user_value) + manager.identify_user(identifiers: {user_key => user_value, 'email' => 'test@example.com', 'phone' => ''}) manager.stop! end @@ -243,7 +285,7 @@ expect(spy_logger).to receive(:log).with(Logger::DEBUG, 'ODP identify event is not dispatched (ODP disabled).') manager = Optimizely::OdpManager.new(disable: true, logger: spy_logger) - manager.identify_user(user_id: user_value) + manager.identify_user(identifiers: {user_key => user_value, 'email' => 'test@example.com'}) manager.stop! end @@ -253,7 +295,7 @@ expect(spy_logger).to receive(:log).with(Logger::DEBUG, 'ODP identify event is not dispatched (ODP not integrated).') manager = Optimizely::OdpManager.new(disable: false, logger: spy_logger) manager.update_odp_config(nil, nil, []) - manager.identify_user(user_id: user_value) + manager.identify_user(identifiers: {user_key => user_value, 'email' => 'test@example.com'}) manager.stop! end @@ -263,7 +305,7 @@ expect(spy_logger).to receive(:log).with(Logger::DEBUG, 'ODP identify event is not dispatched (datafile not ready).') manager = Optimizely::OdpManager.new(disable: false, logger: spy_logger) - manager.identify_user(user_id: user_value) + manager.identify_user(identifiers: {user_key => user_value, 'email' => 'test@example.com'}) manager.stop! end diff --git a/spec/optimizely_user_context_spec.rb b/spec/optimizely_user_context_spec.rb index 42d71065..14360c23 100644 --- a/spec/optimizely_user_context_spec.rb +++ b/spec/optimizely_user_context_spec.rb @@ -907,7 +907,7 @@ it 'should send identify event when user context created' do stub_request(:post, 'https://api.zaius.com/v3/graphql').to_return(status: 200, body: good_response_data.to_json) stub_request(:post, 'https://api.zaius.com/v3/events').to_return(status: 200) - expect(integration_project_instance.odp_manager).to receive(:identify_user).with({user_id: 'tester'}) + expect(integration_project_instance.odp_manager).to receive(:identify_user).with({identifiers: {'fs_user_id' => 'tester'}}) Optimizely::OptimizelyUserContext.new(integration_project_instance, 'tester', {}) integration_project_instance.close @@ -915,7 +915,7 @@ it 'should skip identify with decisions' do stub_request(:post, impression_log_url) - expect(integration_project_instance.odp_manager).to receive(:identify_user).with({user_id: 'tester'}) + expect(integration_project_instance.odp_manager).to receive(:identify_user).with({identifiers: {'fs_user_id' => 'tester'}}) expect(spy_logger).not_to receive(:log).with(Logger::ERROR, anything) user_context = Optimizely::OptimizelyUserContext.new(integration_project_instance, 'tester', {}) diff --git a/spec/project_spec.rb b/spec/project_spec.rb index 9abbc39f..99ffac38 100644 --- a/spec/project_spec.rb +++ b/spec/project_spec.rb @@ -226,7 +226,7 @@ class InvalidErrorHandler; end # rubocop:disable Lint/ConstantDefinitionInBlock it 'should send identify event when called with odp enabled' do project = Optimizely::Project.new(datafile: config_body_integrations_JSON, logger: spy_logger) - expect(project.odp_manager).to receive(:identify_user).with({user_id: 'tester'}) + expect(project.odp_manager).to receive(:identify_user).with({identifiers: {'fs_user_id' => 'tester'}}) project.create_user_context('tester') project.close