Skip to content

Commit

Permalink
Merge branch 'main' into upgrades
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielCliftonGuardian authored Dec 9, 2024
2 parents 9cf307c + cdd23dd commit bab79db
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 100 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ jobs:
-DAPP_SECRET="fake_secret" \
-Duser.timezone=Australia/Sydney \
-jar ./bin/sbt-launch.jar clean compile assets scalafmtCheckAll test Universal/packageBin
- name: Test Summary
uses: test-summary/action@v2
with:
paths: "test-results/**/TEST-*.xml"
if: always()

- uses: guardian/actions-riff-raff@v4
env:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,4 @@ static/src/stylesheets/pasteup/.npmrc
metals.sbt

.java-version
test-results/
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 @@ -304,21 +311,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)
}
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
12 changes: 1 addition & 11 deletions common/app/conf/switches/ABTestSwitches.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ trait ABTestSwitches {
"Test the Opt Out frequency capping feature",
owners = Seq(Owner.withEmail("[email protected]")),
safeState = Off,
sellByDate = Some(LocalDate.of(2024, 12, 2)),
sellByDate = Some(LocalDate.of(2025, 1, 29)),
exposeClientSide = true,
highImpact = false,
)
Expand All @@ -71,16 +71,6 @@ trait ABTestSwitches {
highImpact = false,
)

Switch(
ABTests,
"ab-new-header-bidding-endpoint",
"Test new header bidding (prebid) analytics endpoint",
owners = Seq(Owner.withEmail("[email protected]")),
safeState = Off,
sellByDate = Some(LocalDate.of(2024, 12, 2)),
exposeClientSide = true,
highImpact = false,
)
Switch(
ABTests,
"ab-gpid-prebid-ad-units",
Expand Down
4 changes: 2 additions & 2 deletions common/app/conf/switches/FeatureSwitches.scala
Original file line number Diff line number Diff line change
Expand Up @@ -567,9 +567,9 @@ trait FeatureSwitches {
group = SwitchGroup.Feature,
name = "disable-front-container-show-hide",
description = "For users with no currently hidden containers on a front, removes the ability to hide containers",
owners = Seq(Owner.withGithub("cemms1")),
owners = Seq(Owner.withEmail("[email protected]")),
safeState = On,
sellByDate = LocalDate.of(2024, 11, 29),
sellByDate = LocalDate.of(2025, 2, 4),
exposeClientSide = true,
highImpact = false,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,30 @@ import com.gu.contentapi.client.model.v1.Crossword
import com.gu.contentapi.json.CirceEncoders._
import io.circe.JsonObject
import io.circe.syntax._
import implicits.Dates.CapiRichDateTime

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): String = {
JsonObject(
"quick" -> model.quick.asJson.dropNullValues,
"cryptic" -> model.cryptic.asJson.dropNullValues,
).asJson.dropNullValues.noSpaces
"crosswords" -> model.crosswords.asJson.deepDropNullValues,
).asJson.noSpaces
}
}
3 changes: 2 additions & 1 deletion common/app/navigation/NavLinks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,12 @@ object NavLinks {
usNews,
usPolitics,
world,
usEnvironment,
climateCrisis,
middleEast,
ukraine,
usSoccer,
usBusiness,
usEnvironment,
usTech,
science,
newsletters,
Expand Down
78 changes: 78 additions & 0 deletions common/test/helpers/FaciaTestData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ trait FaciaTestData extends ModelHelper {
"/commentisfree/2013/oct/07/feminism-rebranding-man-hater",
)

val europeFrontTrailIds: Seq[String] = Seq(
"/world/2024/nov/27/plan-to-cut-berlin-arts-budget-will-destroy-citys-culture-directors-warn",
"/stage/2024/nov/27/afd-threats-to-german-democracy-on-stage-maximilian-steinbeis-a-citizen-of-the-people",
"/commentisfree/2024/nov/27/france-cannabis-consumption-europe-tax-decriminalisation-crime",
"/football/2024/nov/27/bodo-glimt-manchester-united-europa-league",
"/books/2024/nov/26/freedom-by-angela-merkel-review-settling-scores-with-silence",
)

val europeBetaFrontTrailIds: Seq[String] = Seq(
"/business/2024/nov/27/just-eat-to-delist-from-london-stock-exchange-to-cut-complexity-and-costs",
"/world/2024/nov/26/uk-labour-cabinet-ministers-sanctions-russia-storm-shadow-missiles",
"/business/2024/nov/27/fining-budget-airlines-will-make-flying-more-expensive-says-easyjet-boss",
"/world/2024/nov/26/irish-pm-simon-harris-slump-drops-points-polls-election-fine-gael",
"/commentisfree/2024/nov/25/the-guardian-view-on-romanias-presidential-election-a-stable-ukrainian-ally-wobblese",
)

val cultureTrailIds: Seq[String] =
Seq(
"/film/2013/oct/08/gravity-science-astrophysicist",
Expand All @@ -82,6 +98,8 @@ trait FaciaTestData extends ModelHelper {
val ukFrontTrails: Seq[PressedContent] = ukFrontTrailIds map TestContent.newFaciaContent
val usFrontTrails: Seq[PressedContent] = usFrontTrailIds map TestContent.newFaciaContent
val auFrontTrails: Seq[PressedContent] = auFrontTrailIds map TestContent.newFaciaContent
val europeFrontTrails: Seq[PressedContent] = europeFrontTrailIds map TestContent.newFaciaContent
val europeBetaFrontTrails: Seq[PressedContent] = europeBetaFrontTrailIds map TestContent.newFaciaContent

val cultureFrontTrails: Seq[PressedContent] = cultureTrailIds map TestContent.newFaciaContent

Expand Down Expand Up @@ -172,6 +190,64 @@ trait FaciaTestData extends ModelHelper {
),
)

val europeFaciaPage: PressedPage = PressedPage(
id = "europe",
SeoData.fromPath("europe"),
FrontProperties.empty,
collections = List(
PressedCollection(
id = "europe/news/regular-stories",
displayName = "",
curated = europeFrontTrails.toList,
backfill = Nil,
treats = Nil,
lastUpdated = None,
href = None,
description = None,
collectionType = "",
groups = None,
uneditable = false,
showTags = false,
showSections = false,
hideKickers = false,
showDateHeader = false,
showLatestUpdate = false,
config = CollectionConfig.empty,
hasMore = false,
targetedTerritory = None,
),
),
)

val europeBetaFaciaPageWithTargetedTerritory: PressedPage = PressedPage(
id = "europe-beta",
SeoData.fromPath("europe-beta"),
FrontProperties.empty,
collections = List(
PressedCollection(
id = "europe-beta/news/regular-stories",
displayName = "",
curated = europeBetaFrontTrails.toList,
backfill = Nil,
treats = Nil,
lastUpdated = None,
href = None,
description = None,
collectionType = "",
groups = None,
uneditable = false,
showTags = false,
showSections = false,
hideKickers = false,
showDateHeader = false,
showLatestUpdate = false,
config = CollectionConfig.empty,
hasMore = false,
targetedTerritory = Some(EU27Territory),
),
),
)

val ukCultureFaciaPage: PressedPage = PressedPage(
id = "uk/culture",
SeoData.fromPath("uk/culture"),
Expand Down Expand Up @@ -334,6 +410,8 @@ trait FaciaTestData extends ModelHelper {
("uk", new TestPageFront("uk", Uk, ukFaciaPage)),
("us", new TestPageFront("us", Us, usFaciaPage)),
("au", new TestPageFront("au", Au, auFaciaPage)),
("europe", new TestPageFront("europe", Au, europeFaciaPage)),
("europe-beta", new TestPageFront("europe-beta", Au, europeBetaFaciaPageWithTargetedTerritory)),
("uk/culture", new TestPageFront("uk/culture", Uk, ukCultureFaciaPage)),
("us/culture", new TestPageFront("us/culture", Us, usCultureFaciaPage)),
("au/culture", new TestPageFront("au/culture", Au, auCultureFaciaPage)),
Expand Down
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
}
}
Loading

0 comments on commit bab79db

Please sign in to comment.