Commit eac50c9c authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents b6de0ffc fe214a21
export const serializeFormEntries = entries =>
entries.reduce((acc, { name, value }) => Object.assign(acc, { [name]: value }), {});
export const serializeForm = form => {
const fdata = new FormData(form);
const entries = Array.from(fdata.keys()).map(key => {
const val = fdata.getAll(key);
return { name: key, value: val.length === 1 ? val[0] : val };
});
return serializeFormEntries(entries);
};
/* eslint-disable func-names, no-var, no-return-assign, one-var, object-shorthand, vars-on-top */
/* eslint-disable func-names, no-var, no-return-assign, object-shorthand, vars-on-top */
import $ from 'jquery';
import Cookies from 'js-cookie';
import { __ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import { visitUrl, mergeUrlParams } from '~/lib/utils/url_utility';
import { serializeForm } from '~/lib/utils/forms';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import projectSelect from '../../project_select';
......@@ -107,9 +108,10 @@ export default class Project {
refLink.href = '#';
return $('.js-project-refs-dropdown').each(function() {
var $dropdown, selected;
$dropdown = $(this);
selected = $dropdown.data('selected');
var $dropdown = $(this);
var selected = $dropdown.data('selected');
var fieldName = $dropdown.data('fieldName');
var shouldVisit = Boolean($dropdown.data('visit'));
return $dropdown.glDropdown({
data(term, callback) {
axios
......@@ -127,7 +129,7 @@ export default class Project {
filterRemote: true,
filterByText: true,
inputFieldName: $dropdown.data('inputFieldName'),
fieldName: $dropdown.data('fieldName'),
fieldName,
renderRow: function(ref) {
var li = refListItem.cloneNode(false);
......@@ -158,15 +160,12 @@ export default class Project {
clicked: function(options) {
const { e } = options;
e.preventDefault();
if ($('input[name="ref"]').length) {
if ($(`input[name="${fieldName}"]`).length) {
var $form = $dropdown.closest('form');
var $visit = $dropdown.data('visit');
var shouldVisit = $visit ? true : $visit;
var action = $form.attr('action');
var divider = action.indexOf('?') === -1 ? '?' : '&';
if (shouldVisit) {
visitUrl(`${action}${divider}${$form.serialize()}`);
visitUrl(mergeUrlParams(serializeForm($form[0]), action));
}
}
},
......
......@@ -2,6 +2,7 @@ import $ from 'jquery';
import Flash from '~/flash';
import Api from '~/api';
import { __ } from '~/locale';
import Project from '~/pages/projects/project';
export default class Search {
constructor() {
......@@ -69,6 +70,8 @@ export default class Search {
},
clicked: () => Search.submitSearch(),
});
Project.initRefSwitcher();
}
eventListeners() {
......
......@@ -3,13 +3,12 @@
class BlobPresenter < Gitlab::View::Presenter::Delegated
presents :blob
def highlight(since: nil, to: nil, plain: nil)
def highlight(plain: nil)
load_all_blob_data
Gitlab::Highlight.highlight(
blob.path,
limited_blob_data(since: since, to: to),
since: since,
blob.data,
language: blob.language_from_gitattributes,
plain: plain
)
......@@ -24,18 +23,4 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def load_all_blob_data
blob.load_all_data! if blob.respond_to?(:load_all_data!)
end
def limited_blob_data(since: nil, to: nil)
return blob.data if since.blank? || to.blank?
limited_blob_lines(since, to).join
end
def limited_blob_lines(since, to)
all_lines[since - 1..to - 1]
end
def all_lines
@all_lines ||= blob.data.lines
end
end
......@@ -21,19 +21,20 @@ module Blobs
load_all_blob_data
@subject = blob
@all_lines = blob.data.lines
super(params)
if full?
self.attributes = { since: 1, to: all_lines.size, bottom: false, unfold: false, offset: 0, indent: 0 }
self.attributes = { since: 1, to: @all_lines.size, bottom: false, unfold: false, offset: 0, indent: 0 }
end
end
# Returns an array of Gitlab::Diff::Line with match line added
def diff_lines
diff_lines = limited_blob_lines(since, to).map.with_index do |line, index|
full_line = line.delete("\n")
diff_lines = lines.map.with_index do |line, index|
full_line = limited_blob_lines[index].delete("\n")
Gitlab::Diff::Line.new(full_line, nil, nil, nil, nil, rich_text: lines[index])
Gitlab::Diff::Line.new(full_line, nil, nil, nil, nil, rich_text: line)
end
add_match_line(diff_lines)
......@@ -42,7 +43,7 @@ module Blobs
end
def lines
@lines ||= highlight(since: since, to: to).lines.map(&:html_safe)
@lines ||= limit(highlight.lines).map(&:html_safe)
end
def match_line_text
......@@ -58,7 +59,7 @@ module Blobs
def add_match_line(diff_lines)
return unless unfold?
if bottom? && to < all_lines.size
if bottom? && to < @all_lines.size
old_pos = to - offset
new_pos = to
elsif since != 1
......@@ -72,5 +73,15 @@ module Blobs
bottom? ? diff_lines.push(match_line) : diff_lines.unshift(match_line)
end
def limited_blob_lines
@limited_blob_lines ||= limit(@all_lines)
end
def limit(lines)
return lines if full?
lines[since - 1..to - 1]
end
end
end
= form_tag search_path, method: :get, class: 'search-page-form js-search-form' do |f|
= hidden_field_tag :snippets, params[:snippets]
= hidden_field_tag :scope, params[:scope]
= hidden_field_tag :repository_ref, params[:repository_ref]
.d-lg-flex.align-items-end
.search-field-holder.form-group.mr-lg-1.mb-lg-0
......
......@@ -2,17 +2,25 @@
= render partial: "search/results/empty"
= render_if_exists 'shared/promotions/promote_advanced_search'
- else
.row-content-block
.row-content-block.d-md-flex.text-left.align-items-center
- unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount)
= search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
- link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project])
= _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
- link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project], class: 'ml-md-1')
- if @scope == 'blobs'
- repository_ref = params[:repository_ref].to_s.presence || @project.default_branch
= s_("SearchCodeResults|in")
.mx-md-1
= render partial: "shared/ref_switcher", locals: { ref: repository_ref, form_path: request.fullpath, field_name: 'repository_ref' }
= s_('SearchCodeResults|of %{link_to_project}').html_safe % { link_to_project: link_to_project }
- else
= _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
- elsif @group
- link_to_group = link_to(@group.name, @group)
- link_to_group = link_to(@group.name, @group, class: 'ml-md-1')
= _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
= render_if_exists 'shared/promotions/promote_advanced_search'
.results.prepend-top-10
- if @scope == 'commits'
%ul.content-list.commit-list
......
- dropdown_toggle_text = @ref || @project.default_branch
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
= hidden_field_tag :destination, destination
- return unless @project
- ref = local_assigns.fetch(:ref, @ref)
- form_path = local_assigns.fetch(:form_path, switch_project_refs_path(@project))
- dropdown_toggle_text = ref || @project.default_branch
- field_name = local_assigns.fetch(:field_name, 'ref')
= form_tag form_path, method: :get, class: "project-refs-form" do
- if defined?(destination)
= hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
- @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil
.dropdown
= dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" }
= dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: field_name, submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" }
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging.qa-branches-dropdown{ class: ("dropdown-menu-right" if local_assigns[:align_right]) }
.dropdown-page-one
= dropdown_title _("Switch branch/tag")
......
---
title: Add branch/tags/commits dropdown filter on the search page for searching codes
merge_request: 28282
author: minghuan lei
type: changed
---
title: Support selective highlighting of lines
merge_request: 31361
author:
type: performance
......@@ -6,16 +6,15 @@ module Gitlab
TIMEOUT_FOREGROUND = 3.seconds
MAXIMUM_TEXT_HIGHLIGHT_SIZE = 1.megabyte
def self.highlight(blob_name, blob_content, since: nil, language: nil, plain: false)
new(blob_name, blob_content, since: since, language: language)
def self.highlight(blob_name, blob_content, language: nil, plain: false)
new(blob_name, blob_content, language: language)
.highlight(blob_content, continue: false, plain: plain)
end
attr_reader :blob_name
def initialize(blob_name, blob_content, since: nil, language: nil)
def initialize(blob_name, blob_content, language: nil)
@formatter = Rouge::Formatters::HTMLGitlab
@since = since
@language = language
@blob_name = blob_name
@blob_content = blob_content
......@@ -54,13 +53,13 @@ module Gitlab
end
def highlight_plain(text)
@formatter.format(Rouge::Lexers::PlainText.lex(text), since: @since).html_safe
@formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
end
def highlight_rich(text, continue: true)
tag = lexer.tag
tokens = lexer.lex(text, continue: continue)
Timeout.timeout(timeout_time) { @formatter.format(tokens, tag: tag, since: @since).html_safe }
Timeout.timeout(timeout_time) { @formatter.format(tokens, tag: tag).html_safe }
rescue Timeout::Error => e
Gitlab::Sentry.track_exception(e)
highlight_plain(text)
......
......@@ -8,8 +8,8 @@ module Rouge
# Creates a new <tt>Rouge::Formatter::HTMLGitlab</tt> instance.
#
# [+tag+] The tag (language) of the lexer used to generate the formatted tokens
def initialize(tag: nil, since: nil)
@line_number = since || 1
def initialize(tag: nil)
@line_number = 1
@tag = tag
end
......
......@@ -6,6 +6,21 @@ describe 'User searches for code' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
def submit_search(search, with_send_keys: false)
page.within('.search') do
field = find_field('search')
field.fill_in(with: search)
if with_send_keys
field.send_keys(:enter)
else
click_button("Go")
end
end
click_link('Code')
end
context 'when signed in' do
before do
project.add_maintainer(user)
......@@ -15,12 +30,7 @@ describe 'User searches for code' do
it 'finds a file' do
visit(project_path(project))
page.within('.search') do
fill_in('search', with: 'application.js')
click_button('Go')
end
click_link('Code')
submit_search('application.js')
expect(page).to have_selector('.file-content .code')
expect(page).to have_selector("span.line[lang='javascript']")
......@@ -48,6 +58,50 @@ describe 'User searches for code' do
end
end
end
context 'search code within refs', :js do
let(:ref_name) { 'v1.0.0' }
before do
visit(project_tree_path(project, ref_name))
submit_search('gitlab-grack', with_send_keys: true)
end
it 'shows ref switcher in code result summary' do
expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
end
it 'persists branch name across search' do
find('.btn-search').click
expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
end
# this example is use to test the desgine that the refs is not
# only repersent the branch as well as the tags.
it 'ref swither list all the branchs and tags' do
find('.js-project-refs-dropdown').click
expect(find('.dropdown-page-one .dropdown-content')).to have_link('sha-starting-with-large-number')
expect(find('.dropdown-page-one .dropdown-content')).to have_link('v1.0.0')
end
it 'search result changes when refs switched' do
expect(find('.search-results')).not_to have_content('path = gitlab-grack')
find('.js-project-refs-dropdown').click
find('.dropdown-page-one .dropdown-content').click_link('master')
expect(find('.search-results')).to have_content('path = gitlab-grack')
end
end
it 'no ref switcher shown in issue result summary', :js do
issue = create(:issue, title: 'test', project: project)
visit(project_tree_path(project))
submit_search('test', with_send_keys: true)
expect(page).to have_selector('.js-project-refs-dropdown')
page.within('.search-filter') do
click_link('Issues')
end
expect(find(:css, '.search-results')).to have_link(issue.title)
expect(page).not_to have_selector('.js-project-refs-dropdown')
end
end
context 'when signed out' do
......@@ -58,8 +112,7 @@ describe 'User searches for code' do
end
it 'finds code' do
fill_in('search', with: 'rspec')
click_button('Go')
submit_search('rspec')
page.within('.results') do
expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
......
import { serializeForm } from '~/lib/utils/forms';
describe('lib/utils/forms', () => {
const createDummyForm = inputs => {
const form = document.createElement('form');
form.innerHTML = inputs
.map(({ type, name, value }) => {
let str = ``;
if (type === 'select') {
str = `<select name="${name}">`;
value.forEach(v => {
if (v.length > 0) {
str += `<option value="${v}"></option> `;
}
});
str += `</select>`;
} else {
str = `<input type="${type}" name="${name}" value="${value}" checked/>`;
}
return str;
})
.join('');
return form;
};
describe('serializeForm', () => {
it('returns an object of key values from inputs', () => {
const form = createDummyForm([
{ type: 'text', name: 'foo', value: 'foo-value' },
{ type: 'text', name: 'bar', value: 'bar-value' },
]);
const data = serializeForm(form);
expect(data).toEqual({
foo: 'foo-value',
bar: 'bar-value',
});
});
it('works with select', () => {
const form = createDummyForm([
{ type: 'select', name: 'foo', value: ['foo-value1', 'foo-value2'] },
{ type: 'text', name: 'bar', value: 'bar-value1' },
]);
const data = serializeForm(form);
expect(data).toEqual({
foo: 'foo-value1',
bar: 'bar-value1',
});
});
it('works with multiple inputs of the same name', () => {
const form = createDummyForm([
{ type: 'checkbox', name: 'foo', value: 'foo-value3' },
{ type: 'checkbox', name: 'foo', value: 'foo-value2' },
{ type: 'checkbox', name: 'foo', value: 'foo-value1' },
{ type: 'text', name: 'bar', value: 'bar-value2' },
{ type: 'text', name: 'bar', value: 'bar-value1' },
]);
const data = serializeForm(form);
expect(data).toEqual({
foo: ['foo-value3', 'foo-value2', 'foo-value1'],
bar: ['bar-value2', 'bar-value1'],
});
});
});
});
......@@ -62,14 +62,6 @@ describe Gitlab::Highlight do
expect(lines[2].text).to eq(' """')
end
context 'since param is present' do
it 'highlights with the LC starting from "since" param' do
lines = described_class.highlight(file_name, content, since: 2).lines
expect(lines[0]).to include('LC2')
end
end
context 'diff highlighting' do
let(:file_name) { 'test.diff' }
let(:content) { "+aaa\n+bbb\n- ccc\n ddd\n"}
......
......@@ -28,70 +28,24 @@ describe BlobPresenter, :seed_helper do
subject { described_class.new(blob) }
it 'returns highlighted content' do
expect(Gitlab::Highlight)
.to receive(:highlight)
.with(
'files/ruby/regex.rb',
git_blob.data,
since: nil,
plain: nil,
language: nil
)
expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: nil)
subject.highlight
end
it 'returns plain content when :plain is true' do
expect(Gitlab::Highlight)
.to receive(:highlight)
.with(
'files/ruby/regex.rb',
git_blob.data,
since: nil,
plain: true,
language: nil
)
expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: true, language: nil)
subject.highlight(plain: true)
end
context '"since" and "to" are present' do
before do
allow(git_blob)
.to receive(:data)
.and_return("line one\nline two\nline 3\nline 4")
end
it 'returns limited highlighted content' do
expect(Gitlab::Highlight)
.to receive(:highlight)
.with(
'files/ruby/regex.rb',
"line two\nline 3\n",
since: 2,
language: nil,
plain: nil
)
subject.highlight(since: 2, to: 3)
end
end
context 'gitlab-language contains a match' do
before do
allow(blob).to receive(:language_from_gitattributes).and_return('ruby')
end
it 'passes language to inner call' do
expect(Gitlab::Highlight)
.to receive(:highlight)
.with(
'files/ruby/regex.rb',
git_blob.data,
since: nil,
plain: nil,
language: 'ruby'
)
expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: 'ruby')
subject.highlight
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