Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EEA countries account-level override #4118

Conversation

przemkaczmarek
Copy link
Collaborator

@przemkaczmarek przemkaczmarek commented Dec 20, 2024

Implements #3678

@przemkaczmarek przemkaczmarek self-assigned this Dec 20, 2024
Copy link
Contributor

@SyntaxNode SyntaxNode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bsardo Configs that can be overridden at the account level are stored in the host level default account object. However, in this case the EEA Countries list is part of a different config area. Curious on your thoughts. Should the current location be deprecated in favor of account level?

@@ -316,8 +316,17 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog

recordImpMetrics(r.BidRequestWrapper, e.me)

// Retrieve host and account-level EEA countries config
eeaCountries := SelectEEACountries(e.privacyConfig.GDPR.EEACountries, r.Account.EEACountries)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: The comment is not necessary. The inputs are clearly host and account level, but if you'd like to make that association clearer consider a revised method name.

eeaCountries := SelectEEACountries(e.privacyConfig.GDPR.EEACountries, r.Account.EEACountries)

// Create a map for efficient lookup
eeaCountriesMap := make(map[string]struct{})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The performance benefit of a map only comes into play when the map is used more than once. As it is now, this map is only referenced once in parseGDPRDefaultValue. This forces the processing of every element with the overhead of map creation. It would be more efficient to use a linear search in this instance.

Eventually, this map can live in the account config object when that caching layer is improved. Doesn't make sense there right now though.

@@ -381,3 +382,12 @@ func (ip *IPv4) Validate(errs []error) []error {
}
return errs
}

func (a *Account) ValidateEEACountries(errs []error) []error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation method is not called.

@@ -42,6 +42,7 @@ type Account struct {
DefaultBidLimit int `mapstructure:"default_bid_limit" json:"default_bid_limit"`
BidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments `mapstructure:"bidadjustments" json:"bidadjustments"`
Privacy AccountPrivacy `mapstructure:"privacy" json:"privacy"`
EEACountries []string `mapstructure:"eea_countries" json:"eea_countries"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it would be better to include this within the AccountGDPR object since it's only used for GDPR enforcement and to match the host configuration of .privacy.gdpr. eea_countries.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Let's move it down a level as suggested.

@bsardo
Copy link
Collaborator

bsardo commented Jan 9, 2025

@bsardo Configs that can be overridden at the account level are stored in the host level default account object. However, in this case the EEA Countries list is part of a different config area. Curious on your thoughts. Should the current location be deprecated in favor of account level?

I agree we should deprecate it but my thought is that this should be done as part of a major release and should involve moving all GDPR fields (e.g. the purpose configs) that can be overridden at the account level to the account defaults object. In that case, I suggest we proceed with the approach Peter has taken here for now and perhaps consider this change for 4.0 or 5.0.

@bsardo bsardo changed the title Account-level override for the list of EEA countries #3678 EEA countries account-level override Jan 30, 2025
Comment on lines 319 to 322
var accountEEACountries []string
if r.Account.GDPR.EEACountries != nil {
accountEEACountries = r.Account.GDPR.EEACountries
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need this. You can just pass r.Account.GDPR.EEACountries into SelectEEACountries as the second parameter. If it's passed in as nil that should be ok.

accountEEACountries = r.Account.GDPR.EEACountries
}

// Retrieve host and account-level EEA countries config
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment should be tweaked a bit since it doesn't retrieve both host and account level configs but rather either host or account.

@@ -584,7 +592,7 @@ func (e *exchange) parseGDPRDefaultValue(r *openrtb_ext.RequestWrapper) gdpr.Sig
if geo != nil {
// If we have a country set, and it is on the list, we assume GDPR applies if not set on the request.
// Otherwise we assume it does not apply as long as it appears "valid" (is 3 characters long).
if _, found := e.privacyConfig.GDPR.EEACountriesMap[strings.ToUpper(geo.Country)]; found {
if _, found := e.privacyConfig.GDPR.EEACountriesMap[strings.ToUpper(geo.Country)]; found || isEEACountry(geo.Country, eeaCountries) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eeaCountries contains either the account config or the host config. We need only check this selected config: if _, found := isEEACountry(geo.Country, eeaCountries) {

exchange/gdpr.go Outdated
Comment on lines 47 to 52
// If account-level configuration is provided, it takes precedence.
if len(accountEEACountries) > 0 {
return accountEEACountries
}

// Otherwise, return the host-level configuration.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO you can remove these comments; the code is self-explanatory.

exchange/gdpr.go Outdated

// SelectEEACountries selects the EEA countries based on host and account configurations.
// Account-level configuration takes precedence over the host-level configuration.
func SelectEEACountries(hostEEACountries []string, accountEEACountries []string) []string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should be private (first letter should be lower case) as it is only used in the exchange package.

exchange/gdpr.go Outdated
// Account-level configuration takes precedence over the host-level configuration.
func SelectEEACountries(hostEEACountries []string, accountEEACountries []string) []string {
// If account-level configuration is provided, it takes precedence.
if len(accountEEACountries) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than checking if there are any elements, I think we should check if accountEEACountries is defined, in which case a not nil check would make more sense here.

Comment on lines 6374 to 6376
if result != tt.expected {
t.Errorf("isEEACountry(%s, %v) = %v; want %v", tt.country, tt.eeaList, result, tt.expected)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use assert.Equal instead.

Comment on lines 6363 to 6368
{"Country in EEA", "FRA", eeaCountries, true},
{"Country in EEA lowercase", "fra", eeaCountries, true},
{"Country not in EEA", "USA", eeaCountries, false},
{"Empty country string", "", eeaCountries, false},
{"EEA list is empty", "FRA", []string{}, false},
{"EEA list is nil", "FRA", nil, false},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please explicitly specify each field name along with the value and put each field/value pair on its own line. The name value should also be short with underscores used instead of spaces. t.Run will automatically substitute underscores for spaces so we like to avoid spaces in our test case names so if a test fails we can do a quick search for the test name failure in our code base without having to do a space to underscore substitution.
For example:

{
    name:     "found_uppercase",
    country:  "FRA",
    eeaList:  eeaCountries,
    expected: true,
},

exchange/gdpr.go Outdated

// SelectEEACountries selects the EEA countries based on host and account configurations.
// Account-level configuration takes precedence over the host-level configuration.
func SelectEEACountries(hostEEACountries []string, accountEEACountries []string) []string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a simple unit test in exchange/gdpr_test.go for this function.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add an exchange package JSON integration test so that we can verify HoldAuction selects the correct EEA countries list. We can discuss this offline.

@bsardo bsardo assigned bsardo and unassigned przemkaczmarek Jan 30, 2025
Copy link
Collaborator

@bsardo bsardo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just a few test nitpicks.

expected []string
}{
{
description: "Account EEA countries provided, return account's",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Use underscores instead of spaces when separating words in test name so t.Run doesn't perform space for underscore substitutions making it easier to find failing tests in the code base via search.

expected []string
}{
{
description: "Account EEA countries provided, return account's",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I would also shorten your names removing the expected return info. That can easily be deciphered. Simply describing the inputs is recommended.

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isEEACountry(tt.country, tt.eeaList)
assert.Equal(t, tt.expected, result, "isEEACountry(%s, %v)", tt.country, tt.eeaList)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified to assert.Equal(t, tt.expected, result). If this test fails, it will provide context on which test case fails making these last parameters unnecessary.

@bsardo bsardo merged commit b335bc5 into prebid:master Feb 14, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants