Skip to content

Commit

Permalink
feat: Add option to omit anonymous contexts in identify and index events
Browse files Browse the repository at this point in the history
  • Loading branch information
louis-launchdarkly committed Jun 26, 2024
1 parent e1bc04b commit c47e6e2
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 23 deletions.
11 changes: 11 additions & 0 deletions lib/ldclient-rb/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Config
# @option opts [BigSegmentsConfig] :big_segments See {#big_segments}.
# @option opts [Hash] :application See {#application}
# @option opts [String] :payload_filter_key See {#payload_filter_key}
# @option opts [Boolean] :omit_anonymous_contexts See {#omit_anonymous_contexts}
# @option hooks [Array<Interfaces::Hooks::Hook]
#
def initialize(opts = {})
Expand Down Expand Up @@ -77,6 +78,7 @@ def initialize(opts = {})
@application = LaunchDarkly::Impl::Util.validate_application_info(opts[:application] || {}, @logger)
@payload_filter_key = opts[:payload_filter_key]
@hooks = (opts[:hooks] || []).keep_if { |hook| hook.is_a? Interfaces::Hooks::Hook }
@omit_anonymous_contexts = opts.has_key?(:omit_anonymous_contexts) && opts[:omit_anonymous_contexts]
@data_source_update_sink = nil
end

Expand Down Expand Up @@ -385,6 +387,15 @@ def diagnostic_opt_out?
#
attr_reader :hooks

#
# Sets whether anonymous contexts should be omitted from index and identify events.
#
# The default value is false. Anonymous contexts will be included in index and identify events.
# @return [Boolean]
#
attr_reader :omit_anonymous_contexts


#
# The default LaunchDarkly client configuration. This configuration sets
# reasonable defaults for most users.
Expand Down
13 changes: 7 additions & 6 deletions lib/ldclient-rb/events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def initialize(sdk_key, config, client = nil, diagnostic_accumulator = nil, test
Impl::EventSender.new(sdk_key, config, client || Util.new_http_client(config.events_uri, config))

@timestamp_fn = (test_properties || {})[:timestamp_fn] || proc { Impl::Util.current_time_millis }
@omit_anonymous_contexts = config.omit_anonymous_contexts

EventDispatcher.new(@inbox, sdk_key, config, diagnostic_accumulator, event_sender)
end
Expand All @@ -167,8 +168,8 @@ def record_eval_event(
end

def record_identify_event(context)
filtered = context.without_anonymous_contexts
post_to_inbox(LaunchDarkly::Impl::IdentifyEvent.new(timestamp, filtered)) if filtered.valid?
target_context = !@omit_anonymous_contexts ? context : context.without_anonymous_contexts
post_to_inbox(LaunchDarkly::Impl::IdentifyEvent.new(timestamp, target_context)) if target_context.valid?
end

def record_custom_event(context, key, data = nil, metric_value = nil)
Expand Down Expand Up @@ -331,14 +332,14 @@ def dispatch_event(event, outbox)
private def get_indexable_context(event, &block)
return if event.context.nil?

filtered = event.context.without_anonymous_contexts
return unless filtered.valid?
context = !@config.omit_anonymous_contexts ? event.context : event.context.without_anonymous_contexts
return unless context.valid?

return if notice_context(filtered)
return if notice_context(context)
return if event.is_a?(LaunchDarkly::Impl::IdentifyEvent)
return if event.is_a?(LaunchDarkly::Impl::MigrationOpEvent)

yield filtered unless block.nil?
yield context unless block.nil?
end

#
Expand Down
8 changes: 8 additions & 0 deletions spec/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,13 @@ module LaunchDarkly
end
end
end
describe ".omit_anonymous_contexts" do
it "defaults to false" do
expect(subject.new.omit_anonymous_contexts).to eq false
end
it "can be set to true" do
expect(subject.new(omit_anonymous_contexts: true).omit_anonymous_contexts).to eq true
end
end
end
end
61 changes: 44 additions & 17 deletions spec/events_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module LaunchDarkly
let(:starting_timestamp) { 1000 }
let(:default_config_opts) { { diagnostic_opt_out: true, logger: $null_log } }
let(:default_config) { Config.new(default_config_opts) }
let(:omit_anonymous_contexts_config) { Config.new(default_config_opts.merge(omit_anonymous_contexts: true))}
let(:context) { LDContext.create({ kind: "user", key: "userkey", name: "Red" }) }
let(:anon_context) { LDContext.create({ kind: "org", key: "orgkey", name: "Organization", anonymous: true }) }

Expand All @@ -25,30 +26,39 @@ module LaunchDarkly
end
end

it "does not queue if anonymous" do
it "does queue if anonymous" do
with_processor_and_sender(default_config, starting_timestamp) do |ep, sender|
ep.record_identify_event(anon_context)

output = flush_and_get_events(ep, sender)
expect(output).to contain_exactly(eq(identify_event(default_config, anon_context)))
end
end

it "does not queue if anonymous and omit_anonymous_contexts" do
with_processor_and_sender(omit_anonymous_contexts_config, starting_timestamp) do |ep, sender|
ep.record_identify_event(anon_context)

output = flush_and_get_events(ep, sender)
expect(output).to be_nil
end
end

it "strips anonymous contexts from multi kind contexts" do
with_processor_and_sender(default_config, starting_timestamp) do |ep, sender|
it "strips anonymous contexts from multi kind contexts if omit_anonymous_contexts" do
with_processor_and_sender(omit_anonymous_contexts_config, starting_timestamp) do |ep, sender|
user = LDContext.create({ kind: "user", key: "userkey", name: "Example User", anonymous: true })
org = LDContext.create({ kind: "org", key: "orgkey", name: "Big Organization" })
device = LDContext.create({ kind: "device", key: "devicekey", name: "IoT Device", anonymous: true })

ep.record_identify_event(LDContext.create_multi([user, org, device]))

output = flush_and_get_events(ep, sender)
expect(output).to contain_exactly(eq(identify_event(default_config, org)))
expect(output).to contain_exactly(eq(identify_event(omit_anonymous_contexts_config, org)))
end
end

it "does not queue if all are anonymous" do
with_processor_and_sender(default_config, starting_timestamp) do |ep, sender|
it "does not queue if all are anonymous and omit_anonymous_contexts" do
with_processor_and_sender(omit_anonymous_contexts_config, starting_timestamp) do |ep, sender|
user = LDContext.create({ kind: "user", key: "userkey", name: "Example User", anonymous: true })
org = LDContext.create({ kind: "org", key: "orgkey", name: "Big Organization", anonymous: true })
device = LDContext.create({ kind: "device", key: "devicekey", name: "IoT Device", anonymous: true })
Expand All @@ -62,13 +72,14 @@ module LaunchDarkly
end

describe "index events" do
it "ignore single-kind anonymous context" do
it "does not ignore single-kind anonymous context" do
with_processor_and_sender(default_config, starting_timestamp) do |ep, sender|
flag = { key: "flagkey", version: 11 }
ep.record_eval_event(anon_context, 'flagkey', 11, 1, 'value', nil, nil, true)

output = flush_and_get_events(ep, sender)
expect(output).to contain_exactly(
eq(index_event(default_config, anon_context)),
eq(feature_event(default_config, flag, anon_context, 1, 'value')),
include(:kind => "summary")
)
Expand All @@ -78,16 +89,32 @@ module LaunchDarkly
end
end

it "ignore anonymous contexts from multi-kind" do
with_processor_and_sender(default_config, starting_timestamp) do |ep, sender|
it "ignore single-kind anonymous context if omit_anonymous_contexts" do
with_processor_and_sender(omit_anonymous_contexts_config, starting_timestamp) do |ep, sender|
flag = { key: "flagkey", version: 11 }
ep.record_eval_event(anon_context, 'flagkey', 11, 1, 'value', nil, nil, true)

output = flush_and_get_events(ep, sender)
expect(output).to contain_exactly(
eq(feature_event(omit_anonymous_contexts_config, flag, anon_context, 1, 'value')),
include(:kind => "summary")
)

summary = output.detect { |e| e[:kind] == "summary" }
expect(summary[:features][:flagkey][:contextKinds]).to contain_exactly("org")
end
end

it "ignore anonymous contexts from multi-kind if omit_anonymous_contexts" do
with_processor_and_sender(omit_anonymous_contexts_config, starting_timestamp) do |ep, sender|
flag = { key: "flagkey", version: 11 }
multi = LDContext.create_multi([context, anon_context])
ep.record_eval_event(multi, 'flagkey', 11, 1, 'value', nil, nil, true)

output = flush_and_get_events(ep, sender)
expect(output).to contain_exactly(
eq(index_event(default_config, context)),
eq(feature_event(default_config, flag, multi, 1, 'value')),
eq(index_event(omit_anonymous_contexts_config, context)),
eq(feature_event(omit_anonymous_contexts_config, flag, multi, 1, 'value')),
include(:kind => "summary")
)

Expand All @@ -96,16 +123,16 @@ module LaunchDarkly
end
end

it "handles mult-kind context being completely anonymous" do
with_processor_and_sender(default_config, starting_timestamp) do |ep, sender|
it "handles mult-kind context being completely anonymous if omit_anonymous_contexts" do
with_processor_and_sender(omit_anonymous_contexts_config, starting_timestamp) do |ep, sender|
flag = { key: "flagkey", version: 11 }
anon_user = LDContext.create({ kind: "user", key: "userkey", name: "User name", anonymous: true })
multi = LDContext.create_multi([anon_user, anon_context])
ep.record_eval_event(multi, 'flagkey', 11, 1, 'value', nil, nil, true)

output = flush_and_get_events(ep, sender)
expect(output).to contain_exactly(
eq(feature_event(default_config, flag, multi, 1, 'value')),
eq(feature_event(omit_anonymous_contexts_config, flag, multi, 1, 'value')),
include(:kind => "summary")
)

Expand All @@ -114,16 +141,16 @@ module LaunchDarkly
end
end

it "anonymous context does not prevent subsequent index events" do
with_processor_and_sender(default_config, starting_timestamp) do |ep, sender|
it "anonymous context does not prevent subsequent index events if omit_anonymous_contexts" do
with_processor_and_sender(omit_anonymous_contexts_config, starting_timestamp) do |ep, sender|
flag = { key: "flagkey", version: 11 }
ep.record_eval_event(anon_context, 'flagkey', 11, 1, 'value', nil, nil, false)
non_anon_context = LDContext.create({ kind: "org", key: "orgkey", name: "Organization", anonymous: false })
ep.record_eval_event(non_anon_context, 'flagkey', 11, 1, 'value', nil, nil, false)

output = flush_and_get_events(ep, sender)
expect(output).to contain_exactly(
eq(index_event(default_config, non_anon_context, starting_timestamp + 1)),
eq(index_event(omit_anonymous_contexts_config, non_anon_context, starting_timestamp + 1)),
include(:kind => "summary")
)

Expand Down

0 comments on commit c47e6e2

Please sign in to comment.