-
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
Modules activities #3057
Modules activities #3057
Conversation
hooks/hookexecution/execution.go
Outdated
|
||
func handleModuleActivities[T any, P any](hook hooks.HookWrapper[T], activityControl privacy.ActivityControl, payload P) P { | ||
// only 2 stages receive bidder request: hookstage.ProcessedAuctionRequestPayload and hookstage.BidderRequestPayload | ||
// they both implement PayloadBidderRequest interface in order to execute mutations on bid request |
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 - This comment isn't necessary.
hooks/hookexecution/execution.go
Outdated
// only 2 stages receive bidder request: hookstage.ProcessedAuctionRequestPayload and hookstage.BidderRequestPayload | ||
// they both implement PayloadBidderRequest interface in order to execute mutations on bid request | ||
var payloadData hookstage.PayloadBidderRequest | ||
ok := true |
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: Might look a bit nicer to group the definitions.. or maybe not,. This one may be subjective. The ok := true
just looks a bit odd to me (although I understand why it's done).
payloadData, ok := any(&payload).(hookstage.PayloadBidderRequest)
!ok {
// payload doesn't have a bid request
return payload
}
hooks/hookexecution/execution.go
Outdated
privacyEnforcement := privacy.Enforcement{} | ||
|
||
// parse hook.Module to split it to type and mame? | ||
// hook.Module example: "mytest.mymodule". Can it be "rtd.mymodule" or "general.mymodule"? |
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.
We need more information from the module to make this determination. Consider adding an RTD flag? .. or a Kind type of flag?
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.
Do you mean we need to add new property to type HookWrapper[T any] struct
? Like IsRTD bool
?
hooks/hookexecution/execution.go
Outdated
privacyEnforcement := privacy.Enforcement{} | ||
|
||
// parse hook.Module to split it to type and mame? | ||
// hook.Module example: "mytest.mymodule". Can it be "rtd.mymodule" or "general.mymodule"? |
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 we use mytest_mymodule
as the full module's name in other places.
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'm confused here.
This is how hooks configuration looks like:
"hook_sequence": [
{
"module_code": "mytest.mymodule",
"hook_impl_code": "mymodule"
},
{
"module_code": "mytest2.mymodule2",
"hook_impl_code": "mymodule2"
}
]
this is how activity configuration looks like:
"rules": [
{
"allow": false,
"condition": {
"componentName": [
"general.mymodule2"
]
}
}
]
Do we expect to have activity component name set to rtd.mytest.mymodule
or general.mytest.mymodule
?
It will need an adjustment for activity rules parser, becaule we only expect to have one .
in componentName.
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 thought is the mytest
part is the same thing as general
and rtd
. It is just we only envision those two categories as being relevant at present. Do you have a different understanding of where the mytest
piece of the name is coming from?
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, this what I was thinking too. The problem here is that these two modules are created and configured by me and I can give any name to the directory where the module will be. Modules should be placed in {PBS_root}/modules directory.
In order to make activities work this directories can only have names rtd
and general
.
Maybe we should set module type to hook_impl_code
, like
{
"module_code": "mytest.mymodule",
"hook_impl_code": "rtd.mymodule"
}
hooks/hookexecution/execution.go
Outdated
// changes need to be applied to new payload and leave original payload unchanged | ||
bidderReq := payloadData.GetBidderRequestPayload() | ||
var bidderReqCopy *openrtb2.BidRequest | ||
bidderReqCopy = ptrutil.Clone(bidderReq) |
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: bidderReqCopy := ptrutil.Clone(bidderReq)
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.
Nevermind. We discovered this breaks IntelliJ auto-complete.
var newPayload = payload | ||
var np = any(&newPayload).(hookstage.PayloadBidderRequest) | ||
np.SetBidderRequestPayload(bidderReqCopy) | ||
return newPayload |
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's the point of newPayload? Does it make a shallow copy?
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.
At this point we know already that request will be changed for this module (for instance user.ID will be removed).
We create a shallow copy before "Apply" and inside of the "Apply" new User will be created and set back to request so this module will not receive restricted data. We don't do this if activity will not change the request.
if privacyEnforcement.AnyActivities() {
// changes need to be applied to new payload and leave original payload unchanged
bidderReq := payloadData.GetBidderRequestPayload()
bidderReqCopy := ptrutil.Clone(bidderReq)
privacyEnforcement.Apply(bidderReqCopy)
var newPayload = payload
var np = any(&newPayload).(hookstage.PayloadBidderRequest)
np.SetBidderRequestPayload(bidderReqCopy)
return newPayload
}
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.
So you have a shallow copy of the bidrequest. That shallow copy includes a pointer to the original User
object. If you remove the ID
from the User
object, you will be modifying the original request.
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 you are saying is correct. In this case User copy will be handled inside of Apply function. It will execute ScrubRequest
function and it will make copy of User at the beginning and then set it back to request
var userCopy *openrtb2.User
userCopy = ptrutil.Clone(bidRequest.User)
...
...
bidRequest.User = userCopy
return bidRequest
}) | ||
} | ||
} | ||
|
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.
Perhaps a test to insure the original request remains unmolested after the payload has been modified for the module?
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, this is what is checked in hooks/hookexecution/executor_test.go where tests are added for the functions ExecuteRawAuctionStage
and ExecuteBidderRequestStage
. The only difference from other tests there is that Activities
with the restricted userFPD option is passed.
Prebid Server 2.0 has been released and Go Module name has changed from Please merge the |
# Conflicts: # hooks/hookexecution/context.go # hooks/hookexecution/execution.go # hooks/hookexecution/executor.go # hooks/hookexecution/executor_test.go
hooks/hookexecution/execution.go
Outdated
return payload | ||
} | ||
|
||
// parse hook.Module to split it to type and mame? |
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.
Does this comment need to be updated? The question mark at the end suggests this to me. And also there's a slight typo mame
--> 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.
This comment is not needed, removed
hooks/hookstage/bidderrequest.go
Outdated
} | ||
|
||
// PayloadBidderRequest indicated of hook carries a bid request. | ||
// used for activities, name can be better |
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.
Just wanted to point out another comment that might need to be updated, can we remove name can be better
?
ortb/clone.go
Outdated
var newReq *openrtb2.BidRequest | ||
newReq = ptrutil.Clone(req) |
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 these lines can be consolidated to just this:
newReq := ptrutil.Clone(req)
Tests still passed when I updated this locally.
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! I recently updated my IntelliJ to the newest version and this feature works and doesn't "yell" at me about generic type!
hooks/hookexecution/context.go
Outdated
moduleContexts *moduleContexts | ||
endpoint string | ||
stage string | ||
accountId string |
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: I think there's a go convention to have Id
be capitalized, so it'd look like accountID
hooks/hookstage/bidderrequest.go
Outdated
brp.Request = br | ||
} | ||
|
||
// PayloadBidderRequest indicated of hook carries a bid request. |
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 we reword this a bit differently? I'm not sure what it's trying to convey.
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, basically I want to say this interface exists to indicate BidRequest is present in payload. Now we have 2 implementations: BidderRequestPayload
and ProcessedAuctionRequestPayload
What do you think a good comment will be?
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 probably just say something like // RequestUpdater allows reading and writing a bid request
. See my other comment about renaming this interface.
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.
Updated
func (brp *BidderRequestPayload) GetBidderRequestPayload() *openrtb_ext.RequestWrapper { | ||
return brp.Request | ||
} | ||
|
||
func (brp *BidderRequestPayload) SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) { | ||
brp.Request = br | ||
} |
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'm curious, do we need a get and set? Could we access the request directly given that the payload is public?
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, we need a unified interface to get and set RequestWraper from payloads where we have it. We also need this interface to check data type in handleModuleActivities
, we only process activities if input type has a BidderRequest in payload:
payloadData, ok := any(&payload).(hookstage.PayloadBidderRequest)
if !ok {
return payload
}
With this interface we don't need to know the exact type of Payload, we only need to know if it has a BidderRequest.
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.
Gotcha, I see the need for the interface. I got a little confused by the name of the interface when looking at the code in handleModuleActivities
. I originally thought the name PayloadBidderRequest
in that function was referring to a specific type of payload, BidderRequestPayload
, rather than an interface. I suggest we change the name of this interface to something like RequestMutator
, RequestReaderWriter
, RequestWriter
, RequestModifier
, RequestUpdater
, RequestReplacer
etc. to reduce confusion and to have the interface name avoid constraining us to a specific underlying data type.
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 RequestUpdater
func (parp *ProcessedAuctionRequestPayload) GetBidderRequestPayload() *openrtb_ext.RequestWrapper { | ||
return parp.Request | ||
} | ||
|
||
func (parp *ProcessedAuctionRequestPayload) SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) { | ||
parp.Request = br | ||
} |
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 comment as in bidderrequest.go
. Why add a get and set that just read back the value or do a simple assignment?
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.
Answered for BidderRequestPayload
hooks/hookexecution/execution.go
Outdated
@@ -311,3 +314,29 @@ func handleHookMutations[P any]( | |||
|
|||
return payload | |||
} | |||
|
|||
func handleModuleActivities[T any, P any](hook hooks.HookWrapper[T], activityControl privacy.ActivityControl, payload P) P { |
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.
Are generics needed here at this time? It looks like we only care about the bidder request hook at the moment. I'm also not sure if generics will work to support additional hooks without more work given that this function always casts the payload to the bidder request payload instead of deriving the payload type somehow from the T and/or P. Thoughts?
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.
We need to know hook hooks.HookWrapper[T]
and payload P
to process activities.
P can be any of hookstage payload: AllProcessedBidResponsesPayload
, AuctionResponsePayload
, RawBidderResponsePayload
and others.
We only need to check and run Activities for implementations of PayloadBidderRequest
Or I don't understand your question.
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 looks like this function signature can be simplified a bit to exclude type parameter T
and instead just pass in hookCode string
instead of hook
:
func handleModuleActivities[P any](hookCode string, activityControl privacy.ActivityControl, payload P) P {
In general, I'm still on the fence here on whether generics make sense, mostly because this function is basically a noop for all payloads that don't include a request. In that case, I thought interfaces were a better fit where P
becomes whatever your interface name is (e.g. RequestModifier
) but that would require the calling code to perform the type analysis on P
and converting it to the interface type which may not make such a change worth it. Given that this code is called by generic code there really isn't any getting away from the type analysis. The only potential benefit I see to such a change, ignoring any potential performance implications, is that this function is better scoped. We can discuss more offline.
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.
hook hooks.HookWrapper[T]
- removed!
I tried to move the P
check outside, but don't think it fits there. It's related to activitites only.
We still need to have a payload(payloadData
) casted to hookstage.RequestUpdater
later in the func handleModuleActivities
.
Can we keep it inside of the handleModuleActivities and don't overload executeGroup
?
@@ -205,6 +205,8 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http | |||
|
|||
activityControl = privacy.NewActivityControl(&account.Privacy) | |||
|
|||
hookExecutor.SetActivityControl(activityControl) |
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.
Do we need to call this for other endpoints as well?
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.
Ooof, yes, I will add this to amp and video.
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 to AMP endpoint only.
In Video endpoint hooks are not configured:
HookExecutor: hookexecution.EmptyHookExecutor{},
Source: &openrtb2.Source{TID: "testTID"}, | ||
} | ||
result := CloneBidderReq(given) | ||
assert.NotSame(t, given, result.BidRequest, "pointer") |
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.
Do we want an assert.Equal
here to ensure the values are the same?
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.
Pointers should not be the same. We clone BidRequest too: newReq := ptrutil.Clone(req)
and then User, Device and Source.
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 but the values should be the same right? I think assert.Equal
is a value comparison rather than a memory address comparison.
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 yes! Values should be the same, but pointers different. Do you want me to add assert.Equal
for values 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.
Added
hooks/hookexecution/executor_test.go
Outdated
@@ -669,13 +672,76 @@ func TestExecuteRawAuctionStage(t *testing.T) { | |||
}, | |||
}, | |||
}, | |||
{ |
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.
If I understand correctly, this test should not modify the payload because it involves the raw auction hook instead of the bidder request hook right? The raw auction hook payload is different from the one used in the bidder request hook and does not currently support activities.
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 test is probably not needed. Idea here is to test that for the stages and payloads without BidderRequest activities do nothing and don't modify anything. Inn this case we execute ExecuteRawAuctionStage
with RawAuctionRequestPayload
and with disabled activity and verify nothing was changed. This test exercises scenario where "ok" is false.
payloadData, ok := any(&payload).(hookstage.PayloadBidderRequest)
if !ok {
return payload
}
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.
Gotcha, yeah I think you can probably get rid of this test. You have appropriate test coverage of the false case in execution_test.go#TestHandleModuleActivitiesNoBidderRequestPayload
.
As for making sure the original payload isn't modified by handleModuleActivities
, I think you can easily ensure that by updating the three tests in execution_test.go
with the following if you'd like:
origInPayloadData := test.inPayloadData
assert.Equal(t, origInPayloadData, test.inPayloadData)
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, done
hooks/hookexecution/executor_test.go
Outdated
@@ -1164,12 +1231,68 @@ func TestExecuteBidderRequestStage(t *testing.T) { | |||
}, | |||
}, | |||
}, | |||
{ | |||
description: "Group payload not changes with transmitUFPD activity restriction", |
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 isn't this test modifying the payload? This involves the bidder request hook, whose payload has activity support, and you've set it up so the activity infrastructure says transmitting FPD is not allowed. Shouldn't the payload be modified?
Is the issue the fact that this test has always compared givenBidderRequest
with expectedBidderRequest
expecting them to be equal, which should still be true even though we are cloning the the bidder request portion of the payload and modifying 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.
In this test we check that input BidderRequest didn't change and remains the same.
In this test we execute ExecuteBidderRequestStage
with 2 groups (each with one hook). We modify BidRequest for first hook, because it matches the restriction config and don't modify BidderRequest for the second group.
Modified payload (BidRequest) is only passed into executeHook
.
newPayload := handleModuleActivities(hook, executionCtx.activityControl, payload)
wg.Add(1)
go func(hw hooks.HookWrapper[H], moduleCtx hookstage.ModuleInvocationContext) {
defer wg.Done()
executeHook(moduleCtx, hw, newPayload, hookHandler, group.Timeout, resp, rejected)
}(hook, mCtx)
Incoming BidRequest remains the same.
In other words: BidRequest outside of func executeGroup[H any, P any](
should remain unchanged.
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 find this test to be a little redundant and confusing, particularly because it has the exact same inputs and outputs as other test cases except for the fact that there is an activities object input.
What do you think about deleting this test case and instead adding this code block to each of the six stage tests in this file right after the call to SetAccount
:
privacyConfig := getTransmitUFPDActivityConfig("foo", false)
ac := privacy.NewActivityControl(privacyConfig)
exec.SetActivityControl(ac)
In this case, we will be taking advantage of the test cases that already exist throughout the file, exercising the activity infrastructure logic for each stage (not a necessity IMO given the nice unit test coverage you have in execution_test.go
but comes for free here), and introducing some symmetry which I think makes it easier for us to maintain this as it makes the coverage intent easier to discover. On top of that, what you're trying to test here will be exercised for each test case because the TestApplyHookMutationsBuilder
used in each of the six stage tests already defines a foo
hook in each stage plan.
Thoughts?
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, modified!
} | ||
} | ||
|
||
func TestHandleModuleActivitiesProcessedAuctionRequestPayload(t *testing.T) { |
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 looks like processed auction request works because the payload type is able to be casted to the bidder request payload type, is that correct?
Are these the only two payload types we care about because they both deal with a bid request?
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.
Correct, ProcessedAuctionRequestPayload
and BidderRequestPayload
are only payloads with BidderRequest and they both implement PayloadBidderRequest interface
.
This makes them to pass type check here payloadData, ok := any(&payload).(hookstage.PayloadBidderRequest)
//check input payload didn't change | ||
origInPayloadData := test.inPayloadData | ||
assert.Equal(t, origInPayloadData, test.inPayloadData) |
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 you need to move the line origInPayloadData := test.inPayloadData
above the call to handleModuleActivities
.
//check input payload didn't change | ||
origInPayloadData := test.inPayloadData | ||
assert.Equal(t, origInPayloadData, test.inPayloadData) |
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 you need to move the line origInPayloadData := test.inPayloadData
above the call to handleModuleActivities
.
//check input payload didn't change | ||
origInPayloadData := test.inPayloadData | ||
assert.Equal(t, origInPayloadData, test.inPayloadData) |
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 you need to move the line origInPayloadData := test.inPayloadData
above the call to handleModuleActivities
.
hooks/hookexecution/executor_test.go
Outdated
privacyConfig := getTransmitUFPDActivityConfig("foo", false) | ||
ac := privacy.NewActivityControl(privacyConfig) | ||
exec.SetActivityControl(ac) | ||
exec.SetAccount(test.givenAccount) |
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.
Are you able to add this to all six stage tests?
TestExecuteRawAuctionStage
TestExecuteProcessedAuctionStage
TestExecuteBidderRequestStage
TestExecuteRawBidderResponseStage
TestExecuteAllProcessedBidResponsesStage
TestExecuteAuctionResponseStage
Added activities support in modules. Only one activity is now added:
ActivityTransmitUserFPD
Activity evaluation is running before each module hook invocation. If data transmission is prohibited by activity - only module hook payload will be modified.