admin/app/dfp/DfpApi.scala (153 lines of code) (raw):
package dfp
// StatementBuilder query language is PQL defined here:
// https://developers.google.com/ad-manager/api/pqlreference
import com.google.api.ads.admanager.axis.utils.v202405.StatementBuilder
import com.google.api.ads.admanager.axis.v202405._
import com.madgag.scala.collection.decorators.MapDecorator
import common.GuLogging
import common.dfp._
import org.joda.time.DateTime
case class DfpLineItems(validItems: Seq[GuLineItem], invalidItems: Seq[GuLineItem])
class DfpApi(dataMapper: DataMapper, dataValidation: DataValidation) extends GuLogging {
import dfp.DfpApi._
private def readLineItems(
stmtBuilder: StatementBuilder,
postFilter: LineItem => Boolean = _ => true,
): DfpLineItems = {
val lineItems = withDfpSession(session => {
session
.lineItems(stmtBuilder)
.filter(postFilter)
.map(dfpLineItem => {
(dataMapper.toGuLineItem(session)(dfpLineItem), dfpLineItem)
})
})
// Note that this will call getTargeting on each
// item, potentially making one API call per lineitem.
val validatedLineItems = lineItems
.groupBy(Function.tupled(dataValidation.isGuLineItemValid))
.mapV(_.map(_._1))
DfpLineItems(
validItems = validatedLineItems.getOrElse(true, Nil),
invalidItems = validatedLineItems.getOrElse(false, Nil),
)
}
def getAllOrders: Seq[GuOrder] = {
val stmtBuilder = new StatementBuilder()
withDfpSession(_.orders(stmtBuilder).map(dataMapper.toGuOrder))
}
def getAllAdvertisers: Seq[GuAdvertiser] = {
val stmtBuilder = new StatementBuilder()
.where("type = :type")
.withBindVariableValue("type", CompanyType.ADVERTISER.toString)
.orderBy("id ASC")
withDfpSession(_.companies(stmtBuilder).map(dataMapper.toGuAdvertiser))
}
def readCurrentLineItems: DfpLineItems = {
val stmtBuilder = new StatementBuilder()
.where("status = :readyStatus OR status = :deliveringStatus")
.withBindVariableValue("readyStatus", ComputedStatus.READY.toString)
.withBindVariableValue("deliveringStatus", ComputedStatus.DELIVERING.toString)
.orderBy("id ASC")
readLineItems(stmtBuilder)
}
def readLineItemsModifiedSince(threshold: DateTime): DfpLineItems = {
val stmtBuilder = new StatementBuilder()
.where("lastModifiedDateTime > :threshold")
.withBindVariableValue("threshold", threshold.getMillis)
readLineItems(stmtBuilder)
}
def readSponsorshipLineItemIds(): Seq[Long] = {
// The advertiser ID for "Amazon Transparent Ad Marketplace"
val amazonAdvertiserId = 4751525411L
val stmtBuilder = new StatementBuilder()
.where(
"(status = :readyStatus OR status = :deliveringStatus) AND lineItemType = :sponsorshipType AND advertiserId != :amazonAdvertiserId",
)
.withBindVariableValue("readyStatus", ComputedStatus.READY.toString)
.withBindVariableValue("deliveringStatus", ComputedStatus.DELIVERING.toString)
.withBindVariableValue("sponsorshipType", LineItemType.SPONSORSHIP.toString)
.withBindVariableValue("amazonAdvertiserId", amazonAdvertiserId.toString)
.orderBy("id ASC")
// Lets avoid Prebid lineitems
val IsPrebid = "(?i).*?prebid.*".r
val lineItems = readLineItems(
stmtBuilder,
lineItem => {
lineItem.getName match {
case IsPrebid() => false
case _ => true
}
},
)
(lineItems.validItems.map(_.id) ++ lineItems.invalidItems.map(_.id)).sorted
}
def readActiveCreativeTemplates(): Seq[GuCreativeTemplate] = {
val stmtBuilder = new StatementBuilder()
.where("status = :active and type = :userDefined")
.withBindVariableValue("active", CreativeTemplateStatus._ACTIVE)
.withBindVariableValue("userDefined", CreativeTemplateType._USER_DEFINED)
withDfpSession {
_.creativeTemplates(stmtBuilder) map dataMapper.toGuCreativeTemplate filterNot (_.isForApps)
}
}
def readTemplateCreativesModifiedSince(threshold: DateTime): Seq[GuCreative] = {
val stmtBuilder = new StatementBuilder()
.where("lastModifiedDateTime > :threshold")
.withBindVariableValue("threshold", threshold.getMillis)
withDfpSession {
_.creatives.get(stmtBuilder) collect { case creative: TemplateCreative =>
creative
} map dataMapper.toGuTemplateCreative
}
}
private def readDescendantAdUnits(rootName: String, stmtBuilder: StatementBuilder): Seq[GuAdUnit] = {
withDfpSession { session =>
session.adUnits(stmtBuilder) filter { adUnit =>
def isRoot(path: Array[AdUnitParent]) = path.length == 1 && adUnit.getName == rootName
def isDescendant(path: Array[AdUnitParent]) = path.length > 1 && path(1).getName == rootName
Option(adUnit.getParentPath) exists { path => isRoot(path) || isDescendant(path) }
} map dataMapper.toGuAdUnit sortBy (_.id)
}
}
def readActiveAdUnits(rootName: String): Seq[GuAdUnit] = {
val stmtBuilder = new StatementBuilder()
.where("status = :status")
.withBindVariableValue("status", InventoryStatus._ACTIVE)
readDescendantAdUnits(rootName, stmtBuilder)
}
def readSpecialAdUnits(rootName: String): Seq[(String, String)] = {
val statementBuilder = new StatementBuilder()
.where("status = :status")
.where("explicitlyTargeted = :targeting")
.withBindVariableValue("status", InventoryStatus._ACTIVE)
.withBindVariableValue("targeting", true)
readDescendantAdUnits(rootName, statementBuilder) map { adUnit =>
(adUnit.id, adUnit.path.mkString("/"))
} sortBy (_._2)
}
def getCreativeIds(lineItemId: Long): Seq[Long] = {
val stmtBuilder = new StatementBuilder()
.where("status = :status AND lineItemId = :lineItemId")
.withBindVariableValue("status", LineItemCreativeAssociationStatus._ACTIVE)
.withBindVariableValue("lineItemId", lineItemId)
withDfpSession { session =>
session.lineItemCreativeAssociations.get(stmtBuilder) map (id => Long2long(id.getCreativeId))
}
}
def getPreviewUrl(lineItemId: Long, creativeId: Long, url: String): Option[String] =
for {
session <- SessionWrapper()
previewUrl <- session.lineItemCreativeAssociations.getPreviewUrl(lineItemId, creativeId, url)
} yield previewUrl
def getReportQuery(reportId: Long): Option[ReportQuery] =
for {
session <- SessionWrapper()
query <- session.getReportQuery(reportId)
} yield query
def runReportJob(report: ReportQuery): Seq[String] = {
withDfpSession { session =>
session.runReportJob(report)
}
}
}
object DfpApi {
def withDfpSession[T](block: SessionWrapper => Seq[T]): Seq[T] = {
val results = for (session <- SessionWrapper()) yield block(session)
results getOrElse Nil
}
}