11package agents
22
3+ import com .gu .contentapi .client .model .v1 .{Content , ElementType }
4+ import com .gu .contentapi .client .utils .CapiModelEnrichment .RenderingFormat
35import common ._
46import conf .Configuration
7+ import conf .switches .Switches .UseTrailsFromS3
58import model .dotcomrendering .Trail
6- import services .S3Async
9+ import services .{ FaciaContentConvert , OphanApi , S3Async }
710
811import scala .concurrent .{ExecutionContext , Future }
12+ import scala .util .control .NonFatal
13+ import contentapi .ContentApiClient
14+ import layout .DiscussionSettings
15+ import model .ContentFormat
916
1017object DeeplyReadS3Agent extends S3Async {
1118 override lazy val bucket = Configuration .cache.bucket;
1219 lazy val stage : String = Configuration .environment.stage.toUpperCase
1320}
1421
15- class DeeplyReadAgent extends GuLogging {
22+ class DeeplyReadAgent ( contentApiClient : ContentApiClient , ophanApi : OphanApi ) extends GuLogging {
1623
1724 private val deeplyReadItems = Box [Map [Edition , Seq [Trail ]]](Map .empty)
1825
19- def refresh ()(implicit ec : ExecutionContext ): Future [Unit ] = {
20- log.debug(s " Deeply Read Agent refresh() " )
26+ def getTrailsFromS3 (edition : Edition )(implicit ec : ExecutionContext ): Future [Unit ] = {
2127 Future
2228 .sequence(Edition .allEditions.map { edition =>
2329 DeeplyReadS3Agent
@@ -43,12 +49,130 @@ class DeeplyReadAgent extends GuLogging {
4349 })
4450 }
4551
52+ def removeStartingSlash (path : String ): String = {
53+ if (path.startsWith(" /" )) path.stripPrefix(" /" ) else path
54+ }
55+
56+ def correctPillar (pillar : String ): String = if (pillar == " arts" ) " culture" else pillar
57+
58+ def deeplyReadUrlToTrail (content : Content ): Option [Trail ] = {
59+
60+ val contentFormat : ContentFormat = ContentFormat (content.design, content.theme, content.display)
61+
62+ for {
63+ webPublicationDate <- content.webPublicationDate
64+ fields <- content.fields
65+ linkText <- fields.trailText
66+ pillar <- content.pillarName
67+ headline <- fields.headline
68+ shortUrl <- fields.shortUrl
69+ } yield Trail (
70+ url = content.webUrl,
71+ linkText = linkText,
72+ showByline = false ,
73+ byline = fields.byline,
74+ masterImage = None ,
75+ image = fields.thumbnail,
76+ carouselImages = Map .empty,
77+ ageWarning = None ,
78+ isLiveBlog = fields.liveBloggingNow.getOrElse(false ),
79+ pillar = correctPillar(pillar.toLowerCase),
80+ designType = content.`type`.toString,
81+ format = contentFormat,
82+ webPublicationDate = webPublicationDate.toString(),
83+ headline = headline,
84+ mediaType = None ,
85+ shortUrl = shortUrl,
86+ kickerText = None ,
87+ starRating = None ,
88+ avatarUrl = None ,
89+ branding = None ,
90+ discussion = DiscussionSettings .fromTrail(FaciaContentConvert .contentToFaciaContent(content)),
91+ trailText = content.fields.flatMap(_.trailText),
92+ galleryCount =
93+ content.elements.map(_.count(el => el.`type` == ElementType .Image && el.relation == " gallery" )).filter(_ > 0 ),
94+ )
95+ }
96+
97+ def getTrailsFromCAPI (edition : Edition )(implicit ec : ExecutionContext ): Future [Unit ] = {
98+ /*
99+ We query Ophan for the deeply read URLs and use them to queryCapi
100+ then use this information to create a sequence of trails that we cache
101+ using a Box structure.
102+ */
103+ Future
104+ .sequence(Edition .allEditions.map { edition =>
105+ ophanApi
106+ .getDeeplyRead(edition)
107+ .flatMap { ophanDeeplyReadItems =>
108+ log.debug(s " Fetched ${ophanDeeplyReadItems.size} Deeply Read items for ${edition.displayName}" )
109+ val constructedTrail : Seq [Future [Option [Trail ]]] = ophanDeeplyReadItems.map { ophanItem =>
110+ log.debug(s " CAPI lookup for Ophan deeply read item: ${ophanItem.toString}" )
111+ val path = removeStartingSlash(ophanItem.path)
112+ log.debug(s " CAPI Lookup for path: $path" )
113+ val capiRequest = contentApiClient
114+ .item(path)
115+ .showTags(" all" )
116+ .showFields(" all" )
117+ .showReferences(" none" )
118+ .showAtoms(" none" )
119+
120+ contentApiClient
121+ .getResponse(capiRequest)
122+ .map { res =>
123+ res.content.flatMap { capiData =>
124+ log.debug(s " Retrieved CAPI data for Deeply Read item: ${path}" )
125+ deeplyReadUrlToTrail(capiData)
126+ }
127+ }
128+ .recover { case NonFatal (e) =>
129+ log.error(s " Error retrieving CAPI data for Deeply Read item: ${path}. ${e.getMessage}" )
130+ None
131+ }
132+ }
133+ Future
134+ .sequence(constructedTrail)
135+ .map { maybeTrails =>
136+ (edition, maybeTrails.flatten.take(10 ))
137+ }
138+
139+ }
140+ .recover { e =>
141+ log.error(s " Failed to fetch Deeply Read items for ${edition.displayName}. ${e.getMessage()}" )
142+ (edition, Seq .empty)
143+ }
144+ })
145+ .map(trailsList => {
146+ val map = trailsList.toMap
147+ for {
148+ (edition, list) <- map
149+ } yield log.debug(s " Deeply Read in ${edition.displayName}, ${list.size} items: ${list.map(_.url).toString()}" )
150+
151+ val mapWithTenItems = map.filter { case (_, list) => list.size == 10 }
152+ log.debug(
153+ s " Updating the following ${mapWithTenItems.size} editions: ${mapWithTenItems.keys.map(_.id).toList.sorted.toString()}" ,
154+ )
155+
156+ deeplyReadItems.alter(deeplyReadItems.get() ++ mapWithTenItems)
157+ })
158+ }
159+
160+ def refresh ()(implicit ec : ExecutionContext ): Future [Unit ] = {
161+ if (UseTrailsFromS3 .isSwitchedOn) {
162+ // TODO revert to log.debug once we switch to S3 permanently
163+ log.info(s " Deeply Read Agent refresh() - Using S3 " )
164+ getTrailsFromS3(Edition .defaultEdition)
165+ } else {
166+ log.info(s " Deeply Read Agent refresh() - Using CAPI " )
167+ getTrailsFromCAPI(Edition .defaultEdition)
168+ }
169+ }
170+
46171 def getTrails (edition : Edition )(implicit ec : ExecutionContext ): Seq [Trail ] = {
47172 val updatedTrails = deeplyReadItems.get().getOrElse(edition, Seq .empty)
48173 if (updatedTrails.isEmpty) {
49174 refresh()
50175 }
51176 updatedTrails
52177 }
53-
54178}
0 commit comments