Skip to content

Commit

Permalink
Merge branch 'master' of github.com:guardian/frontend into rk-cleanup…
Browse files Browse the repository at this point in the history
…-wed-2
  • Loading branch information
Regis Kuckaertz committed Feb 20, 2017
2 parents f2cb141 + d062451 commit 31f8ba7
Show file tree
Hide file tree
Showing 563 changed files with 269 additions and 118 deletions.
1 change: 1 addition & 0 deletions applications/app/controllers/ApplicationsControllers.scala
Expand Up @@ -39,4 +39,5 @@ trait ApplicationsControllers {
lazy val indexController = wire[IndexController]
lazy val siteVerificationController = wire[SiteVerificationController]
lazy val shareCountController = wire[ShareCountController]
lazy val structuredDataIndexController = wire[StructuredDataIndexController]
}
45 changes: 45 additions & 0 deletions applications/app/controllers/StructuredDataIndexController.scala
@@ -0,0 +1,45 @@
package controllers

import com.gu.contentapi.client.model.RecipesQuery
import common.ExecutionContexts
import common.`package`._
import contentapi.ContentApiClient
import model.Cached.RevalidatableResult
import model.{ApplicationContext, Cached, MetaData, SectionSummary, SimplePage, Tags}
import org.joda.time.DateTime
import play.api.mvc.Action
import services.{IndexPage, IndexPagePagination}
import structureddata.AtomTransformer._

class StructuredDataIndexController(val contentApiClient: ContentApiClient)(implicit val context: ApplicationContext) extends Paging with ExecutionContexts {

def render(filterType: String, filterValue: String) = Action.async { implicit request =>

val page = inferPage(request)
val pageSize = if (request.isRss) IndexPagePagination.rssPageSize else IndexPagePagination.pageSize
val baseQuery = contentApiClient.recipes.page(page).pageSize(pageSize)

val query: String => RecipesQuery = filterType match {
case "cuisine" => baseQuery.cuisines(_)
case "dietary" => baseQuery.dietary(_)
case "celebration" => baseQuery.celebration(_)
case "category" => baseQuery.categories(_)
}

contentApiClient.getResponse(query(filterValue)) map { response =>

val indexPage = IndexPage(
page = SimplePage(MetaData.make(id = "", section = Some(SectionSummary("lifeandstyle/food-and-drink")), webTitle = s"${filterValue.capitalize} Recipes")),
contents = response.results flatMap recipeAtomToContent,
tags = Tags(List.empty),
date = DateTime.now,
tzOverride = None
)

Cached(indexPage.page)(RevalidatableResult.Ok(views.html.index(indexPage)))
}
}

}


90 changes: 90 additions & 0 deletions applications/app/structureddata/AtomTransformer.scala
@@ -0,0 +1,90 @@
package structureddata

import com.gu.contentapi.client.model.v1.{Asset => ApiAsset, AssetFields => ApiAssetFields, AssetType => ApiAssetType, Atoms => ApiAtoms, CapiDateTime, ContentFields => ApiContentFields, ElementType => ApiElementType, Tag => ApiTag, TagType => ApiTagType, Content => ApiContent, Element => ApiElement}
import com.gu.contentatom.thrift.AtomData.Recipe
import com.gu.contentatom.thrift.{Atom, Image}
import services.IndexPageItem


object AtomTransformer {

def recipeAtomToContent(atom: Atom): Option[IndexPageItem] = atom.data match {
case Recipe(recipe) =>

def trailText: String = {
val credits = if (recipe.credits.nonEmpty) Some(s"by ${recipe.credits.mkString(", ")}") else None
val maybeServes = recipe.serves map { s => if (s.to == s.from) s"${s.`type`} ${s.from}" else s"${s.`type`} ${s.from} - ${s.to}" }
val maybeCookingTime = recipe.time.cooking.map (cTime => s"cooking time $cTime minutes")
val maybePreparationTime = recipe.time.preparation.map (pTime => s"preparation time $pTime minutes")

Seq(credits, maybeServes, maybeCookingTime, maybePreparationTime).flatten.mkString(", ").capitalize
}

val maybeByline = if (recipe.credits.nonEmpty) Some(recipe.credits.mkString(", ")) else None
val webUrl = s"${conf.Configuration.site.host}/${recipe.sourceArticleId}"
val apiUrl = s"${conf.Configuration.contentApi.contentApiHost}/${recipe.sourceArticleId}"
val webPublicationDate = atom.contentChangeDetails.published.map(d => CapiDateTime(d.date, ""))
val fields = Some(ApiContentFields(headline = Some(s"${recipe.title}"), byline = maybeByline, trailText = Some(trailText)))
val imageElements = recipe.images map atomImageToApiElement

def tag(id: String, tagType: ApiTagType, webTitle: String) = ApiTag(
id = id,
`type` = tagType,
sectionId = None,
sectionName = None,
webTitle = webTitle,
webUrl = s"${conf.Configuration.site.host}/$id",
apiUrl = "",
twitterHandle = None,
bio = None,
description = None,
emailAddress = None,
bylineImageUrl = None,
podcast = None,
references = Seq.empty,
paidContentType = None
)

recipe.sourceArticleId.map { articleId =>
IndexPageItem(
ApiContent(
id = articleId,
sectionId = None,
sectionName = None,
webPublicationDate = webPublicationDate,
webTitle = recipe.title,
webUrl = webUrl,
apiUrl = apiUrl,
fields = fields,
tags = Seq(
tag("tone/recipes", ApiTagType.Tone, webTitle = "Recipes")
),
elements = Some(imageElements),
atoms = Some(ApiAtoms(recipes = Some(Seq(atom))))
)
)
}

case _ => None
}

def atomImageToApiElement(image: Image): ApiElement =
ApiElement(
id = image.mediaId,
relation = "main",
`type` = ApiElementType.Image,
galleryIndex = None,
assets = image.assets.map { asset =>
ApiAsset(
`type` = ApiAssetType.Image,
mimeType = asset.mimeType,
file = Some(asset.file),
typeData = Some(ApiAssetFields(
height = asset.dimensions.map(_.height),
width = asset.dimensions.map(_.width)
))
)
}
)

}
3 changes: 3 additions & 0 deletions applications/conf/routes
Expand Up @@ -58,6 +58,9 @@ GET /opt/$choice<in|out|delete>/:feature
GET /service-worker.js controllers.WebAppController.serviceWorker()
GET /2015-06-24-manifest.json controllers.WebAppController.manifest()

# Index pages for structured data
GET /sdata/recipes/$filterType<cuisine|dietary|celebration|category>/:filterValue controllers.StructuredDataIndexController.render(filterType, filterValue)

# Newspaper pages
GET /theguardian controllers.NewspaperController.latestGuardianNewspaper()
GET /theobserver controllers.NewspaperController.latestObserverNewspaper()
Expand Down
7 changes: 6 additions & 1 deletion common/app/contentapi/ContentApiClient.scala
Expand Up @@ -4,7 +4,8 @@ import akka.actor.ActorSystem
import akka.pattern.CircuitBreaker
import com.gu.contentapi.client.ContentApiClientLogic
import com.gu.contentapi.client.model._
import com.gu.contentapi.client.model.v1.ItemResponse
import com.gu.contentapi.client.model.v1.{AtomsResponse, ItemResponse}
import com.gu.contentapi.client.thrift.ThriftDeserializer
import com.gu.contentapi.client.utils.CapiModelEnrichment.RichCapiDateTime
import common._
import conf.Configuration
Expand All @@ -13,6 +14,7 @@ import conf.switches.Switches.CircuitBreakerSwitch
import model.{Content, Trail}
import org.joda.time.DateTime
import org.scala_tools.time.Implicits._

import scala.concurrent.duration.{Duration, MILLISECONDS}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
Expand Down Expand Up @@ -187,6 +189,7 @@ class ContentApiClient(httpClient: HttpClient) extends ApiQueryDefaults {
def search = getClient.search
def sections = getClient.sections
def editions = getClient.editions
def recipes = getClient.recipes

def getResponse(itemQuery: ItemQuery)(implicit context: ExecutionContext) = getClient.getResponse(itemQuery)

Expand All @@ -197,6 +200,8 @@ class ContentApiClient(httpClient: HttpClient) extends ApiQueryDefaults {
def getResponse(sectionsQuery: SectionsQuery)(implicit context: ExecutionContext) = getClient.getResponse(sectionsQuery)

def getResponse(editionsQuery: EditionsQuery)(implicit context: ExecutionContext) = getClient.getResponse(editionsQuery)

def getResponse(recipesQuery: RecipesQuery)(implicit context: ExecutionContext): Future[AtomsResponse] = getClient.getResponse(recipesQuery)
}

// The Admin server uses this PreviewContentApi to check the preview environment.
Expand Down
9 changes: 7 additions & 2 deletions common/app/views/fragments/atoms/youtube.scala.html
Expand Up @@ -3,9 +3,10 @@
@import views.support.{RenderClasses, Video700}
@import views.html.fragments.atoms.mediaAtomCaption
@import com.netaporter.uri.dsl._
@import model.pressed.CardStyle

@import model.ImageMedia
@(media: model.content.MediaAtom, displayCaption: Boolean, embedPage: Boolean, displayDuration: Boolean = true, displayEndSlate: Boolean = true, playable: Boolean = true, mainMedia: Boolean = false, posterImageOverride: Option[ImageMedia] = None)(implicit request: RequestHeader)
@(media: model.content.MediaAtom, displayCaption: Boolean, embedPage: Boolean, displayDuration: Boolean = true, displayEndSlate: Boolean = true, playable: Boolean = true, mainMedia: Boolean = false, posterImageOverride: Option[ImageMedia] = None, cardStyle: Option[CardStyle] = None)(implicit request: RequestHeader)
@defining(media.expired.getOrElse(false)){expired: Boolean =>
<div data-media-atom-id="@media.id" class="@RenderClasses(Map(
("u-responsive-ratio", true),
Expand Down Expand Up @@ -39,7 +40,11 @@
<div class="@RenderClasses(Map("youtube-media-atom__overlay" -> true, "vjs-big-play-button" -> !expired))" style="background-image: url(@Video700.bestFor(image))">

@if(!expired) {
<div class="youtube-media-atom__play-button vjs-control-text">Play Video</div>
@defining(!cardStyle.exists(c => c.toneString != "media" && !playable)) { showPlayButton: Boolean =>
@if(showPlayButton) {
<div class="youtube-media-atom__play-button vjs-control-text">Play Video</div>
}
}
@if(displayDuration) {
<div class="youtube-media-atom__bottom-bar">
<div class="youtube-media-atom__bottom-bar__icon">
Expand Down
Expand Up @@ -98,7 +98,8 @@
displayDuration = false,
displayEndSlate = item.cardTypes.showYouTubeMediaAtomEndSlate,
playable = item.cardTypes.showYouTubeMediaAtomPlayer,
posterImageOverride = media.posterImageOverride)
posterImageOverride = media.posterImageOverride,
cardStyle = Some(item.cardStyle))
</div>
</div>
}
Expand Down
8 changes: 2 additions & 6 deletions common/test/services/IndexPageTest.scala
Expand Up @@ -48,20 +48,16 @@ import scala.concurrent.Future
fail("Wrong type (expected: IndexPage, real: Result)")
case Some(page) =>
val front = IndexPage.makeFront(page, edition)
front.containers.length should be(1)
front.containers should not be empty

val firstContainer = front.containers.head
val formatter = DateTimeFormat.forPattern("d MMMM yyyy")
val parsedDate = formatter.parseDateTime(firstContainer.displayName.get)
parsedDate shouldBe a[DateTime]
firstContainer.container.isInstanceOf[Fixed] should be(true)
firstContainer.index should be(0)
firstContainer.containerLayout.get.slices.length should be(2)
firstContainer.containerLayout.get.remainingCards.length should be(1)

firstContainer.items.length should be(pageSize)
firstContainer.items.head.header.headline should be("Rio 2016 Olympic venues abandoned and derelict six months after Games – video")
firstContainer.items.head.header.url should be("/sport/video/2017/feb/10/rio-2016-olympic-venues-abandoned-derelict-video")
firstContainer.items should not be empty
}
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion dev-build/app/controllers/FakeGeolocationController.scala
Expand Up @@ -9,7 +9,7 @@ class FakeGeolocationController extends Controller {
implicit val jf: Format[FakeGeolocation] = Json.format[FakeGeolocation]

def geolocation = Action {
val fake = FakeGeolocation("AU")
val fake = FakeGeolocation("GB")
Ok(Json.toJson(fake))
}
}
2 changes: 2 additions & 0 deletions dev-build/conf/routes
Expand Up @@ -429,6 +429,8 @@ GET /$path<[\w\d-]*(/[\w\d-]*)?/(interactive|ng-interactive)/.*>


# Applications

GET /sdata/recipes/$filterType<cuisine|dietary|celebration|category>/:filterValue controllers.StructuredDataIndexController.render(filterType, filterValue)
GET /$path<international> controllers.FaciaController.renderFront(path)
GET /$path<[\w\d-]*(/[\w\d-]*)?(/[\w\d-]*)?>/trails.json controllers.IndexController.renderTrailsJson(path)
GET /$path<[\w\d-]*(/[\w\d-]*)?(/[\w\d-]*)?>/trails controllers.IndexController.renderTrails(path)
Expand Down
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Expand Up @@ -32,7 +32,7 @@ object Dependencies {
val commonsLang = "commons-lang" % "commons-lang" % "2.4"
val commonsIo = "commons-io" % "commons-io" % "2.4"
val cssParser = "net.sourceforge.cssparser" % "cssparser" % "0.9.21"
val contentApiClient = "com.gu" %% "content-api-client" % "11.1"
val contentApiClient = "com.gu" %% "content-api-client" % "11.3"
val dfpAxis = "com.google.api-ads" % "dfp-axis" % "2.20.0"
val dispatch = "net.databinder.dispatch" %% "dispatch-core" % dispatchVersion
val dispatchTest = "net.databinder.dispatch" %% "dispatch-core" % dispatchVersion % Test
Expand Down
7 changes: 7 additions & 0 deletions static/src/javascripts-legacy/boot.js
Expand Up @@ -22,12 +22,14 @@ define([
'domReady',
'common/utils/raven',
'common/utils/user-timing',
'common/utils/capture-perf-timings',
'common/utils/config'
], function (
Promise,
domReady,
raven,
userTiming,
capturePerfTimings,
config
) {
// curl’s promise API is broken, so we must cast it to a real Promise
Expand Down Expand Up @@ -80,6 +82,11 @@ define([
.then(function (boot) {
userTiming.mark('enhanced boot');
boot();
if (document.readyState === 'complete') {
capturePerfTimings();
} else {
window.addEventListener('load', capturePerfTimings);
}
});
}
};
Expand Down
Expand Up @@ -51,7 +51,11 @@ define([
function onIntersect(entries, observer) {
var advertIds = [];

entries.forEach(function (entry) {
entries
.filter(function (entry) {
return !('isIntersecting' in entry) || entry.isIntersecting;
})
.forEach(function (entry) {
observer.unobserve(entry.target);
displayAd(entry.target.id);
advertIds.push(entry.target.id);
Expand Down
Expand Up @@ -9,7 +9,9 @@ define([
'common/utils/fastdom-promise',
'common/utils/mediator',
'common/utils/storage',
'common/utils/geolocation'
'common/utils/geolocation',
'common/utils/template',
'text!common/views/contributions-epic-equal-buttons.html'

], function (commercialFeatures,
targetingTool,
Expand All @@ -21,7 +23,9 @@ define([
fastdom,
mediator,
storage,
geolocation) {
geolocation,
template,
contributionsEpicEqualButtons) {

var membershipURL = 'https://membership.theguardian.com/supporter';
var contributionsURL = 'https://contribute.theguardian.com';
Expand Down Expand Up @@ -114,6 +118,19 @@ define([
return this.id + ':' + event;
};

function controlTemplate(membershipUrl, contributionUrl) {
return template(contributionsEpicEqualButtons, {
linkUrl1: membershipUrl,
linkUrl2: contributionUrl,
title: 'Since you’re here …',
p1: '… we’ve got a small favour to ask. More people are reading the Guardian than ever, but far fewer are paying for it. Advertising revenues across the media are falling fast. And unlike some other news organisations, we haven’t put up a paywall – we want to keep our journalism open to all. So you can see why we need to ask for your help. The Guardian’s independent, investigative journalism takes a lot of time, money and hard work to produce. But we do it because we believe our perspective matters – because it might well be your perspective, too.',
p2: 'If everyone who reads our reporting, who likes it, helps to support it, our future would be much more secure.',
p3: '',
cta1: 'Become a Supporter',
cta2: 'Make a contribution'
});
}

function ContributionsABTestVariant(options, test) {
this.campaignId = test.campaignId;
this.id = options.id;
Expand All @@ -123,10 +140,12 @@ define([
this.contributeURL = options.contributeURL || this.makeURL(contributionsURL, test.contributionsCampaignPrefix);
this.membershipURL = options.membershipURL || this.makeURL(membershipURL, test.membershipCampaignPrefix);

this.template = options.template || controlTemplate;

var trackingCampaignId = test.epic ? 'epic_' + test.campaignId : test.campaignId;

this.test = function () {
var component = $.create(options.template(this.contributeURL, this.membershipURL));
var component = $.create(this.template(this.contributeURL, this.membershipURL));
var onInsert = options.onInsert || noop;
var onView = options.onView || noop;

Expand Down

0 comments on commit 31f8ba7

Please sign in to comment.