Skip to content

Commit

Permalink
OTT-1863 :: Support to read adpod configs from UI in openwrap module (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
pm-nikhil-vaidya authored and pm-viral-vala committed Sep 26, 2024
1 parent 0662954 commit 1c72687
Show file tree
Hide file tree
Showing 15 changed files with 720 additions and 93 deletions.
196 changes: 177 additions & 19 deletions modules/pubmatic/openwrap/adpod/adpod.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package adpod
import (
"encoding/json"
"errors"
"fmt"

"github.com/buger/jsonparser"
"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache"
"github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics"
"github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models"
"github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adpodconfig"
"github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig"
"github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/utils"
"github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/ortb"
"github.com/prebid/prebid-server/v2/util/ptrutil"
)

Expand All @@ -32,30 +35,19 @@ func setDefaultValues(adpodConfig *models.AdPod) {

}

func GetAdpodConfigs(impVideo *openrtb2.Video, requestExtConfigs *models.ExtRequestAdPod, adUnitConfig *adunitconfig.AdConfig, partnerConfigMap map[int]map[string]string, pubId string, me metrics.MetricsEngine) (*models.AdPod, error) {
adpodConfigs, ok, err := resolveAdpodConfigs(impVideo, requestExtConfigs, adUnitConfig, pubId, me)
func GetV25AdpodConfigs(impVideo *openrtb2.Video, requestExtConfigs *models.ExtRequestAdPod, adUnitConfig *adunitconfig.AdConfig, partnerConfigMap map[int]map[string]string, pubId string, me metrics.MetricsEngine) (*models.AdPod, error) {
adpodConfigs, ok, err := resolveV25AdpodConfigs(impVideo, requestExtConfigs, adUnitConfig, pubId, me)
if !ok || err != nil {
return nil, err
}

videoAdDuration := models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.VideoAdDurationKey)
if len(videoAdDuration) > 0 {
adpodConfigs.VideoAdDuration = utils.GetIntArrayFromString(videoAdDuration, models.ArraySeparator)
}

videoAdDurationMatchingPolicy := models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.VideoAdDurationMatchingKey)
if len(videoAdDurationMatchingPolicy) > 0 {
adpodConfigs.VideoAdDurationMatching = videoAdDurationMatchingPolicy
}

// Set default value if adpod object does not exists
setDefaultValues(adpodConfigs)

return adpodConfigs, nil

}

func resolveAdpodConfigs(impVideo *openrtb2.Video, requestExtConfigs *models.ExtRequestAdPod, adUnitConfig *adunitconfig.AdConfig, pubId string, me metrics.MetricsEngine) (*models.AdPod, bool, error) {
func resolveV25AdpodConfigs(impVideo *openrtb2.Video, requestExtConfigs *models.ExtRequestAdPod, adUnitConfig *adunitconfig.AdConfig, pubId string, me metrics.MetricsEngine) (*models.AdPod, bool, error) {
var adpodConfig *models.AdPod

// Check in impression extension
Expand All @@ -79,10 +71,9 @@ func resolveAdpodConfigs(impVideo *openrtb2.Video, requestExtConfigs *models.Ext
}

return nil, false, nil

}

func Validate(config *models.AdPod) error {
func ValidateV25Configs(rCtx models.RequestCtx, config *models.AdPod) error {
if config == nil {
return nil
}
Expand Down Expand Up @@ -119,9 +110,9 @@ func Validate(config *models.AdPod) error {
return errors.New("adpod.adminduration must be less than adpod.admaxduration")
}

if len(config.VideoAdDuration) > 0 {
if rCtx.AdpodProfileConfig != nil && len(rCtx.AdpodProfileConfig.AdserverCreativeDurations) > 0 {
validDurations := false
for _, videoDuration := range config.VideoAdDuration {
for _, videoDuration := range rCtx.AdpodProfileConfig.AdserverCreativeDurations {
if videoDuration >= config.MinDuration && videoDuration <= config.MaxDuration {
validDurations = true
break
Expand All @@ -135,3 +126,170 @@ func Validate(config *models.AdPod) error {

return nil
}

func GetAdpodConfigs(rctx models.RequestCtx, cache cache.Cache, adunit *adunitconfig.AdConfig) ([]models.PodConfig, error) {
// Fetch Adpod Configs from UI
pods, err := cache.GetAdpodConfig(rctx.PubID, rctx.ProfileID, rctx.DisplayVersionID)
if err != nil {
return nil, err
}

var uiAdpodConfigs []models.PodConfig
if pods != nil {
uiAdpodConfigs = append(uiAdpodConfigs, decouplePodConfigs(pods)...)
}

// Vmap adpod configs
var adrules []models.PodConfig
if adunit != nil && adunit.Adrule != nil {
for _, rule := range adunit.Adrule {
if rule != nil {
adrules = append(adrules, models.PodConfig{
PodID: rule.PodID,
PodDur: rule.PodDur,
MaxSeq: rule.MaxSeq,
MinDuration: rule.MinDuration,
MaxDuration: rule.MaxDuration,
RqdDurs: rule.RqdDurs,
})
}
}
}

var podConfigs []models.PodConfig
if len(uiAdpodConfigs) > 0 && adunit.Video != nil && adunit.Video.UsePodConfig != nil && *adunit.Video.UsePodConfig {
podConfigs = append(podConfigs, uiAdpodConfigs...)
} else if len(adrules) > 0 && rctx.AdruleFlag {
podConfigs = append(podConfigs, adrules...)
}

return podConfigs, nil
}

func decouplePodConfigs(pods *adpodconfig.AdpodConfig) []models.PodConfig {
if pods == nil {
return nil
}

var podConfigs []models.PodConfig
// Add all dynamic adpods
for i, dynamic := range pods.Dynamic {
podConfigs = append(podConfigs, models.PodConfig{
PodID: fmt.Sprintf("dynamic-%d", i+1),
PodDur: dynamic.PodDur,
MaxSeq: dynamic.MaxSeq,
MinDuration: dynamic.MinDuration,
MaxDuration: dynamic.MaxDuration,
RqdDurs: dynamic.RqdDurs,
})
}

// Add all structured adpods
for i, structured := range pods.Structured {
podConfigs = append(podConfigs, models.PodConfig{
PodID: fmt.Sprintf("structured-%d", i+1),
MinDuration: structured.MinDuration,
MaxDuration: structured.MaxDuration,
RqdDurs: structured.RqdDurs,
})
}

// Add all hybrid adpods
for i, hybrid := range pods.Hybrid {
pod := models.PodConfig{
PodID: fmt.Sprintf("hybrid-%d", i+1),
MinDuration: hybrid.MinDuration,
MaxDuration: hybrid.MaxDuration,
RqdDurs: hybrid.RqdDurs,
}

if hybrid.PodDur != nil {
pod.PodDur = *hybrid.PodDur
}

if hybrid.MaxSeq != nil {
pod.MaxSeq = *hybrid.MaxSeq
}

podConfigs = append(podConfigs, pod)
}

return podConfigs
}

func ValidateAdpodConfigs(configs []models.PodConfig) error {
for _, config := range configs {
if config.RqdDurs == nil && config.MinDuration == 0 && config.MaxDuration == 0 {
return errors.New("slot duration is missing in adpod config")
}

if config.MinDuration > config.MaxDuration {
return errors.New("min duration should be less than max duration")
}

if config.RqdDurs == nil && config.MaxDuration <= 0 {
return errors.New("max duration should be greater than 0")
}

if config.PodDur < 0 {
return errors.New("pod duration should be positive number")
}

if config.MaxSeq < 0 {
return errors.New("max sequence should be positive number")
}
}

return nil
}

func ApplyAdpodConfigs(rctx models.RequestCtx, bidRequest *openrtb2.BidRequest) *openrtb2.BidRequest {
if len(rctx.ImpAdPodConfig) == 0 {
return bidRequest
}

imps := make([]openrtb2.Imp, 0)
for _, imp := range bidRequest.Imp {
if imp.Video == nil {
imps = append(imps, imp)
continue
}

// Give priority to adpod config in request
if len(imp.Video.PodID) > 0 {
imps = append(imps, imp)
continue
}

impPodConfig, ok := rctx.ImpAdPodConfig[imp.ID]
if !ok || len(impPodConfig) == 0 {
imps = append(imps, imp)
continue
}

// Apply adpod config
for i, podConfig := range impPodConfig {
impCopy := ortb.DeepCloneImpression(&imp)
impCopy.ID = fmt.Sprintf("%s-%s-%d", impCopy.ID, podConfig.PodID, i)
impCopy.Video.PodID = podConfig.PodID
impCopy.Video.MaxSeq = podConfig.MaxSeq
impCopy.Video.PodDur = podConfig.PodDur
impCopy.Video.MinDuration = podConfig.MinDuration
impCopy.Video.MaxDuration = podConfig.MaxDuration
impCopy.Video.RqdDurs = podConfig.RqdDurs

impCtx := rctx.ImpBidCtx[imp.ID]
impCtxCopy := impCtx.DeepCopy()
impCtxCopy.Video = impCopy.Video

rctx.ImpBidCtx[impCopy.ID] = impCtxCopy
imps = append(imps, *impCopy)
}

// Delete original imp from context
delete(rctx.ImpBidCtx, imp.ID)
}

bidRequest.Imp = imps
return bidRequest
}
13 changes: 7 additions & 6 deletions modules/pubmatic/openwrap/adpod/auction/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func FormAdpodBidsAndPerformExclusion(response *openrtb2.BidResponse, rctx model
return nil, errs
}

impAdpodBidsMap, _ := generateAdpodBids(response.SeatBid, rctx.ImpBidCtx)
impAdpodBidsMap, _ := generateAdpodBids(response.SeatBid, rctx.ImpBidCtx, rctx.AdpodProfileConfig)
adpodBids, errs := doAdPodExclusions(impAdpodBidsMap, rctx.ImpBidCtx)
if len(errs) > 0 {
return nil, errs
Expand Down Expand Up @@ -77,7 +77,7 @@ func addTargetingKey(bid *openrtb2.Bid, key openrtb_ext.TargetingKey, value stri
return err
}

func generateAdpodBids(seatBids []openrtb2.SeatBid, impCtx map[string]models.ImpCtx) (map[string]*AdPodBid, []openrtb2.SeatBid) {
func generateAdpodBids(seatBids []openrtb2.SeatBid, impCtx map[string]models.ImpCtx, adpodProfileCfg *models.AdpodProfileConfig) (map[string]*AdPodBid, []openrtb2.SeatBid) {
impAdpodBidsMap := make(map[string]*AdPodBid)
videoSeatBids := make([]openrtb2.SeatBid, 0)

Expand Down Expand Up @@ -133,12 +133,13 @@ func generateAdpodBids(seatBids []openrtb2.SeatBid, impCtx map[string]models.Imp
// }

//get duration of creative
duration, status := getBidDuration(bid, *eachImpCtx.AdpodConfig, eachImpCtx.ImpAdPodCfg, sequence)
duration, status := getBidDuration(bid, *eachImpCtx.AdpodConfig, adpodProfileCfg, eachImpCtx.ImpAdPodCfg, sequence)
if eachImpCtx.BidIDToDur == nil {
eachImpCtx.BidIDToDur = map[string]int64{}
}
eachImpCtx.BidIDToDur[bid.ID] = duration
impCtx[impId] = eachImpCtx

eachImpBid := Bid{
Bid: bid,
ExtBid: ext,
Expand Down Expand Up @@ -185,7 +186,7 @@ it will try to get the actual ad duration returned by the bidder using prebid.vi
if prebid.video.duration not present then uses defaultDuration passed as an argument
if video lengths matching policy is present for request then it will validate and update duration based on policy
*/
func getBidDuration(bid *openrtb2.Bid, adpodConfig models.AdPod, config []*models.ImpAdPodConfig, sequence int) (int64, int64) {
func getBidDuration(bid *openrtb2.Bid, adpodConfig models.AdPod, adpodProfileCfg *models.AdpodProfileConfig, config []*models.ImpAdPodConfig, sequence int) (int64, int64) {

// C1: Read it from bid.ext.prebid.video.duration field
duration, err := jsonparser.GetInt(bid.Ext, "prebid", "video", "duration")
Expand All @@ -201,8 +202,8 @@ func getBidDuration(bid *openrtb2.Bid, adpodConfig models.AdPod, config []*model
}

// C2: Based on video lengths matching policy validate and return duration
if len(adpodConfig.VideoAdDurationMatching) > 0 {
return getDurationBasedOnDurationMatchingPolicy(duration, adpodConfig.VideoAdDurationMatching, config)
if adpodProfileCfg != nil && len(adpodProfileCfg.AdserverCreativeDurationMatchingPolicy) > 0 {
return getDurationBasedOnDurationMatchingPolicy(duration, adpodProfileCfg.AdserverCreativeDurationMatchingPolicy, config)
}

//default return duration which is present in bid.ext.prebid.vide.duration field
Expand Down
13 changes: 9 additions & 4 deletions modules/pubmatic/openwrap/adpod/impressions/duration_ranges.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ type DurRangeConfig struct { //IImpressions interface
}

// newByDurationRanges will create new object ob byDurRangeConfig for creating impressions for adpod request
func newByDurationRanges(policy string, durations []int, maxAds, adMinDuration, adMaxDuration int) DurRangeConfig {
return DurRangeConfig{
policy: policy,
durations: durations,
func newByDurationRanges(adpodProfileCfg *models.AdpodProfileConfig, maxAds, adMinDuration, adMaxDuration int) DurRangeConfig {
cfg := DurRangeConfig{
maxAds: maxAds,
adMinDuration: adMinDuration,
adMaxDuration: adMaxDuration,
}

if adpodProfileCfg != nil {
cfg.durations = adpodProfileCfg.AdserverCreativeDurations
cfg.policy = adpodProfileCfg.AdserverCreativeDurationMatchingPolicy
}

return cfg
}

// Get function returns lists of min,max duration ranges ganerated based on durations
Expand Down
23 changes: 10 additions & 13 deletions modules/pubmatic/openwrap/adpod/impressions/impression.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ type ImpGenerator interface {
// Algorithm() int // returns algorithm used for computing number of impressions
}

func GenerateImpressions(request *openrtb_ext.RequestWrapper, impCtx map[string]models.ImpCtx, pubId string, me metrics.MetricsEngine) ([]*openrtb_ext.ImpWrapper, []error) {
func GenerateImpressions(request *openrtb_ext.RequestWrapper, impCtx map[string]models.ImpCtx, adpodProfileCfg *models.AdpodProfileConfig, pubId string, me metrics.MetricsEngine) ([]*openrtb_ext.ImpWrapper, []error) {
var imps []*openrtb_ext.ImpWrapper
var errs []error

for _, impWrapper := range request.GetImp() {
eachImpCtx := impCtx[impWrapper.ID]

impAdpodConfig, err := getAdPodImpConfig(impWrapper.Imp, eachImpCtx.AdpodConfig)
impAdpodConfig, err := getAdPodImpConfig(impWrapper.Imp, eachImpCtx.AdpodConfig, adpodProfileCfg)
if impAdpodConfig == nil {
imps = append(imps, impWrapper)
if err != nil {
Expand Down Expand Up @@ -95,13 +95,13 @@ func generateImpressionID(impID string, seqNo int) string {
}

// getAdPodImpsConfigs will return number of impressions configurations within adpod
func getAdPodImpConfig(imp *openrtb2.Imp, adpod *models.AdPod) ([]*models.ImpAdPodConfig, error) {
func getAdPodImpConfig(imp *openrtb2.Imp, adpod *models.AdPod, adpodProfileCfg *models.AdpodProfileConfig) ([]*models.ImpAdPodConfig, error) {
// This case for non adpod video impression
if adpod == nil {
return nil, nil
}
selectedAlgorithm := SelectAlgorithm(adpod)
impGen := NewImpressions(imp.Video.MinDuration, imp.Video.MaxDuration, adpod, selectedAlgorithm)
selectedAlgorithm := SelectAlgorithm(adpod, adpodProfileCfg)
impGen := NewImpressions(imp.Video.MinDuration, imp.Video.MaxDuration, adpod, adpodProfileCfg, selectedAlgorithm)
impRanges := impGen.Get()

// labels := metrics.PodLabels{AlgorithmName: impressions.MonitorKey[selectedAlgorithm], NoOfImpressions: new(int)}
Expand Down Expand Up @@ -132,10 +132,10 @@ func getAdPodImpConfig(imp *openrtb2.Imp, adpod *models.AdPod) ([]*models.ImpAdP
// Return Value:
// - MinMaxAlgorithm (default)
// - ByDurationRanges: if reqAdPod extension has VideoAdDuration and VideoAdDurationMatchingPolicy is "exact" algorithm
func SelectAlgorithm(reqAdPod *models.AdPod) int {
func SelectAlgorithm(reqAdPod *models.AdPod, adpodProfileCfg *models.AdpodProfileConfig) int {
if reqAdPod != nil {
if len(reqAdPod.VideoAdDuration) > 0 &&
(models.OWExactVideoAdDurationMatching == reqAdPod.VideoAdDurationMatching || models.OWRoundupVideoAdDurationMatching == reqAdPod.VideoAdDurationMatching) {
if adpodProfileCfg != nil && len(adpodProfileCfg.AdserverCreativeDurations) > 0 &&
(models.OWExactVideoAdDurationMatching == adpodProfileCfg.AdserverCreativeDurationMatchingPolicy || models.OWRoundupVideoAdDurationMatching == adpodProfileCfg.AdserverCreativeDurationMatchingPolicy) {
return models.ByDurationRanges
}
}
Expand All @@ -146,7 +146,7 @@ func SelectAlgorithm(reqAdPod *models.AdPod) int {
// based on input algorithm type
// if invalid algorithm type is passed, it returns default algorithm which will compute
// impressions based on minimum ad slot duration
func NewImpressions(podMinDuration, podMaxDuration int64, adpod *models.AdPod, algorithm int) ImpGenerator {
func NewImpressions(podMinDuration, podMaxDuration int64, adpod *models.AdPod, adpodProfileCfg *models.AdpodProfileConfig, algorithm int) ImpGenerator {
switch algorithm {
case models.MaximizeForDuration:
g := newMaximizeForDuration(podMinDuration, podMaxDuration, adpod)
Expand All @@ -157,10 +157,7 @@ func NewImpressions(podMinDuration, podMaxDuration int64, adpod *models.AdPod, a
return &g

case models.ByDurationRanges:
g := newByDurationRanges(adpod.VideoAdDurationMatching, adpod.VideoAdDuration,
int(adpod.MaxAds),
adpod.MinDuration, adpod.MaxDuration)

g := newByDurationRanges(adpodProfileCfg, int(adpod.MaxAds), adpod.MinDuration, adpod.MaxDuration)
return &g
}

Expand Down
Loading

0 comments on commit 1c72687

Please sign in to comment.