Commit 65317192 authored by Zack Cuddy's avatar Zack Cuddy Committed by Tomas Bulva

Global Search - Support non-js searches

This change updates the header search
to also support a hidden form in the
HAML before the JS is bootstrapped.

This will prevent blocking the users
from trying to search before the JS
bootstraps.

Changelog: changed
parent fecbce92
...@@ -5,7 +5,7 @@ import createStore from './store'; ...@@ -5,7 +5,7 @@ import createStore from './store';
Vue.use(Translate); Vue.use(Translate);
export const initHeaderSearchApp = () => { export const initHeaderSearchApp = (search = '') => {
const el = document.getElementById('js-header-search'); const el = document.getElementById('js-header-search');
if (!el) { if (!el) {
...@@ -18,7 +18,7 @@ export const initHeaderSearchApp = () => { ...@@ -18,7 +18,7 @@ export const initHeaderSearchApp = () => {
return new Vue({ return new Vue({
el, el,
store: createStore({ searchPath, issuesPath, mrPath, autocompletePath, searchContext }), store: createStore({ searchPath, issuesPath, mrPath, autocompletePath, searchContext, search }),
render(createElement) { render(createElement) {
return createElement(HeaderSearchApp); return createElement(HeaderSearchApp);
}, },
......
...@@ -13,11 +13,12 @@ export const getStoreConfig = ({ ...@@ -13,11 +13,12 @@ export const getStoreConfig = ({
mrPath, mrPath,
autocompletePath, autocompletePath,
searchContext, searchContext,
search,
}) => ({ }) => ({
actions, actions,
getters, getters,
mutations, mutations,
state: createState({ searchPath, issuesPath, mrPath, autocompletePath, searchContext }), state: createState({ searchPath, issuesPath, mrPath, autocompletePath, searchContext, search }),
}); });
const createStore = (config) => new Vuex.Store(getStoreConfig(config)); const createStore = (config) => new Vuex.Store(getStoreConfig(config));
......
const createState = ({ searchPath, issuesPath, mrPath, autocompletePath, searchContext }) => ({ const createState = ({
searchPath, searchPath,
issuesPath, issuesPath,
mrPath, mrPath,
autocompletePath, autocompletePath,
searchContext, searchContext,
search: '', search,
}) => ({
searchPath,
issuesPath,
mrPath,
autocompletePath,
searchContext,
search,
autocompleteOptions: [], autocompleteOptions: [],
autocompleteError: false, autocompleteError: false,
loading: false, loading: false,
......
...@@ -116,16 +116,18 @@ function deferredInitialisation() { ...@@ -116,16 +116,18 @@ function deferredInitialisation() {
); );
} }
const search = document.querySelector('#search'); const searchInputBox = document.querySelector('#search');
if (search) { if (searchInputBox) {
search.addEventListener( searchInputBox.addEventListener(
'focus', 'focus',
() => { () => {
if (gon.features?.newHeaderSearch) { if (gon.features?.newHeaderSearch) {
import(/* webpackChunkName: 'globalSearch' */ '~/header_search') import(/* webpackChunkName: 'globalSearch' */ '~/header_search')
.then(async ({ initHeaderSearchApp }) => { .then(async ({ initHeaderSearchApp }) => {
await initHeaderSearchApp(); // In case the user started searching before we bootstrapped, let's pass the search along.
document.querySelector('#search').focus(); const initialSearchValue = searchInputBox.value;
await initHeaderSearchApp(initialSearchValue);
searchInputBox.focus();
}) })
.catch(() => {}); .catch(() => {});
} else { } else {
......
#js-header-search.header-search{ data: { 'search-context' => header_search_context.to_json,
'search-path' => search_path,
'issues-path' => issues_dashboard_path,
'mr-path' => merge_requests_dashboard_path,
'autocomplete-path' => search_autocomplete_path } }
= form_tag search_path, method: :get do |_f|
.gl-search-box-by-type
= sprite_icon('search', css_class: 'gl-search-box-by-type-search-icon gl-icon')
%input{ id: 'search', name: 'search', type: "text", placeholder: s_('GlobalSearch|Search GitLab'), class: 'form-control gl-form-input gl-search-box-by-type-input', autocomplete: 'off' }
= hidden_field_tag :group_id, header_search_context[:group][:id] if header_search_context[:group]
= hidden_field_tag :project_id, header_search_context[:project][:id] if header_search_context[:project]
- if header_search_context[:group] || header_search_context[:project]
= hidden_field_tag :scope, header_search_context[:scope]
= hidden_field_tag :search_code, header_search_context[:code_search]
= hidden_field_tag :snippets, header_search_context[:for_snippets]
= hidden_field_tag :repository_ref, header_search_context[:ref]
= hidden_field_tag :nav_source, 'navbar'
-# workaround for non-JS feature specs, see spec/support/helpers/search_helpers.rb
- if ENV['RAILS_ENV'] == 'test'
%noscript= button_tag 'Search'
...@@ -41,14 +41,7 @@ ...@@ -41,14 +41,7 @@
%li.nav-item.header-search-new.d-none.d-lg-block.m-auto %li.nav-item.header-search-new.d-none.d-lg-block.m-auto
- unless current_controller?(:search) - unless current_controller?(:search)
- if Feature.enabled?(:new_header_search) - if Feature.enabled?(:new_header_search)
#js-header-search.header-search{ data: { 'search-context' => header_search_context.to_json, = render 'layouts/header_search'
'search-path' => search_path,
'issues-path' => issues_dashboard_path,
'mr-path' => merge_requests_dashboard_path,
'autocomplete-path' => search_autocomplete_path } }
.gl-search-box-by-type
= sprite_icon('search', css_class: 'gl-search-box-by-type-search-icon gl-icon')
%input{ type: "text", placeholder: s_('GlobalSearch|Search GitLab'), class: 'form-control gl-form-input gl-search-box-by-type-input', id: 'search', autocomplete: 'off' }
- else - else
= render 'layouts/search' = render 'layouts/search'
%li.nav-item{ class: 'd-none d-sm-inline-block d-lg-none' } %li.nav-item{ class: 'd-none d-sm-inline-block d-lg-none' }
......
...@@ -72,6 +72,10 @@ RSpec.describe 'Global search' do ...@@ -72,6 +72,10 @@ RSpec.describe 'Global search' do
# TODO: Remove this along with feature flag #339348 # TODO: Remove this along with feature flag #339348
stub_feature_flags(new_header_search: true) stub_feature_flags(new_header_search: true)
visit dashboard_projects_path visit dashboard_projects_path
# intialize javascript loaded input search input field
find('#search').click
find('body').click
end end
it 'renders updated search bar' do it 'renders updated search bar' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'layouts/_header_search' do
let(:project) { nil }
let(:group) { nil }
let(:scope) { nil }
let(:ref) { nil }
let(:code_search) { false }
let(:for_snippets) { false}
let(:header_search_context) do
{
project: project,
group: group,
scope: scope,
ref: ref,
code_search: code_search,
for_snippets: for_snippets
}
end
before do
allow(view).to receive(:header_search_context).and_return(header_search_context)
end
shared_examples 'hidden fields are properly set' do
context 'when search_context has a scope value' do
let(:scope) { 'issues' }
it 'sets scope input to issues' do
render
expect(rendered).to have_css("input[name='scope'][value='#{scope}']", count: 1, visible: false)
end
end
context 'when search_context has a code_search value' do
let(:code_search) { true }
it 'sets search_code input to true' do
render
expect(rendered).to have_css("input[name='search_code'][value='#{code_search}']", count: 1, visible: false)
end
end
context 'when search_context has a ref value' do
let(:ref) { 'test-branch' }
it 'sets repository_ref input to test-branch' do
render
expect(rendered).to have_css("input[name='repository_ref'][value='#{ref}']", count: 1, visible: false)
end
end
context 'when search_context has a for_snippets value' do
let(:for_snippets) { true }
it 'sets for_snippets input to true' do
render
expect(rendered).to have_css("input[name='snippets'][value='#{for_snippets}']", count: 1, visible: false)
end
end
context 'nav_source' do
it 'always set to navbar' do
render
expect(rendered).to have_css("input[name='nav_source'][value='navbar']", count: 1, visible: false)
end
end
context 'submit button' do
it 'always renders for specs' do
render
expect(rendered).to have_css('noscript button', text: 'Search')
end
end
end
context 'when doing a project level search' do
let(:project) do
{ id: 123, name: 'foo' }
end
it 'sets project_id field' do
render
expect(rendered).to have_css("input[name='project_id'][value='#{project[:id]}']", count: 1, visible: false)
end
it_behaves_like 'hidden fields are properly set'
end
context 'when doing a group level search' do
let(:group) do
{ id: 123, name: 'bar' }
end
it 'sets group_id field' do
render
expect(rendered).to have_css("input[name='group_id'][value='#{group[:id]}']", count: 1, visible: false)
end
it_behaves_like 'hidden fields are properly set'
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