Commit 00f94399 authored by Zack Cuddy's avatar Zack Cuddy Committed by Michael Kozono

Global Search - Bold Issue Search Term

This adds a method to bold the search
term on the issues results page.
parent 1f859f89
...@@ -11,6 +11,8 @@ module Projects ...@@ -11,6 +11,8 @@ module Projects
push_frontend_feature_flag(:ci_key_autocomplete, default_enabled: true) push_frontend_feature_flag(:ci_key_autocomplete, default_enabled: true)
end end
helper_method :highlight_badge
def show def show
end end
...@@ -50,6 +52,10 @@ module Projects ...@@ -50,6 +52,10 @@ module Projects
private private
def highlight_badge(name, content, language = nil)
Gitlab::Highlight.highlight(name, content, language: language)
end
def update_params def update_params
params.require(:project).permit(*permitted_project_params) params.require(:project).permit(*permitted_project_params)
end end
......
...@@ -38,6 +38,7 @@ class SearchController < ApplicationController ...@@ -38,6 +38,7 @@ class SearchController < ApplicationController
@show_snippets = search_service.show_snippets? @show_snippets = search_service.show_snippets?
@search_results = search_service.search_results @search_results = search_service.search_results
@search_objects = search_service.search_objects(preload_method) @search_objects = search_service.search_objects(preload_method)
@search_highlight = search_service.search_highlight
render_commits if @scope == 'commits' render_commits if @scope == 'commits'
eager_load_user_status if @scope == 'users' eager_load_user_status if @scope == 'users'
......
# frozen_string_literal: true # frozen_string_literal: true
module BlobHelper module BlobHelper
def highlight(file_name, file_content, language: nil, plain: false)
highlighted = Gitlab::Highlight.highlight(file_name, file_content, plain: plain, language: language)
raw %(<pre class="code highlight"><code>#{highlighted}</code></pre>)
end
def no_highlight_files def no_highlight_files
%w(credits changelog news copying copyright license authors) %w(credits changelog news copying copyright license authors)
end end
......
...@@ -294,6 +294,13 @@ module SearchHelper ...@@ -294,6 +294,13 @@ module SearchHelper
sanitize(html, tags: %w(a p ol ul li pre code)) sanitize(html, tags: %w(a p ol ul li pre code))
end end
def simple_search_highlight_and_truncate(text, phrase, options = {})
truncate_length = options.delete(:length) { 200 }
text = truncate(text, length: truncate_length)
phrase = phrase.split
highlight(text, phrase, options)
end
def show_user_search_tab? def show_user_search_tab?
return false if Feature.disabled?(:users_search, default_enabled: true) return false if Feature.disabled?(:users_search, default_enabled: true)
......
...@@ -65,6 +65,10 @@ class SearchService ...@@ -65,6 +65,10 @@ class SearchService
@search_objects ||= redact_unauthorized_results(search_results.objects(scope, page: params[:page], per_page: per_page, preload_method: preload_method)) @search_objects ||= redact_unauthorized_results(search_results.objects(scope, page: params[:page], per_page: per_page, preload_method: preload_method))
end end
def search_highlight
search_results.highlight_map(scope)
end
private private
def per_page def per_page
......
...@@ -15,18 +15,18 @@ ...@@ -15,18 +15,18 @@
.col-md-2.text-center .col-md-2.text-center
Markdown Markdown
.col-md-10.code.js-syntax-highlight .col-md-10.code.js-syntax-highlight
= highlight('.md', badge.to_markdown, language: 'markdown') = highlight_badge('.md', badge.to_markdown, language: 'markdown')
.row .row
%hr %hr
.row .row
.col-md-2.text-center .col-md-2.text-center
HTML HTML
.col-md-10.code.js-syntax-highlight .col-md-10.code.js-syntax-highlight
= highlight('.html', badge.to_html, language: 'html') = highlight_badge('.html', badge.to_html, language: 'html')
.row .row
%hr %hr
.row .row
.col-md-2.text-center .col-md-2.text-center
AsciiDoc AsciiDoc
.col-md-10.code.js-syntax-highlight .col-md-10.code.js-syntax-highlight
= highlight('.adoc', badge.to_asciidoc) = highlight_badge('.adoc', badge.to_asciidoc)
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
%span.term.str-truncated.gl-font-weight-bold.gl-ml-2= issue.title %span.term.str-truncated.gl-font-weight-bold.gl-ml-2= issue.title
.gl-text-gray-500.gl-my-3 .gl-text-gray-500.gl-my-3
= sprintf(s_(' %{project_name}#%{issue_iid} &middot; opened %{issue_created} by %{author}'), { project_name: issue.project.full_name, issue_iid: issue.iid, issue_created: time_ago_with_tooltip(issue.created_at, placement: 'bottom'), author: link_to_member(@project, issue.author, avatar: false) }).html_safe = sprintf(s_(' %{project_name}#%{issue_iid} &middot; opened %{issue_created} by %{author}'), { project_name: issue.project.full_name, issue_iid: issue.iid, issue_created: time_ago_with_tooltip(issue.created_at, placement: 'bottom'), author: link_to_member(@project, issue.author, avatar: false) }).html_safe
- if issue.description.present? .description.term.col-sm-10.gl-px-0
.description.term.col-sm-10.gl-px-0 - if search_service.use_elasticsearch? && @search_highlight[issue.id]&.description.present?
= truncate(issue.description, length: 200) = Truncato.truncate(@search_highlight[issue.id].description.first, count_tags: false, count_tail: false, max_length: 200).html_safe
- elsif issue.description.present?
= simple_search_highlight_and_truncate(issue.description, @search_term, highlighter: '<span class="gl-text-black-normal gl-font-weight-bold">\1</span>')
---
title: Global Search - Bold Issue's Search Term
merge_request: 41411
author:
type: changed
...@@ -44,7 +44,13 @@ module Elastic ...@@ -44,7 +44,13 @@ module Elastic
memo[field.to_sym] = {} memo[field.to_sym] = {}
end end
{ fields: es_fields } # Adding number_of_fragments: 0 to not split results into snippets. This way controllers can decide how to handle the highlighted data.
{
fields: es_fields,
number_of_fragments: 0,
pre_tags: ['<span class="gl-text-black-normal gl-font-weight-bold">'],
post_tags: ['</span>']
}
end end
def basic_query_hash(fields, query) def basic_query_hash(fields, query)
......
...@@ -46,6 +46,25 @@ module Gitlab ...@@ -46,6 +46,25 @@ module Gitlab
end end
end end
# Pull the highlight attribute out of Elasticsearch results
# and map it to the result id
def highlight_map(scope)
results = case scope
when 'projects'
projects
when 'issues'
issues
when 'merge_requests'
merge_requests
when 'milestones'
milestones
when 'notes'
notes
end
results.to_h { |x| [x[:_source][:id], x[:highlight]] } if results.present?
end
def formatted_count(scope) def formatted_count(scope)
case scope case scope
when 'projects' when 'projects'
......
...@@ -16,6 +16,10 @@ module Gitlab ...@@ -16,6 +16,10 @@ module Gitlab
limited_snippet_titles_count limited_snippet_titles_count
end end
def highlight_map(scope)
snippet_titles.to_h { |x| [x[:_source][:id], x[:highlight]] }
end
private private
def snippet_titles def snippet_titles
......
...@@ -12,6 +12,32 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -12,6 +12,32 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
let(:project_2) { create(:project, :public, :repository, :wiki_repo) } let(:project_2) { create(:project, :public, :repository, :wiki_repo) }
let(:limit_project_ids) { [project_1.id] } let(:limit_project_ids) { [project_1.id] }
describe '#highlight_map' do
using RSpec::Parameterized::TableSyntax
let(:results) { described_class.new(user, 'hello world', limit_project_ids) }
where(:scope, :results_method, :expected) do
'projects' | :projects | { 1 => 'test <span class="gl-text-black-normal gl-font-weight-bold">highlight</span>' }
'milestones' | :milestones | { 1 => 'test <span class="gl-text-black-normal gl-font-weight-bold">highlight</span>' }
'notes' | :notes | { 1 => 'test <span class="gl-text-black-normal gl-font-weight-bold">highlight</span>' }
'issues' | :issues | { 1 => 'test <span class="gl-text-black-normal gl-font-weight-bold">highlight</span>' }
'merge_requests' | :merge_requests | { 1 => 'test <span class="gl-text-black-normal gl-font-weight-bold">highlight</span>' }
'blobs' | nil | nil
'wiki_blobs' | nil | nil
'commits' | nil | nil
'users' | nil | nil
'unknown' | nil | nil
end
with_them do
it 'returns the expected highlight map' do
expect(results).to receive(results_method).and_return([{ _source: { id: 1 }, highlight: 'test <span class="gl-text-black-normal gl-font-weight-bold">highlight</span>' }]) if results_method
expect(results.highlight_map(scope)).to eq(expected)
end
end
end
describe '#formatted_count' do describe '#formatted_count' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
...@@ -542,7 +568,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -542,7 +568,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
end end
describe 'Blobs' do describe 'blobs' do
before do before do
project_1.repository.index_commits_and_blobs project_1.repository.index_commits_and_blobs
...@@ -752,7 +778,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -752,7 +778,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
end end
describe 'Wikis' do describe 'wikis' do
let(:results) { described_class.new(user, 'term', limit_project_ids) } let(:results) { described_class.new(user, 'term', limit_project_ids) }
subject(:wiki_blobs) { results.objects('wiki_blobs') } subject(:wiki_blobs) { results.objects('wiki_blobs') }
...@@ -839,7 +865,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -839,7 +865,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
end end
describe 'Commits' do describe 'commits' do
before do before do
project_1.repository.index_commits_and_blobs project_1.repository.index_commits_and_blobs
ensure_elasticsearch_index! ensure_elasticsearch_index!
...@@ -875,7 +901,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -875,7 +901,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
end end
describe 'Visibility levels' do describe 'visibility levels' do
let(:internal_project) { create(:project, :internal, :repository, :wiki_repo, description: "Internal project") } let(:internal_project) { create(:project, :internal, :repository, :wiki_repo, description: "Internal project") }
let(:private_project1) { create(:project, :private, :repository, :wiki_repo, description: "Private project") } let(:private_project1) { create(:project, :private, :repository, :wiki_repo, description: "Private project") }
let(:private_project2) { create(:project, :private, :repository, :wiki_repo, description: "Private project where I'm a member") } let(:private_project2) { create(:project, :private, :repository, :wiki_repo, description: "Private project where I'm a member") }
...@@ -886,7 +912,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -886,7 +912,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
private_project2.project_members.create(user: user, access_level: ProjectMember::DEVELOPER) private_project2.project_members.create(user: user, access_level: ProjectMember::DEVELOPER)
end end
context 'Issues' do context 'issues' do
it 'finds right set of issues' do it 'finds right set of issues' do
issue_1 = create :issue, project: internal_project, title: "Internal project" issue_1 = create :issue, project: internal_project, title: "Internal project"
create :issue, project: private_project1, title: "Private project" create :issue, project: private_project1, title: "Private project"
...@@ -913,7 +939,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -913,7 +939,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
end end
context 'Milestones' do context 'milestones' do
let!(:milestone_1) { create(:milestone, project: internal_project, title: "Internal project") } let!(:milestone_1) { create(:milestone, project: internal_project, title: "Internal project") }
let!(:milestone_2) { create(:milestone, project: private_project1, title: "Private project") } let!(:milestone_2) { create(:milestone, project: private_project1, title: "Private project") }
let!(:milestone_3) { create(:milestone, project: private_project2, title: "Private project which user is member") } let!(:milestone_3) { create(:milestone, project: private_project2, title: "Private project which user is member") }
...@@ -1033,7 +1059,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1033,7 +1059,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
end end
context 'Projects' do context 'projects' do
it_behaves_like 'a paginated object', 'projects' it_behaves_like 'a paginated object', 'projects'
it 'finds right set of projects' do it 'finds right set of projects' do
...@@ -1062,7 +1088,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1062,7 +1088,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
end end
context 'Merge Requests' do context 'merge requests' do
it 'finds right set of merge requests' do it 'finds right set of merge requests' do
merge_request_1 = create :merge_request, target_project: internal_project, source_project: internal_project, title: "Internal project" merge_request_1 = create :merge_request, target_project: internal_project, source_project: internal_project, title: "Internal project"
create :merge_request, target_project: private_project1, source_project: private_project1, title: "Private project" create :merge_request, target_project: private_project1, source_project: private_project1, title: "Private project"
...@@ -1089,7 +1115,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1089,7 +1115,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
end end
context 'Wikis' do context 'wikis' do
before do before do
[public_project, internal_project, private_project1, private_project2].each do |project| [public_project, internal_project, private_project1, private_project2].each do |project|
project.wiki.create_page('index_page', 'term') project.wiki.create_page('index_page', 'term')
...@@ -1116,7 +1142,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1116,7 +1142,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
end end
context 'Commits' do context 'commits' do
it 'finds right set of commits' do it 'finds right set of commits' do
[internal_project, private_project1, private_project2, public_project].each do |project| [internal_project, private_project1, private_project2, public_project].each do |project|
project.repository.create_file( project.repository.create_file(
...@@ -1148,7 +1174,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1148,7 +1174,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
end end
context 'Blobs' do context 'blobs' do
it 'finds right set of blobs' do it 'finds right set of blobs' do
[internal_project, private_project1, private_project2, public_project].each do |project| [internal_project, private_project1, private_project2, public_project].each do |project|
project.repository.create_file( project.repository.create_file(
......
...@@ -40,6 +40,13 @@ RSpec.describe Gitlab::Elastic::SnippetSearchResults, :elastic, :sidekiq_might_n ...@@ -40,6 +40,13 @@ RSpec.describe Gitlab::Elastic::SnippetSearchResults, :elastic, :sidekiq_might_n
end end
end end
describe '#highlight_map' do
it 'returns the expected highlight map' do
expect(results).to receive(:snippet_titles).and_return([{ _source: { id: 1 }, highlight: 'test <span class="gl-text-black-normal gl-font-weight-bold">highlight</span>' }])
expect(results.highlight_map('snippet_titles')).to eq({ 1 => 'test <span class="gl-text-black-normal gl-font-weight-bold">highlight</span>' })
end
end
context 'when user is not author' do context 'when user is not author' do
let(:results) { described_class.new(create(:user), 'foo', []) } let(:results) { described_class.new(create(:user), 'foo', []) }
......
...@@ -116,6 +116,11 @@ module Gitlab ...@@ -116,6 +116,11 @@ module Gitlab
UsersFinder.new(current_user, search: query).execute UsersFinder.new(current_user, search: query).execute
end end
# highlighting is only performed by Elasticsearch backed results
def highlight_map(scope)
{}
end
private private
def projects def projects
......
...@@ -17,10 +17,10 @@ RSpec.describe 'list of badges' do ...@@ -17,10 +17,10 @@ RSpec.describe 'list of badges' do
expect(page).to have_content 'Markdown' expect(page).to have_content 'Markdown'
expect(page).to have_content 'HTML' expect(page).to have_content 'HTML'
expect(page).to have_content 'AsciiDoc' expect(page).to have_content 'AsciiDoc'
expect(page).to have_css('.highlight', count: 3) expect(page).to have_css('.js-syntax-highlight', count: 3)
expect(page).to have_xpath("//img[@alt='pipeline status']") expect(page).to have_xpath("//img[@alt='pipeline status']")
page.within('.highlight', match: :first) do page.within('.js-syntax-highlight', match: :first) do
expect(page).to have_content 'badges/master/pipeline.svg' expect(page).to have_content 'badges/master/pipeline.svg'
end end
end end
...@@ -32,10 +32,10 @@ RSpec.describe 'list of badges' do ...@@ -32,10 +32,10 @@ RSpec.describe 'list of badges' do
expect(page).to have_content 'Markdown' expect(page).to have_content 'Markdown'
expect(page).to have_content 'HTML' expect(page).to have_content 'HTML'
expect(page).to have_content 'AsciiDoc' expect(page).to have_content 'AsciiDoc'
expect(page).to have_css('.highlight', count: 3) expect(page).to have_css('.js-syntax-highlight', count: 3)
expect(page).to have_xpath("//img[@alt='coverage report']") expect(page).to have_xpath("//img[@alt='coverage report']")
page.within('.highlight', match: :first) do page.within('.js-syntax-highlight', match: :first) do
expect(page).to have_content 'badges/master/coverage.svg' expect(page).to have_content 'badges/master/coverage.svg'
end end
end end
......
...@@ -5,16 +5,6 @@ require 'spec_helper' ...@@ -5,16 +5,6 @@ require 'spec_helper'
RSpec.describe BlobHelper do RSpec.describe BlobHelper do
include TreeHelper include TreeHelper
describe '#highlight' do
it 'wraps highlighted content' do
expect(helper.highlight('test.rb', '52')).to eq(%q[<pre class="code highlight"><code><span id="LC1" class="line" lang="ruby"><span class="mi">52</span></span></code></pre>])
end
it 'handles plain version' do
expect(helper.highlight('test.rb', '52', plain: true)).to eq(%q[<pre class="code highlight"><code><span id="LC1" class="line" lang="">52</span></code></pre>])
end
end
describe "#sanitize_svg_data" do describe "#sanitize_svg_data" do
let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') } let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') }
let(:data) { File.read(input_svg_path) } let(:data) { File.read(input_svg_path) }
......
...@@ -58,6 +58,25 @@ RSpec.describe Gitlab::SearchResults do ...@@ -58,6 +58,25 @@ RSpec.describe Gitlab::SearchResults do
end end
end end
describe '#highlight_map' do
using RSpec::Parameterized::TableSyntax
where(:scope, :expected) do
'projects' | {}
'issues' | {}
'merge_requests' | {}
'milestones' | {}
'users' | {}
'unknown' | {}
end
with_them do
it 'returns the expected highlight_map' do
expect(results.highlight_map(scope)).to eq(expected)
end
end
end
describe '#formatted_limited_count' do describe '#formatted_limited_count' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
......
...@@ -21,6 +21,12 @@ RSpec.describe Gitlab::SnippetSearchResults do ...@@ -21,6 +21,12 @@ RSpec.describe Gitlab::SnippetSearchResults do
end end
end end
describe '#highlight_map' do
it 'returns the expected highlight map' do
expect(results.highlight_map('snippet_titles')).to eq({})
end
end
describe '#objects' do describe '#objects' do
it 'uses page and per_page to paginate results' do it 'uses page and per_page to paginate results' do
snippet2 = create(:snippet, :public, content: 'foo', file_name: 'foo') snippet2 = create(:snippet, :public, content: 'foo', file_name: 'foo')
......
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