Skip to content

Commit 84bcaa3

Browse files
committed
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).
1 parent 9e310ca commit 84bcaa3

File tree

5 files changed

+96
-8
lines changed

5 files changed

+96
-8
lines changed

app/presenters/content_item/updatable.rb

+8-8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ def history
1313
reverse_chronological_change_history
1414
end
1515

16+
def first_public_at
17+
content_item["details"]["first_public_at"]
18+
end
19+
20+
def public_updated_at
21+
content_item["public_updated_at"]
22+
end
23+
1624
private
1725

1826
def change_history
@@ -39,13 +47,5 @@ def any_updates?
3947
false
4048
end
4149
end
42-
43-
def first_public_at
44-
content_item["details"]["first_public_at"]
45-
end
46-
47-
def public_updated_at
48-
content_item["public_updated_at"]
49-
end
5050
end
5151
end

app/presenters/news_article_presenter.rb

+4
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ class NewsArticlePresenter < ContentItemPresenter
1010
def image
1111
content_item["details"]["image"]
1212
end
13+
14+
def structured_data
15+
NewsArticleStructured.new(self).structured_data
16+
end
1317
end
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
class NewsArticleStructured
2+
def initialize(presenter)
3+
@presenter = presenter
4+
end
5+
6+
def structured_data
7+
return {} unless enough_structured_data?
8+
9+
# http://schema.org/NewsArticle
10+
{
11+
"@context" => "http://schema.org",
12+
"@type" => "NewsArticle",
13+
"mainEntityOfPage" => {
14+
"@type" => "WebPage",
15+
"@id" => page_url,
16+
},
17+
"headline" => presenter.title,
18+
"datePublished" => presenter.first_public_at,
19+
"dateModified" => presenter.public_updated_at,
20+
"description" => presenter.description,
21+
"publisher" => {
22+
"@type" => "Organization",
23+
"name" => "GOV.UK",
24+
"url" => "https://www.gov.uk",
25+
"logo" => {
26+
"@type" => "ImageObject",
27+
# TODO: change this to a better image, without the URL hard coded.
28+
"url" => "https://assets.publishing.service.gov.uk/static/opengraph-image-a1f7d89ffd0782738b1aeb0da37842d8bd0addbd724b8e58c3edbc7287cc11de.png",
29+
},
30+
},
31+
"image" => [
32+
image["url"],
33+
],
34+
"author" => {
35+
"@type" => "Organization",
36+
"name" => publishing_organisation["title"],
37+
"url" => Plek.current.website_root + publishing_organisation["base_path"],
38+
},
39+
}
40+
end
41+
42+
private
43+
44+
attr_reader :presenter
45+
46+
def enough_structured_data?
47+
# The author (for which we use the publishing org) and image are required
48+
# fields. If the news article doesn't have them, don't use structured data
49+
# at all.
50+
publishing_organisation && image
51+
end
52+
53+
def publishing_organisation
54+
presenter.content_item.dig("links", "primary_publishing_organisation").to_a.first
55+
end
56+
57+
def page_url
58+
Plek.current.website_root + presenter.content_item["base_path"]
59+
end
60+
61+
def image
62+
presenter.image
63+
end
64+
end

app/views/content_items/news_article.html.erb

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
<script type="application/ld+json">
2+
<%= @content_item.structured_data.to_json.html_safe %>
3+
</script>
4+
15
<div class="grid-row">
26
<div class="column-two-thirds">
37
<%= render 'govuk_component/title', @content_item.title_and_context %>

test/presenters/news_article_presenter_test.rb

+16
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,22 @@ class PresentedNewsArticleTest < NewsArticlePresenterTestCase
4545
test 'presents the locale' do
4646
assert_equal schema_item['locale'], presented_item.locale
4747
end
48+
49+
test 'has no structured data if no publishing org' do
50+
expected = {}
51+
assert_equal expected, presented_item.structured_data
52+
end
53+
54+
test 'has structured data' do
55+
item = { "links" => { "primary_publishing_organisation" => [{ "title" => "Ministry of Magic", "base_path" => "/government/organisations/magic" }] } }
56+
57+
structured_data = presented_item("news_article", item).structured_data
58+
59+
assert_equal "https://www.test.gov.uk/government/news/christmas-2016-prime-ministers-message", structured_data["mainEntityOfPage"]["@id"]
60+
assert_equal "2016-12-25T00:15:02.000+00:00", structured_data["datePublished"]
61+
assert_equal "Ministry of Magic", structured_data["author"]["name"]
62+
assert_equal "https://www.test.gov.uk/government/organisations/magic", structured_data["author"]["url"]
63+
end
4864
end
4965

5066
class HistoryModePresentedNewsArticle < NewsArticlePresenterTestCase

0 commit comments

Comments
 (0)