Commit aa137fd3 authored by minghuan lei's avatar minghuan lei Committed by Paul Slaughter

Add repo-refs control for search page

- only in blobs result
parent e0e6e9e7
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 $ from 'jquery';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { __ } from '~/locale'; 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 axios from '~/lib/utils/axios_utils';
import flash from '~/flash'; import flash from '~/flash';
import projectSelect from '../../project_select'; import projectSelect from '../../project_select';
...@@ -107,9 +108,10 @@ export default class Project { ...@@ -107,9 +108,10 @@ export default class Project {
refLink.href = '#'; refLink.href = '#';
return $('.js-project-refs-dropdown').each(function() { return $('.js-project-refs-dropdown').each(function() {
var $dropdown, selected; var $dropdown = $(this);
$dropdown = $(this); var selected = $dropdown.data('selected');
selected = $dropdown.data('selected'); var fieldName = $dropdown.data('fieldName');
var shouldVisit = Boolean($dropdown.data('visit'));
return $dropdown.glDropdown({ return $dropdown.glDropdown({
data(term, callback) { data(term, callback) {
axios axios
...@@ -127,7 +129,7 @@ export default class Project { ...@@ -127,7 +129,7 @@ export default class Project {
filterRemote: true, filterRemote: true,
filterByText: true, filterByText: true,
inputFieldName: $dropdown.data('inputFieldName'), inputFieldName: $dropdown.data('inputFieldName'),
fieldName: $dropdown.data('fieldName'), fieldName,
renderRow: function(ref) { renderRow: function(ref) {
var li = refListItem.cloneNode(false); var li = refListItem.cloneNode(false);
...@@ -158,15 +160,12 @@ export default class Project { ...@@ -158,15 +160,12 @@ export default class Project {
clicked: function(options) { clicked: function(options) {
const { e } = options; const { e } = options;
e.preventDefault(); e.preventDefault();
if ($('input[name="ref"]').length) { if ($(`input[name="${fieldName}"]`).length) {
var $form = $dropdown.closest('form'); var $form = $dropdown.closest('form');
var $visit = $dropdown.data('visit');
var shouldVisit = $visit ? true : $visit;
var action = $form.attr('action'); var action = $form.attr('action');
var divider = action.indexOf('?') === -1 ? '?' : '&';
if (shouldVisit) { if (shouldVisit) {
visitUrl(`${action}${divider}${$form.serialize()}`); visitUrl(mergeUrlParams(serializeForm($form[0]), action));
} }
} }
}, },
......
...@@ -2,6 +2,7 @@ import $ from 'jquery'; ...@@ -2,6 +2,7 @@ import $ from 'jquery';
import Flash from '~/flash'; import Flash from '~/flash';
import Api from '~/api'; import Api from '~/api';
import { __ } from '~/locale'; import { __ } from '~/locale';
import Project from '~/pages/projects/project';
export default class Search { export default class Search {
constructor() { constructor() {
...@@ -69,6 +70,8 @@ export default class Search { ...@@ -69,6 +70,8 @@ export default class Search {
}, },
clicked: () => Search.submitSearch(), clicked: () => Search.submitSearch(),
}); });
Project.initRefSwitcher();
} }
eventListeners() { eventListeners() {
......
= form_tag search_path, method: :get, class: 'search-page-form js-search-form' do |f| = form_tag search_path, method: :get, class: 'search-page-form js-search-form' do |f|
= hidden_field_tag :snippets, params[:snippets] = hidden_field_tag :snippets, params[:snippets]
= hidden_field_tag :scope, params[:scope] = hidden_field_tag :scope, params[:scope]
= hidden_field_tag :repository_ref, params[:repository_ref]
.d-lg-flex.align-items-end .d-lg-flex.align-items-end
.search-field-holder.form-group.mr-lg-1.mb-lg-0 .search-field-holder.form-group.mr-lg-1.mb-lg-0
......
...@@ -2,17 +2,25 @@ ...@@ -2,17 +2,25 @@
= render partial: "search/results/empty" = render partial: "search/results/empty"
= render_if_exists 'shared/promotions/promote_advanced_search' = render_if_exists 'shared/promotions/promote_advanced_search'
- else - else
.row-content-block .row-content-block.d-md-flex.text-left.align-items-center
- unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount) - unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount)
= search_entries_info(@search_objects, @scope, @search_term) = search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets - unless @show_snippets
- if @project - if @project
- link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @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 } = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
- elsif @group - 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 } = _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
= render_if_exists 'shared/promotions/promote_advanced_search' = render_if_exists 'shared/promotions/promote_advanced_search'
.results.prepend-top-10 .results.prepend-top-10
- if @scope == 'commits' - if @scope == 'commits'
%ul.content-list.commit-list %ul.content-list.commit-list
......
- dropdown_toggle_text = @ref || @project.default_branch - return unless @project
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
- 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 = hidden_field_tag :destination, destination
- if defined?(path) - if defined?(path)
= hidden_field_tag :path, path = hidden_field_tag :path, path
- @options && @options.each do |key, value| - @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil = hidden_field_tag key, value, id: nil
.dropdown .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-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-page-one
= dropdown_title _("Switch branch/tag") = 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
...@@ -9666,6 +9666,12 @@ msgstr "" ...@@ -9666,6 +9666,12 @@ msgstr ""
msgid "SearchAutocomplete|in this project" msgid "SearchAutocomplete|in this project"
msgstr "" msgstr ""
msgid "SearchCodeResults|in"
msgstr ""
msgid "SearchCodeResults|of %{link_to_project}"
msgstr ""
msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\"" msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
msgstr "" msgstr ""
......
...@@ -6,6 +6,21 @@ describe 'User searches for code' do ...@@ -6,6 +6,21 @@ describe 'User searches for code' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) } 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 context 'when signed in' do
before do before do
project.add_maintainer(user) project.add_maintainer(user)
...@@ -15,12 +30,7 @@ describe 'User searches for code' do ...@@ -15,12 +30,7 @@ describe 'User searches for code' do
it 'finds a file' do it 'finds a file' do
visit(project_path(project)) visit(project_path(project))
page.within('.search') do submit_search('application.js')
fill_in('search', with: 'application.js')
click_button('Go')
end
click_link('Code')
expect(page).to have_selector('.file-content .code') expect(page).to have_selector('.file-content .code')
expect(page).to have_selector("span.line[lang='javascript']") expect(page).to have_selector("span.line[lang='javascript']")
...@@ -48,6 +58,50 @@ describe 'User searches for code' do ...@@ -48,6 +58,50 @@ describe 'User searches for code' do
end end
end 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 end
context 'when signed out' do context 'when signed out' do
...@@ -58,8 +112,7 @@ describe 'User searches for code' do ...@@ -58,8 +112,7 @@ describe 'User searches for code' do
end end
it 'finds code' do it 'finds code' do
fill_in('search', with: 'rspec') submit_search('rspec')
click_button('Go')
page.within('.results') do page.within('.results') do
expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions') 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'],
});
});
});
});
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