Commit 98455e78 authored by Paul Slaughter's avatar Paul Slaughter

Merge branch '36844-fj-add-schema-to-breadcrumb-json' into 'master'

Add schema markup to breadcrumb

See merge request gitlab-org/gitlab!46991
parents 7472a375 9b6d8dbb
......@@ -32,4 +32,46 @@ module BreadcrumbsHelper
@breadcrumb_dropdown_links[location] ||= []
@breadcrumb_dropdown_links[location] << link
end
def push_to_schema_breadcrumb(text, link)
list_item = schema_list_item(text, link, schema_breadcrumb_list.size + 1)
schema_breadcrumb_list.push(list_item)
end
def schema_breadcrumb_json
{
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
'itemListElement': build_item_list_elements
}.to_json
end
private
def schema_breadcrumb_list
@schema_breadcrumb_list ||= []
end
def build_item_list_elements
return @schema_breadcrumb_list unless @breadcrumbs_extra_links&.any?
last_element = schema_breadcrumb_list.pop
@breadcrumbs_extra_links.each do |el|
push_to_schema_breadcrumb(el[:text], el[:link])
end
last_element['position'] = schema_breadcrumb_list.last['position'] + 1
schema_breadcrumb_list.push(last_element)
end
def schema_list_item(text, link, position)
{
'@type' => 'ListItem',
'position' => position,
'name' => text,
'item' => link
}
end
end
......@@ -94,12 +94,19 @@ module GroupsHelper
else
full_title << breadcrumb_list_item(group_title_link(parent, hidable: false))
end
push_to_schema_breadcrumb(simple_sanitize(parent.name), group_path(parent))
end
full_title << render("layouts/nav/breadcrumbs/collapsed_dropdown", location: :before, title: _("Show parent subgroups"))
full_title << breadcrumb_list_item(group_title_link(group))
full_title << ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path breadcrumb-item-text js-breadcrumb-item-text') if name
push_to_schema_breadcrumb(simple_sanitize(group.name), group_path(group))
if name
full_title << ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path breadcrumb-item-text js-breadcrumb-item-text')
push_to_schema_breadcrumb(simple_sanitize(name), url)
end
full_title.join.html_safe
end
......
......@@ -84,18 +84,8 @@ module ProjectsHelper
end
def project_title(project)
namespace_link =
if project.group
group_title(project.group, nil, nil)
else
owner = project.namespace.owner
link_to(simple_sanitize(owner.name), user_path(owner))
end
project_link = link_to project_path(project) do
icon = project_icon(project, alt: project.name, class: 'avatar-tile', width: 15, height: 15) if project.avatar_url && !Rails.env.test?
[icon, content_tag("span", simple_sanitize(project.name), class: "breadcrumb-item-text js-breadcrumb-item-text")].join.html_safe
end
namespace_link = build_namespace_breadcrumb_link(project)
project_link = build_project_breadcrumb_link(project)
namespace_link = breadcrumb_list_item(namespace_link) unless project.group
project_link = breadcrumb_list_item project_link
......@@ -787,6 +777,30 @@ module ProjectsHelper
def project_access_token_available?(project)
can?(current_user, :admin_resource_access_tokens, project)
end
def build_project_breadcrumb_link(project)
project_name = simple_sanitize(project.name)
push_to_schema_breadcrumb(project_name, project_path(project))
link_to project_path(project) do
icon = project_icon(project, alt: project_name, class: 'avatar-tile', width: 15, height: 15) if project.avatar_url && !Rails.env.test?
[icon, content_tag("span", project_name, class: "breadcrumb-item-text js-breadcrumb-item-text")].join.html_safe
end
end
def build_namespace_breadcrumb_link(project)
if project.group
group_title(project.group, nil, nil)
else
owner = project.namespace.owner
name = simple_sanitize(owner.name)
url = user_path(owner)
push_to_schema_breadcrumb(name, url)
link_to(name, url)
end
end
end
ProjectsHelper.prepend_if_ee('EE::ProjectsHelper')
- container = @no_breadcrumb_container ? 'container-fluid' : container_class
- hide_top_links = @hide_top_links || false
- push_to_schema_breadcrumb(@breadcrumb_title, breadcrumb_title_link)
%nav.breadcrumbs{ role: "navigation", class: [container, @content_class] }
.breadcrumbs-container{ class: ("border-bottom-0" if @no_breadcrumb_border) }
......@@ -17,4 +18,7 @@
= render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :after
%li
%h2.breadcrumbs-sub-title= link_to @breadcrumb_title, breadcrumb_title_link
%script{ type:'application/ld+json' }
:plain
#{schema_breadcrumb_json}
= yield :header_content
---
title: Add SEO schema markup to breadcrumbs
merge_request: 46991
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Breadcrumbs schema markup', :aggregate_failures do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, namespace: user.namespace) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:subgroup) { create(:group, :public, parent: group) }
let_it_be(:group_project) { create(:project, :public, namespace: subgroup) }
it 'generates the breadcrumb schema for user projects' do
visit project_url(project)
item_list = get_schema_content
expect(item_list.size).to eq 3
expect(item_list[0]['name']).to eq project.namespace.name
expect(item_list[0]['item']).to eq user_path(project.owner)
expect(item_list[1]['name']).to eq project.name
expect(item_list[1]['item']).to eq project_path(project)
expect(item_list[2]['name']).to eq 'Details'
expect(item_list[2]['item']).to eq project_path(project)
end
it 'generates the breadcrumb schema for group projects' do
visit project_url(group_project)
item_list = get_schema_content
expect(item_list.size).to eq 4
expect(item_list[0]['name']).to eq group.name
expect(item_list[0]['item']).to eq group_path(group)
expect(item_list[1]['name']).to eq subgroup.name
expect(item_list[1]['item']).to eq group_path(subgroup)
expect(item_list[2]['name']).to eq group_project.name
expect(item_list[2]['item']).to eq project_path(group_project)
expect(item_list[3]['name']).to eq 'Details'
expect(item_list[3]['item']).to eq project_path(group_project)
end
it 'generates the breadcrumb schema for group' do
visit group_url(subgroup)
item_list = get_schema_content
expect(item_list.size).to eq 3
expect(item_list[0]['name']).to eq group.name
expect(item_list[0]['item']).to eq group_path(group)
expect(item_list[1]['name']).to eq subgroup.name
expect(item_list[1]['item']).to eq group_path(subgroup)
expect(item_list[2]['name']).to eq 'Details'
expect(item_list[2]['item']).to eq group_path(subgroup)
end
it 'generates the breadcrumb schema for issues' do
visit project_issues_url(project)
item_list = get_schema_content
expect(item_list.size).to eq 3
expect(item_list[0]['name']).to eq project.namespace.name
expect(item_list[0]['item']).to eq user_path(project.owner)
expect(item_list[1]['name']).to eq project.name
expect(item_list[1]['item']).to eq project_path(project)
expect(item_list[2]['name']).to eq 'Issues'
expect(item_list[2]['item']).to eq project_issues_path(project)
end
it 'generates the breadcrumb schema for specific issue' do
visit project_issue_url(project, issue)
item_list = get_schema_content
expect(item_list.size).to eq 4
expect(item_list[0]['name']).to eq project.namespace.name
expect(item_list[0]['item']).to eq user_path(project.owner)
expect(item_list[1]['name']).to eq project.name
expect(item_list[1]['item']).to eq project_path(project)
expect(item_list[2]['name']).to eq 'Issues'
expect(item_list[2]['item']).to eq project_issues_path(project)
expect(item_list[3]['name']).to eq issue.to_reference
expect(item_list[3]['item']).to eq project_issue_path(project, issue)
end
def get_schema_content
content = find('script[type="application/ld+json"]', visible: false).text(:all)
expect(content).not_to be_nil
Gitlab::Json.parse(content)['itemListElement']
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BreadcrumbsHelper do
describe '#push_to_schema_breadcrumb' do
it 'enqueue element name, link and position' do
element = %w(element1 link1)
helper.push_to_schema_breadcrumb(element[0], element[1])
list = helper.instance_variable_get(:@schema_breadcrumb_list)
aggregate_failures do
expect(list[0]['name']).to eq element[0]
expect(list[0]['item']).to eq element[1]
expect(list[0]['position']).to eq(1)
end
end
end
describe '#schema_breadcrumb_json' do
let(:elements) do
[
%w(element1 link1),
%w(element2 link2)
]
end
subject { helper.schema_breadcrumb_json }
it 'returns the breadcrumb schema in json format' do
enqueue_breadcrumb_elements
expected_result = {
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => [
{
'@type' => 'ListItem',
'position' => 1,
'name' => elements[0][0],
'item' => elements[0][1]
},
{
'@type' => 'ListItem',
'position' => 2,
'name' => elements[1][0],
'item' => elements[1][1]
}
]
}.to_json
expect(subject).to eq expected_result
end
context 'when extra breadcrumb element is added' do
let(:extra_elements) do
[
%w(extra_element1 extra_link1),
%w(extra_element2 extra_link2)
]
end
it 'include the extra elements before the last element' do
enqueue_breadcrumb_elements
extra_elements.each do |el|
add_to_breadcrumbs(el[0], el[1])
end
expected_result = {
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => [
{
'@type' => 'ListItem',
'position' => 1,
'name' => elements[0][0],
'item' => elements[0][1]
},
{
'@type' => 'ListItem',
'position' => 2,
'name' => extra_elements[0][0],
'item' => extra_elements[0][1]
},
{
'@type' => 'ListItem',
'position' => 3,
'name' => extra_elements[1][0],
'item' => extra_elements[1][1]
},
{
'@type' => 'ListItem',
'position' => 4,
'name' => elements[1][0],
'item' => elements[1][1]
}
]
}.to_json
expect(subject).to eq expected_result
end
end
def enqueue_breadcrumb_elements
elements.each do |el|
helper.push_to_schema_breadcrumb(el[0], el[1])
end
end
end
end
......@@ -87,15 +87,26 @@ RSpec.describe GroupsHelper do
end
describe 'group_title' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
let_it_be(:group) { create(:group) }
let_it_be(:nested_group) { create(:group, parent: group) }
let_it_be(:deep_nested_group) { create(:group, parent: nested_group) }
let_it_be(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
subject { helper.group_title(very_deep_nested_group) }
it 'outputs the groups in the correct order' do
expect(helper.group_title(very_deep_nested_group))
expect(subject)
.to match(%r{<li style="text-indent: 16px;"><a.*>#{deep_nested_group.name}.*</li>.*<a.*>#{very_deep_nested_group.name}</a>}m)
end
it 'enqueues the elements in the breadcrumb schema list' do
expect(helper).to receive(:push_to_schema_breadcrumb).with(group.name, group_path(group))
expect(helper).to receive(:push_to_schema_breadcrumb).with(nested_group.name, group_path(nested_group))
expect(helper).to receive(:push_to_schema_breadcrumb).with(deep_nested_group.name, group_path(deep_nested_group))
expect(helper).to receive(:push_to_schema_breadcrumb).with(very_deep_nested_group.name, group_path(very_deep_nested_group))
subject
end
end
# rubocop:disable Layout/SpaceBeforeComma
......
......@@ -999,4 +999,15 @@ RSpec.describe ProjectsHelper do
end
end
end
describe '#project_title' do
subject { helper.project_title(project) }
it 'enqueues the elements in the breadcrumb schema list' do
expect(helper).to receive(:push_to_schema_breadcrumb).with(project.namespace.name, user_path(project.owner))
expect(helper).to receive(:push_to_schema_breadcrumb).with(project.name, project_path(project))
subject
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment