Skip to content

Commit 540ee91

Browse files
authored
Remove support for secondary attribute (#213)
As decided in the [spec], we are removing the special behavior of the secondary attribute. Going forward, secondary will be treated like any other attribute, and will no longer be included when determining the bucket for a context. [spec]: https://launchdarkly.atlassian.net/wiki/spaces/ENG/pages/2165212563/Consistent+and+Transparent+Rollout+Behavior+Unifying+Percent+Rollout+and+Traffic+Allocation
1 parent a500be9 commit 540ee91

File tree

7 files changed

+15
-52
lines changed

7 files changed

+15
-52
lines changed

Makefile

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ TEMP_TEST_OUTPUT=/tmp/contract-test-service.log
77
# - "events": These test suites will be unavailable until more of the U2C implementation is done.
88
TEST_HARNESS_PARAMS := $(TEST_HARNESS_PARAMS) \
99
-skip 'evaluation/bucketing/bucket by non-key attribute/in rollouts/string value/complex attribute reference' \
10-
-skip 'evaluation/bucketing/secondary' \
1110
-skip 'evaluation/parameterized/attribute references' \
1211
-skip 'evaluation/parameterized/bad attribute reference errors' \
1312
-skip 'evaluation/parameterized/prerequisites' \

lib/ldclient-rb/context.rb

+6-13
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,16 @@ class LDContext
3636
# @param kind [String, nil]
3737
# @param name [String, nil]
3838
# @param anonymous [Boolean, nil]
39-
# @param secondary [String, nil]
4039
# @param attributes [Hash, nil]
4140
# @param private_attributes [Array<String>, nil]
4241
# @param error [String, nil]
4342
# @param contexts [Array<LDContext>, nil]
4443
#
45-
def initialize(key, kind, name = nil, anonymous = nil, secondary = nil, attributes = nil, private_attributes = nil, error = nil, contexts = nil)
44+
def initialize(key, kind, name = nil, anonymous = nil, attributes = nil, private_attributes = nil, error = nil, contexts = nil)
4645
@key = key
4746
@kind = kind
4847
@name = name
4948
@anonymous = anonymous || false
50-
@secondary = secondary
5149
@attributes = attributes
5250
@private_attributes = private_attributes
5351
@error = error
@@ -235,8 +233,6 @@ def individual_context(kind)
235233
@name
236234
when :anonymous
237235
@anonymous
238-
when :secondary
239-
@secondary
240236
else
241237
@attributes&.fetch(name, nil)
242238
end
@@ -322,15 +318,15 @@ def self.create_multi(contexts)
322318

323319
return contexts[0] if contexts.length == 1
324320

325-
new(nil, "multi", nil, false, nil, nil, nil, nil, contexts)
321+
new(nil, "multi", nil, false, nil, nil, nil, contexts)
326322
end
327323

328324
#
329325
# @param error [String]
330326
# @return [LDContext]
331327
#
332328
private_class_method def self.create_invalid_context(error)
333-
new(nil, nil, nil, false, nil, nil, nil, error)
329+
new(nil, nil, nil, false, nil, nil, error)
334330
end
335331

336332
#
@@ -376,7 +372,7 @@ def self.create_multi(contexts)
376372
return create_invalid_context("The provided private attributes are not an array")
377373
end
378374

379-
new(key.to_s, KIND_DEFAULT, name, anonymous, data[:secondary], attributes, private_attributes)
375+
new(key.to_s, KIND_DEFAULT, name, anonymous, attributes, private_attributes)
380376
end
381377

382378
#
@@ -416,21 +412,18 @@ def self.create_multi(contexts)
416412

417413
# We only need to create an attribute hash if there are keys set outside
418414
# of the ones we store in dedicated instance variables.
419-
#
420-
# :secondary is not a supported top level key in the new schema.
421-
# However, someone could still include it so we need to ignore it.
422415
attributes = nil
423416
data.each do |k, v|
424417
case k
425-
when :kind, :key, :name, :anonymous, :secondary, :_meta
418+
when :kind, :key, :name, :anonymous, :_meta
426419
next
427420
else
428421
attributes ||= {}
429422
attributes[k] = v.clone
430423
end
431424
end
432425

433-
new(key.to_s, kind, name, anonymous, meta[:secondary], attributes, private_attributes)
426+
new(key.to_s, kind, name, anonymous, attributes, private_attributes)
434427
end
435428
end
436429
end

lib/ldclient-rb/events.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def stop
6262
end
6363

6464
MAX_FLUSH_WORKERS = 5
65-
USER_ATTRS_TO_STRINGIFY_FOR_EVENTS = [ :key, :secondary, :ip, :country, :email, :firstName, :lastName,
65+
USER_ATTRS_TO_STRINGIFY_FOR_EVENTS = [ :key, :ip, :country, :email, :firstName, :lastName,
6666
:avatar, :name ]
6767

6868
private_constant :MAX_FLUSH_WORKERS

lib/ldclient-rb/impl/evaluator_bucketing.rb

-4
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,6 @@ def self.bucket_context(context, context_kind, key, bucket_by, salt, seed)
6060
id_hash = bucketable_string_value(context_value)
6161
return 0.0 if id_hash.nil?
6262

63-
if matched_context.get_value(:secondary)
64-
id_hash += "." + matched_context.get_value(:secondary).to_s
65-
end
66-
6763
if seed
6864
hash_key = "%d.%s" % [seed, id_hash]
6965
else

spec/context_spec.rb

+3-17
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,16 @@
6767
it "overwrite custom properties with built-ins when collisons occur" do
6868
context = {
6969
key: "user-key",
70-
secondary: "secondary",
70+
ip: "192.168.1.1",
7171
avatar: "avatar",
7272
custom: {
73-
secondary: "custom secondary",
73+
ip: "127.0.0.1",
7474
avatar: "custom avatar",
7575
},
7676
}
7777

7878
result = subject.create(context)
79-
expect(result.get_value(:secondary)).to eq("secondary")
79+
expect(result.get_value(:ip)).to eq("192.168.1.1")
8080
expect(result.get_value(:avatar)).to eq("avatar")
8181
end
8282
end
@@ -136,20 +136,6 @@
136136
}
137137
expect(subject.create(context).valid?).to be false
138138
end
139-
140-
it "overwrite secondary property if also specified at top level" do
141-
context = {
142-
key: "user-key",
143-
kind: "user",
144-
secondary: "invalid secondary",
145-
_meta: {
146-
secondary: "real secondary",
147-
},
148-
}
149-
150-
result = subject.create(context)
151-
expect(result.get_value(:secondary)).to eq("real secondary")
152-
end
153139
end
154140

155141
describe "multi-kind contexts" do

spec/events_spec.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
let(:default_config) { LaunchDarkly::Config.new(default_config_opts) }
1414
let(:user) { { key: "userkey", name: "Red" } }
1515
let(:filtered_user) { { key: "userkey", privateAttrs: [ "name" ] } }
16-
let(:numeric_user) { { key: 1, secondary: 2, ip: 3, country: 4, email: 5, firstName: 6, lastName: 7,
16+
let(:numeric_user) { { key: 1, ip: 3, country: 4, email: 5, firstName: 6, lastName: 7,
1717
avatar: 8, name: 9, anonymous: false, custom: { age: 99 } } }
18-
let(:stringified_numeric_user) { { key: '1', secondary: '2', ip: '3', country: '4', email: '5', firstName: '6',
18+
let(:stringified_numeric_user) { { key: '1', ip: '3', country: '4', email: '5', firstName: '6',
1919
lastName: '7', avatar: '8', name: '9', anonymous: false, custom: { age: 99 } } }
2020

2121
def with_processor_and_sender(config)

spec/impl/evaluator_rule_spec.rb

+3-14
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,6 @@ module Impl
8181
expect(result.detail.value).to eq(true)
8282
end
8383

84-
it "coerces secondary key to a string for evaluation" do
85-
# We can't really verify that the rollout calculation works correctly, but we can at least
86-
# make sure it doesn't error out if there's a non-string secondary value (ch35189)
87-
rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
88-
rollout: { salt: '', variations: [ { weight: 100000, variation: 1 } ] } }
89-
flag = factory.boolean_flag_with_rules([rule])
90-
context = LDContext.create({ key: "userkey", secondary: 999 })
91-
result = basic_evaluator.evaluate(flag, context)
92-
expect(result.detail.reason).to eq(EvaluationReason::rule_match(0, 'ruleid'))
93-
end
94-
9584
describe "rule experiment/rollout behavior" do
9685
it "evaluates rollout for rule" do
9786
rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
@@ -122,7 +111,7 @@ module Impl
122111
rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
123112
rollout: { kind: 'experiment', variations: [ { weight: 100000, variation: 1, untracked: false } ] } }
124113
flag = factory.boolean_flag_with_rules([rule])
125-
context = LDContext.create({ key: "userkey", secondary: 999 })
114+
context = LDContext.create({ key: "userkey" })
126115
result = basic_evaluator.evaluate(flag, context)
127116
expect(result.detail.reason.to_json).to include('"inExperiment":true')
128117
expect(result.detail.reason.in_experiment).to eq(true)
@@ -132,7 +121,7 @@ module Impl
132121
rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
133122
rollout: { kind: 'rollout', variations: [ { weight: 100000, variation: 1, untracked: false } ] } }
134123
flag = factory.boolean_flag_with_rules([rule])
135-
context = LDContext.create({ key: "userkey", secondary: 999 })
124+
context = LDContext.create({ key: "userkey" })
136125
result = basic_evaluator.evaluate(flag, context)
137126
expect(result.detail.reason.to_json).to_not include('"inExperiment":true')
138127
expect(result.detail.reason.in_experiment).to eq(nil)
@@ -142,7 +131,7 @@ module Impl
142131
rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
143132
rollout: { kind: 'experiment', variations: [ { weight: 100000, variation: 1, untracked: true } ] } }
144133
flag = factory.boolean_flag_with_rules([rule])
145-
context = LDContext.create({ key: "userkey", secondary: 999 })
134+
context = LDContext.create({ key: "userkey" })
146135
result = basic_evaluator.evaluate(flag, context)
147136
expect(result.detail.reason.to_json).to_not include('"inExperiment":true')
148137
expect(result.detail.reason.in_experiment).to eq(nil)

0 commit comments

Comments
 (0)