From 84bcaa3c87efb92c328d5db71e2d7e31c27fd724 Mon Sep 17 00:00:00 2001 From: Tijmen Brommet Date: Fri, 13 Apr 2018 17:29:32 +0100 Subject: [PATCH] Add structured data for news articles This implements a minimum of structured data for news articles. As per Google: > Structured data is a standardized format for providing information about a page and classifying the page content; for example, on a recipe page, what are the ingredients, the cooking time and temperature, the calories, and so on. https://developers.google.com/search/docs/guides/intro-structured-data We use the JSON-LD (JSON "Linked Data", https://json-ld.org) here. It's Google's recommended way of implementing this and avoids a lot of the hassle involved with Microdata, which interleaves the structured data with HTML. The output of this is tested with the Structured data testing tool: https://search.google.com/structured-data/testing-tool Unfortunately, the "publisher" attribute needs a logo. I've chosen the Opengraph image for this because it's not likely to change and will probably stay around for a long time. If we're going to implement more structured data, we'll have to come up with an alternative (and a better image). --- app/presenters/content_item/updatable.rb | 16 ++--- app/presenters/news_article_presenter.rb | 4 ++ app/presenters/news_article_structured.rb | 64 +++++++++++++++++++ app/views/content_items/news_article.html.erb | 4 ++ .../presenters/news_article_presenter_test.rb | 16 +++++ 5 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 app/presenters/news_article_structured.rb diff --git a/app/presenters/content_item/updatable.rb b/app/presenters/content_item/updatable.rb index d434687c0..bd43cdec3 100644 --- a/app/presenters/content_item/updatable.rb +++ b/app/presenters/content_item/updatable.rb @@ -13,6 +13,14 @@ def history reverse_chronological_change_history end + def first_public_at + content_item["details"]["first_public_at"] + end + + def public_updated_at + content_item["public_updated_at"] + end + private def change_history @@ -39,13 +47,5 @@ def any_updates? false end end - - def first_public_at - content_item["details"]["first_public_at"] - end - - def public_updated_at - content_item["public_updated_at"] - end end end diff --git a/app/presenters/news_article_presenter.rb b/app/presenters/news_article_presenter.rb index b9507ed38..72e2316f1 100644 --- a/app/presenters/news_article_presenter.rb +++ b/app/presenters/news_article_presenter.rb @@ -10,4 +10,8 @@ class NewsArticlePresenter < ContentItemPresenter def image content_item["details"]["image"] end + + def structured_data + NewsArticleStructured.new(self).structured_data + end end diff --git a/app/presenters/news_article_structured.rb b/app/presenters/news_article_structured.rb new file mode 100644 index 000000000..d3c9c61e4 --- /dev/null +++ b/app/presenters/news_article_structured.rb @@ -0,0 +1,64 @@ +class NewsArticleStructured + def initialize(presenter) + @presenter = presenter + end + + def structured_data + return {} unless enough_structured_data? + + # http://schema.org/NewsArticle + { + "@context" => "http://schema.org", + "@type" => "NewsArticle", + "mainEntityOfPage" => { + "@type" => "WebPage", + "@id" => page_url, + }, + "headline" => presenter.title, + "datePublished" => presenter.first_public_at, + "dateModified" => presenter.public_updated_at, + "description" => presenter.description, + "publisher" => { + "@type" => "Organization", + "name" => "GOV.UK", + "url" => "https://www.gov.uk", + "logo" => { + "@type" => "ImageObject", + # TODO: change this to a better image, without the URL hard coded. + "url" => "https://assets.publishing.service.gov.uk/static/opengraph-image-a1f7d89ffd0782738b1aeb0da37842d8bd0addbd724b8e58c3edbc7287cc11de.png", + }, + }, + "image" => [ + image["url"], + ], + "author" => { + "@type" => "Organization", + "name" => publishing_organisation["title"], + "url" => Plek.current.website_root + publishing_organisation["base_path"], + }, + } + end + +private + + attr_reader :presenter + + def enough_structured_data? + # The author (for which we use the publishing org) and image are required + # fields. If the news article doesn't have them, don't use structured data + # at all. + publishing_organisation && image + end + + def publishing_organisation + presenter.content_item.dig("links", "primary_publishing_organisation").to_a.first + end + + def page_url + Plek.current.website_root + presenter.content_item["base_path"] + end + + def image + presenter.image + end +end diff --git a/app/views/content_items/news_article.html.erb b/app/views/content_items/news_article.html.erb index 3c201cac8..018a00e4d 100644 --- a/app/views/content_items/news_article.html.erb +++ b/app/views/content_items/news_article.html.erb @@ -1,3 +1,7 @@ + +
<%= render 'govuk_component/title', @content_item.title_and_context %> diff --git a/test/presenters/news_article_presenter_test.rb b/test/presenters/news_article_presenter_test.rb index 7dd09d040..989420411 100644 --- a/test/presenters/news_article_presenter_test.rb +++ b/test/presenters/news_article_presenter_test.rb @@ -45,6 +45,22 @@ class PresentedNewsArticleTest < NewsArticlePresenterTestCase test 'presents the locale' do assert_equal schema_item['locale'], presented_item.locale end + + test 'has no structured data if no publishing org' do + expected = {} + assert_equal expected, presented_item.structured_data + end + + test 'has structured data' do + item = { "links" => { "primary_publishing_organisation" => [{ "title" => "Ministry of Magic", "base_path" => "/government/organisations/magic" }] } } + + structured_data = presented_item("news_article", item).structured_data + + assert_equal "https://www.test.gov.uk/government/news/christmas-2016-prime-ministers-message", structured_data["mainEntityOfPage"]["@id"] + assert_equal "2016-12-25T00:15:02.000+00:00", structured_data["datePublished"] + assert_equal "Ministry of Magic", structured_data["author"]["name"] + assert_equal "https://www.test.gov.uk/government/organisations/magic", structured_data["author"]["url"] + end end class HistoryModePresentedNewsArticle < NewsArticlePresenterTestCase