From 37a0d364ecab845a8d8191784c18ef33bc64c629 Mon Sep 17 00:00:00 2001 From: Emma Imber Date: Mon, 21 Oct 2024 13:29:23 +0100 Subject: [PATCH] Try adding graph back in --- admin/app/controllers/AdminControllers.scala | 1 + .../admin/commercial/TeamKPIController.scala | 116 ++++++++++++++++++ .../commercial/commercialMenu.scala.html | 12 ++ admin/app/views/dateLineCharts.scala.html | 27 ++++ admin/conf/routes | 1 + dev-build/conf/routes | 3 +- 6 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 admin/app/controllers/admin/commercial/TeamKPIController.scala create mode 100644 admin/app/views/dateLineCharts.scala.html diff --git a/admin/app/controllers/AdminControllers.scala b/admin/app/controllers/AdminControllers.scala index 1869d0c3e82..87ded9dc99e 100644 --- a/admin/app/controllers/AdminControllers.scala +++ b/admin/app/controllers/AdminControllers.scala @@ -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] } diff --git a/admin/app/controllers/admin/commercial/TeamKPIController.scala b/admin/app/controllers/admin/commercial/TeamKPIController.scala new file mode 100644 index 00000000000..ac267eec060 --- /dev/null +++ b/admin/app/controllers/admin/commercial/TeamKPIController.scala @@ -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.
+ So the cumulative figures for today may initially appear to be falling off a cliff, but they will increase as the day goes on.""", + ), + ), + lastUpdated = lastUpdated, + ), + ), + ) + } +} diff --git a/admin/app/views/commercial/commercialMenu.scala.html b/admin/app/views/commercial/commercialMenu.scala.html index 9f5f8a62eb7..edbb59487fd 100644 --- a/admin/app/views/commercial/commercialMenu.scala.html +++ b/admin/app/views/commercial/commercialMenu.scala.html @@ -79,6 +79,18 @@

Resources

+
+

Performance

+
+
Links to useful resources.
+ +
+
+ } diff --git a/admin/app/views/dateLineCharts.scala.html b/admin/app/views/dateLineCharts.scala.html new file mode 100644 index 00000000000..6f8c53fadb9 --- /dev/null +++ b/admin/app/views/dateLineCharts.scala.html @@ -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 => +

@t

+ } + + @lastUpdated.map{ updated => +

Last updated @updated.format(ofPattern("d MMMM HH:mm"))

+ } + + @description.map { d => +
@d
+ } + + @* some servers have no data *@ + @defining(charts.filterNot(_.hasData).map(_.name).mkString(", ")){ noData => + @if(noData.nonEmpty){ +

No data for: @noData

+ } + } + + @charts.filter(_.hasData).map{ chart => @fragments.dateLineChart(chart) } +} \ No newline at end of file diff --git a/admin/conf/routes b/admin/conf/routes index 67fc81211d1..5046685fc44 100644 --- a/admin/conf/routes +++ b/admin/conf/routes @@ -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 diff --git a/dev-build/conf/routes b/dev-build/conf/routes index 68244268ba1..6b09ee9d280 100644 --- a/dev-build/conf/routes +++ b/dev-build/conf/routes @@ -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()