Skip to content

Commit c28d1ac

Browse files
rtyleyjonathonherbertfrederickobrien
authored
Basic Live-Harness endpoint functionality (#28438)
Co-authored-by: Jonathon Herbert <[email protected]> Co-authored-by: Frederick O'Brien <[email protected]>
1 parent 217a205 commit c28d1ac

File tree

5 files changed

+134
-6
lines changed

5 files changed

+134
-6
lines changed

applications/app/controllers/InteractiveController.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ class InteractiveController(
7373
capiLookup.lookup(path, range = Some(ArticleBlocks))
7474
}
7575

76+
def modelAndRenderHtml(response: ItemResponse)(
77+
modifier: BlocksOn[InteractivePage] => BlocksOn[InteractivePage] = identity,
78+
)(implicit req: RequestHeader): Future[Result] =
79+
modelAndRender(response)(pageBlocks => renderHtml(modifier(pageBlocks)))
80+
7681
def modelAndRender(
7782
response: ItemResponse,
7883
)(render: BlocksOn[InteractivePage] => Future[Result])(implicit req: RequestHeader): Future[Result] = {

common/app/model/meta/BlocksOn.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ import com.gu.contentapi.client.model.v1.Blocks
88
*
99
* https://docondev.com/blog/2020/6/2/refactoring-introduce-parameter-object
1010
*/
11-
case class BlocksOn[+P](page: P, blocks: Blocks)
11+
case class BlocksOn[+P](page: P, blocks: Blocks) {
12+
def mapBoth[Q >: P](p: P => Q, b: Blocks => Blocks) = BlocksOn(p(page), b(blocks))
13+
}
Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,63 @@
11
package controllers
22

3-
import play.api.mvc.{Action, AnyContent, BaseController, ControllerComponents}
3+
import com.gu.contentapi.client.model.v1.ContentType
4+
import common.{GuLogging, ImplicitControllerExecutionContext}
5+
import contentapi.ContentApiClient
6+
import model.{ApplicationContext, ArticleBlocks, GenericFallback}
7+
import play.api.libs.json.{JsError, JsSuccess, JsValue}
8+
import play.api.libs.ws.WSClient
9+
import play.api.mvc.{Action, BaseController, ControllerComponents}
10+
import renderers.DotcomRenderingService
11+
import services.{CAPILookup, NewsletterService}
12+
import utils.LiveHarness.inject
13+
import utils.LiveHarnessInteractiveAtom
14+
415
import scala.concurrent.Future
516

6-
class LiveHarnessController(val controllerComponents: ControllerComponents) extends BaseController {
7-
def renderLiveHarness(path: String): Action[AnyContent] = Action.async { implicit request =>
8-
Future.successful(Ok("Hello, world!"))
17+
class LiveHarnessController(
18+
contentApiClient: ContentApiClient,
19+
val controllerComponents: ControllerComponents,
20+
ws: WSClient,
21+
newsletterService: NewsletterService,
22+
)(implicit val context: ApplicationContext)
23+
extends BaseController
24+
with GuLogging
25+
with ImplicitControllerExecutionContext {
26+
27+
private val renderingService = DotcomRenderingService()
28+
29+
private val capiLookup: CAPILookup = new CAPILookup(contentApiClient)
30+
31+
private val articleController =
32+
new ArticleController(
33+
contentApiClient,
34+
controllerComponents,
35+
ws,
36+
renderingService,
37+
newsletterService,
38+
)
39+
40+
private val interactiveController =
41+
new InteractiveController(
42+
contentApiClient,
43+
ws,
44+
controllerComponents,
45+
renderingService,
46+
)
47+
48+
def renderLiveHarness(path: String): Action[JsValue] = Action.async(parse.json) { implicit request =>
49+
request.body.validate[List[LiveHarnessInteractiveAtom]] match {
50+
case JsSuccess(atoms, _) =>
51+
capiLookup.lookup(path, Some(ArticleBlocks)).flatMap { response =>
52+
response.content
53+
.map(_.`type`)
54+
.collect {
55+
case ContentType.Article => articleController.mapAndRender(path, GenericFallback)(inject(atoms))
56+
case ContentType.Interactive => interactiveController.modelAndRenderHtml(response)(inject(atoms))
57+
}
58+
.getOrElse(Future(BadRequest))
59+
}
60+
case JsError(errors) => Future(BadRequest(errors.toString()))
61+
}
962
}
1063
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package utils
2+
3+
import com.gu.contentapi.client.model.v1.{BlockElement, ContentAtomElementFields, ElementType}
4+
import model.content.InteractiveAtom
5+
import model.meta.BlocksOn
6+
import model.{ArticlePage, Content, ContentPage, InteractivePage}
7+
import play.api.libs.json.{Json, Reads}
8+
9+
case class LiveHarnessInteractiveAtom(
10+
id: String,
11+
title: String,
12+
css: String,
13+
html: String,
14+
js: String,
15+
weighting: String,
16+
) {
17+
val atom = InteractiveAtom(
18+
id = id,
19+
`type` = "interactive",
20+
title = title,
21+
css = css,
22+
html = html,
23+
mainJS = Some(js),
24+
docData = None,
25+
placeholderUrl = None,
26+
)
27+
28+
val blockElement = BlockElement(
29+
`type` = ElementType.Contentatom,
30+
assets = Seq.empty,
31+
contentAtomTypeData = Some(
32+
ContentAtomElementFields(
33+
atomId = id,
34+
atomType = "interactive",
35+
role = Some(weighting),
36+
isMandatory = None,
37+
),
38+
),
39+
)
40+
}
41+
42+
object LiveHarnessInteractiveAtom {
43+
implicit val reads: Reads[LiveHarnessInteractiveAtom] = Json.reads
44+
}
45+
46+
object LiveHarness {
47+
48+
trait PageUpdater[P <: ContentPage] { def update(page: P, content: Content): P }
49+
50+
implicit val iu: PageUpdater[InteractivePage] = (ip, nc) => ip.copy(interactive = ip.item.copy(content = nc))
51+
implicit val au: PageUpdater[ArticlePage] = (ap, nc) => ap.copy(article = ap.item.copy(content = nc))
52+
53+
def inject[P <: ContentPage](harnessAtoms: Seq[LiveHarnessInteractiveAtom])(implicit
54+
updater: PageUpdater[P],
55+
): BlocksOn[P] => BlocksOn[P] = _.mapBoth(
56+
page => {
57+
val content = page.item.content
58+
updater.update(page, content.copy(atoms = content.atoms.map(_.copy(interactives = harnessAtoms.map(_.atom)))))
59+
},
60+
blocks =>
61+
blocks.copy(body = blocks.body.map { bodyBlocks =>
62+
val (headBlockOpt, tailBlocks) = bodyBlocks.splitAt(1)
63+
headBlockOpt.map { headBlock =>
64+
headBlock.copy(elements = harnessAtoms.map(_.blockElement) ++ headBlock.elements)
65+
} ++ tailBlocks
66+
}),
67+
)
68+
}

preview/conf/routes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ GET /*path/email.emailjson controllers.ArticleController.renderEmail(pa
193193
GET /*path/email.emailtxt controllers.ArticleController.renderEmail(path)
194194

195195
# Harnesses
196-
POST /desktop-auth/*path controllers.LiveHarnessController.renderLiveHarness(path)
196+
POST /desktop-auth/render-harness/*path controllers.LiveHarnessController.renderLiveHarness(path)
197197

198198
# Don't forward requests for favicon.ico to the Content API
199199
GET /favicon.ico controllers.FaviconController.favicon

0 commit comments

Comments
 (0)