Skip to content

Commit

Permalink
Openrtb2blocking module part 3: RawBidderResponse Hook implementation (
Browse files Browse the repository at this point in the history
…prebid#2534)

Co-authored-by: 4lexvav <[email protected]>
  • Loading branch information
legendko and 4lexvav authored Jan 24, 2023
1 parent bb2157f commit 5729eb1
Show file tree
Hide file tree
Showing 11 changed files with 1,498 additions and 20 deletions.
2 changes: 1 addition & 1 deletion hooks/hookanalytics/analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Result struct {
}

type AppliedTo struct {
Bidders []string `json:"bidders,omitempty"`
Bidder string `json:"bidder,omitempty"`
BidIds []string `json:"bidids,omitempty"`
ImpIds []string `json:"impids,omitempty"`
Request bool `json:"request,omitempty"`
Expand Down
5 changes: 4 additions & 1 deletion hooks/hookexecution/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ func handleHookResponses[P any](
return groupOutcome, payload, groupModuleCtx, nil
}

// moduleReplacer changes unwanted symbols to be in compliance with metric naming requirements
var moduleReplacer = strings.NewReplacer(".", "_", "-", "_")

// handleHookResponse is a strategy function that selects and applies
// one of the available algorithms to handle hook response.
func handleHookResponse[P any](
Expand All @@ -173,7 +176,7 @@ func handleHookResponse[P any](
metricEngine metrics.MetricsEngine,
) (P, HookOutcome, *RejectError) {
var rejectErr *RejectError
labels := metrics.ModuleLabels{Module: hr.HookID.ModuleCode, Stage: ctx.stage, AccountID: ctx.accountId}
labels := metrics.ModuleLabels{Module: moduleReplacer.Replace(hr.HookID.ModuleCode), Stage: ctx.stage, AccountID: ctx.accountId}
metricEngine.RecordModuleCalled(labels, hr.ExecutionTime)

hookOutcome := HookOutcome{
Expand Down
3 changes: 2 additions & 1 deletion hooks/hookexecution/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ func (e *hookExecutor) ExecuteRawBidderResponseStage(response *adapters.BidderRe
executionCtx := e.newContext(stageName)
payload := hookstage.RawBidderResponsePayload{Bids: response.Bids, Bidder: bidder}

outcome, _, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
response.Bids = payload.Bids
outcome.Entity = entity(bidder)
outcome.Stage = stageName

Expand Down
17 changes: 9 additions & 8 deletions hooks/hookexecution/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,9 @@ func TestMetricsAreGatheredDuringHookExecution(t *testing.T) {
metricEngine := &metrics.MetricsEngineMock{}
builder := TestAllHookResultsBuilder{}
exec := NewHookExecutor(TestAllHookResultsBuilder{}, "/openrtb2/auction", metricEngine)
moduleName := "module.x-1"
moduleLabels := metrics.ModuleLabels{
Module: "module-1",
Module: moduleReplacer.Replace(moduleName),
Stage: "entrypoint",
}
rTime := func(dur time.Duration) bool { return dur.Nanoseconds() > 0 }
Expand Down Expand Up @@ -2576,20 +2577,20 @@ func (e TestAllHookResultsBuilder) PlanForEntrypointStage(_ string) hooks.Plan[h
hooks.Group[hookstage.Entrypoint]{
Timeout: 10 * time.Millisecond,
Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{
{Module: "module-1", Code: "code-1", Hook: mockUpdateHeaderEntrypointHook{}},
{Module: "module-1", Code: "code-3", Hook: mockTimeoutHook{}},
{Module: "module-1", Code: "code-4", Hook: mockFailureHook{}},
{Module: "module-1", Code: "code-5", Hook: mockErrorHook{}},
{Module: "module-1", Code: "code-6", Hook: mockFailedMutationHook{}},
{Module: "module-1", Code: "code-7", Hook: mockModuleContextHook{key: "key", val: "val"}},
{Module: "module.x-1", Code: "code-1", Hook: mockUpdateHeaderEntrypointHook{}},
{Module: "module.x-1", Code: "code-3", Hook: mockTimeoutHook{}},
{Module: "module.x-1", Code: "code-4", Hook: mockFailureHook{}},
{Module: "module.x-1", Code: "code-5", Hook: mockErrorHook{}},
{Module: "module.x-1", Code: "code-6", Hook: mockFailedMutationHook{}},
{Module: "module.x-1", Code: "code-7", Hook: mockModuleContextHook{key: "key", val: "val"}},
},
},
// place the reject hook in a separate group because it rejects the stage completely
// thus we can not make accurate mock calls if it is processed in parallel with others
hooks.Group[hookstage.Entrypoint]{
Timeout: 10 * time.Second,
Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{
{Module: "module-1", Code: "code-2", Hook: mockRejectHook{}},
{Module: "module.x-1", Code: "code-2", Hook: mockRejectHook{}},
},
},
}
Expand Down
43 changes: 43 additions & 0 deletions hooks/hookstage/rawbidderresponse_mutations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package hookstage

import (
"errors"

"github.com/prebid/prebid-server/adapters"
)

func (c *ChangeSet[T]) RawBidderResponse() ChangeSetRawBidderResponse[T] {
return ChangeSetRawBidderResponse[T]{changeSet: c}
}

type ChangeSetRawBidderResponse[T any] struct {
changeSet *ChangeSet[T]
}

func (c ChangeSetRawBidderResponse[T]) Bids() ChangeSetBids[T] {
return ChangeSetBids[T]{changeSetRawBidderResponse: c}
}

func (c ChangeSetRawBidderResponse[T]) castPayload(p T) (RawBidderResponsePayload, error) {
if payload, ok := any(p).(RawBidderResponsePayload); ok {
return payload, nil
}
return RawBidderResponsePayload{}, errors.New("failed to cast RawBidderResponsePayload")
}

type ChangeSetBids[T any] struct {
changeSetRawBidderResponse ChangeSetRawBidderResponse[T]
}

func (c ChangeSetBids[T]) Update(bids []*adapters.TypedBid) {
c.changeSetRawBidderResponse.changeSet.AddMutation(func(p T) (T, error) {
bidderPayload, err := c.changeSetRawBidderResponse.castPayload(p)
if err == nil {
bidderPayload.Bids = bids
}
if payload, ok := any(bidderPayload).(T); ok {
return payload, nil
}
return p, errors.New("failed to cast RawBidderResponsePayload")
}, MutationUpdate, "bids")
}
94 changes: 94 additions & 0 deletions modules/prebid/ortb2blocking/analytics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package ortb2blocking

import (
"github.com/prebid/prebid-server/hooks/hookanalytics"
"github.com/prebid/prebid-server/hooks/hookstage"
)

const enforceBlockingTag = "enforce_blocking"

const (
attributesAnalyticKey = "attributes"
badvAnalyticKey = "adomain"
cattaxAnalyticKey = "bcat"
bappAnalyticKey = "bundle"
battrAnalyticKey = "attr"
)

// ortb2blocking module has only 1 activity: `enforce_blocking` which will be used in further result processing
func newEnforceBlockingTags() hookanalytics.Analytics {
return hookanalytics.Analytics{
Activities: []hookanalytics.Activity{
{
Name: enforceBlockingTag,
Status: hookanalytics.ActivityStatusSuccess,
},
},
}
}

func addFailedStatusTag(result *hookstage.HookResult[hookstage.RawBidderResponsePayload]) {
result.AnalyticsTags.Activities[0].Status = hookanalytics.ActivityStatusError
}

func addAllowedAnalyticTag(result *hookstage.HookResult[hookstage.RawBidderResponsePayload], bidder, ImpID string) {
newAllowedResult := hookanalytics.Result{
Status: hookanalytics.ResultStatusAllow,
AppliedTo: hookanalytics.AppliedTo{
Bidder: bidder,
ImpIds: []string{ImpID},
},
}

result.AnalyticsTags.Activities[0].Results = append(result.AnalyticsTags.Activities[0].Results, newAllowedResult)
}

func addBlockedAnalyticTag(
result *hookstage.HookResult[hookstage.RawBidderResponsePayload],
bidder, ImpID string,
failedAttributes []string,
data map[string]interface{},
) {
values := make(map[string]interface{})

values[attributesAnalyticKey] = failedAttributes
for _, attribute := range [5]string{
"badv",
"bcat",
"cattax",
"bapp",
"battr",
} {
if _, ok := data[attribute]; ok {
analyticKey := getAnalyticKeyForAttribute(attribute)
values[analyticKey] = data[attribute]
}
}

newBlockedResult := hookanalytics.Result{
Status: hookanalytics.ResultStatusBlock,
Values: values,
AppliedTo: hookanalytics.AppliedTo{
Bidder: bidder,
ImpIds: []string{ImpID},
},
}

result.AnalyticsTags.Activities[0].Results = append(result.AnalyticsTags.Activities[0].Results, newBlockedResult)
}

// most of the attributes have their own representation for an analytic key
func getAnalyticKeyForAttribute(attribute string) string {
switch attribute {
case "badv":
return badvAnalyticKey
case "cattax":
return cattaxAnalyticKey
case "bapp":
return bappAnalyticKey
case "battr":
return battrAnalyticKey
default:
return attribute
}
}
4 changes: 4 additions & 0 deletions modules/prebid/ortb2blocking/hook_bidderrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ func getIds(override Override) ([]int, error) {
return override.Ids, nil
}

func getIsActive(override Override) (bool, error) {
return override.IsActive, nil
}

type mediaTypes map[string]struct{}

func (m mediaTypes) String() string {
Expand Down
Loading

0 comments on commit 5729eb1

Please sign in to comment.