-
Notifications
You must be signed in to change notification settings - Fork 769
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
Adserver targeting #2593
Adserver targeting #2593
Conversation
b9cbc78
to
6c94e28
Compare
6c94e28
to
ea03cb9
Compare
// path points to value in response, not seat or ext | ||
// bidder response has limited number of properties | ||
// instead of unmarshal the whole response with seats and bids | ||
// try to find data by field name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move comments like this up a level on getValueFromResp
directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, moved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really what I was thinking, but it could work too. I meant to suggest something like this:
// getValueFromResp optimizes retrieval of paths for a bid response (not a seat or ext)
func getValueFromResp(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh ok, modified
adservertargeting/respcache.go
Outdated
func (brh *respCache) GetRespData(bidderResp *openrtb2.BidResponse, field string) (string, error) { | ||
if len(brh.bidRespData) == 0 { | ||
// this code should be modified if there are changes in Op[enRtb format | ||
// it's up to date with OpenRTB 2.5 and 2.6 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment will not be adhered to. You should add a test to fail when the assumptions made by this function change. I can reach out separately to help construct a test like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a comment for now. Please reach me out to discuss how to create this test, it would be good to have it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent update! This is a lot easier to follow and appears to match the spec. I would like to see end to end tests to ensure this is hooked into the main auction flow correctly and to provide examples on the feature's usage.
The only feature from the spec I didn't see represented is:
If the value points to a JSON path that is an object or array, there should be a warning in debug mode
Is that already accounted for? .. and if so, where?
exchange/exchange.go
Outdated
@@ -425,6 +427,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * | |||
|
|||
// Build the response | |||
bidResponse, err := e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequestWrapper.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs) | |||
|
|||
bidResponse = adservertargeting.ProcessAdServerTargeting(r.BidRequestWrapper, r.ResolvedBidRequest, bidResponse, r.QueryParams, bidResponseExt, r.Account.TruncateTargetAttribute) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "ad server targeting" information from the package name is repeated in the function call. To avoid, consider renaming ProcessAdServerTargeting
to Apply
, which then produces the simplified code:
bidResponse = adservertargeting.Appy(r.BidRequestWrapper, r.ResolvedBidRequest, bidResponse, r.QueryParams, bidResponseExt, r.Account.TruncateTargetAttribute)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, renamed!
@@ -1711,8 +1711,6 @@ func TestBidResponseCurrency(t *testing.T) { | |||
ID: "some-request-id", | |||
SeatBid: sampleSeatBid, | |||
Cur: "USD", | |||
Ext: json.RawMessage(`{"responsetimemillis":{"appnexus":5},"tmaxrequest":500} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you moved the call to encodeBidResponseExt
. I assume these tests are only in scope of the old location. Is there test coverage of encodeBidResponseExt
in its new location?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There were no tests for encodeBidResponseExt
. It was tested together with buildBidResponse
function.
Func encodeBidResponseExt
is now 100% covered by many other exchange tests including json tests. Do you want me to add specific test for encodeBidResponseExt
?
openrtb_ext/request.go
Outdated
@@ -71,6 +71,15 @@ type ExtRequestPrebid struct { | |||
// - basic: excludes debugmessages and analytic_tags from output | |||
// any other value or an empty string disables trace output at all. | |||
Trace string `json:"trace,omitempty"` | |||
|
|||
// | |||
AdServerTargeting []AdServerTargeting `json:"adservertargeting,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No comment is necessary here IMHO. Feel free to remove the stub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest a slight renaming, to avoid thing = list of things. Either AdServerTargeting []AdServerTarget
, AdServerTargeting []AdServerTargetRule
, or AdserverTargets []AdServerTargeting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed to AdServerTargeting []AdServerTarget
truncateTargetAttribute *int) *openrtb2.BidResponse { | ||
|
||
adServerTargeting, warnings := CollectAdServerTargeting(reqWrapper, resolvedRequest, queryParams) | ||
response, warnings = ResolveAdServerTargeting(adServerTargeting, response, warnings, truncateTargetAttribute) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The functions CollectAdServerTargeting
and ResolveAdServerTargeting
are no longer used outside of this package. You're welcomed to simplify their names and and make them package scoped. Perhaps collect
and resolve
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes!
return response | ||
} | ||
|
||
func CollectAdServerTargeting( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment to help the reader understand what this function is doing. Even if you change it to package scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
@@ -0,0 +1,104 @@ | |||
package adservertargeting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The term "processor" is not descriptive of the functionality IMHO. How about something like "requestlookup"? .. or just a description which better translates to "looks up and returns a value".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
return nil, err | ||
} | ||
return reqExtPrebid.AdServerTargeting, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding line breaks between the error checking:
reqExt, err := reqWrapper.GetRequestExt()
if err != nil {
return nil, err
}
reqExtPrebid := reqExt.GetPrebid()
if reqExtPrebid == nil {
return nil, err
}
return reqExtPrebid.AdServerTargeting, nil
const MaxKeyLength = 20 | ||
|
||
func processRequestTargetingData(adServerTargetingData *adServerTargetingData, targetingData map[string]string, bidImpId string) { | ||
if len(adServerTargetingData.RequestTargetingData) == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The length check isn't necessary. Keep in mind that Go's range loop gracefully handles nil or empty lists such that no optimization is necessary.
warnings []openrtb_ext.ExtBidderMessage) { | ||
if len(adServerTargetingData.ResponseTargetingData) == 0 { | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same deal here. No need for the length check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, removed.
|
||
} | ||
targetingData[key] = value | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO it might be easier to read the method by moving the parameters to a single line. I've also noticed there is no return values which increases the number of arguments. How do you feel about:
func getValueFromSeatBidBid(path string, targetingData map[string]string, bidsHolder bidsCache, bidderName string, bid openrtb2.Bid) error {
bidBytes, err := bidsHolder.GetBid(bidderName, bid.ID, bid)
if err != nil {
return createWarning(err.Error())
}
path = strings.TrimPrefix(path, "seatbid.bid.")
value, err := splitAndGet(path, bidBytes, pathDelimiter)
if err != nil {
return createWarning(err.Error())
}
return value
}
where the call site now looks like:
if v, err := getValueFromSeatBidBid(path, targetingData, bidsHolder, bidderName, bid); err == nil {
targetingData[key] = v
} else {
warnings = append(warnings, err)
}
Such that the responsibility of setting the response and collecting errors is moved up a level. Same comment applies to the other getValue methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great point! How I didn't see it while coding (I wanted to hide all those appends and set to map statements)... It's even better, error check and value set is only required once for all "getValue" functions at the end of processResponseTargetingData
# Conflicts: # errortypes/code.go # openrtb_ext/bid.go # openrtb_ext/request.go
Need to add end to end test. |
…added end to end unit tests
warnings []openrtb_ext.ExtBidderMessage, | ||
truncateTargetAttribute *int) (*openrtb2.BidResponse, []openrtb_ext.ExtBidderMessage) { | ||
if adServerTargetingData == nil { | ||
return response, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to cover this line with a test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I realized this will never be the case and removed the check.
} | ||
|
||
// ad server targeting data will go to seatbid[].bid[].ext.prebid.targeting | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: can this blank line be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the entire comment. I don't think it's needed.
adservertargeting/reqcache.go
Outdated
impsData []json.RawMessage | ||
} | ||
|
||
func (dh *reqImpCache) GetReqJson() []byte { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the variable dh
get a more descriptive name? Is it supposed to represent dataHolder
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
adservertargeting/reqcache.go
Outdated
impToBid := make(map[string][]byte, 0) | ||
bdh.bids[bidderName] = impToBid | ||
} | ||
_, biddExists := bdh.bids[bidderName][bidId] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: the variable biddExists
is mispelled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
return res, err | ||
} | ||
|
||
return res, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this line be covered by tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried and it looks like no. This case is logically unreachable, this line exists to satisfy the compiler requirement to have a return statement.
All "suspicious" cases will be handled and returned with a proper error message in the code statement above: getDataFromRequest
switch pathSplit[0] { | ||
|
||
case "seatbid": | ||
switch pathSplit[1] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any check needed here to ensure pathSplit[1]
exists? Does a split call above always give us at least 2 values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! Path split may have just 1 element, for instance "cur". It means the value should be taken from bid response itself, like "resp.cur". However I added targeting keys path validation to "collect" function to make sure it's not empty.
if truncateTargetAttribute != nil { | ||
maxLength = *truncateTargetAttribute | ||
if maxLength < 0 { | ||
maxLength = MaxKeyLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we cover this line with a test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
} | ||
|
||
func getRespData(bidderResp *openrtb2.BidResponse, field string) (string, error) { | ||
// this code should be modified if there are changes in Op[enRtb format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Mispelling Op[enRtb
--> OpenRtb
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
case "id": | ||
return bidderResp.ID, nil | ||
case "bidid": | ||
return bidderResp.BidID, nil | ||
case "cur": | ||
return bidderResp.Cur, nil | ||
case "customdata": | ||
return bidderResp.CustomData, nil | ||
case "nbr": | ||
return fmt.Sprint(bidderResp.NBR.Val()), nil | ||
|
||
default: | ||
return "", errors.Errorf("key not found for field in bid response: %s", field) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block needs test coverage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added. And found and fixed related bug.
Good comments, Alex! Please re-review |
adservertargeting/requestlookup.go
Outdated
|
||
reqExtPrebid := reqExt.GetPrebid() | ||
if reqExtPrebid == nil { | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What error are you returning here? Is it possible to get to this line when err != nil
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, no error, refactored
warnings []openrtb_ext.ExtBidderMessage, | ||
truncateTargetAttribute *int) json.RawMessage { | ||
|
||
targetingDataTruncated := truncateTargetingKeys(targetingData, truncateTargetAttribute) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to truncate the keys back when we create them, rather than doing this loop through all the keys again?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea here was to separate "get values" logic from everything else, because "get logic" became complicated.
Func truncateTargetingKeys
will be needed to execute for "request" keys and for "response" keys.
Also I'll need to pass truncateTargetAttribute
parameter downstream to where we set keys and values to final map, I think it will overload logic and number of parameters.
I think having this logic here makes the code easier to understand.
I'm open to discuss moving it to another place, if you feel otherwise.
@@ -51,8 +51,7 @@ | |||
{ | |||
"id": "applogy-bid", | |||
"impid": "some-impression-id", | |||
"price": 0, | |||
"ext": {"prebid":{"type":"banner"}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did this ext
need to be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears bid.ext was not validated in unit tests before.
Actual bid.ext turns out to be
{"origbidcpm":0,"origbidcur":"USD","prebid":{"meta":{"adaptercode":"applogy"},"type":"banner"}}
not
"ext": {"prebid":{"type":"banner"}}
After I added bid.ext comparison logic it stoped working.
File auction_test.go -> func assertBidResponseEqual:
if len(expectedBid.Ext) > 0 {
assert.JSONEq(t, string(expectedBid.Ext), string(actualBidMap[bidID].Ext), "Incorrect bid extension")
}
I removed bid.ext from expected result, because it was not validated before and it was not valid anyways.
I can add expected bid.ext back and modify them accordingly. So far it's filled with default values only.
# Conflicts: # exchange/exchange_test.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Left one small comment.
adservertargeting/requestlookup.go
Outdated
} | ||
|
||
// get data by key from request | ||
requestValue, err := getDataFromRequest(path, dataHolder) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: the function names getValueFromBidRequest
vs. getDataFromRequest
are pretty similar and I'm wondering if there's a better name for one of them that'll help distinguish the two functions? If you feel fine about them then I'm happy to keep them as they are, but thought it was worth a mention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, understand. in this function we try to get a key that was not found in query param or in imps. this is a default case. Renamed to getDataFromRequestJson
# Conflicts: # exchange/exchange.go # openrtb_ext/request.go
if test.expectedError { | ||
assert.Error(t, err, "unexpected error returned") | ||
} else { | ||
assert.NoError(t, err, "expected error not returned") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since what you're looking for is for no error to be returned, should the message be something like "unexpected error returned"
I see you use that message for other NoError
assertions in other parts of testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, these should be swapped.
const ( | ||
reqValid = `{ | ||
"id": "req_id", | ||
"imp": [ | ||
{ | ||
"id": "test_imp1", | ||
"ext": {"appnexus": {"placementId": 250419771}}, | ||
"banner": {"format": [{"h": 250, "w": 300}]} | ||
}, | ||
{ | ||
"id": "test_imp2", | ||
"ext": {"appnexus": {"placementId": 250419771}}, | ||
"banner": {"format": [{"h": 250, "w": 300}]} | ||
} | ||
], | ||
"site": {"page": "test.com"} | ||
}` | ||
|
||
reqInvalid = `{ | ||
"id": "req_id", | ||
"imp": { | ||
"incorrect":true | ||
}, | ||
"site": {"page": "test.com"} | ||
}` | ||
|
||
reqNoImps = `{ | ||
"id": "req_id", | ||
"site": {"page": "test.com"} | ||
}` | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be too tedious to extract this consts to their own json files? I'm happy with whatever you think, but it might make tests a little cleaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea!
expectedError: false, | ||
}, | ||
{ | ||
description: "valid request with correct ext.prebid, no ad server targeting", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this description needs to be updated? I think I see ad server targeting present in request ext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I modified the description.
This looks good! I'm interested to see what @hhhjort comments before I approve. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Implementation of #1167