Skip to content

Commit

Permalink
Merge pull request #27168 from guardian/ei/set-affiliate-links-live
Browse files Browse the repository at this point in the history
Set affiliate links live
  • Loading branch information
SiAdcock authored May 21, 2024
2 parents 36ab64e + 7d0888a commit aeb19f7
Show file tree
Hide file tree
Showing 10 changed files with 30 additions and 140 deletions.
3 changes: 0 additions & 3 deletions applications/app/views/fragments/galleryBody.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@
defaultOffTags = Configuration.affiliateLinks.defaultOffTags,
alwaysOffTags = Configuration.affiliateLinks.alwaysOffTags,
tagPaths = page.gallery.content.tags.tags.map(_.id),
firstPublishedDate = page.gallery.content.fields.firstPublicationDate,
pageUrl = request.uri,
contentType = "gallery",
)
)

Expand Down
2 changes: 0 additions & 2 deletions applications/app/views/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ object GalleryCaptionCleaners {
page.gallery.content.fields.showAffiliateLinks,
appendDisclaimer = Some(isFirstRow && page.item.lightbox.containsAffiliateableLinks),
tags = page.gallery.content.tags.tags.map(_.id),
page.gallery.content.fields.firstPublicationDate,
contentType = "gallery",
),
)

Expand Down
2 changes: 0 additions & 2 deletions article/app/views/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ object BodyProcessor {
sectionId = article.content.metadata.sectionId,
showAffiliateLinks = article.content.fields.showAffiliateLinks,
tags = article.content.tags.tags.map(_.id),
publishedDate = article.content.fields.firstPublicationDate,
contentType = "article",
),
) ++
ListIf(true)(VideoEmbedCleaner(article))
Expand Down
10 changes: 0 additions & 10 deletions common/app/experiments/Experiments.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ object ActiveExperiments extends ExperimentsDefinition {
UpdatedHeaderDesign,
UpdateLogoAdPartner,
MastheadWithHighlights,
AffiliateLinksDCR,
)
implicit val canCheckExperiment: CanCheckExperiment = new CanCheckExperiment(this)
}
Expand Down Expand Up @@ -58,15 +57,6 @@ object UpdateLogoAdPartner
participationGroup = Perc0A,
)

object AffiliateLinksDCR
extends Experiment(
name = "affiliate-links-dcr",
description = "Display affiliate links on all eligible DCR articles",
owners = Seq(Owner.withGithub("[email protected]")),
sellByDate = LocalDate.of(2024, 7, 30),
participationGroup = Perc0E,
)

object DCRTagPages
extends Experiment(
name = "dcr-tag-pages",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ object DotcomBlocksRenderingDataModel {
bodyBlocks: Seq[APIBlock],
): DotcomBlocksRenderingDataModel = {
val content = page.item
val shouldAddAffiliateLinks = DotcomRenderingUtils.shouldAddAffiliateLinks(content)(request)
val shouldAddAffiliateLinks = DotcomRenderingUtils.shouldAddAffiliateLinks(content)
val contentDateTimes = DotcomRenderingUtils.contentDateTimes(content)

val edition = Edition(request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ object DotcomRenderingDataModel {
blocks.exists(block => DotcomRenderingUtils.stringContainsAffiliateableLinks(block.bodyHtml))
}

val shouldAddAffiliateLinks = DotcomRenderingUtils.shouldAddAffiliateLinks(content)(request)
val shouldAddAffiliateLinks = DotcomRenderingUtils.shouldAddAffiliateLinks(content)
val shouldAddDisclaimer = hasAffiliateLinks(bodyBlocks)

val contentDateTimes: ArticleDateTimes = ArticleDateTimes(
Expand Down
12 changes: 8 additions & 4 deletions common/app/model/dotcomrendering/DotcomRenderingUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,16 @@ object DotcomRenderingUtils {
}
}

def shouldAddAffiliateLinks(content: ContentType)(implicit request: RequestHeader): Boolean = {
def shouldAddAffiliateLinks(content: ContentType): Boolean = {
val contentHtml = Jsoup.parse(content.fields.body)
val bodyElements = contentHtml.select("body").first().children()

/**
* On smaller devices, the disclaimer is inserted before paragraph 2 of the article body and floats left.
* This logic ensures there are two clear paragraphs of text at the top of the article.
* We don't support inserting the disclaimer next to other element types.
* It also ensures the second paragraph is long enough to accommodate the disclaimer appearing alongside it.
*/
if (bodyElements.size >= 2) {
val firstEl = bodyElements.get(0)
val secondEl = bodyElements.get(1)
Expand All @@ -257,9 +264,6 @@ object DotcomRenderingUtils {
defaultOffTags = Configuration.affiliateLinks.defaultOffTags,
alwaysOffTags = Configuration.affiliateLinks.alwaysOffTags,
tagPaths = content.content.tags.tags.map(_.id),
firstPublishedDate = content.content.fields.firstPublicationDate,
pageUrl = content.metadata.id,
contentType = "article",
)
} else false
} else false
Expand Down
11 changes: 5 additions & 6 deletions common/app/views/fragments/affiliateLinksDisclaimer.scala.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<br>
<br />
<p class="gallery__disclaimer">
The Guardian’s product and service reviews are independent and are in no
way influenced by any advertiser or commercial initiative. We will earn a
commission from the retailer if you buy something through an affiliate link.
The Guardian’s journalism is independent. We will earn a commission if you
buy something through an affiliate link.
<a
href="https://www.theguardian.com/info/2017/nov/01/reader-information-on-affiliate-links"
href="https://www.theguardian.com/info/2017/nov/01/reader-information-on-affiliate-links"
>Learn more</a
>.
</p>
</p>
51 changes: 6 additions & 45 deletions common/app/views/support/HtmlCleaner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import common.{Edition, GuLogging, LinkTo}
import conf.Configuration.affiliateLinks._
import conf.Configuration.site.host
import conf.switches.Switches._
import experiments.{ActiveExperiments, AffiliateLinksDCR}
import layout.ContentWidths
import layout.ContentWidths._
import model._
Expand Down Expand Up @@ -872,10 +871,7 @@ case class AffiliateLinksCleaner(
showAffiliateLinks: Option[Boolean],
appendDisclaimer: Option[Boolean] = None,
tags: List[String],
publishedDate: Option[DateTime],
contentType: String,
)(implicit request: RequestHeader)
extends HtmlCleaner
) extends HtmlCleaner
with GuLogging {

override def clean(document: Document): Document = {
Expand All @@ -888,9 +884,6 @@ case class AffiliateLinksCleaner(
defaultOffTags,
alwaysOffTags,
tags,
publishedDate,
pageUrl,
contentType,
)
) {
AffiliateLinksCleaner.replaceLinksInHtml(document, pageUrl, skimlinksId)
Expand Down Expand Up @@ -945,46 +938,14 @@ object AffiliateLinksCleaner {
defaultOffTags: Set[String],
alwaysOffTags: Set[String],
tagPaths: List[String],
firstPublishedDate: Option[DateTime],
pageUrl: String,
contentType: String,
)(implicit request: RequestHeader): Boolean = {
val publishedCutOffDate = new DateTime(2020, 8, 14, 0, 0)

val cleanedPageUrl = if (pageUrl.charAt(0) == '/') {
pageUrl.substring(1);
} else pageUrl

val affiliateLinksAllowList = List(
"lifeandstyle/2024/jan/03/six-winter-warmers-tried-and-tested-the-heated-poncho-has-changed-me-i-will-never-have-sex-again",
"lifeandstyle/2024/mar/11/im-south-asian-and-have-dark-eye-circles-what-can-i-do",
"fashion/2024/mar/08/the-four-makeup-staples-i-cant-live-without",
"travel/2023/mar/03/readers-favourite-budget-beach-campsites-hotels-in-europe",
"travel/2024/feb/25/10-of-the-best-places-in-the-uk-to-see-them-bloom",
"lifeandstyle/2023/dec/10/with-christmas-around-the-corner-what-to-give-the-gardener-in-your-life-",
"fashion/2024/mar/01/spring-is-around-the-corner-time-to-soothe-and-restore-your-cracked-heels",
"fashion/2024/mar/10/compact-and-bijou-why-women-need-a-pocket-mirror",
"fashion/2024/mar/03/how-to-reset-your-wardrobe-for-spring",
"lifeandstyle/2024/mar/03/beauty-spot-eyebrow-essentials-10-of-the-best",
"fashion/2024/mar/17/beauty-spot-10-best-root-cover-ups",
"fashion/2024/apr/05/peptides-help-with-good-looking-skin-but-dont-expect-botox-in-a-bottle",
"fashion/2024/apr/13/sali-hughes-top-50-beauty-products-for-under-20-pounds",
)

val urlIsInAllowList = affiliateLinksAllowList.contains(cleanedPageUrl)

// Never include affiliate links if it is tagged with an always off tag, or if it was published before our cut off date.
// The cut off date is temporary while we are working on improving the compliance of affiliate links.
// The cut off date does not apply to any URL on the allow list or to galleries
if (
!contentHasAlwaysOffTag(tagPaths, alwaysOffTags) && (firstPublishedDate.exists(
_.isBefore(publishedCutOffDate),
) || urlIsInAllowList || contentType == "gallery" || ActiveExperiments.isParticipating(AffiliateLinksDCR))
) {
): Boolean = {

// Never include affiliate links if it is tagged with an always off tag
if (!contentHasAlwaysOffTag(tagPaths, alwaysOffTags) && switchedOn) {
if (showAffiliateLinks.isDefined) {
showAffiliateLinks.contains(true)
} else {
switchedOn && supportedSections.contains(section) && !tagPaths.exists(path => defaultOffTags.contains(path))
supportedSections.contains(section) && !tagPaths.exists(path => defaultOffTags.contains(path))
}
} else false
}
Expand Down
75 changes: 9 additions & 66 deletions common/test/views/support/cleaner/AffiliateLinksCleanerTest.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package views.support.cleaner
import conf.Configuration
import conf.switches.Switches.ServerSideExperiments
import org.joda.time.DateTime
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import play.api.test.FakeRequest
import views.support.AffiliateLinksCleaner._

class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
Expand All @@ -18,11 +16,7 @@ class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
}

"shouldAddAffiliateLinks" should "correctly determine when to add affiliate links" in {
val fakeTestControlRequest = FakeRequest().withHeaders("X-GU-Experiment-0perc-E" -> "control")
val supportedSections = Set("film", "books", "fashion")
val oldPublishedDate = Some(new DateTime(2020, 8, 13, 0, 0))
val newPublishedDate = Some(new DateTime(2020, 8, 15, 0, 0))
val deniedPageUrl = "/fashion/2024/feb/16/sunscreen-in-winter-yep-spf-moisturiser-is-essential-all-year-round"

shouldAddAffiliateLinks(
switchedOn = false,
Expand All @@ -32,10 +26,7 @@ class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
Set.empty,
Set.empty,
List.empty,
oldPublishedDate,
deniedPageUrl,
"article",
)(fakeTestControlRequest) should be(false)
) should be(false)
shouldAddAffiliateLinks(
switchedOn = true,
"film",
Expand All @@ -44,10 +35,7 @@ class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
Set.empty,
Set.empty,
List.empty,
oldPublishedDate,
deniedPageUrl,
"article",
)(fakeTestControlRequest) should be(true)
) should be(true)
shouldAddAffiliateLinks(
switchedOn = true,
"film",
Expand All @@ -56,10 +44,7 @@ class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
Set.empty,
Set.empty,
List.empty,
oldPublishedDate,
deniedPageUrl,
"article",
)(fakeTestControlRequest) should be(false)
) should be(false)
shouldAddAffiliateLinks(
switchedOn = true,
"news",
Expand All @@ -68,10 +53,7 @@ class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
Set.empty,
Set.empty,
List.empty,
oldPublishedDate,
deniedPageUrl,
"article",
)(fakeTestControlRequest) should be(true)
) should be(true)
shouldAddAffiliateLinks(
switchedOn = true,
"news",
Expand All @@ -80,10 +62,7 @@ class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
Set("bereavement"),
Set.empty,
List("bereavement"),
oldPublishedDate,
deniedPageUrl,
"article",
)(fakeTestControlRequest) should be(false)
) should be(false)
shouldAddAffiliateLinks(
switchedOn = true,
"news",
Expand All @@ -92,10 +71,7 @@ class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
Set("bereavement"),
Set.empty,
List("tech"),
oldPublishedDate,
deniedPageUrl,
"article",
)(fakeTestControlRequest) should be(false)
) should be(false)
shouldAddAffiliateLinks(
switchedOn = true,
"fashion",
Expand All @@ -104,10 +80,7 @@ class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
Set("bereavement"),
Set.empty,
List("tech"),
oldPublishedDate,
deniedPageUrl,
"article",
)(fakeTestControlRequest) should be(true)
) should be(true)
shouldAddAffiliateLinks(
switchedOn = true,
"fashion",
Expand All @@ -116,10 +89,7 @@ class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
Set.empty,
Set("bereavement"),
List("bereavement"),
oldPublishedDate,
deniedPageUrl,
"article",
)(fakeTestControlRequest) should be(false)
) should be(false)
shouldAddAffiliateLinks(
switchedOn = true,
"fashion",
Expand All @@ -128,33 +98,6 @@ class AffiliateLinksCleanerTest extends AnyFlatSpec with Matchers {
Set.empty,
Set("bereavement"),
List("tech"),
oldPublishedDate,
deniedPageUrl,
"article",
)(fakeTestControlRequest) should be(true)
shouldAddAffiliateLinks(
switchedOn = true,
"film",
None,
supportedSections,
Set.empty,
Set.empty,
List.empty,
newPublishedDate,
deniedPageUrl,
"article",
)(fakeTestControlRequest) should be(false)
shouldAddAffiliateLinks(
switchedOn = true,
"film",
None,
supportedSections,
Set.empty,
Set.empty,
List.empty,
newPublishedDate,
deniedPageUrl,
"gallery",
)(fakeTestControlRequest) should be(true)
) should be(true)
}
}

0 comments on commit aeb19f7

Please sign in to comment.