Skip to content

Commit

Permalink
Crossword picker and JSON route (#26843)
Browse files Browse the repository at this point in the history
* route for DCR-JSON crossword data

* move crossword classes

* create crosswordpicker and remote rendering for crosswords

---------

Co-authored-by: Alina Boghiu <[email protected]>
  • Loading branch information
andrew-nowak and alinaboghiu authored Jan 25, 2024
1 parent 1d827d0 commit 7a52965
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 44 deletions.
120 changes: 82 additions & 38 deletions applications/app/controllers/CrosswordsController.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package controllers

import com.gu.contentapi.client.model.v1.{Crossword, ItemResponse, Content => ApiContent, Section => ApiSection}
import common.{Edition, ImplicitControllerExecutionContext, GuLogging}
import common.{Edition, GuLogging, ImplicitControllerExecutionContext}
import conf.Static
import contentapi.ContentApiClient
import pages.{CrosswordHtmlPage, IndexHtmlPage, PrintableCrosswordHtmlPage}
import crosswords.{
AccessibleCrosswordPage,
AccessibleCrosswordRows,
CrosswordPageWithContent,
CrosswordPageWithSvg,
CrosswordSearchPageNoResult,
CrosswordSearchPageWithResults,
Expand All @@ -21,6 +22,10 @@ import play.api.data._
import play.api.mvc.{Action, RequestHeader, Result, _}
import services.{IndexPage, IndexPageItem}
import html.HtmlPageHelpers.ContentCSSFile
import model.dotcomrendering.{DotcomRenderingDataModel, PageType}
import play.api.libs.ws.WSClient
import renderers.DotcomRenderingService
import services.dotcomrendering.{CrosswordsPicker, RemoteRender}

import scala.concurrent.Future
import scala.concurrent.duration._
Expand All @@ -29,6 +34,9 @@ trait CrosswordController extends BaseController with GuLogging with ImplicitCon

def contentApiClient: ContentApiClient

val remoteRenderer: DotcomRenderingService = DotcomRenderingService()
val wsClient: WSClient

def noResults()(implicit request: RequestHeader): Result

def getCrossword(crosswordType: String, id: Int)(implicit request: RequestHeader): Future[ItemResponse] = {
Expand All @@ -38,14 +46,14 @@ trait CrosswordController extends BaseController with GuLogging with ImplicitCon
}

def withCrossword(crosswordType: String, id: Int)(
f: (Crossword, ApiContent) => Result,
f: (Crossword, ApiContent) => Future[Result],
)(implicit request: RequestHeader): Future[Result] = {
getCrossword(crosswordType, id).map { response =>
getCrossword(crosswordType, id).flatMap { response =>
val maybeCrossword = for {
content <- response.content
crossword <- content.crossword
} yield f(crossword, content)
maybeCrossword getOrElse noResults()
maybeCrossword getOrElse Future.successful(noResults())
} recover {
case t: Throwable =>
log.error(s"Error retrieving $crosswordType crossword id $id from API", t)
Expand All @@ -58,22 +66,31 @@ trait CrosswordController extends BaseController with GuLogging with ImplicitCon
context: ApplicationContext,
): Future[Result] = {
withCrossword(crosswordType, id) { (crossword, content) =>
Cached(60.seconds)(
RevalidatableResult.Ok(
CrosswordHtmlPage.html(
CrosswordPageWithSvg(
CrosswordContent.make(CrosswordData.fromCrossword(crossword, content), content),
CrosswordSvg(crossword, None, None, false),
val page = CrosswordPageWithSvg(
CrosswordContent.make(CrosswordData.fromCrossword(crossword, content), content),
CrosswordSvg(crossword, None, None, false),
)

if (CrosswordsPicker.getTier(page) == RemoteRender)
remoteRenderer.getCrossword(wsClient, page, PageType(page, request, context))
else
Future.successful(
Cached(60.seconds)(
RevalidatableResult.Ok(
CrosswordHtmlPage.html(page),
),
),
),
)
)
}
}
}

class CrosswordPageController(val contentApiClient: ContentApiClient, val controllerComponents: ControllerComponents)(
implicit context: ApplicationContext,
class CrosswordPageController(
val contentApiClient: ContentApiClient,
val controllerComponents: ControllerComponents,
val wsClient: WSClient,
)(implicit
context: ApplicationContext,
) extends CrosswordController {

def noResults()(implicit request: RequestHeader): Result =
Expand All @@ -84,19 +101,41 @@ class CrosswordPageController(val contentApiClient: ContentApiClient, val contro
renderCrosswordPage(crosswordType, id)
}

def renderJson(crosswordType: String, id: Int): Action[AnyContent] = {
Action.async { implicit request =>
withCrossword(crosswordType, id) { (crossword, content) =>
val crosswordContent = CrosswordContent.make(CrosswordData.fromCrossword(crossword, content), content)
val crosswordPage = new CrosswordPageWithContent(crosswordContent)

val pageType = PageType(crosswordPage, request, context)
Future.successful(
common.renderJson(getDCRJson(crosswordPage, pageType), crosswordPage).as("application/json"),
)
}
}
}
private def getDCRJson(crosswordPage: CrosswordPageWithContent, pageType: PageType)(implicit
request: RequestHeader,
): String =
DotcomRenderingDataModel.toJson(
DotcomRenderingDataModel.forCrossword(crosswordPage, request, pageType),
)

def accessibleCrossword(crosswordType: String, id: Int): Action[AnyContent] =
Action.async { implicit request =>
withCrossword(crosswordType, id) { (crossword, content) =>
Cached(60.seconds)(
RevalidatableResult.Ok(
CrosswordHtmlPage.html(
AccessibleCrosswordPage(
CrosswordContent.make(
CrosswordData
.fromCrossword(crossword.copy(name = s"Accessible version of ${crossword.name}"), content),
content,
Future.successful(
Cached(60.seconds)(
RevalidatableResult.Ok(
CrosswordHtmlPage.html(
AccessibleCrosswordPage(
CrosswordContent.make(
CrosswordData
.fromCrossword(crossword.copy(name = s"Accessible version of ${crossword.name}"), content),
content,
),
AccessibleCrosswordRows(crossword),
),
AccessibleCrosswordRows(crossword),
),
),
),
Expand All @@ -107,12 +146,14 @@ class CrosswordPageController(val contentApiClient: ContentApiClient, val contro
def printableCrossword(crosswordType: String, id: Int): Action[AnyContent] =
Action.async { implicit request =>
withCrossword(crosswordType, id) { (crossword, content) =>
Cached(60.seconds)(
RevalidatableResult.Ok(
PrintableCrosswordHtmlPage.html(
CrosswordPageWithSvg(
CrosswordContent.make(CrosswordData.fromCrossword(crossword, content), content),
CrosswordSvg(crossword, None, None, false),
Future.successful(
Cached(60.seconds)(
RevalidatableResult.Ok(
PrintableCrosswordHtmlPage.html(
CrosswordPageWithSvg(
CrosswordContent.make(CrosswordData.fromCrossword(crossword, content), content),
CrosswordSvg(crossword, None, None, false),
),
),
),
),
Expand All @@ -127,22 +168,25 @@ class CrosswordPageController(val contentApiClient: ContentApiClient, val contro

val globalStylesheet = Static(s"stylesheets/$ContentCSSFile.css")

Cached(60.seconds) {
val body = s"""$xml"""
RevalidatableResult(
Cors {
Ok(body).as("image/svg+xml")
},
body,
)
}
Future.successful(
Cached(60.seconds) {
val body = s"""$xml"""
RevalidatableResult(
Cors {
Ok(body).as("image/svg+xml")
},
body,
)
},
)
}
}
}

class CrosswordSearchController(
val contentApiClient: ContentApiClient,
val controllerComponents: ControllerComponents,
val wsClient: WSClient,
)(implicit context: ApplicationContext)
extends CrosswordController {
val searchForm = Form(
Expand Down
51 changes: 51 additions & 0 deletions applications/app/services/dotcomrendering/CrosswordsPicker.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package services.dotcomrendering

import common.GuLogging
import crosswords.CrosswordPageWithContent
import model.Cors.RichRequestHeader
import play.api.mvc.RequestHeader
import utils.DotcomponentsLogger

object CrosswordsPicker extends GuLogging {

/**
*
* Add to this function any logic for including/excluding
* a crossword page from being rendered with DCR
*
* Currently defaulting to false until we implement crosswords in DCR
*
* */
private def dcrCouldRender(crosswordPageWithContent: CrosswordPageWithContent): Boolean = {
false
}

def getTier(
crosswordPageWithContent: CrosswordPageWithContent,
)(implicit
request: RequestHeader,
): RenderType = {

val participatingInTest = false // until we create a test for this content type
val dcrCanRender = dcrCouldRender(crosswordPageWithContent)

val tier = {
if (request.forceDCROff) LocalRender
else if (request.forceDCR) RemoteRender
else if (dcrCanRender && participatingInTest) RemoteRender
else LocalRender
}

if (tier == RemoteRender) {
DotcomponentsLogger.logger.logRequest(
s"path executing in dotcomponents",
Map.empty,
crosswordPageWithContent.item,
)
} else {
DotcomponentsLogger.logger.logRequest(s"path executing in web", Map.empty, crosswordPageWithContent.item)
}

tier
}
}
1 change: 1 addition & 0 deletions applications/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ GET /survey/thankyou

# NOTE: Leave this as it is, otherwise we don't render /crosswords/series/prize, for example.
GET /crosswords/$crosswordType<cryptic|quick|quiptic|prize|everyman|azed|special|genius|speedy|weekend>/:id.svg controllers.CrosswordPageController.thumbnail(crosswordType: String, id: Int)
GET /crosswords/$crosswordType<cryptic|quick|quiptic|prize|everyman|azed|special|genius|speedy|weekend>/:id.json controllers.CrosswordPageController.renderJson(crosswordType: String, id: Int)
GET /crosswords/$crosswordType<cryptic|quick|quiptic|prize|everyman|azed|special|genius|speedy|weekend>/:id controllers.CrosswordPageController.crossword(crosswordType: String, id: Int)
GET /crosswords/$crosswordType<cryptic|quick|quiptic|prize|everyman|special|genius|speedy|weekend>/:id/print controllers.CrosswordPageController.printableCrossword(crosswordType: String, id: Int)
GET /crosswords/accessible/$crosswordType<cryptic|quick|quiptic|prize|everyman|azed|special|genius|speedy|weekend>/:id controllers.CrosswordPageController.accessibleCrossword(crosswordType: String, id: Int)
Expand Down
2 changes: 1 addition & 1 deletion applications/test/CrosswordDataTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import org.scalatest.time.{Millis, Span}
"CrosswordData" - {

lazy val crosswordPageController =
new CrosswordPageController(testContentApiClient, play.api.test.Helpers.stubControllerComponents())
new CrosswordPageController(testContentApiClient, play.api.test.Helpers.stubControllerComponents(), wsClient)

"fromCrossword should normalize separators for grouped entries" in {

Expand Down
2 changes: 1 addition & 1 deletion applications/test/CrosswordPageMetaDataTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.scalatest.{BeforeAndAfterAll, DoNotDiscover}

val crosswordUrl = "crosswords/cryptic/26697"
lazy val crosswordPageController =
new CrosswordPageController(testContentApiClient, play.api.test.Helpers.stubControllerComponents())
new CrosswordPageController(testContentApiClient, play.api.test.Helpers.stubControllerComponents(), wsClient)

it should "not include the ios deep link" in {
val result = crosswordPageController.crossword("cryptic", 26697)(TestRequest(crosswordUrl))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package crosswords

import com.gu.contentapi.client.model.v1.{CrosswordDimensions, Crossword}
import com.gu.contentapi.client.model.v1.{Crossword, CrosswordDimensions}
import model.CrosswordPosition

case class AccessibleCrosswordRow(rowNumber: Int, blankColumns: List[Char])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package crosswords

import crosswords.CrosswordSvg.{BorderSize, CellSize}
import model._
import java.time.LocalDateTime

import java.time.LocalDateTime
import scala.xml.Elem

sealed trait CrosswordPage extends Page
Expand All @@ -27,8 +28,6 @@ class CrosswordPageWithContent(content: CrosswordContent) extends ContentPage {
override lazy val item = content
val crossword = content.crossword

import crosswords.CrosswordSvg.{BorderSize, CellSize}

case class SvgDimensions(width: Int, height: Int) {
def styleString: String = s"width: $width; height: $height"
}
Expand Down
File renamed without changes.
28 changes: 28 additions & 0 deletions common/app/model/dotcomrendering/DotcomRenderingDataModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import common.Maps.RichMap
import common.commercial.EditionCommercialProperties
import common.{CanonicalLink, Chronos, Edition, Localisation, RichRequestHeader}
import conf.Configuration
import crosswords.CrosswordPageWithContent
import experiments.ActiveExperiments
import model.dotcomrendering.DotcomRenderingUtils._
import model.dotcomrendering.pageElements.{PageElement, TextCleaner}
Expand All @@ -16,6 +17,7 @@ import model.{
CanonicalLiveBlog,
ContentFormat,
ContentPage,
CrosswordData,
GUDateTimeFormatNew,
GalleryPage,
ImageContentPage,
Expand Down Expand Up @@ -108,6 +110,7 @@ case class DotcomRenderingDataModel(
showTableOfContents: Boolean,
lang: Option[String],
isRightToLeftLang: Boolean,
crossword: Option[CrosswordData],
)

object DotcomRenderingDataModel {
Expand Down Expand Up @@ -187,6 +190,7 @@ object DotcomRenderingDataModel {
"showTableOfContents" -> model.showTableOfContents,
"lang" -> model.lang,
"isRightToLeftLang" -> model.isRightToLeftLang,
"crossword" -> model.crossword,
)

ElementsEnhancer.enhanceDcrObject(obj)
Expand Down Expand Up @@ -338,6 +342,28 @@ object DotcomRenderingDataModel {
)
}

def forCrossword(
crosswordPage: CrosswordPageWithContent,
request: RequestHeader,
pageType: PageType,
): DotcomRenderingDataModel = {
val linkedData = LinkedData.forArticle(
article = crosswordPage.item,
baseURL = Configuration.dotcom.baseUrl,
fallbackLogo = Configuration.images.fallbackLogo,
)

apply(
page = crosswordPage,
request = request,
pageType = pageType,
linkedData = linkedData,
mainBlock = None,
bodyBlocks = Seq.empty,
crossword = Some(crosswordPage.crossword),
)
}

def keyEventsFallback(
blocks: APIBlocks,
): Seq[APIBlock] = {
Expand Down Expand Up @@ -442,6 +468,7 @@ object DotcomRenderingDataModel {
filterKeyEvents: Boolean = false,
mostRecentBlockId: Option[String] = None,
forceLive: Boolean = false,
crossword: Option[CrosswordData] = None,
): DotcomRenderingDataModel = {

val edition = Edition.edition(request)
Expand Down Expand Up @@ -635,6 +662,7 @@ object DotcomRenderingDataModel {
showTableOfContents = content.fields.showTableOfContents.getOrElse(false),
lang = content.fields.lang,
isRightToLeftLang = content.fields.isRightToLeftLang,
crossword = crossword,
)
}
}
Loading

0 comments on commit 7a52965

Please sign in to comment.