Skip to content

Commit

Permalink
Initial implementation - cache wurl, cache vast rewrite, bid.ext.preb…
Browse files Browse the repository at this point in the history
…id.events

Missing timestamp (waiting for prebid#1584)
  • Loading branch information
laurb9 committed Dec 1, 2020
1 parent d0bfd5d commit 822fb5f
Show file tree
Hide file tree
Showing 13 changed files with 693 additions and 48 deletions.
17 changes: 9 additions & 8 deletions endpoints/events/vtrack.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"sort"
"strings"
"time"

"github.com/golang/glog"
"github.com/julienschmidt/httprouter"
accountService "github.com/prebid/prebid-server/account"
Expand All @@ -13,12 +20,6 @@ import (
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/prebid_cache_client"
"github.com/prebid/prebid-server/stored_requests"
"io"
"io/ioutil"
"net/http"
"sort"
"strings"
"time"
)

const (
Expand Down Expand Up @@ -230,7 +231,7 @@ func (v *vtrackEndpoint) cachePutObjects(ctx context.Context, req *BidCacheReque
}

if _, ok := biddersAllowingVastUpdate[c.Bidder]; ok && nc.Data != nil {
nc.Data = modifyVastXml(v.Cfg.ExternalURL, nc.Data, c.BidID, c.Bidder, accountId, c.Timestamp)
nc.Data = ModifyVastXml(v.Cfg.ExternalURL, nc.Data, c.BidID, c.Bidder, accountId, c.Timestamp)
}

cacheables = append(cacheables, *nc)
Expand Down Expand Up @@ -270,7 +271,7 @@ func getAccountId(httpRequest *http.Request) string {
}

// modifyVastXml modifies BidCacheRequest element Vast XML data
func modifyVastXml(externalUrl string, data json.RawMessage, bidid string, bidder string, accountId string, timestamp int64) json.RawMessage {
func ModifyVastXml(externalUrl string, data json.RawMessage, bidid string, bidder string, accountId string, timestamp int64) json.RawMessage {
c := string(data)
ci := strings.Index(c, ImpressionCloseTag)

Expand Down
1 change: 1 addition & 0 deletions endpoints/openrtb2/auction_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) {
nil,
&config.Configuration{},
newTestMetrics(),
infos,
gdpr.AlwaysAllow{},
currencies.NewRateConverter(&http.Client{}, "", time.Duration(0)),
empty_fetcher.EmptyFetcher{},
Expand Down
10 changes: 6 additions & 4 deletions exchange/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func (a *auction) setRoundedPrices(priceGranularity openrtb_ext.PriceGranularity
a.roundedPrices = roundedPrices
}

func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string, debugLog *DebugLog) []error {
func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, evData *eventsData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string, debugLog *DebugLog) []error {
var bids, vast, includeBidderKeys, includeWinners bool = targData.includeCacheBids, targData.includeCacheVast, targData.includeBidderKeys, targData.includeWinners
if !((bids || vast) && (includeBidderKeys || includeWinners)) {
return nil
Expand Down Expand Up @@ -176,7 +176,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client,
expByImp[imp.ID] = imp.Exp
}
for _, topBidsPerImp := range a.winningBidsByBidder {
for _, topBidPerBidder := range topBidsPerImp {
for bidderName, topBidPerBidder := range topBidsPerImp {
impID := topBidPerBidder.bid.ImpID
isOverallWinner := a.winningBids[impID] == topBidPerBidder
if !includeBidderKeys && !isOverallWinner {
Expand All @@ -195,6 +195,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client,
}
if bids {
if jsonBytes, err := json.Marshal(topBidPerBidder.bid); err == nil {
jsonBytes = evData.modifyBidJSON(topBidPerBidder.bid, bidderName, jsonBytes)
if useCustomCacheKey {
// not allowed if bids is true; log error and cache normally
errs = append(errs, errors.New("cannot use custom cache key for non-vast bids"))
Expand All @@ -210,8 +211,9 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client,
}
}
if vast && topBidPerBidder.bidType == openrtb_ext.BidTypeVideo {
vast := makeVAST(topBidPerBidder.bid)
if jsonBytes, err := json.Marshal(vast); err == nil {
vastXML := makeVAST(topBidPerBidder.bid)
vastXML = evData.modifyVAST(topBidPerBidder.bid, bidderName, vastXML)
if jsonBytes, err := json.Marshal(vastXML); err == nil {
if useCustomCacheKey {
toCache = append(toCache, prebid_cache_client.Cacheable{
Type: prebid_cache_client.TypeXML,
Expand Down
3 changes: 2 additions & 1 deletion exchange/auction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) {
winningBidsByBidder: winningBidsByBidder,
roundedPrices: roundedPrices,
}
_ = testAuction.doCache(ctx, cache, targData, &specData.BidRequest, 60, &specData.DefaultTTLs, bidCategory, &specData.DebugLog)
evData := &eventsData{}
_ = testAuction.doCache(ctx, cache, targData, evData, &specData.BidRequest, 60, &specData.DefaultTTLs, bidCategory, &specData.DebugLog)

if len(specData.ExpectedCacheables) > len(cache.items) {
t.Errorf("%s: [CACHE_ERROR] Less elements were cached than expected \n", fileDisplayName)
Expand Down
89 changes: 89 additions & 0 deletions exchange/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package exchange

import (
"encoding/json"
"time"

jsonpatch "github.com/evanphx/json-patch"
"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/analytics"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/endpoints/events"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/prebid/prebid-server/pbsmetrics"
)

// eventsData has configuration fields needed for adding event tracking to an auction response
type eventsData struct {
accountID string
enabledForAccount bool
enabledForRequest bool
auctionTimestampMs int64
integration pbsmetrics.DemandSource // web app amp
bidderInfos adapters.BidderInfos
externalURL string
}

// getExtEventsData creates an eventsData object from the different configuration sources
func getExtEventsData(requestExtPrebid *openrtb_ext.ExtRequestPrebid, ts time.Time, account *config.Account, bidderInfos adapters.BidderInfos, externalURL string) *eventsData {
return &eventsData{
accountID: account.ID,
enabledForAccount: account.EventsEnabled,
enabledForRequest: requestExtPrebid != nil && requestExtPrebid.Events != nil, // || account.analytics.auction-events.<web|app|amp>
auctionTimestampMs: ts.UnixNano() / 1e+6,
integration: "", // FIXME
bidderInfos: bidderInfos,
externalURL: externalURL,
}
}

// isModifyingVASTXMLAllowed returns true if this bidder config allows modifying VAST XML for event tracking
func (ev *eventsData) isModifyingVASTXMLAllowed(bidderName string) bool {
return ev.bidderInfos[bidderName].ModifyingVastXmlAllowed
}

// modifyVAST injects event Impression url if needed, otherwise returns original VAST string
func (ev *eventsData) modifyVAST(bid *openrtb.Bid, bidderName openrtb_ext.BidderName, vastXML string) string {
if ev.isModifyingVASTXMLAllowed(bidderName.String()) {
vastXML = string(events.ModifyVastXml(ev.externalURL, json.RawMessage(vastXML), bid.ID, bidderName.String(), ev.accountID, ev.auctionTimestampMs))
}
return vastXML
}

// modifyBidJSON injects "wurl" (win) event url if needed, otherwise returns original json
func (ev *eventsData) modifyBidJSON(bid *openrtb.Bid, bidderName openrtb_ext.BidderName, jsonBytes []byte) []byte {
if !ev.enabledForAccount && !ev.enabledForRequest {
return jsonBytes
}
// wurl attribute is not in the schema, so we have to patch
if patch, err := json.Marshal(map[string]string{"wurl": ev.makeEventURL(analytics.Win, bid, bidderName)}); err == nil {
if modifiedJSON, err := jsonpatch.MergePatch(jsonBytes, patch); err == nil {
jsonBytes = modifiedJSON
}
}
return jsonBytes
}

// makeBidExtEvents make the data for bid.ext.prebid.events if needed, otherwise returns nil
func (ev *eventsData) makeBidExtEvents(bid *openrtb.Bid, bidderName openrtb_ext.BidderName) *openrtb_ext.ExtBidPrebidEvents {
if !ev.enabledForAccount && !ev.enabledForRequest {
return nil
}
return &openrtb_ext.ExtBidPrebidEvents{
Win: ev.makeEventURL(analytics.Win, bid, bidderName),
Imp: ev.makeEventURL(analytics.Imp, bid, bidderName),
}
}

// makeEventURL returns an analytics event url for the requested type (win or imp)
func (ev *eventsData) makeEventURL(evType analytics.EventType, bid *openrtb.Bid, bidderName openrtb_ext.BidderName) string {
return events.EventRequestToUrl(ev.externalURL,
&analytics.EventRequest{
Type: evType,
BidID: bid.ID,
Bidder: string(bidderName),
AccountID: ev.accountID,
Timestamp: ev.auctionTimestampMs,
})
}
133 changes: 133 additions & 0 deletions exchange/events_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package exchange

import (
"testing"

"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"
)

func Test_eventsData_makeBidExtEvents(t *testing.T) {
type args struct {
enabledForAccount bool
enabledForRequest bool
bid *openrtb.Bid
bidderName openrtb_ext.BidderName
}
tests := []struct {
name string
args args
want *openrtb_ext.ExtBidPrebidEvents
}{
{
name: "Events enabled for request, disabled for account",
args: args{
enabledForAccount: false,
enabledForRequest: true,
bid: &openrtb.Bid{ID: "BID-1"},
bidderName: openrtb_ext.BidderOpenx,
},
want: &openrtb_ext.ExtBidPrebidEvents{
Win: "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890",
Imp: "http://localhost/event?t=imp&b=BID-1&a=123456&bidder=openx&ts=1234567890",
},
},
{
name: "Events enabled for account, disabled for request",
args: args{
enabledForAccount: false,
enabledForRequest: true,
bid: &openrtb.Bid{ID: "BID-1"},
bidderName: openrtb_ext.BidderOpenx,
},
want: &openrtb_ext.ExtBidPrebidEvents{
Win: "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890",
Imp: "http://localhost/event?t=imp&b=BID-1&a=123456&bidder=openx&ts=1234567890",
},
},
{
name: "Events disabled for account and request",
args: args{
enabledForAccount: false,
enabledForRequest: false,
bid: &openrtb.Bid{ID: "BID-1"},
bidderName: openrtb_ext.BidderOpenx,
},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
evData := &eventsData{
enabledForAccount: tt.args.enabledForAccount,
enabledForRequest: tt.args.enabledForRequest,
accountID: "123456",
auctionTimestampMs: 1234567890,
externalURL: "http://localhost",
}
assert.Equal(t, tt.want, evData.makeBidExtEvents(tt.args.bid, tt.args.bidderName))
})
}
}

func Test_eventsData_modifyBidJSON(t *testing.T) {
type args struct {
enabledForAccount bool
enabledForRequest bool
bid *openrtb.Bid
bidderName openrtb_ext.BidderName
jsonBytes []byte
}
tests := []struct {
name string
args args
want []byte
}{
{
name: "Events enabled for request, disabled for account",
args: args{
enabledForAccount: false,
enabledForRequest: true,
bid: &openrtb.Bid{ID: "BID-1"},
bidderName: openrtb_ext.BidderOpenx,
jsonBytes: []byte(`{"ID": "something"}`),
},
want: []byte(`{"ID": "something", "wurl": "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890"}`),
},
{
name: "Events enabled for account, disabled for request",
args: args{
enabledForAccount: false,
enabledForRequest: true,
bid: &openrtb.Bid{ID: "BID-1"},
bidderName: openrtb_ext.BidderOpenx,
jsonBytes: []byte(`{"ID": "something"}`),
},
want: []byte(`{"ID": "something", "wurl": "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890"}`),
},
{
name: "Events disabled for account and request",
args: args{
enabledForAccount: false,
enabledForRequest: false,
bid: &openrtb.Bid{ID: "BID-1"},
bidderName: openrtb_ext.BidderOpenx,
jsonBytes: []byte(`{"ID": "something"}`),
},
want: []byte(`{"ID": "something"}`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
evData := &eventsData{
enabledForAccount: tt.args.enabledForAccount,
enabledForRequest: tt.args.enabledForRequest,
accountID: "123456",
auctionTimestampMs: 1234567890,
externalURL: "http://localhost",
}
assert.JSONEq(t, string(tt.want), string(evData.modifyBidJSON(tt.args.bid, tt.args.bidderName, tt.args.jsonBytes)))
})
}
}
Loading

0 comments on commit 822fb5f

Please sign in to comment.