onward/app/controllers/MostPopularController.scala (243 lines of code) (raw):
package controllers
import com.madgag.scala.collection.decorators.MapDecorator
import java.time.Instant
import java.time.temporal.ChronoUnit
import common._
import conf.switches.Switches
import contentapi.ContentApiClient
import feed.{DayMostPopularAgent, GeoMostPopularAgent, MostPopularAgent}
import layout.ContentCard
import model.Cached.RevalidatableResult
import model._
import model.dotcomrendering.{
MostPopularGeoResponse,
MostPopularCollectionResponse,
OnwardCollectionResponse,
OnwardCollectionResponseDCR,
Trail,
}
import play.api.libs.json._
import play.api.mvc._
import views.support.FaciaToMicroFormat2Helpers._
import scala.concurrent.Future
import agents.DeeplyReadAgent
class MostPopularController(
contentApiClient: ContentApiClient,
geoMostPopularAgent: GeoMostPopularAgent,
dayMostPopularAgent: DayMostPopularAgent,
mostPopularAgent: MostPopularAgent,
deeplyReadAgent: DeeplyReadAgent,
val controllerComponents: ControllerComponents,
)(implicit context: ApplicationContext)
extends BaseController
with GuLogging
with ImplicitControllerExecutionContext {
val page = SimplePage(
MetaData.make(
"most-read",
Some(SectionId.fromId("most-read")),
"Most read",
),
)
def renderHtml(path: String): Action[AnyContent] = render(path)
def render(path: String): Action[AnyContent] =
Action.async { implicit request =>
val edition = Edition(request)
// Synchronous global popular, from the mostPopularAgent (stateful)
val globalPopular: Option[MostPopular] = {
val globalPopularContent = mostPopularAgent.mostPopular(edition)
if (globalPopularContent.nonEmpty)
Some(MostPopular("Across the Guardian", "", globalPopularContent.map(_.faciaContent)))
else
None
}
// Async section specific most Popular.
val sectionPopular: Future[List[MostPopular]] =
if (path.nonEmpty) lookup(edition, path).map(_.toList) else Future(Nil)
// map is not on a list, but on a Future
sectionPopular.map { sectionPopular =>
val sectionFirst = sectionPopular ++ globalPopular
val globalFirst = globalPopular.toList ++ sectionPopular
val mostPopular: List[MostPopular] = if (path == "global-development") sectionFirst else globalFirst
mostPopular match {
case Nil => NotFound
case popular if request.forceDCR => jsonResponse(popular, mostCards())
case popular if !request.isJson =>
Cached(900) {
RevalidatableResult.Ok(views.html.mostPopular(page, popular))
}
case popular =>
Cached(2) {
JsonComponent(
"html" -> {
if (Switches.ExtendedMostPopular.isSwitchedOn) {
views.html.fragments.collections.popularExtended(popular, mostCards())
} else {
views.html.fragments.collections.popular(popular)
}
},
"rightHtml" -> views.html.fragments.rightMostPopular(globalPopular),
)
}
}
}
}
private val countryNames = Map(
"AU" -> "Australia",
"US" -> "US",
"IN" -> "India",
)
def renderPopularGeo(): Action[AnyContent] =
Action { implicit request =>
val headers = request.headers.toSimpleMap
val countryCode = headers.getOrElse("X-GU-GeoLocation", "country:row").replace("country:", "")
val countryPopular =
MostPopular("Across the Guardian", "", geoMostPopularAgent.mostPopular(countryCode).map(_.faciaContent))
if (request.forceDCR) {
jsonResponse(countryPopular, countryCode)
} else {
Cached(900) {
JsonComponent(
"html" -> {
if (Switches.ExtendedMostPopularFronts.isSwitchedOn) {
views.html.fragments.collections.popularExtended(Seq(countryPopular), mostCards())
} else {
views.html.fragments.collections.popular(Seq(countryPopular))
}
},
"rightHtml" -> views.html.fragments
.rightMostPopularGeoGarnett(countryPopular, countryNames.get(countryCode), countryCode),
"country" -> countryCode,
)
}
}
}
def jsonResponse(mostPopulars: Seq[MostPopular], mostCards: Map[String, Option[ContentCard]])(implicit
request: RequestHeader,
): Result = {
val tabs = mostPopulars.map { section =>
OnwardCollectionResponse(
heading = section.heading,
trails = section.trails.map(Trail.pressedContentToTrail).take(10),
)
}
val mostCommented = mostCards.getOrElse("most_commented", None).flatMap { contentCard =>
Trail.contentCardToTrail(contentCard)
}
val mostShared = mostCards.getOrElse("most_shared", None).flatMap { contentCard =>
Trail.contentCardToTrail(contentCard)
}
val response = OnwardCollectionResponseDCR(tabs, mostCommented, mostShared)
Cached(900)(JsonComponent.fromWritable(response))
}
def jsonResponseTrails(mostPopulars: Seq[MostPopularCollectionResponse], mostCards: Map[String, Option[ContentCard]])(
implicit request: RequestHeader,
): Result = {
val tabs = mostPopulars.map { tab =>
OnwardCollectionResponse(tab.heading, tab.trails)
}
val mostCommented = mostCards.getOrElse("most_commented", None).flatMap { contentCard =>
Trail.contentCardToTrail(contentCard)
}
val mostShared = mostCards.getOrElse("most_shared", None).flatMap { contentCard =>
Trail.contentCardToTrail(contentCard)
}
val response = OnwardCollectionResponseDCR(tabs, mostCommented, mostShared)
Cached(900)(JsonComponent.fromWritable(response))
}
def jsonResponse(mostPopular: MostPopular, countryCode: String)(implicit request: RequestHeader): Result = {
val data = MostPopularGeoResponse(
country = countryNames.get(countryCode),
heading = mostPopular.heading,
trails = mostPopular.trails.map(Trail.pressedContentToTrail).take(10),
)
Cached(900)(JsonComponent.fromWritable(data))
}
def renderPopularDay(countryCode: String): Action[AnyContent] =
Action { implicit request =>
Cached(900) {
JsonComponent(
"trails" -> JsArray(dayMostPopularAgent.mostPopular(countryCode).map { trail =>
Json.obj(
("url", trail.content.metadata.url),
("headline", trail.content.trail.headline),
)
}),
)
}
}
def renderPopularMicroformat2: Action[AnyContent] =
Action { implicit request =>
val edition = Edition(request)
val mostPopular = mostPopularAgent.mostPopular(edition) take 5
Cached(900) {
JsonComponent(
"items" -> JsArray(
Seq(
Json.obj(
"displayName" -> "most viewed",
"showContent" -> mostPopular.nonEmpty,
"content" -> JsArray(mostPopular.map(content => isCuratedContent(content.faciaContent))),
),
),
),
)
}
}
def renderWithDeeplyRead(): Action[AnyContent] =
Action.async { implicit request =>
val headers = request.headers.toSimpleMap
val countryCode = headers.getOrElse("X-GU-GeoLocation", "country:row").replace("country:", "")
// Synchronous edition popular, from the mostPopularAgent (stateful)
val editionPopular: Option[MostPopularCollectionResponse] = {
val editionPopularContent = geoMostPopularAgent.mostPopular(countryCode)
if (editionPopularContent.isEmpty) None
Some(
MostPopularCollectionResponse(
"Most viewed",
"",
editionPopularContent
.map(_.faciaContent)
.map(Trail.pressedContentToTrail),
),
)
}
val edition = countryCode match {
case "GB" => editions.Uk
case "US" => editions.Us
case "CA" => editions.Us
case "AU" => editions.Au
case "NZ" => editions.Au
case _ => Edition.defaultEdition
}
val deeplyReadItems = deeplyReadAgent.getTrails(edition)
// Async global deeply read
val deeplyRead: Option[MostPopularCollectionResponse] = {
if (deeplyReadItems.isEmpty) None
Some(
MostPopularCollectionResponse(
"Deeply read",
"",
deeplyReadItems,
),
)
}
val response = (editionPopular, deeplyRead) match {
case (Some(p), Some(d)) =>
jsonResponseTrails(editionPopular.toSeq ++ deeplyRead.toSeq, mostCards())
case (_, _) => NotFound
}
Future(response)
}
// Get "Most Commented" & "Most Shared" cards for Extended "Most Read" container
private def mostCards(): Map[String, Option[ContentCard]] =
mostPopularAgent.mostSingleCardsBox.get().mapV(ContentCard.fromApiContent)
private def lookup(edition: Edition, path: String)(implicit request: RequestHeader): Future[Option[MostPopular]] = {
logInfoWithRequestId(s"Fetching most popular: $path for edition $edition")
val capiItem = contentApiClient
.item(path, edition)
.tag(None)
.showMostViewed(true)
val capiItemWithDate =
if (path == "film") capiItem.dateParam("from-date", Instant.now.minus(180, ChronoUnit.DAYS)) else capiItem
contentApiClient.getResponse(capiItemWithDate).map { response =>
val heading = response.section.map(s => "in " + s.webTitle).getOrElse("Across the Guardian")
val popular = response.mostViewed.getOrElse(Nil) take 10 map (RelatedContentItem(_))
if (popular.isEmpty) None else Some(MostPopular(heading, path, popular.map(_.faciaContent).toSeq))
}
}
}