From 92440cf29dba10dfb70653c8ee92d0024b933b92 Mon Sep 17 00:00:00 2001 From: David Blatcher Date: Fri, 15 Nov 2024 10:13:34 +0000 Subject: [PATCH 1/5] new snap type, identify snap links to newsletter API --- common/app/layout/SnapStuff.scala | 50 ++++++++++++++++++++++++++++--- common/app/layout/SnapType.scala | 1 + 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/common/app/layout/SnapStuff.scala b/common/app/layout/SnapStuff.scala index c3ee65fb7b23..aeecb80691ec 100644 --- a/common/app/layout/SnapStuff.scala +++ b/common/app/layout/SnapStuff.scala @@ -1,7 +1,10 @@ package layout +import java.net.URI import model.pressed._ import views.support._ +import services.NewsletterService +import conf.Configuration.newsletterApi case class SnapStuff( dataAttributes: String, @@ -10,6 +13,7 @@ case class SnapStuff( embedHtml: Option[String], embedCss: Option[String] = None, embedJs: Option[String] = None, + newsletterId: Option[String] = None, ) { def cssClasses: Seq[String] = Seq( @@ -43,13 +47,51 @@ object SnapStuff { case _ => None } + val newsletterId = faciaContent.properties.href match { + case Some(href) => { + extractNewsletterId(href) + } + case None => None + } + faciaContent.properties.embedType match { - case Some("latest") => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLatestSnap, embedHtml)) - case Some("link") => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLinkSnap, embedHtml)) + case Some("latest") => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLatestSnap, embedHtml, newsletterId = None)) + case Some("link") => newsletterId match { + case Some(id) => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendNewsletterSnap, embedHtml, newsletterId = Some(id))) + case None => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLinkSnap, embedHtml, newsletterId = None)) + } case Some("interactive") => - Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLinkSnap, embedHtml, embedCss, embedJs)) - case Some(_) => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendOtherSnap, embedHtml)) + Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLinkSnap, embedHtml, embedCss, embedJs, newsletterId = None)) + case Some(_) => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendOtherSnap, embedHtml, newsletterId = None)) case None => None } } + + private def isNewsletterApiUri(href:String):Boolean = { + val prefix:Option[String] = for { + host <- newsletterApi.host + origin <- newsletterApi.origin + } yield { + s"$host/api/newsletters" + } + prefix match { + case Some(url) => { + href.startsWith(prefix) + } + case None => { + println("newsletters API not configured!") + false + } + } + } + + private def extractNewsletterId(newsleterApiUri:String):Option[String] = { + isNewsletterApiUri(newsleterApiUri) match { + case true => { + val lastPartofPath:String = new URI(newsleterApiUri).getPath.tail + Some(lastPartofPath) + } + case _ => None + } + } } diff --git a/common/app/layout/SnapType.scala b/common/app/layout/SnapType.scala index cb6d320f71c6..2eb7cfce4a51 100644 --- a/common/app/layout/SnapType.scala +++ b/common/app/layout/SnapType.scala @@ -5,3 +5,4 @@ sealed trait SnapType case object FrontendLatestSnap extends SnapType case object FrontendLinkSnap extends SnapType case object FrontendOtherSnap extends SnapType +case object FrontendNewsletterSnap extends SnapType From a3dce9162506f16a185a79af5095429ca634bfa1 Mon Sep 17 00:00:00 2001 From: David Blatcher Date: Fri, 15 Nov 2024 10:44:24 +0000 Subject: [PATCH 2/5] add newsletters data to DotcomFrontsRenderingDataModel --- .../DotcomFrontsRenderingDataModel.scala | 29 +++++++++++++++++++ .../renderers/DotcomRenderingService.scala | 2 ++ facia/app/controllers/FaciaController.scala | 5 ++++ 3 files changed, 36 insertions(+) diff --git a/common/app/model/dotcomrendering/DotcomFrontsRenderingDataModel.scala b/common/app/model/dotcomrendering/DotcomFrontsRenderingDataModel.scala index dbec33a6051b..09c9e3d0b8b8 100644 --- a/common/app/model/dotcomrendering/DotcomFrontsRenderingDataModel.scala +++ b/common/app/model/dotcomrendering/DotcomFrontsRenderingDataModel.scala @@ -8,6 +8,8 @@ import com.gu.contentapi.client.model.v1.Content import experiments.ActiveExperiments import layout.ContentCard import model.{PressedPage, RelatedContentItem} +import services.NewsletterData +import services.newsletters.model.NewsletterResponseV2 import navigation.{FooterLinks, Nav} import play.api.libs.json.{JsObject, JsValue, Json, OWrites} import play.api.mvc.RequestHeader @@ -33,6 +35,7 @@ case class DotcomFrontsRenderingDataModel( deeplyRead: Option[Seq[Trail]], contributionsServiceUrl: String, canonicalUrl: String, + newsletters: List[NewsletterData], ) object DotcomFrontsRenderingDataModel { @@ -46,6 +49,7 @@ object DotcomFrontsRenderingDataModel { mostCommented: Option[Content], mostShared: Option[Content], deeplyRead: Option[Seq[Trail]], + newsletters: List[NewsletterResponseV2], ): DotcomFrontsRenderingDataModel = { val edition = Edition.edition(request) val nav = Nav(page, edition) @@ -75,6 +79,14 @@ object DotcomFrontsRenderingDataModel { .map { _.perEdition.mapKeys(_.id) } .getOrElse(Map.empty[String, EditionCommercialProperties]) + // TO DO - could reduce payload size to DCR by only including the + // live newsletters for which there is some some item in some collection + // in the page which has a FrontendNewsletterSnap for which the + // newsletterId is the identityName of the newsletter + val newsletterData = newsletters + .filter((newsletter) => newsletter.status.equalsIgnoreCase(("live"))) + .map((newsletter) => convertNewsletterResponseToData(newsletter)) + DotcomFrontsRenderingDataModel( pressedPage = page, nav = nav, @@ -95,6 +107,7 @@ object DotcomFrontsRenderingDataModel { deeplyRead = deeplyRead, contributionsServiceUrl = Configuration.contributionsService.url, canonicalUrl = CanonicalLink(request, page.metadata.webUrl), + newsletters = newsletterData, ) } @@ -102,4 +115,20 @@ object DotcomFrontsRenderingDataModel { val jsValue = Json.toJson(model) Json.stringify(DotcomRenderingUtils.withoutNull(jsValue)) } + + // TO DO - refactor. this function is repeated in 3 places now + private def convertNewsletterResponseToData(response: NewsletterResponseV2): NewsletterData = { + NewsletterData( + response.identityName, + response.name, + response.theme, + response.signUpDescription, + response.frequency, + response.listId, + response.group, + response.mailSuccessDescription.getOrElse("You are subscribed"), + response.regionFocus, + response.illustrationCard, + ) + } } diff --git a/common/app/renderers/DotcomRenderingService.scala b/common/app/renderers/DotcomRenderingService.scala index d9cf9b59e0a1..a3063c168e18 100644 --- a/common/app/renderers/DotcomRenderingService.scala +++ b/common/app/renderers/DotcomRenderingService.scala @@ -265,6 +265,7 @@ class DotcomRenderingService extends GuLogging with ResultWithPreconnectPreload mostCommented: Option[Content], mostShared: Option[Content], deeplyRead: Option[Seq[Trail]], + newsletters: List[NewsletterResponseV2], )(implicit request: RequestHeader): Future[Result] = { val dataModel = DotcomFrontsRenderingDataModel( page, @@ -274,6 +275,7 @@ class DotcomRenderingService extends GuLogging with ResultWithPreconnectPreload mostCommented, mostShared, deeplyRead, + newsletters, ) val json = DotcomFrontsRenderingDataModel.toJson(dataModel) diff --git a/facia/app/controllers/FaciaController.scala b/facia/app/controllers/FaciaController.scala index ca80f685e31f..685dfb37a7ba 100644 --- a/facia/app/controllers/FaciaController.scala +++ b/facia/app/controllers/FaciaController.scala @@ -24,6 +24,7 @@ import play.twirl.api.Html import renderers.DotcomRenderingService import services.dotcomrendering.{FaciaPicker, RemoteRender} import services.fronts.{FrontJsonFapi, FrontJsonFapiLive} +import services.newsletters.NewsletterSignupAgent import services.{CollectionConfigWithId, ConfigAgent} import utils.TargetedCollections import views.html.fragments.containers.facia_cards.container @@ -32,6 +33,8 @@ import views.support.FaciaToMicroFormat2Helpers.getCollection import scala.concurrent.Future import scala.concurrent.Future.successful +// TO DO - need to wire the NewsletterSignupAgent into this controller + trait FaciaController extends BaseController with GuLogging @@ -272,6 +275,7 @@ trait FaciaController mostCommented = mostViewedAgent.mostCommented, mostShared = mostViewedAgent.mostShared, deeplyRead = deeplyRead, + newsletters = List.empty, )(request), targetedTerritories, ) @@ -297,6 +301,7 @@ trait FaciaController mostCommented = mostViewedAgent.mostCommented, mostShared = mostViewedAgent.mostShared, deeplyRead = deeplyRead, + newsletters = List.empty ), ) } else JsonFront(faciaPage) From bf37779236a70425de31fe8d8648e2361310facf Mon Sep 17 00:00:00 2001 From: David Blatcher Date: Fri, 15 Nov 2024 17:37:30 +0000 Subject: [PATCH 3/5] wire the newsletters service into facia controller --- facia/app/AppLoader.scala | 3 +++ facia/app/controllers/FaciaController.scala | 13 ++++++++++--- facia/app/controllers/FaciaControllers.scala | 2 ++ preview/app/AppLoader.scala | 4 ++++ preview/app/controllers/FaciaDraftController.scala | 2 ++ 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/facia/app/AppLoader.scala b/facia/app/AppLoader.scala index e1677a9c5c29..622c2d624ee3 100644 --- a/facia/app/AppLoader.scala +++ b/facia/app/AppLoader.scala @@ -23,6 +23,7 @@ import play.api.libs.ws.WSClient import services._ import services.fronts.{FrontJsonFapiDraft, FrontJsonFapiLive} import router.Routes +import services.newsletters.{NewsletterSignupAgent, NewsletterApi} import scala.concurrent.ExecutionContext @@ -44,11 +45,13 @@ trait AppComponents extends FrontendComponents with FaciaControllers with FapiSe lazy val capiHttpClient: HttpClient = wire[CapiHttpClient] lazy val contentApiClient = wire[ContentApiClient] + lazy val newsletterApi = wire[NewsletterApi] lazy val healthCheck = wire[HealthCheck] lazy val devAssetsController = wire[DevAssetsController] lazy val ophanApi = wire[OphanApi] lazy val mostViewedAgent = wire[MostViewedAgent] lazy val deeplyReadAgent = wire[DeeplyReadAgent] + lazy val newsletterSignupAgent = wire[NewsletterSignupAgent] override lazy val lifecycleComponents = List( wire[ConfigAgentLifecycle], diff --git a/facia/app/controllers/FaciaController.scala b/facia/app/controllers/FaciaController.scala index 685dfb37a7ba..acdba4c856a3 100644 --- a/facia/app/controllers/FaciaController.scala +++ b/facia/app/controllers/FaciaController.scala @@ -33,7 +33,6 @@ import views.support.FaciaToMicroFormat2Helpers.getCollection import scala.concurrent.Future import scala.concurrent.Future.successful -// TO DO - need to wire the NewsletterSignupAgent into this controller trait FaciaController extends BaseController @@ -44,6 +43,7 @@ trait FaciaController val frontJsonFapi: FrontJsonFapi val ws: WSClient val mostViewedAgent: MostViewedAgent + val newsletterSignupAgent: NewsletterSignupAgent val deeplyReadAgent: DeeplyReadAgent val remoteRenderer: DotcomRenderingService = DotcomRenderingService() val assets: Assets @@ -275,7 +275,10 @@ trait FaciaController mostCommented = mostViewedAgent.mostCommented, mostShared = mostViewedAgent.mostShared, deeplyRead = deeplyRead, - newsletters = List.empty, + newsletters = newsletterSignupAgent.getV2Newsletters() match { + case Right(newsletters) => newsletters + case Left(_) => List.empty + }, )(request), targetedTerritories, ) @@ -301,7 +304,10 @@ trait FaciaController mostCommented = mostViewedAgent.mostCommented, mostShared = mostViewedAgent.mostShared, deeplyRead = deeplyRead, - newsletters = List.empty + newsletters = newsletterSignupAgent.getV2Newsletters() match { + case Right(newsletters) => newsletters + case Left(_) => List.empty + }, ), ) } else JsonFront(faciaPage) @@ -556,5 +562,6 @@ class FaciaControllerImpl( val mostViewedAgent: MostViewedAgent, val deeplyReadAgent: DeeplyReadAgent, val assets: Assets, + val newsletterSignupAgent: NewsletterSignupAgent, )(implicit val context: ApplicationContext) extends FaciaController diff --git a/facia/app/controllers/FaciaControllers.scala b/facia/app/controllers/FaciaControllers.scala index c9c9d86c8fab..a02987f94102 100644 --- a/facia/app/controllers/FaciaControllers.scala +++ b/facia/app/controllers/FaciaControllers.scala @@ -6,6 +6,7 @@ import model.ApplicationContext import play.api.libs.ws.WSClient import play.api.mvc.ControllerComponents import services.fronts.FrontJsonFapiLive +import services.newsletters.NewsletterSignupAgent trait FaciaControllers { def frontJsonFapiLive: FrontJsonFapiLive @@ -14,6 +15,7 @@ trait FaciaControllers { def mostViewedAgent: MostViewedAgent def deeplyReadAgent: DeeplyReadAgent def assets: Assets + def newsletterSignupAgent: NewsletterSignupAgent implicit def appContext: ApplicationContext lazy val faciaController = wire[FaciaControllerImpl] } diff --git a/preview/app/AppLoader.scala b/preview/app/AppLoader.scala index 73b9df1b38c5..bb91b89fc7e6 100644 --- a/preview/app/AppLoader.scala +++ b/preview/app/AppLoader.scala @@ -33,6 +33,7 @@ import services.fronts.FrontJsonFapiDraft import services.newsletters.NewsletterSignupLifecycle import services.{ConfigAgentLifecycle, OphanApi, SkimLinksCacheLifeCycle} import conf.Configuration.aws.mandatoryCredentials +import services.newsletters.{NewsletterSignupAgent, NewsletterApi} trait PreviewLifecycleComponents extends SportServices @@ -141,6 +142,9 @@ trait AppComponents ) override lazy val httpErrorHandler: HttpErrorHandler = wire[PreviewErrorHandler] + + override lazy val newsletterApi = wire[NewsletterApi] + override lazy val newsletterSignupAgent = wire[NewsletterSignupAgent] } class AppLoader extends FrontendApplicationLoader { diff --git a/preview/app/controllers/FaciaDraftController.scala b/preview/app/controllers/FaciaDraftController.scala index 63edc64e1d0b..66b8b352dd08 100644 --- a/preview/app/controllers/FaciaDraftController.scala +++ b/preview/app/controllers/FaciaDraftController.scala @@ -10,6 +10,7 @@ import play.api.libs.ws.WSClient import play.api.mvc._ import services.ConfigAgent import services.fronts.FrontJsonFapiDraft +import services.newsletters.NewsletterSignupAgent import scala.concurrent.Future @@ -19,6 +20,7 @@ class FaciaDraftController( sectionsLookUp: SectionsLookUp, val controllerComponents: ControllerComponents, val ws: WSClient, + val newsletterSignupAgent: NewsletterSignupAgent, val mostViewedAgent: MostViewedAgent, val deeplyReadAgent: DeeplyReadAgent, val assets: Assets, From a125476207892e8629b384e82dcf2f12d09c9fe8 Mon Sep 17 00:00:00 2001 From: David Blatcher Date: Fri, 15 Nov 2024 17:41:33 +0000 Subject: [PATCH 4/5] scalafmt --- common/app/layout/SnapStuff.scala | 49 ++++++++++++++++----- facia/app/controllers/FaciaController.scala | 1 - 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/common/app/layout/SnapStuff.scala b/common/app/layout/SnapStuff.scala index aeecb80691ec..ad9c22f0cd89 100644 --- a/common/app/layout/SnapStuff.scala +++ b/common/app/layout/SnapStuff.scala @@ -55,20 +55,45 @@ object SnapStuff { } faciaContent.properties.embedType match { - case Some("latest") => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLatestSnap, embedHtml, newsletterId = None)) - case Some("link") => newsletterId match { - case Some(id) => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendNewsletterSnap, embedHtml, newsletterId = Some(id))) - case None => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLinkSnap, embedHtml, newsletterId = None)) - } + case Some("latest") => + Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLatestSnap, embedHtml, newsletterId = None)) + case Some("link") => + newsletterId match { + case Some(id) => + Some( + SnapStuff( + snapData, + faciaContent.properties.embedCss, + FrontendNewsletterSnap, + embedHtml, + newsletterId = Some(id), + ), + ) + case None => + Some( + SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLinkSnap, embedHtml, newsletterId = None), + ) + } case Some("interactive") => - Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendLinkSnap, embedHtml, embedCss, embedJs, newsletterId = None)) - case Some(_) => Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendOtherSnap, embedHtml, newsletterId = None)) - case None => None + Some( + SnapStuff( + snapData, + faciaContent.properties.embedCss, + FrontendLinkSnap, + embedHtml, + embedCss, + embedJs, + newsletterId = None, + ), + ) + case Some(_) => + Some(SnapStuff(snapData, faciaContent.properties.embedCss, FrontendOtherSnap, embedHtml, newsletterId = None)) + case None => None } } - private def isNewsletterApiUri(href:String):Boolean = { - val prefix:Option[String] = for { + private def isNewsletterApiUri(href: String): Boolean = { + val prefix: Option[String] = for { host <- newsletterApi.host origin <- newsletterApi.origin } yield { @@ -85,10 +110,10 @@ object SnapStuff { } } - private def extractNewsletterId(newsleterApiUri:String):Option[String] = { + private def extractNewsletterId(newsleterApiUri: String): Option[String] = { isNewsletterApiUri(newsleterApiUri) match { case true => { - val lastPartofPath:String = new URI(newsleterApiUri).getPath.tail + val lastPartofPath: String = new URI(newsleterApiUri).getPath.tail Some(lastPartofPath) } case _ => None diff --git a/facia/app/controllers/FaciaController.scala b/facia/app/controllers/FaciaController.scala index f5b7f6b9dcac..7788d1930135 100644 --- a/facia/app/controllers/FaciaController.scala +++ b/facia/app/controllers/FaciaController.scala @@ -33,7 +33,6 @@ import views.support.FaciaToMicroFormat2Helpers.getCollection import scala.concurrent.Future import scala.concurrent.Future.successful - trait FaciaController extends BaseController with GuLogging From 0aa60c6b5876a163f67923b8c89410e44d3033d8 Mon Sep 17 00:00:00 2001 From: David Blatcher Date: Tue, 26 Nov 2024 10:25:36 +0000 Subject: [PATCH 5/5] put the newsletter api url in the dcr page model --- .../DotcomFrontsRenderingDataModel.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/common/app/model/dotcomrendering/DotcomFrontsRenderingDataModel.scala b/common/app/model/dotcomrendering/DotcomFrontsRenderingDataModel.scala index 09c9e3d0b8b8..ed48b435aa1b 100644 --- a/common/app/model/dotcomrendering/DotcomFrontsRenderingDataModel.scala +++ b/common/app/model/dotcomrendering/DotcomFrontsRenderingDataModel.scala @@ -14,6 +14,7 @@ import navigation.{FooterLinks, Nav} import play.api.libs.json.{JsObject, JsValue, Json, OWrites} import play.api.mvc.RequestHeader import views.support.{CamelCase, JavaScriptPage} +import conf.Configuration.newsletterApi case class DotcomFrontsRenderingDataModel( pressedPage: PressedPage, @@ -36,6 +37,7 @@ case class DotcomFrontsRenderingDataModel( contributionsServiceUrl: String, canonicalUrl: String, newsletters: List[NewsletterData], + newsletterApiUri: Option[String], ) object DotcomFrontsRenderingDataModel { @@ -108,6 +110,7 @@ object DotcomFrontsRenderingDataModel { contributionsServiceUrl = Configuration.contributionsService.url, canonicalUrl = CanonicalLink(request, page.metadata.webUrl), newsletters = newsletterData, + newsletterApiUri = getNewsletterApiUri(), ) } @@ -131,4 +134,12 @@ object DotcomFrontsRenderingDataModel { response.illustrationCard, ) } + + private def getNewsletterApiUri(): Option[String] = { + for { + host <- newsletterApi.host + } yield { + s"$host/api/newsletters" + } + } }