Skip to content

Commit

Permalink
Merge pull request #27546 from guardian/ei/fix-prebid-tables
Browse files Browse the repository at this point in the history
Try adding prebid graph back in
  • Loading branch information
emma-imber authored Oct 22, 2024
2 parents 929ae1e + a936bfd commit ee0ab9a
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 1 deletion.
1 change: 1 addition & 0 deletions admin/app/controllers/AdminControllers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,5 @@ trait AdminControllers {
lazy val tablesController = wire[TablesController]
lazy val frontsController = wire[FrontsController]
lazy val adsDotTextController = wire[AdsDotTextEditController]
lazy val commercialKPIController = wire[TeamKPIController]
}
116 changes: 116 additions & 0 deletions admin/app/controllers/admin/commercial/TeamKPIController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package controllers.admin.commercial

import java.time.LocalDate

import common.GuLogging
import jobs.CommercialDfpReporting
import model.{ApplicationContext, NoCache}
import play.api.i18n.I18nSupport
import play.api.mvc._
import play.twirl.api.Html
import tools.{Chart, ChartFormat, ChartRow}

class TeamKPIController(val controllerComponents: ControllerComponents)(implicit context: ApplicationContext)
extends BaseController
with I18nSupport
with GuLogging {

def renderPrebidDashboard(): Action[AnyContent] =
Action { implicit request =>
case class DataPoint(date: LocalDate, bidderName: String, impressionCount: Int, eCpm: Double)

val (dataPoints, lastUpdated) = (
for {
report <- CommercialDfpReporting.getCustomReport(CommercialDfpReporting.prebidBidderPerformance)
} yield {
(
report.rows.flatMap { row =>
val fields = row.fields
for {
date <- fields.lift(0).map(LocalDate.parse(_))
customCriteria <- fields.lift(1)
impressionCount <- fields.lift(3).map(_.toInt)
eCpm <- fields.lift(4).map(_.toDouble / 1000000.0d) // convert DFP micropounds to pounds
} yield DataPoint(
date = date,
bidderName = customCriteria.stripPrefix("hb_bidder="),
impressionCount = impressionCount,
eCpm = eCpm,
)
},
Some(report.lastUpdated),
)
}
).getOrElse((Nil, None))

trait BidPerformanceChart extends Chart[LocalDate] {
def formatRowKey(key: LocalDate): String =
s"new Date(${key.getYear}, ${key.getMonthValue - 1}, ${key.getDayOfMonth})"
val bidderNames = dataPoints.map(_.bidderName).distinct.sorted
def labels = "Date" +: bidderNames
def format = ChartFormat.MultiLine
}

val impressionChart = new BidPerformanceChart {
val name = "Number of winning bids per day"
val dataset = dataPoints
.groupBy(_.date)
.foldLeft(Seq.empty[ChartRow[LocalDate]]) { case (acc, (date, points)) =>
val impressionCounts = bidderNames.foldLeft(Seq.empty[Double]) { (soFar, label) =>
soFar :+ points.find(_.bidderName == label).map(_.impressionCount.toDouble).getOrElse(0d)
}
acc :+ ChartRow(date, impressionCounts)
}
.sortBy(_.rowKey.toEpochDay)
}

val cpmChart = new BidPerformanceChart {
val name = "Average CPM per day"
override val vAxisTitle = Some("GBP")
val dataset = dataPoints
.groupBy(_.date)
.foldLeft(Seq.empty[ChartRow[LocalDate]]) { case (acc, (date, points)) =>
val cpms = bidderNames.foldLeft(Seq.empty[Double]) { (soFar, label) =>
soFar :+ points.find(_.bidderName == label).map(_.eCpm).getOrElse(0d)
}
acc :+ ChartRow(date, cpms)
}
.sortBy(_.rowKey.toEpochDay)
}

val revenueChart = new BidPerformanceChart {
val name = "Indicative revenue per day"
override val vAxisTitle = Some("GBP")
val dataset = dataPoints
.groupBy(_.date)
.foldLeft(Seq.empty[ChartRow[LocalDate]]) { case (acc, (date, points)) =>
val revenues = bidderNames.foldLeft(Seq.empty[Double]) { (soFar, label) =>
soFar :+ points
.find(_.bidderName == label)
.map { point =>
point.impressionCount * point.eCpm / 1000
}
.getOrElse(0d)
}
acc :+ ChartRow(date, revenues)
}
.sortBy(_.rowKey.toEpochDay)
}

NoCache(
Ok(
views.html.dateLineCharts(
charts = Seq(impressionChart, cpmChart, revenueChart),
title = Some("Prebid Bidder Performance"),
description = Some(
Html(
"""NB: Today's figures update every 30 minutes.<br \>
So <b>the cumulative figures for today</b> may initially appear to be falling off a cliff, but they <b>will increase</b> as the day goes on.""",
),
),
lastUpdated = lastUpdated,
),
),
)
}
}
12 changes: 12 additions & 0 deletions admin/app/views/commercial/commercialMenu.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ <h3>Resources</h3>
</div>
</div>

<div class="col-md-4">
<h3>Performance</h3>
<div class="panel panel-default well--front">
<div class="panel-heading">Links to useful resources.</div>
<div class="panel-body">
<ul>
<li><a href="@controllers.admin.commercial.routes.TeamKPIController.renderPrebidDashboard()">Prebid Bidder Performance</a></li>
</ul>
</div>
</div>
</div>

</div>

}
27 changes: 27 additions & 0 deletions admin/app/views/dateLineCharts.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@import java.time.LocalDateTime
@import java.time.format.DateTimeFormatter.ofPattern
@(charts: Seq[tools.Chart[java.time.LocalDate]], title: Option[String] = None, description: Option[Html] = None, lastUpdated: Option[LocalDateTime])(implicit request: RequestHeader, context: model.ApplicationContext)

@admin_main(title.getOrElse("Dashboard"), isAuthed = true, hasCharts = true) {

@title.map{ t =>
<h3>@t</h3>
}

@lastUpdated.map{ updated =>
<p>Last updated @updated.format(ofPattern("d MMMM HH:mm"))</p>
}

@description.map { d =>
<div class="alert alert-info">@d</div>
}

@* some servers have no data *@
@defining(charts.filterNot(_.hasData).map(_.name).mkString(", ")){ noData =>
@if(noData.nonEmpty){
<p>No data for: <strong>@noData</strong></p>
}
}

@charts.filter(_.hasData).map{ chart => @fragments.dateLineChart(chart) }
}
1 change: 1 addition & 0 deletions admin/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ GET /commercial/adops/ads-txt
POST /commercial/adops/ads-txt controllers.admin.commercial.AdsDotTextEditController.postAdsDotText()
GET /commercial/adops/app-ads-txt controllers.admin.commercial.AdsDotTextEditController.renderAppAdsDotText()
POST /commercial/adops/app-ads-txt controllers.admin.commercial.AdsDotTextEditController.postAppAdsDotText()
GET /commercial/prebid/revenue controllers.admin.commercial.TeamKPIController.renderPrebidDashboard()

# Config

Expand Down
3 changes: 2 additions & 1 deletion dev-build/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ GET /commercial/non-refreshable-line-items.json
GET /ads.txt commercial.controllers.AdsDotTextViewController.renderTextFile()
GET /app-ads.txt commercial.controllers.AdsDotTextViewController.renderAppTextFile()
GET /commercial/ias-passback/:size commercial.controllers.PassbackController.renderIasPassback(size)
POST /commercial/api/hb commercial.controllers.PrebidAnalyticsController.insert()
GET /commercial/prebid/revenue controllers.admin.commercial.TeamKPIController.renderPrebidDashboard()
POST /commercial/api/hb commercial.controllers.PrebidAnalyticsController.insert()

# Commercial Admin
GET /commercial controllers.admin.CommercialController.renderCommercialMenu()
Expand Down

0 comments on commit ee0ab9a

Please sign in to comment.