Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crossword picker and JSON route #26843

Merged
merged 3 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
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
Loading