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

OTT-130: Injecting Video Tracking Events in VAST #130

Merged
merged 22 commits into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b6d4c5c
OTT-105_VCR: basic changes
pm-shriprasad-marathe Feb 26, 2021
574d43a
OTT-105: Changes for injecting Tracking Events
pm-shriprasad-marathe Feb 28, 2021
ffa25db
OTT-130_VCR: Added framework with unit tests for injecting Video Even…
pm-shriprasad-marathe Mar 2, 2021
b7bfc53
OTT-130_VCR: Reverted Path replace
pm-shriprasad-marathe Mar 2, 2021
d5cdfae
OTT-130: Replaced etree dep with original repository
pm-shriprasad-marathe Mar 2, 2021
de1df06
Merge branch 'master' into OTT-130_VCR
pm-shriprasad-marathe Mar 4, 2021
edf6712
OTT-130: Refactored as per latest master
pm-shriprasad-marathe Mar 4, 2021
e352622
Merge branch 'master' into OTT-130_VCR
pm-shriprasad-marathe Mar 4, 2021
d932936
OTT-130: Refactored
pm-shriprasad-marathe Mar 4, 2021
6f26efc
OTT-130: Refactored
pm-shriprasad-marathe Mar 4, 2021
1ab93c1
OTT-130: Reverted unwanted changes
pm-shriprasad-marathe Mar 4, 2021
fcde3f4
OTT-130: Added unit tests and refactoring
pm-shriprasad-marathe Mar 15, 2021
6e4084b
OTT-130: InjectVideoEventTrackers will now return error if any
pm-shriprasad-marathe Mar 15, 2021
1debe67
OTT-130: Refactored
pm-shriprasad-marathe Mar 16, 2021
da3a63a
OTT-130: Removed custom macro support
pm-shriprasad-marathe Mar 16, 2021
726b779
OTT-130: Reverted the unwanted changes
pm-shriprasad-marathe Mar 17, 2021
b9b8efa
OTT-130: Reverted unwanted changes
pm-shriprasad-marathe Mar 17, 2021
992afa2
Merge branch 'OTT-105_VCR' into OTT-130_VCR
pm-shriprasad-marathe Mar 17, 2021
4abfaac
OTT-130: Added Url Query Escape Functionality
pm-shriprasad-marathe Mar 17, 2021
d1585a4
OTT-130: Fixed test failures
pm-shriprasad-marathe Mar 17, 2021
5d968e3
OTT-130: Refactored
pm-shriprasad-marathe Mar 18, 2021
dab2ff2
OTT-130: Addressed code review comments from Viral
pm-shriprasad-marathe Mar 18, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ type Configuration struct {
// RequestValidation specifies the request validation options.
RequestValidation RequestValidation `mapstructure:"request_validation"`
// When true, PBS will assign a randomly generated UUID to req.Source.TID if it is empty
AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"`
AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"`
TrackerURL string `mapstructure:"tracker_url"`
}

const MIN_COOKIE_SIZE_BYTES = 500
Expand Down
160 changes: 160 additions & 0 deletions endpoints/events/vtrack.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,27 @@ package events
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"

"github.com/PubMatic-OpenWrap/openrtb"
accountService "github.com/PubMatic-OpenWrap/prebid-server/account"
"github.com/PubMatic-OpenWrap/prebid-server/adapters"
"github.com/PubMatic-OpenWrap/prebid-server/analytics"
"github.com/PubMatic-OpenWrap/prebid-server/config"
"github.com/PubMatic-OpenWrap/prebid-server/errortypes"
"github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext"
"github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client"
"github.com/PubMatic-OpenWrap/prebid-server/stored_requests"

// "github.com/beevik/etree"
"github.com/PubMatic-OpenWrap/etree"
"github.com/golang/glog"
"github.com/julienschmidt/httprouter"
)
Expand Down Expand Up @@ -46,6 +53,18 @@ type CacheObject struct {
UUID string `json:"uuid"`
}

// standard VAST macros
// https://interactiveadvertisingbureau.github.io/vast/vast4macros/vast4-macros-latest.html#macro-spec-adcount
const (
VASTAdTypeMacro = "[ADTYPE]"
VASTAppBundleMacro = "[APPBUNDLE]"
VASTDomainMacro = "[DOMAIN]"
VASTPageURLMacro = "[PAGEURL]"
PBSEventIDMacro = "[EVENT_ID]" // macro for injecting PBS defined video event tracker id
)

var trackingEvents = []string{"firstQuartile", "midpoint", "thirdQuartile", "complete"}

func NewVTrackEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, cache prebid_cache_client.Client, bidderInfos adapters.BidderInfos) httprouter.Handle {
vte := &vtrackEndpoint{
Cfg: cfg,
Expand Down Expand Up @@ -302,3 +321,144 @@ func ModifyVastXmlJSON(externalUrl string, data json.RawMessage, bidid, bidder,
}
return json.RawMessage(vast)
}

//InjectVideoEventTrackers injects the video tracking events
Copy link
Collaborator

@pm-viral-vala pm-viral-vala Mar 18, 2021

Choose a reason for hiding this comment

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

write parameter single line description

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

func InjectVideoEventTrackers(trackerURL, vastXML string, bid *openrtb.Bid, bidder, accountID string, timestamp int64, bidRequest *openrtb.BidRequest) ([]byte, bool, error) {
// parse VAST
doc := etree.NewDocument()
err := doc.ReadFromString(vastXML)
if nil != err {
err := fmt.Sprintf("Error parsing VAST XML. '%v'", err.Error())
Copy link
Collaborator

Choose a reason for hiding this comment

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

fmt.Errorf use instead Sprintf

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

glog.Errorf(err)
return []byte(vastXML), false, errors.New(err) // false indicates events trackers are not injected
Copy link
Collaborator

Choose a reason for hiding this comment

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

return err itself

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

}
eventURLMap := GetVideoEventTracking(trackerURL, bid, bidder, accountID, timestamp, bidRequest, doc)
trackersInjected := false
// return if if no tracking URL
if len(eventURLMap) == 0 {
return []byte(vastXML), false, errors.New("Event URLs are not found")
}

// Find Creatives of Linear and NonLinear Type
// Injecting Tracking Events for Companion is not supported here
creatives := doc.FindElements("VAST/Ad/InLine/Creatives/Creative/Linear")
creatives = append(creatives, doc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/Linear")...)
creatives = append(creatives, doc.FindElements("VAST/Ad/InLine/Creatives/Creative/NonLinearAds")...)
creatives = append(creatives, doc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/NonLinearAds")...)

if "" == strings.Trim(bid.AdM, " ") || strings.HasPrefix(strings.TrimLeft(strings.TrimRight(bid.AdM, "]]>"), "<![CDATA["), "http") {
Copy link
Collaborator

Choose a reason for hiding this comment

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

  • use strings.TrimSpace() instead of strings.Trim()
  • bid.AdM never comes inside CDATA Tag if it is returning VAST Tag URL. check trimmed bid.AdM starts with http

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

//Maintaining BidRequest Impression Map (Copied from exchange.go#applyCategoryMapping)
//TODO: It should be optimized by forming once and reusing
impMap := make(map[string]*openrtb.Imp)
for i := range bidRequest.Imp {
impMap[bidRequest.Imp[i].ID] = &bidRequest.Imp[i]
}

// determine which creative type to be created based on linearity
if imp, ok := impMap[bid.ImpID]; ok && nil != imp.Video {
// create creative object
creatives = doc.FindElements("VAST/Ad/Wrapper/Creatives")
// var creative *etree.Element
// if len(creatives) > 0 {
// creative = creatives[0] // consider only first creative
// } else {
creative := doc.CreateElement("Creative")
creatives[0].AddChild(creative)

// }

switch imp.Video.Linearity {
case openrtb.VideoLinearityLinearInStream:
creative.AddChild(doc.CreateElement("Linear"))
case openrtb.VideoLinearityNonLinearOverlay:
creative.AddChild(doc.CreateElement("NonLinearAds"))
default: // create both type of creatives
creative.AddChild(doc.CreateElement("Linear"))
creative.AddChild(doc.CreateElement("NonLinearAds"))
}
creatives = creative.ChildElements() // point to actual cratives
}
}
for _, creative := range creatives {
trackingEvents := creative.SelectElement("TrackingEvents")
if nil == trackingEvents {
trackingEvents = creative.CreateElement("TrackingEvents")
creative.AddChild(trackingEvents)
}
// Inject
for event, url := range eventURLMap {
trackingEle := trackingEvents.CreateElement("Tracking")
trackingEle.CreateAttr("event", event)
trackingEle.SetText(fmt.Sprintf("%s", url))
trackersInjected = true
}
}

out := []byte(vastXML)
var wErr error
if trackersInjected {
out, wErr = doc.WriteToBytes()
trackersInjected = trackersInjected && nil == wErr
if nil != wErr {
glog.Errorf("%v", wErr.Error())
}
}
return out, trackersInjected, wErr
}

// GetVideoEventTracking returns map containing key as event name value as associaed video event tracking URL
// By default PBS will expect [EVENT_ID] macro in trackerURL to inject event information
// [EVENT_ID] will be injected with one of the following values
// firstQuartile, midpoint, thirdQuartile, complete
// If your company can not use [EVENT_ID] and has its own macro. provide config.TrackerMacros implementation
// and ensure that your macro is part of trackerURL configuration
func GetVideoEventTracking(trackerURL string, bid *openrtb.Bid, bidder string, accountId string, timestamp int64, req *openrtb.BidRequest, doc *etree.Document) map[string]string {
eventURLMap := make(map[string]string)
if "" == strings.Trim(trackerURL, " ") {
return eventURLMap
}

// PubMatic specific event IDs
// This will go in event-config once PreBid modular design is in place
eventIDMap := map[string]string{
"firstQuartile": "4",
"midpoint": "3",
"thirdQuartile": "5",
"complete": "6",
}
for _, event := range trackingEvents {
eventURL := trackerURL
// macros := make(map[string]string)
// if nil != config.TrackerMacros {
// macros = config.TrackerMacros(event, req, bidder, bid)
// }
// if nil != macros && len(macros) > 0 {
// for macro, v := range macros {
// eventURL = replaceMacro(eventURL, macro, v)
// }
// }
// replace standard macros
eventURL = replaceMacro(eventURL, VASTAdTypeMacro, string(openrtb_ext.BidTypeVideo))
if nil != req && nil != req.App {
eventURL = replaceMacro(eventURL, VASTAppBundleMacro, req.App.Bundle)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Check Macro here, Domain or Bundle

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

[DOMAIN] will be req.App.Bundle in case of Apps

}
if nil != req && nil != req.Site {
eventURL = replaceMacro(eventURL, VASTDomainMacro, req.Site.Domain)
eventURL = replaceMacro(eventURL, VASTPageURLMacro, req.Site.Page)
}

// replace [EVENT_ID] macro with PBS defined event ID
eventURL = replaceMacro(eventURL, PBSEventIDMacro, eventIDMap[event])
eventURLMap[event] = eventURL
}
return eventURLMap
}

func replaceMacro(trackerURL, macro, value string) string {
if strings.HasPrefix(macro, "[") && strings.HasSuffix(macro, "]") && len(strings.Trim(value, " ")) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

will it replace " [MACRO] "? Check with trimmed value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added TrimSpace

trackerURL = strings.ReplaceAll(trackerURL, macro, url.QueryEscape(value))
} else {
glog.Warningf("Invalid macro '%v'. Either empty or missing prefix '[' or suffix ']", macro)
}
return trackerURL
}
Loading