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

Integration Type Implementation #2111

Merged
merged 12 commits into from
Feb 14, 2022

Conversation

AlexBVolcy
Copy link
Contributor

This PR addresses the Integration Type portion of the Integration Type and Channel issue #1428

This PR does the following:

  • Reads integration type value from request from the path request.ext.prebid.integration
  • Allows account/publisher to have a DefaultIntegration value, that will be utilized in the case where an integration type isn't found in request
  • Once integration value is captured/set, it is then set as a query parameter for event URL and vtrack URL

(Note: Integration Type value is not required, nor does there need to be validation of the value per Bret)

@AlexBVolcy AlexBVolcy changed the title Integration Type Integration Integration Type Implementation Dec 14, 2021
@hhhjort hhhjort requested review from bsardo and mansinahar December 14, 2021 18:24
AccountID string `json:"account_id,omitempty"`
Bidder string `json:"bidder,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
IntegrationType string `json:"integration_type,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Where did the field name integration_type come from? Is that something that was discussed with Bret or did we come up with that name ourselves?

Copy link
Contributor Author

@AlexBVolcy AlexBVolcy Jan 10, 2022

Choose a reason for hiding this comment

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

Came up with the name myself, but I'll check the PBS-Java code to see what they do, but like you commented somewhere else, I might just update it to integration

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 It looks like they do call it integration in PBS-Java.

CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"`
DefaultIntegration string `mapstructure:"default_integration"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same question as above: is default_integration the name PBS-Java is using too? From the corresponding issue, it seems like the name should just be integration.

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 I see PBS-Java is using default-integration.

CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"`
DefaultIntegration string `mapstructure:"default_integration"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like you're missing json:"default_integration" in the annotations.

config/config.go Outdated
@@ -88,6 +88,8 @@ type Configuration struct {
GenerateBidID bool `mapstructure:"generate_bid_id"`
// GenerateRequestID overrides the bidrequest.id in an AMP Request or an App Stored Request with a generated UUID if set to true. The default is false.
GenerateRequestID bool `mapstructure:"generate_request_id"`
// Integration Type value lives in config so that it can be updated from the request, and then passed for the event URL
IntegrationType string
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure I understand why this field is in the config. It seems like the integration type can only be defined in the account config as a default for that account or in the request. What am I missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's because I was having trouble getting the integration information to update the vTrack URL integration parameter to the cachePutObjects function. The cachePutObjects function, which eventually calls EventRequestToUrl, has the config object as a member of its function, so by adding the IntegrationType value to the config object, it allowed me to then access it to update the vtrack URL.

This was confusing for me to type, so it might be confusing for you to read, so we can also talk about it tomorrow on a call if you'd like to see if there's an easier way for me to get the integration type value to the vtrack URL.

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 chat about this as a group tomorrow. I need to brush up on how the vTrack endpoint works. From looking at the diagrams here #1015 (comment) it looks like prebid.js calls the vtrack endpoint. I was trying to piece this together so I took a look at PBS-Java and it looks like they are looking for the integration under the int query parameter in the vtrack handler, however, that is missing from their documentation on the prebid.org site. @mansinahar do you have any insight on this?

config/config.go Outdated
@@ -970,6 +972,7 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("auto_gen_source_tid", true)
v.SetDefault("generate_bid_id", false)
v.SetDefault("generate_request_id", false)
v.SetDefault("account_defaults.default_integration", "") // Default Integration For Accounts Can Be Set Here
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you can remove this comment.

@@ -32,6 +32,7 @@ type ExtRequestPrebid struct {
Channel *ExtRequestPrebidChannel `json:"channel,omitempty"`
Data *ExtRequestPrebidData `json:"data,omitempty"`
Debug bool `json:"debug,omitempty"`
Integration string `json:"integration,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

If a request contains .ext.prebid, integration will always be set given that this is a string and not a pointer to a string. In that case, if integration is not specified it will be an empty string. Is the plan to just check for an empty string when determining whether to apply the default integration value? Is there a use case where a publisher does not want to set an integration type, in which case they would explicitly set it to empty string? If so, then a pointer to a string would be needed as we would need to detect presence to differentiate between not set and set to empty string.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a good question to bring up in tomorrow's standup, but the current implementation I have is if integration is blank then we apply the default integration value.

However, the answer for how a publisher could omit an integration type in general is unclear. They could just set their default integration value to be blank, but I don't know if that approaches is what we want.

Copy link
Collaborator

@bsardo bsardo Jan 11, 2022

Choose a reason for hiding this comment

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

@mansinahar thoughts on this? Do we need to confirm this behavior with Bret? I did a little digging and it appears this implementation matches what's in PBS-Java and the edge case I described above is unaccounted for and perhaps not valid. In Ortb2RequestFactory.java, the StringUtils.isBlank(integration) call checks that the request integration is null or empty string before applying the default integration.

Copy link
Contributor

Choose a reason for hiding this comment

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

@bsardo Yeah I think this should be fine. I don't see a specific use case for a publisher to be able to not specify any integration at all being mentioned in the issue description #1428. And as you mentioned it matches with the Java implementation as well.

endpoints/openrtb2/auction.go Show resolved Hide resolved
if reqPrebid == nil {
reqPrebid = &openrtb_ext.ExtRequestPrebid{Integration: deps.cfg.AccountDefaults.DefaultIntegration}
reqExt.SetPrebid(reqPrebid)
req.RebuildRequest()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it make sense to rebuild the request here? This function is called from parseRequest which is called from Auction and at the end of Auction we rebuild the request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll look into this, but I think you're right!

expectedIntegrationType: "TestIntegrationType",
},
{
description: "Request doesn't have request.ext.prebid path, expect default integration value",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we add a test case for when prebid is defined but integration is not?

TimestampParameter = "ts"
FormatParameter = "f"
AnalyticsParameter = "x"
IntegrationTypeParameter = "int"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just checking, is this a standardized query parameter agreed upon with the PBS committee?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I believe so, I'll double check with Mansi, but I believe this is correct

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see that this is the name of the parameter in PBS-Java.

Comment on lines 1838 to 1844
if reqPrebid == nil {
reqPrebid = &openrtb_ext.ExtRequestPrebid{Integration: deps.cfg.AccountDefaults.DefaultIntegration}
reqExt.SetPrebid(reqPrebid)
} else if reqPrebid.Integration == "" {
reqPrebid.Integration = deps.cfg.AccountDefaults.DefaultIntegration
reqExt.SetPrebid(reqPrebid)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Super nitpick - IMO this is a slight improvement that makes it a bit easier to reason:

if reqPrebid == nil {
    reqPrebid = &openrtb_ext.ExtRequestPrebid{}
}
if reqPrebid.Integration == "" {
    reqPrebid.Integration = deps.cfg.AccountDefaults.DefaultIntegration
    reqExt.SetPrebid(reqPrebid)
}

config/config.go Outdated
@@ -88,6 +88,8 @@ type Configuration struct {
GenerateBidID bool `mapstructure:"generate_bid_id"`
// GenerateRequestID overrides the bidrequest.id in an AMP Request or an App Stored Request with a generated UUID if set to true. The default is false.
GenerateRequestID bool `mapstructure:"generate_request_id"`
// Integration Type value lives in config so that it can be updated from the request, and then passed for the event URL
IntegrationType 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 chat about this as a group tomorrow. I need to brush up on how the vTrack endpoint works. From looking at the diagrams here #1015 (comment) it looks like prebid.js calls the vtrack endpoint. I was trying to piece this together so I took a look at PBS-Java and it looks like they are looking for the integration under the int query parameter in the vtrack handler, however, that is missing from their documentation on the prebid.org site. @mansinahar do you have any insight on this?

endpoints/openrtb2/auction.go Show resolved Hide resolved
return nil
}

func getIntegrationFromRequest(req *openrtb_ext.RequestWrapper) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this function is only used in a test.. Do we still need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I wrote the function specifically for tests, I'll move the function to the test file!

@@ -268,16 +273,20 @@ func getAccountId(httpRequest *http.Request) string {
return httpRequest.URL.Query().Get(AccountParameter)
}

func getIntegrationType(httpRequest *http.Request) string {
return httpRequest.URL.Query().Get(IntegrationParameter)
Copy link
Contributor

Choose a reason for hiding this comment

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

@mansinahar
Copy link
Contributor

mansinahar commented Jan 25, 2022

Can we please add some JSON tests for these changes similar to the event tests in: https://github.com/prebid/prebid-server/tree/master/exchange/exchangetest.

Also, shouldn't the event URLs in these existing JSON tests should now have the integration query param in them too? Like here for example: https://github.com/prebid/prebid-server/blob/master/exchange/exchangetest/events-vast-account-off-request-on.json#L124 or here: https://github.com/prebid/prebid-server/blob/master/exchange/exchangetest/events-bid-account-on-request-off.json#L76

@@ -1836,21 +1838,37 @@ func (deps *endpointDeps) setIntegrationType(req *openrtb_ext.RequestWrapper, ac
return err
}
reqPrebid := reqExt.GetPrebid()
if reqPrebid == nil {
if reqPrebid == nil && account != nil {
err := validateIntegrationType(account.DefaultIntegration)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we think this is where we want to validate the default integration? I'd be more inclined to validate it when we look up the account in account/account.go#GetAccount.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Discussed offline with the team and we will only validate on the event and vtrack endpoints to be consistent with Java so this can be disregarded.

@@ -1836,21 +1838,37 @@ func (deps *endpointDeps) setIntegrationType(req *openrtb_ext.RequestWrapper, ac
return err
}
reqPrebid := reqExt.GetPrebid()
if reqPrebid == nil {
if reqPrebid == nil && account != nil {
err := validateIntegrationType(account.DefaultIntegration)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, it looks like we're considering an invalid default integration value an error that kills the auction since the calling code uses the error to determine whether to early return before calling HoldAuction. I just want to confirm that this is what we want. I imagine so since it will result in a 400 informing the user of the issue. The alternative would be to dynamically set it to empty string since the value is not required and does not appear to be critical to the auction but that would happen silently.

Copy link
Collaborator

Choose a reason for hiding this comment

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

N/A since integration type validation will be removed from the auction endpoint.

reqPrebid.Integration = account.DefaultIntegration
reqExt.SetPrebid(reqPrebid)
} else {
err := validateIntegrationType(reqPrebid.Integration)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should move the validation of this request field to where we validate all other request fields in endpoints/openrtb2/auction.go#validateRequest.

Moving this validation and the default integration validation will also greatly simplify this logic so it's more like what was described here: #2111 (comment)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Discussed offline with the team and we will only validate on the event and vtrack endpoints to be consistent with Java so this can be disregarded.

return errors.New("integration type length is too long")
}
for _, char := range integrationType {
if !(unicode.IsDigit(char) || unicode.IsLetter(char)) && char != '-' && char != '_' {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nitpick: this might be easier to read if you used all not/and operators: !unicode.IsDigit(char) && !unicode.IsLetter(char) && ...

return err
}
reqPrebid := reqExt.GetPrebid()
if reqPrebid == nil && account != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we need to check if the account is nil for both the if and elseif sections, in which case I suggest an early return before you execute the if-else if:

if account == nil {
    return nil
}

You can then remove the account != nil check here which will effectively guarantee reqPrebid is defined when you try to access it as part of the else if statement (as currently written you could get an error here when reqPrebid and account are both nil).

Comment on lines 694 to 700
Type: analytics.Imp,
BidID: "bidid",
AccountID: "accountId",
Bidder: "bidder",
Timestamp: 1234567,
Format: analytics.Blank,
Analytics: analytics.Enabled,
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can simplify all of the givenEventRequest objects defined in these test cases by just defining an empty object since none of these values are relevant to the tests and Integration can just take on the default empty string value.

Comment on lines +283 to +285
if err != nil {
return "", err
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you add test coverage for when the validate function returns an error here?

@@ -162,6 +166,10 @@ func ParseEventRequest(r *http.Request) (*analytics.EventRequest, []error) {
errs = append(errs, err)
}

if err := readIntegrationType(event, r); err != nil {
errs = append(errs, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you add coverage for this line? You should be able to just add another test which is basically a copy of TestShouldReturnBadRequestWhenFormatValueIsInvalid.

bsardo
bsardo previously approved these changes Feb 8, 2022
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.

LGTM

for _, test := range testCases {
testEventRequest := &analytics.EventRequest{}
err := readIntegrationType(testEventRequest, test.givenHttpRequest)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

The if condition here should be if test.expectedError != nil because you wanna check for an error if you expect one in the test case. Consider a test case where you expect an error but don't get back one, the execution here will fall to the else block which asserts that the error is empty which it will be because your else block only executes when error is empty/nil. This will result to a false positive.

// get integration value from request parameter
integrationType, err := getIntegrationType(r)
if err != nil {
w.Write([]byte(fmt.Sprintf("Invalid integration type: %s\n", err.Error())))
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add the http.StatusBadRequest response code when returning an error here. Look at L85 for reference


for _, test := range testCases {
integrationType, err := getIntegrationType(test.givenHttpRequest)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as above for the readIntegrationType function test

endpoints/openrtb2/auction.go Show resolved Hide resolved
@mansinahar
Copy link
Contributor

LGTM in general other than the minor comments above.

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.

LGTM

@mansinahar mansinahar merged commit 674f096 into prebid:master Feb 14, 2022
pm-nilesh-chate added a commit to PubMatic-OpenWrap/prebid-server that referenced this pull request Mar 30, 2022
jorgeluisrocha pushed a commit to jwplayer/prebid-server that referenced this pull request Sep 28, 2022
shunj-nb pushed a commit to ParticleMedia/prebid-server that referenced this pull request Nov 8, 2022
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.

3 participants