Skip to content

Commit

Permalink
Add schema.org Event serializer for Breadcrumb (decidim#13522)
Browse files Browse the repository at this point in the history
* Add schema.org Event serializer for Breadcrumb

* Skip empty items

* Add name to BreadcrumbList schema

* Fix rubocop offense

* Fix specs
  • Loading branch information
andreslucena authored Oct 16, 2024
1 parent 39fbb94 commit 543f6f4
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 0 deletions.
6 changes: 6 additions & 0 deletions decidim-core/app/helpers/decidim/breadcrumb_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,11 @@ def active_breadcrumb_item(target_menu)
active: active_item.active?
}
end

def render_schema_org_breadcrumb_list(breadcrumb_items)
exporter_options = { breadcrumb_items:, base_url: request.base_url, organization_name: current_organization_name }
exported_breadcrumb_list = Decidim::Exporters::JSON.new([exporter_options], Decidim::SchemaOrgBreadcrumbListSerializer).export.read
JSON.pretty_generate(JSON.parse(exported_breadcrumb_list).first)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

require "uri"

module Decidim
class SchemaOrgBreadcrumbListSerializer < Decidim::Exporters::Serializer
include Decidim::SanitizeHelper

# Public: Initializes the serializer with a list of breadcrumb items.
def initialize(options)
@breadcrumb_items = options[:breadcrumb_items]
@base_url = options[:base_url]
@organization_name = options[:organization_name]
end

# Serializes a breadcrumb items list for the Schema.org BreadcrumbList type
#
# @see https://schema.org/BreadcrumbList
# @see https://developers.google.com/search/docs/appearance/structured-data/breadcrumb?hl=en
def serialize
return {} if breadcrumb_items.none? { |item| item.has_key?(:url) }

{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
name: "#{organization_name} breadcrumb",
itemListElement: breadcrumb_items_serialized
}
end

private

attr_reader :breadcrumb_items, :base_url, :organization_name

def breadcrumb_items_serialized
all_items = []

breadcrumb_items.each_with_index do |item, index|
next if item.empty?

all_items << {
"@type": "ListItem",
position: index + 1,
name: decidim_sanitize_translated(item[:label]),
item: URI.join(base_url, item[:url]).to_s
}
end

all_items
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<script type="application/ld+json">
<%== render_schema_org_breadcrumb_list(breadcrumb_items) %>
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@
<% end %>
<% end %>
<% end %>
<%= render partial: "layouts/decidim/schema_org_breadcrumb_list", locals: { breadcrumb_items: } %>
41 changes: 41 additions & 0 deletions decidim-core/spec/helpers/decidim/breadcrumb_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

require "spec_helper"

module Decidim
describe BreadcrumbHelper do
describe "#render_schema_org_breadcrumb_list" do
subject { helper.render_schema_org_breadcrumb_list(breadcrumb_items) }

let(:breadcrumb_items) do
[
{
label: "Processes",
url: "/processes",
active: true
},
{
label: { ca: "Hola mon", es: "Hola mundo", en: "Hello world" },
url: "/processes/hello-world",
dropdown_cell: "decidim/participatory_processes/process_dropdown_metadata",
resource: participatory_process
}

]
end

let(:participatory_process) { create(:participatory_process) }

before do
allow(helper).to receive(:current_organization).and_return(participatory_process.organization)
end

it "renders a schema.org event" do
keys = JSON.parse(subject).keys
expect(keys).to include("@context")
expect(keys).to include("@type")
expect(keys).to include("itemListElement")
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# frozen_string_literal: true

require "spec_helper"

module Decidim
describe SchemaOrgBreadcrumbListSerializer do
subject do
described_class.new({ breadcrumb_items:, base_url:, organization_name: })
end

let(:breadcrumb_items) do
[
{
label: "Processes",
url: "/processes",
active: true
},
{
label: { ca: "Hola mon", es: "Hola mundo", en: "Hello world" },
url: "/processes/hello-world",
dropdown_cell: "decidim/participatory_processes/process_dropdown_metadata",
resource: participatory_process
}
]
end

let(:base_url) { "https://example.org" }
let(:participatory_process) { create(:participatory_process) }
let(:organization_name) { "ACME Corp" }

describe "#serialize" do
let(:serialized) { subject.serialize }

it "serializes the @context" do
expect(serialized[:@context]).to eq("https://schema.org")
end

it "serializes the @type" do
expect(serialized[:@type]).to eq("BreadcrumbList")
end

it "serializes the name" do
expect(serialized[:name]).to eq("ACME Corp breadcrumb")
end

it "serializes the breadcrumb items" do
expected_items_elements = [
{ "@type": "ListItem", position: 1, name: "Processes", item: "https://example.org/processes" },
{ "@type": "ListItem", position: 2, name: "Hello world", item: "https://example.org/processes/hello-world" }
]
expect(serialized[:itemListElement]).to eq(expected_items_elements)
end

context "when there are empty items" do
let(:breadcrumb_items) do
[
{
label: "Processes",
url: "/processes",
active: true
},
{
label: { ca: "Hola mon", es: "Hola mundo", en: "Hello world" },
url: "/processes/hello-world",
dropdown_cell: "decidim/participatory_processes/process_dropdown_metadata",
resource: participatory_process
},
{}
]
end

it "ignores them" do
expected_items_elements = [
{ "@type": "ListItem", position: 1, name: "Processes", item: "https://example.org/processes" },
{ "@type": "ListItem", position: 2, name: "Hello world", item: "https://example.org/processes/hello-world" }
]
expect(serialized[:itemListElement]).to eq(expected_items_elements)
end
end

context "when there are only items without URLs" do
let(:breadcrumb_items) do
[
{
label: "Profile",
active: true
}
]
end

it "returns an empty JSON" do
expect(serialized).to eq({})
end
end
end
end
end

0 comments on commit 543f6f4

Please sign in to comment.