Skip to content

Commit

Permalink
Merge branch 'main' into use-jsvalue-not-string-for-dcar
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieB-gu committed Dec 10, 2024
2 parents aa37687 + c9c2bca commit 812db30
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 25 deletions.
58 changes: 44 additions & 14 deletions applications/app/controllers/CrosswordsController.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package controllers

import com.gu.contentapi.client.model.v1.CrosswordType.{Cryptic, Quick}
import com.gu.contentapi.client.model.v1.{Crossword, ItemResponse, Content => ApiContent, Section => ApiSection}
import com.gu.contentapi.client.model.v1.{
Crossword,
ItemResponse,
SearchResponse,
Content => ApiContent,
Section => ApiSection,
}
import common.{Edition, GuLogging, ImplicitControllerExecutionContext}
import conf.Static
import contentapi.ContentApiClient
import com.gu.contentapi.client.model.SearchQuery
import crosswords.{
AccessibleCrosswordPage,
AccessibleCrosswordRows,
Expand All @@ -18,6 +24,7 @@ import html.HtmlPageHelpers.ContentCSSFile
import model.Cached.{RevalidatableResult, WithoutRevalidationResult}
import model._
import model.dotcomrendering.pageElements.EditionsCrosswordRenderingDataModel
import model.dotcomrendering.pageElements.EditionsCrosswordRenderingDataModel.toJson
import model.dotcomrendering.{DotcomRenderingDataModel, PageType}
import org.joda.time.{DateTime, LocalDate}
import pages.{CrosswordHtmlPage, IndexHtmlPage, PrintableCrosswordHtmlPage}
Expand Down Expand Up @@ -305,21 +312,44 @@ class CrosswordEditionsController(
def digitalEdition: Action[AnyContent] = Action.async { implicit request =>
getCrosswords
.map(parseCrosswords)
.flatMap {
case Some(crosswordPage) =>
remoteRenderer.getEditionsCrossword(wsClient, crosswordPage)
case None => Future.successful(NotFound)
.flatMap { crosswords =>
remoteRenderer.getEditionsCrossword(wsClient, crosswords)
}
}

private lazy val crosswordsQuery = contentApiClient.item("crosswords")
def digitalEditionJson: Action[AnyContent] = Action.async { implicit request =>
getCrosswords
.map(parseCrosswords)
.map { crosswords =>
Cached(CacheTime.Default)(RevalidatableResult.Ok(toJson(crosswords))).as("application/json")
}
}

private def getCrosswords: Future[ItemResponse] = contentApiClient.getResponse(crosswordsQuery)
private def getCrosswords: Future[SearchResponse] =
contentApiClient.getResponse(crosswordsQuery)

/** Search for playable crosswords sorted by print publication date. This will exclude older, originally print-only
* crosswords that happen to have been re-published in a digital format recently.
*/
private lazy val crosswordsQuery =
SearchQuery()
.contentType("crossword")
.tag(crosswordTags)
.useDate("newspaper-edition")
.pageSize(25)

private lazy val crosswordTags = Seq(
"crosswords/series/quick",
"crosswords/series/cryptic",
"crosswords/series/prize",
"crosswords/series/weekend-crossword",
"crosswords/series/quick-cryptic",
"crosswords/series/everyman",
"crosswords/series/speedy",
"crosswords/series/quiptic",
).mkString("|")

private def parseCrosswords(response: SearchResponse): EditionsCrosswordRenderingDataModel =
EditionsCrosswordRenderingDataModel(response.results.flatMap(_.crossword))

private def parseCrosswords(response: ItemResponse): Option[EditionsCrosswordRenderingDataModel] =
for {
results <- response.results
quick <- results.find(_.crossword.exists(_.`type` == Quick)).flatMap(_.crossword)
cryptic <- results.find(_.crossword.exists(_.`type` == Cryptic)).flatMap(_.crossword)
} yield EditionsCrosswordRenderingDataModel(quick, cryptic)
}
3 changes: 2 additions & 1 deletion applications/app/views/fragments/gallerySlot.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Seq("gallery-inline", "dark") ++ (if(isMobile) Some("mobile") else None),
Map(),
optId = if(isMobile) Some(s"$slotName--mobile") else None,
optClassNames = if(isMobile) Some("mobile-only") else Some("hide-until-tablet")
optClassNames = if(isMobile) Some("mobile-only") else Some("hide-until-tablet"),
useFlexContainer = true
){ }
}
1 change: 1 addition & 0 deletions applications/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ GET /crosswords/lookup

# Crosswords digital edition
GET /crosswords/digital-edition controllers.CrosswordEditionsController.digitalEdition
GET /crosswords/digital-edition.json controllers.CrosswordEditionsController.digitalEditionJson

# Email paths
GET /email/form/$emailType<plain|plaindark|plaintone>/$listId<[0-9]+> controllers.EmailSignupController.renderForm(emailType: String, listId: Int)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,33 @@ package model.dotcomrendering.pageElements
import com.gu.contentapi.client.model.v1.Crossword
import com.gu.contentapi.json.CirceEncoders._
import io.circe.syntax._
import implicits.Dates.CapiRichDateTime
import model.dotcomrendering.DotcomRenderingUtils
import play.api.libs.json.{JsObject, Json, JsValue}

case class EditionsCrosswordRenderingDataModel(
quick: Crossword,
cryptic: Crossword,
crosswords: Iterable[Crossword],
)

object EditionsCrosswordRenderingDataModel {
def apply(crosswords: Iterable[Crossword]): EditionsCrosswordRenderingDataModel =
new EditionsCrosswordRenderingDataModel(crosswords.map(crossword => {
val shipSolutions =
crossword.dateSolutionAvailable
.map(_.toJoda.isBeforeNow)
.getOrElse(crossword.solutionAvailable)

if (shipSolutions) {
crossword
} else {
crossword.copy(entries = crossword.entries.map(_.copy(solution = None)))
}
}))

def toJson(model: EditionsCrosswordRenderingDataModel): JsValue =
DotcomRenderingUtils.withoutNull(
Json.obj(
"quick" -> Json.parse(model.quick.asJson.toString()),
"cryptic" -> Json.parse(model.cryptic.asJson.toString()),
"crosswords" -> Json.parse(model.crosswords.asJson.toString()),
),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package model.dotcomrendering

import com.gu.contentapi.client.model.v1.{CapiDateTime, Crossword, CrosswordType, CrosswordDimensions, CrosswordEntry}
import model.dotcomrendering.pageElements.EditionsCrosswordRenderingDataModel
import org.mockito.Mockito.when
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatestplus.mockito.MockitoSugar
import org.joda.time.DateTime

class EditionsCrosswordRenderingDataModelTest extends AnyFlatSpec with Matchers with MockitoSugar {
val mockEntry = CrosswordEntry(
id = "mockId",
solution = Some("Mock solution"),
)

val mockCrossword = Crossword(
name = "Mock name",
`type` = CrosswordType.Quick,
number = 1,
date = CapiDateTime(DateTime.now().getMillis(), "date"),
dimensions = CrosswordDimensions(1, 1),
entries = Seq(mockEntry, mockEntry),
solutionAvailable = true,
hasNumbers = false,
randomCluesOrdering = false,
)

"apply" should "provide solutions when 'dateSolutionAvailable' is in the past" in {
val crossword = mockCrossword.copy(
solutionAvailable = true,
dateSolutionAvailable = Some(CapiDateTime(DateTime.now().minusDays(1).getMillis(), "date")),
)

val crosswords =
EditionsCrosswordRenderingDataModel(Seq(crossword, crossword)).crosswords.toSeq

crosswords(0).entries(0).solution shouldBe Some("Mock solution")
crosswords(0).entries(1).solution shouldBe Some("Mock solution")
crosswords(1).entries(0).solution shouldBe Some("Mock solution")
crosswords(1).entries(1).solution shouldBe Some("Mock solution")
}

"apply" should "provide solutions when 'dateSolutionAvailable' is 'None' and solutionAvailable is 'true'" in {
val crossword = mockCrossword.copy(
solutionAvailable = true,
dateSolutionAvailable = None,
)

val crosswords =
EditionsCrosswordRenderingDataModel(Seq(crossword, crossword)).crosswords.toSeq

crosswords(0).entries(0).solution shouldBe Some("Mock solution")
crosswords(0).entries(1).solution shouldBe Some("Mock solution")
crosswords(1).entries(0).solution shouldBe Some("Mock solution")
crosswords(1).entries(1).solution shouldBe Some("Mock solution")
}

"apply" should "not provide solutions when 'dateSolutionAvailable' is in the future" in {
val crossword = mockCrossword.copy(
solutionAvailable = true,
dateSolutionAvailable = Some(CapiDateTime(DateTime.now().plusDays(1).getMillis(), "date")),
)

val crosswords =
EditionsCrosswordRenderingDataModel(Seq(crossword, crossword)).crosswords.toSeq

crosswords(0).entries(0).solution shouldBe None
crosswords(0).entries(1).solution shouldBe None
crosswords(1).entries(0).solution shouldBe None
crosswords(1).entries(1).solution shouldBe None
}

"apply" should "not provide solutions when 'dateSolutionAvailable' is 'None' and solutionAvailable is 'false'" in {
val crossword = mockCrossword.copy(
solutionAvailable = false,
dateSolutionAvailable = None,
)

val crosswords =
EditionsCrosswordRenderingDataModel(Seq(crossword, crossword)).crosswords.toSeq

crosswords(0).entries(0).solution shouldBe None
crosswords(0).entries(1).solution shouldBe None
crosswords(1).entries(0).solution shouldBe None
crosswords(1).entries(1).solution shouldBe None
}
}
1 change: 1 addition & 0 deletions dev-build/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ GET /crosswords/lookup

# Crosswords digital edition
GET /crosswords/digital-edition controllers.CrosswordEditionsController.digitalEdition
GET /crosswords/digital-edition.json controllers.CrosswordEditionsController.digitalEditionJson

# Email paths
GET /email/form/$emailType<plain|plaindark|plaintone>/$listId<[0-9]+> controllers.EmailSignupController.renderForm(emailType: String, listId: Int)
Expand Down
2 changes: 1 addition & 1 deletion project/ProjectSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object ProjectSettings {
Compile / packageDoc / publishArtifact := false,
Compile / doc / sources := Seq.empty,
Compile / doc := target.map(_ / "none").value,
scalaVersion := "2.13.14",
scalaVersion := "2.13.15",
cleanAll := Def.taskDyn {
val allProjects = ScopeFilter(inAnyProject)
clean.all(allProjects)
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.10.2
sbt.version=1.10.5
6 changes: 3 additions & 3 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ libraryDependencies ++= Seq(

resolvers ++= Resolver.sonatypeOssRepos("releases")

addSbtPlugin("org.playframework" % "sbt-plugin" % "3.0.5")
addSbtPlugin("org.playframework" % "sbt-plugin" % "3.0.6")

addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4")

addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4")

addSbtPlugin("com.github.sbt" % "sbt-git" % "2.0.1")
addSbtPlugin("com.github.sbt" % "sbt-git" % "2.1.0")

addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")

addDependencyTreePlugin

addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.12.0")
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1")
2 changes: 1 addition & 1 deletion static/src/stylesheets/module/_adslot.scss
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@
.ad-slot--gallery-inline,
.ad-slot--liveblog-inline {
width: $mpu-original-width;
margin: $gs-baseline auto;
margin: $gs-baseline 0;
min-width: $mpu-original-width;
min-height: $mpu-original-height + $mpu-ad-label-height;
text-align: center;
Expand Down

0 comments on commit 812db30

Please sign in to comment.