Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
0c7c4493
Commit
0c7c4493
authored
Feb 09, 2021
by
Terri Chu
Committed by
Matthias Käppler
Feb 09, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Change search tabs to Vue component
Move search tabs to a Vue component and remove all unused code.
parent
2c5cf14f
Changes
39
Hide whitespace changes
Inline
Side-by-side
Showing
39 changed files
with
667 additions
and
274 deletions
+667
-274
app/assets/javascripts/pages/search/show/refresh_counts.js
app/assets/javascripts/pages/search/show/refresh_counts.js
+0
-24
app/assets/javascripts/search/index.js
app/assets/javascripts/search/index.js
+0
-2
app/assets/javascripts/search/store/actions.js
app/assets/javascripts/search/store/actions.js
+54
-2
app/assets/javascripts/search/store/mutation_types.js
app/assets/javascripts/search/store/mutation_types.js
+3
-0
app/assets/javascripts/search/store/mutations.js
app/assets/javascripts/search/store/mutations.js
+11
-0
app/assets/javascripts/search/store/state.js
app/assets/javascripts/search/store/state.js
+1
-0
app/assets/javascripts/search/topbar/components/app.vue
app/assets/javascripts/search/topbar/components/app.vue
+42
-24
app/assets/javascripts/search/topbar/components/scope_tabs.vue
...ssets/javascripts/search/topbar/components/scope_tabs.vue
+73
-0
app/assets/javascripts/search/topbar/constants.js
app/assets/javascripts/search/topbar/constants.js
+14
-0
app/assets/javascripts/search/topbar/index.js
app/assets/javascripts/search/topbar/index.js
+5
-1
app/assets/stylesheets/pages/search.scss
app/assets/stylesheets/pages/search.scss
+12
-0
app/helpers/projects_helper.rb
app/helpers/projects_helper.rb
+2
-1
app/helpers/search_helper.rb
app/helpers/search_helper.rb
+13
-29
app/views/search/_category.html.haml
app/views/search/_category.html.haml
+0
-35
app/views/search/show.html.haml
app/views/search/show.html.haml
+1
-2
changelogs/unreleased/262066-global-search-scope-tabs-vue-component.yml
...eleased/262066-global-search-scope-tabs-vue-component.yml
+5
-0
ee/app/helpers/ee/search_helper.rb
ee/app/helpers/ee/search_helper.rb
+15
-1
ee/app/views/search/_category_elasticsearch.html.haml
ee/app/views/search/_category_elasticsearch.html.haml
+0
-5
ee/app/views/search/_epics_filter_link.html.haml
ee/app/views/search/_epics_filter_link.html.haml
+0
-2
ee/spec/features/search/elastic/global_search_spec.rb
ee/spec/features/search/elastic/global_search_spec.rb
+9
-8
ee/spec/features/search/elastic/group_search_spec.rb
ee/spec/features/search/elastic/group_search_spec.rb
+2
-1
ee/spec/features/search/elastic/project_search_spec.rb
ee/spec/features/search/elastic/project_search_spec.rb
+12
-9
ee/spec/helpers/search_helper_spec.rb
ee/spec/helpers/search_helper_spec.rb
+63
-0
locale/gitlab.pot
locale/gitlab.pot
+3
-0
qa/qa/page/search/results.rb
qa/qa/page/search/results.rb
+1
-1
spec/features/global_search_spec.rb
spec/features/global_search_spec.rb
+1
-1
spec/features/search/user_searches_for_code_spec.rb
spec/features/search/user_searches_for_code_spec.rb
+8
-7
spec/features/search/user_searches_for_comments_spec.rb
spec/features/search/user_searches_for_comments_spec.rb
+1
-1
spec/features/search/user_searches_for_users_spec.rb
spec/features/search/user_searches_for_users_spec.rb
+2
-2
spec/features/snippets/search_snippets_spec.rb
spec/features/snippets/search_snippets_spec.rb
+1
-1
spec/frontend/pages/search/show/__snapshots__/refresh_counts_spec.js.snap
...ges/search/show/__snapshots__/refresh_counts_spec.js.snap
+0
-7
spec/frontend/pages/search/show/refresh_counts_spec.js
spec/frontend/pages/search/show/refresh_counts_spec.js
+0
-38
spec/frontend/search/mock_data.js
spec/frontend/search/mock_data.js
+25
-0
spec/frontend/search/store/actions_spec.js
spec/frontend/search/store/actions_spec.js
+35
-9
spec/frontend/search/store/mutations_spec.js
spec/frontend/search/store/mutations_spec.js
+35
-1
spec/frontend/search/topbar/components/app_spec.js
spec/frontend/search/topbar/components/app_spec.js
+14
-0
spec/frontend/search/topbar/components/scope_tabs_spec.js
spec/frontend/search/topbar/components/scope_tabs_spec.js
+122
-0
spec/helpers/search_helper_spec.rb
spec/helpers/search_helper_spec.rb
+82
-57
spec/views/search/show.html.haml_spec.rb
spec/views/search/show.html.haml_spec.rb
+0
-3
No files found.
app/assets/javascripts/pages/search/show/refresh_counts.js
deleted
100644 → 0
View file @
2c5cf14f
import
axios
from
'
~/lib/utils/axios_utils
'
;
function
showCount
(
el
,
count
)
{
el
.
textContent
=
count
;
el
.
classList
.
remove
(
'
hidden
'
);
}
function
refreshCount
(
el
)
{
const
{
url
}
=
el
.
dataset
;
return
axios
.
get
(
url
)
.
then
(({
data
})
=>
showCount
(
el
,
data
.
count
))
.
catch
((
e
)
=>
{
// eslint-disable-next-line no-console
console
.
error
(
`Failed to fetch search count from '
${
url
}
'.`
,
e
);
});
}
export
default
function
refreshCounts
()
{
const
elements
=
Array
.
from
(
document
.
querySelectorAll
(
'
.js-search-count
'
));
return
Promise
.
all
(
elements
.
map
(
refreshCount
));
}
app/assets/javascripts/search/index.js
View file @
0c7c4493
import
setHighlightClass
from
'
ee_else_ce/search/highlight_blob_search_result
'
;
import
Project
from
'
~/pages/projects/project
'
;
import
refreshCounts
from
'
~/pages/search/show/refresh_counts
'
;
import
{
queryToObject
}
from
'
~/lib/utils/url_utility
'
;
import
createStore
from
'
./store
'
;
import
{
initTopbar
}
from
'
./topbar
'
;
...
...
@@ -20,6 +19,5 @@ export const initSearchApp = () => {
initSearchSort
(
store
);
setHighlightClass
(
query
.
search
);
// Code Highlighting
refreshCounts
();
// Other Scope Tab Counts
Project
.
initRefSwitcher
();
// Code Search Branch Picker
};
app/assets/javascripts/search/store/actions.js
View file @
0c7c4493
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
Api
from
'
~/api
'
;
import
createFlash
from
'
~/flash
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
visitUrl
,
setUrlParams
}
from
'
~/lib/utils/url_utility
'
;
import
*
as
types
from
'
./mutation_types
'
;
/* private */
const
getCount
=
({
params
,
state
,
activeCount
})
=>
{
const
globalSearchCountsPath
=
'
/search/count
'
;
const
url
=
Api
.
buildUrl
(
globalSearchCountsPath
);
// count is known for active tab, so return it and skip the Api call
if
(
params
.
scope
===
state
.
query
?.
scope
)
{
return
{
scope
:
params
.
scope
,
count
:
activeCount
};
}
return
axios
.
get
(
url
,
{
params
})
.
then
(({
data
})
=>
{
return
{
scope
:
params
.
scope
,
count
:
data
.
count
};
})
.
catch
((
e
)
=>
{
throw
e
;
});
};
export
const
fetchGroups
=
({
commit
},
search
)
=>
{
commit
(
types
.
REQUEST_GROUPS
);
Api
.
groups
(
search
)
...
...
@@ -38,6 +59,21 @@ export const fetchProjects = ({ commit, state }, search) => {
}
};
export
const
fetchSearchCounts
=
({
commit
,
state
},
{
scopeTabs
,
activeCount
})
=>
{
commit
(
types
.
REQUEST_SEARCH_COUNTS
,
{
scopeTabs
,
activeCount
});
const
promises
=
scopeTabs
.
map
((
scope
)
=>
getCount
({
params
:
{
...
state
.
query
,
scope
},
state
,
activeCount
}),
);
Promise
.
all
(
promises
)
.
then
((
data
)
=>
{
commit
(
types
.
RECEIVE_SEARCH_COUNTS_SUCCESS
,
data
);
})
.
catch
(()
=>
{
createFlash
({
message
:
__
(
'
There was an error fetching the Search Counts
'
)
});
});
};
export
const
setQuery
=
({
commit
},
{
key
,
value
})
=>
{
commit
(
types
.
SET_QUERY
,
{
key
,
value
});
};
...
...
@@ -46,6 +82,22 @@ export const applyQuery = ({ state }) => {
visitUrl
(
setUrlParams
({
...
state
.
query
,
page
:
null
}));
};
export
const
resetQuery
=
({
state
})
=>
{
visitUrl
(
setUrlParams
({
...
state
.
query
,
page
:
null
,
state
:
null
,
confidential
:
null
}));
export
const
resetQuery
=
({
state
},
snippets
=
false
)
=>
{
let
defaultQuery
=
{
page
:
null
,
state
:
null
,
confidential
:
null
,
nav_source
:
null
,
};
if
(
snippets
)
{
defaultQuery
=
{
snippets
:
true
,
group_id
:
null
,
project_id
:
null
,
...
defaultQuery
,
};
}
visitUrl
(
setUrlParams
({
...
state
.
query
,
...
defaultQuery
}));
};
app/assets/javascripts/search/store/mutation_types.js
View file @
0c7c4493
...
...
@@ -6,4 +6,7 @@ export const REQUEST_PROJECTS = 'REQUEST_PROJECTS';
export
const
RECEIVE_PROJECTS_SUCCESS
=
'
RECEIVE_PROJECTS_SUCCESS
'
;
export
const
RECEIVE_PROJECTS_ERROR
=
'
RECEIVE_PROJECTS_ERROR
'
;
export
const
REQUEST_SEARCH_COUNTS
=
'
REQUEST_SEARCH_COUNTS
'
;
export
const
RECEIVE_SEARCH_COUNTS_SUCCESS
=
'
RECEIVE_SEARCH_COUNTS_SUCCESS
'
;
export
const
SET_QUERY
=
'
SET_QUERY
'
;
app/assets/javascripts/search/store/mutations.js
View file @
0c7c4493
import
{
ALL_SCOPE_TABS
}
from
'
~/search/topbar/constants
'
;
import
*
as
types
from
'
./mutation_types
'
;
export
default
{
...
...
@@ -23,6 +24,16 @@ export default {
state
.
fetchingProjects
=
false
;
state
.
projects
=
[];
},
[
types
.
REQUEST_SEARCH_COUNTS
](
state
,
{
scopeTabs
,
activeCount
})
{
state
.
inflatedScopeTabs
=
scopeTabs
.
map
((
tab
)
=>
{
return
{
...
ALL_SCOPE_TABS
[
tab
],
count
:
tab
===
state
.
query
?.
scope
?
activeCount
:
''
};
});
},
[
types
.
RECEIVE_SEARCH_COUNTS_SUCCESS
](
state
,
data
)
{
state
.
inflatedScopeTabs
=
data
.
map
((
tab
)
=>
{
return
{
...
ALL_SCOPE_TABS
[
tab
.
scope
],
count
:
tab
.
count
};
});
},
[
types
.
SET_QUERY
](
state
,
{
key
,
value
})
{
state
.
query
[
key
]
=
value
;
},
...
...
app/assets/javascripts/search/store/state.js
View file @
0c7c4493
...
...
@@ -4,5 +4,6 @@ const createState = ({ query }) => ({
fetchingGroups
:
false
,
projects
:
[],
fetchingProjects
:
false
,
inflatedScopeTabs
:
[],
});
export
default
createState
;
app/assets/javascripts/search/topbar/components/app.vue
View file @
0c7c4493
...
...
@@ -3,6 +3,7 @@ import { mapState, mapActions } from 'vuex';
import
{
GlForm
,
GlSearchBoxByType
,
GlButton
}
from
'
@gitlab/ui
'
;
import
GroupFilter
from
'
./group_filter.vue
'
;
import
ProjectFilter
from
'
./project_filter.vue
'
;
import
ScopeTabs
from
'
./scope_tabs.vue
'
;
export
default
{
name
:
'
GlobalSearchTopbar
'
,
...
...
@@ -12,6 +13,7 @@ export default {
GroupFilter
,
ProjectFilter
,
GlButton
,
ScopeTabs
,
},
props
:
{
groupInitialData
:
{
...
...
@@ -24,6 +26,16 @@ export default {
required
:
false
,
default
:
()
=>
({}),
},
scopeTabs
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
count
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
...
mapState
([
'
query
'
]),
...
...
@@ -38,6 +50,9 @@ export default {
showFilters
()
{
return
!
this
.
query
.
snippets
||
this
.
query
.
snippets
===
'
false
'
;
},
showScopeTabs
()
{
return
this
.
query
.
search
;
},
},
methods
:
{
...
mapActions
([
'
applyQuery
'
,
'
setQuery
'
]),
...
...
@@ -46,28 +61,31 @@ export default {
</
script
>
<
template
>
<gl-form
class=
"search-page-form"
@
submit.prevent=
"applyQuery"
>
<section
class=
"gl-lg-display-flex gl-align-items-flex-end"
>
<div
class=
"gl-flex-fill-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2"
>
<label>
{{
__
(
'
What are you searching for?
'
)
}}
</label>
<gl-search-box-by-type
id=
"dashboard_search"
v-model=
"search"
name=
"search"
:placeholder=
"__(`Search for projects, issues, etc.`)"
/>
</div>
<div
v-if=
"showFilters"
class=
"gl-mb-4 gl-lg-mb-0 gl-lg-mx-2"
>
<label
class=
"gl-display-block"
>
{{
__
(
'
Group
'
)
}}
</label>
<group-filter
:initial-data=
"groupInitialData"
/>
</div>
<div
v-if=
"showFilters"
class=
"gl-mb-4 gl-lg-mb-0 gl-lg-mx-2"
>
<label
class=
"gl-display-block"
>
{{
__
(
'
Project
'
)
}}
</label>
<project-filter
:initial-data=
"projectInitialData"
/>
</div>
<gl-button
class=
"btn-search gl-lg-ml-2"
variant=
"success"
type=
"submit"
>
{{
__
(
'
Search
'
)
}}
</gl-button>
</section>
</gl-form>
<section>
<gl-form
class=
"search-page-form"
@
submit.prevent=
"applyQuery"
>
<section
class=
"gl-lg-display-flex gl-align-items-flex-end"
>
<div
class=
"gl-flex-fill-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2"
>
<label>
{{
__
(
'
What are you searching for?
'
)
}}
</label>
<gl-search-box-by-type
id=
"dashboard_search"
v-model=
"search"
name=
"search"
:placeholder=
"__(`Search for projects, issues, etc.`)"
/>
</div>
<div
v-if=
"showFilters"
class=
"gl-mb-4 gl-lg-mb-0 gl-lg-mx-2"
>
<label
class=
"gl-display-block"
>
{{
__
(
'
Group
'
)
}}
</label>
<group-filter
:initial-data=
"groupInitialData"
/>
</div>
<div
v-if=
"showFilters"
class=
"gl-mb-4 gl-lg-mb-0 gl-lg-mx-2"
>
<label
class=
"gl-display-block"
>
{{
__
(
'
Project
'
)
}}
</label>
<project-filter
:initial-data=
"projectInitialData"
/>
</div>
<gl-button
class=
"btn-search gl-lg-ml-2"
variant=
"success"
type=
"submit"
>
{{
__
(
'
Search
'
)
}}
</gl-button>
</section>
</gl-form>
<scope-tabs
v-if=
"showScopeTabs"
:scope-tabs=
"scopeTabs"
:count=
"count"
/>
</section>
</
template
>
app/assets/javascripts/search/topbar/components/scope_tabs.vue
0 → 100644
View file @
0c7c4493
<
script
>
import
{
GlTabs
,
GlTab
,
GlBadge
}
from
'
@gitlab/ui
'
;
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
export
default
{
name
:
'
ScopeTabs
'
,
components
:
{
GlTabs
,
GlTab
,
GlBadge
,
},
props
:
{
scopeTabs
:
{
type
:
Array
,
required
:
true
,
},
count
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
computed
:
{
...
mapState
([
'
query
'
,
'
inflatedScopeTabs
'
]),
},
created
()
{
this
.
fetchSearchCounts
({
scopeTabs
:
this
.
scopeTabs
,
activeCount
:
this
.
count
});
},
methods
:
{
...
mapActions
([
'
fetchSearchCounts
'
,
'
setQuery
'
,
'
resetQuery
'
]),
handleTabChange
(
scope
)
{
this
.
setQuery
({
key
:
'
scope
'
,
value
:
scope
});
this
.
resetQuery
(
scope
===
'
snippet_titles
'
);
},
isTabActive
(
scope
)
{
return
scope
===
this
.
query
.
scope
;
},
},
};
</
script
>
<
template
>
<div>
<gl-tabs
content-class=
"gl-p-0"
nav-class=
"search-filter search-nav-tabs gl-display-flex gl-overflow-x-auto"
>
<gl-tab
v-for=
"tab in inflatedScopeTabs"
:key=
"tab.scope"
class=
"gl-display-flex"
:active=
"isTabActive(tab.scope)"
:data-testid=
"`tab-$
{tab.scope}`"
:title-link-attributes="{ 'data-qa-selector': tab.qaSelector }"
title-link-class="gl-white-space-nowrap"
@click="handleTabChange(tab.scope)"
>
<template
#title
>
<span
data-testid=
"tab-title"
>
{{
tab
.
title
}}
</span>
<gl-badge
v-show=
"tab.count"
:data-scope=
"tab.scope"
:data-testid=
"`badge-$
{tab.scope}`"
:variant="isTabActive(tab.scope) ? 'neutral' : 'muted'"
size="sm"
>
{{
tab
.
count
}}
</gl-badge>
</
template
>
</gl-tab>
</gl-tabs>
</div>
</template>
app/assets/javascripts/search/topbar/constants.js
View file @
0c7c4493
...
...
@@ -19,3 +19,17 @@ export const PROJECT_DATA = {
selectedDisplayValue
:
'
name_with_namespace
'
,
itemsDisplayValue
:
'
name_with_namespace
'
,
};
export
const
ALL_SCOPE_TABS
=
{
blobs
:
{
scope
:
'
blobs
'
,
title
:
__
(
'
Code
'
),
qaSelector
:
'
code_tab
'
},
issues
:
{
scope
:
'
issues
'
,
title
:
__
(
'
Issues
'
)
},
merge_requests
:
{
scope
:
'
merge_requests
'
,
title
:
__
(
'
Merge requests
'
)
},
milestones
:
{
scope
:
'
milestones
'
,
title
:
__
(
'
Milestones
'
)
},
notes
:
{
scope
:
'
notes
'
,
title
:
__
(
'
Comments
'
)
},
wiki_blobs
:
{
scope
:
'
wiki_blobs
'
,
title
:
__
(
'
Wiki
'
)
},
commits
:
{
scope
:
'
commits
'
,
title
:
__
(
'
Commits
'
)
},
epics
:
{
scope
:
'
epics
'
,
title
:
__
(
'
Epics
'
)
},
users
:
{
scope
:
'
users
'
,
title
:
__
(
'
Users
'
)
},
snippet_titles
:
{
scope
:
'
snippet_titles
'
,
title
:
__
(
'
Titles and Descriptions
'
)
},
projects
:
{
scope
:
'
projects
'
,
title
:
__
(
'
Projects
'
),
qaSelector
:
'
projects_tab
'
},
};
app/assets/javascripts/search/topbar/index.js
View file @
0c7c4493
...
...
@@ -11,10 +11,12 @@ export const initTopbar = (store) => {
return
false
;
}
let
{
groupInitialData
,
projectInitialData
}
=
el
.
dataset
;
let
{
groupInitialData
,
projectInitialData
,
scopeTabs
}
=
el
.
dataset
;
const
{
count
}
=
el
.
dataset
;
groupInitialData
=
JSON
.
parse
(
groupInitialData
);
projectInitialData
=
JSON
.
parse
(
projectInitialData
);
scopeTabs
=
JSON
.
parse
(
scopeTabs
);
return
new
Vue
({
el
,
...
...
@@ -24,6 +26,8 @@ export const initTopbar = (store) => {
props
:
{
groupInitialData
,
projectInitialData
,
scopeTabs
,
count
,
},
});
},
...
...
app/assets/stylesheets/pages/search.scss
View file @
0c7c4493
...
...
@@ -2,6 +2,7 @@ $search-dropdown-max-height: 400px;
$search-avatar-size
:
16px
;
$search-sidebar-min-width
:
240px
;
$search-sidebar-max-width
:
300px
;
$search-topbar-min-height
:
111px
;
.search-results
{
.search-result-row
{
...
...
@@ -19,6 +20,12 @@ $search-sidebar-max-width: 300px;
}
}
.search-topbar
{
@include
media-breakpoint-up
(
md
)
{
min-height
:
$search-topbar-min-height
;
}
}
.search-sidebar
{
@include
media-breakpoint-up
(
md
)
{
min-width
:
$search-sidebar-min-width
;
...
...
@@ -26,6 +33,11 @@ $search-sidebar-max-width: 300px;
}
}
.search-nav-tabs
{
overflow-y
:
hidden
;
flex-wrap
:
nowrap
;
}
.search
form
:hover
,
.file-finder-input
:hover
,
.issuable-search-form
:hover
,
...
...
app/helpers/projects_helper.rb
View file @
0c7c4493
...
...
@@ -511,7 +511,8 @@ module ProjectsHelper
commits: :download_code
,
merge_requests: :read_merge_request
,
notes:
[
:read_merge_request
,
:download_code
,
:read_issue
,
:read_snippet
],
members: :read_project_member
members: :read_project_member
,
wiki_blobs: :read_wiki
)
end
...
...
app/helpers/search_helper.rb
View file @
0c7c4493
# frozen_string_literal: true
module
SearchHelper
SEARCH_GENERIC_PARAMS
=
[
:search
,
:scope
,
:project_id
,
:group_id
,
:repository_ref
,
:snippets
,
:sort
,
:force_search_results
].
freeze
PROJECT_SEARCH_TABS
=
%i{blobs issues merge_requests milestones notes wiki_blobs commits}
.
freeze
BASIC_SEARCH_TABS
=
%i{projects issues merge_requests milestones}
.
freeze
def
search_autocomplete_opts
(
term
)
return
unless
current_user
...
...
@@ -283,27 +275,19 @@ module SearchHelper
Sanitize
.
clean
(
str
)
end
def
search_filter_link
(
scope
,
label
,
data:
{},
search:
{})
search_params
=
params
.
merge
(
search
)
.
merge
({
scope:
scope
})
.
permit
(
SEARCH_GENERIC_PARAMS
)
def
search_nav_tabs
return
[
:snippet_titles
]
if
!
@project
&&
@show_snippets
if
@scope
==
scope
li_class
=
'active'
count
=
@search_results
.
formatted_count
(
scope
)
else
badge_class
=
'js-search-count hidden'
badge_data
=
{
url:
search_count_path
(
search_params
)
}
end
content_tag
:li
,
class:
li_class
,
data:
data
do
link_to
search_path
(
search_params
)
do
concat
label
concat
' '
concat
content_tag
(
:span
,
count
,
class:
[
'badge badge-pill'
,
badge_class
],
data:
badge_data
)
tabs
=
if
@project
PROJECT_SEARCH_TABS
.
select
{
|
tab
|
project_search_tabs?
(
tab
)
}
else
BASIC_SEARCH_TABS
.
dup
end
end
tabs
<<
:users
if
show_user_search_tab?
tabs
end
def
search_filter_input_options
(
type
,
placeholder
=
_
(
'Search or filter results...'
))
...
...
app/views/search/_category.html.haml
deleted
100644 → 0
View file @
2c5cf14f
-
users
=
capture_haml
do
-
if
show_user_search_tab?
=
search_filter_link
'users'
,
_
(
"Users"
)
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left
=
sprite_icon
(
'chevron-lg-left'
,
size:
12
)
.fade-right
=
sprite_icon
(
'chevron-lg-right'
,
size:
12
)
%ul
.nav-links.search-filter.scrolling-tabs.nav.nav-tabs
-
if
@project
-
if
project_search_tabs?
(
:blobs
)
=
search_filter_link
'blobs'
,
_
(
"Code"
),
data:
{
qa_selector:
'code_tab'
}
-
if
project_search_tabs?
(
:issues
)
=
search_filter_link
'issues'
,
_
(
"Issues"
)
-
if
project_search_tabs?
(
:merge_requests
)
=
search_filter_link
'merge_requests'
,
_
(
"Merge requests"
)
-
if
project_search_tabs?
(
:milestones
)
=
search_filter_link
'milestones'
,
_
(
"Milestones"
)
-
if
project_search_tabs?
(
:notes
)
=
search_filter_link
'notes'
,
_
(
"Comments"
)
-
if
project_search_tabs?
(
:wiki
)
=
search_filter_link
'wiki_blobs'
,
_
(
"Wiki"
)
-
if
project_search_tabs?
(
:commits
)
=
search_filter_link
'commits'
,
_
(
"Commits"
)
=
users
-
elsif
@show_snippets
=
search_filter_link
'snippet_titles'
,
_
(
"Titles and Descriptions"
),
search:
{
snippets:
true
,
group_id:
nil
,
project_id:
nil
}
-
else
=
search_filter_link
'projects'
,
_
(
"Projects"
),
data:
{
qa_selector:
'projects_tab'
}
=
search_filter_link
'issues'
,
_
(
"Issues"
)
=
search_filter_link
'merge_requests'
,
_
(
"Merge requests"
)
=
search_filter_link
'milestones'
,
_
(
"Milestones"
)
=
render_if_exists
'search/epics_filter_link'
=
render_if_exists
'search/category_elasticsearch'
=
users
app/views/search/show.html.haml
View file @
0c7c4493
...
...
@@ -16,7 +16,6 @@
=
render_if_exists
'search/form_elasticsearch'
,
attrs:
{
class:
'mb-2 mb-sm-0 align-self-center'
}
.gl-mt-3
#js-search-topbar
{
data:
{
"group-initial-data"
:
@group
.
to_json
,
"project-initial-data"
:
project_attributes
.
to_json
}
}
#js-search-topbar
.search-topbar
{
data:
{
"group-initial-data"
:
@group
.
to_json
,
"project-initial-data"
:
project_attributes
.
to_json
,
"scope-tabs"
:
search_nav_tabs
.
to_json
,
count:
@search_results
&
.
formatted_count
(
@scope
)
}
}
-
if
@search_term
=
render
'search/category'
=
render
'search/results'
changelogs/unreleased/262066-global-search-scope-tabs-vue-component.yml
0 → 100644
View file @
0c7c4493
---
title
:
Change search tab to Vue component
merge_request
:
52018
author
:
type
:
changed
ee/app/helpers/ee/search_helper.rb
View file @
0c7c4493
...
...
@@ -3,8 +3,8 @@ module EE
module
SearchHelper
extend
::
Gitlab
::
Utils
::
Override
SWITCH_TO_BASIC_SEARCHABLE_TABS
=
%w[projects issues merge_requests milestones users epics]
.
freeze
PLACEHOLDER
=
'_PLACEHOLDER_'
ADVANCED_SEARCH_TABS
=
%i{notes blobs commits wiki_blobs}
.
freeze
override
:search_filter_input_options
def
search_filter_input_options
(
type
,
placeholder
=
_
(
'Search or filter results...'
))
...
...
@@ -130,6 +130,20 @@ module EE
options
+
super
end
override
:search_nav_tabs
def
search_nav_tabs
return
super
if
@project
||
@show_snippets
tabs
=
[]
tabs
<<
:epics
if
search_service
.
show_epics?
tabs
.
push
(
*
ADVANCED_SEARCH_TABS
)
if
search_service
.
use_elasticsearch?
super_tabs
=
super
users_index
=
super_tabs
.
index
(
:users
)
||
-
1
super_tabs
.
insert
(
users_index
,
*
tabs
)
end
private
def
recent_epics_autocomplete
(
term
)
...
...
ee/app/views/search/_category_elasticsearch.html.haml
deleted
100644 → 0
View file @
2c5cf14f
-
if
search_service
.
use_elasticsearch?
=
search_filter_link
'notes'
,
_
(
"Comments"
)
=
search_filter_link
'blobs'
,
_
(
"Code"
),
data:
{
qa_selector:
'code_tab'
}
=
search_filter_link
'commits'
,
_
(
"Commits"
)
=
search_filter_link
'wiki_blobs'
,
_
(
"Wiki"
)
ee/app/views/search/_epics_filter_link.html.haml
deleted
100644 → 0
View file @
2c5cf14f
-
if
search_service
.
show_epics?
=
search_filter_link
'epics'
,
_
(
"Epics"
)
ee/spec/features/search/elastic/global_search_spec.rb
View file @
0c7c4493
...
...
@@ -77,7 +77,7 @@ RSpec.describe 'Global elastic search', :elastic, :sidekiq_inline do
end
end
describe
'I search through the issues and I see pagination'
do
describe
'I search through the issues and I see pagination'
,
:js
do
before
do
create_list
(
:issue
,
21
,
project:
project
,
title:
'initial'
)
...
...
@@ -94,7 +94,7 @@ RSpec.describe 'Global elastic search', :elastic, :sidekiq_inline do
end
end
describe
'I search through the notes and I see pagination'
do
describe
'I search through the notes and I see pagination'
,
:js
do
before
do
issue
=
create
(
:issue
,
project:
project
,
title:
'initial'
)
create_list
(
:note
,
21
,
noteable:
issue
,
project:
project
,
note:
'foo'
)
...
...
@@ -112,7 +112,7 @@ RSpec.describe 'Global elastic search', :elastic, :sidekiq_inline do
end
end
describe
'I search through the blobs'
do
describe
'I search through the blobs'
,
:js
do
let
(
:project_2
)
{
create
(
:project
,
:repository
,
:wiki_repo
)
}
before
do
...
...
@@ -156,7 +156,7 @@ RSpec.describe 'Global elastic search', :elastic, :sidekiq_inline do
end
end
describe
'I search through the wiki blobs'
do
describe
'I search through the wiki blobs'
,
:js
do
before
do
project
.
wiki
.
create_page
(
'test.md'
,
'# term'
)
project
.
wiki
.
index_wiki_blobs
...
...
@@ -175,9 +175,10 @@ RSpec.describe 'Global elastic search', :elastic, :sidekiq_inline do
end
end
describe
'I search through the commits'
do
describe
'I search through the commits'
,
:js
do
before
do
project
.
repository
.
index_commits_and_blobs
ensure_elasticsearch_index!
end
...
...
@@ -187,7 +188,7 @@ RSpec.describe 'Global elastic search', :elastic, :sidekiq_inline do
submit_search
(
'add'
)
select_search_scope
(
'Commits'
)
expect
(
page
).
to
have_selector
(
'.commit-row-
description
'
)
expect
(
page
).
to
have_selector
(
'.commit-row-
message
'
)
expect
(
page
).
to
have_selector
(
'.project-namespace'
)
end
...
...
@@ -197,7 +198,7 @@ RSpec.describe 'Global elastic search', :elastic, :sidekiq_inline do
submit_search
(
'add'
)
select_search_scope
(
'Commits'
)
expected_message
=
"
Add directory structure for tree_helper spec
"
expected_message
=
"
Merge branch 'tree_helper_spec' into 'master'
"
expect
(
page
).
not_to
have_content
(
expected_message
)
...
...
@@ -231,7 +232,7 @@ RSpec.describe 'Global elastic search', :elastic, :sidekiq_inline do
end
end
RSpec
.
describe
'Global elastic search redactions'
,
:elastic
do
RSpec
.
describe
'Global elastic search redactions'
,
:elastic
,
:js
do
context
'when block_anonymous_global_searches is disabled'
do
before
do
stub_feature_flags
(
block_anonymous_global_searches:
false
)
...
...
ee/spec/features/search/elastic/group_search_spec.rb
View file @
0c7c4493
...
...
@@ -83,6 +83,7 @@ RSpec.describe 'Group elastic search', :js, :elastic, :sidekiq_might_not_need_in
describe
'commit search'
do
before
do
project
.
repository
.
index_commits_and_blobs
ensure_elasticsearch_index!
end
...
...
@@ -95,7 +96,7 @@ RSpec.describe 'Group elastic search', :js, :elastic, :sidekiq_might_not_need_in
end
end
RSpec
.
describe
'Group elastic search redactions'
,
:elastic
do
RSpec
.
describe
'Group elastic search redactions'
,
:elastic
,
:js
do
it_behaves_like
'a redacted search results page'
do
let
(
:search_path
)
{
group_path
(
public_group
)
}
end
...
...
ee/spec/features/search/elastic/project_search_spec.rb
View file @
0c7c4493
...
...
@@ -10,7 +10,7 @@ RSpec.describe 'Project elastic search', :js, :elastic do
stub_ee_application_setting
(
elasticsearch_search:
true
,
elasticsearch_indexing:
true
)
end
describe
'searching'
do
describe
'searching'
,
:sidekiq_inline
do
before
do
project
.
add_maintainer
(
user
)
sign_in
(
user
)
...
...
@@ -18,7 +18,7 @@ RSpec.describe 'Project elastic search', :js, :elastic do
visit
project_path
(
project
)
end
it
'finds issues'
,
:sidekiq_inline
do
it
'finds issues'
do
create
(
:issue
,
project:
project
,
title:
'Test searching for an issue'
)
ensure_elasticsearch_index!
...
...
@@ -28,7 +28,7 @@ RSpec.describe 'Project elastic search', :js, :elastic do
expect
(
page
).
to
have_selector
(
'.results'
,
text:
'Test searching for an issue'
)
end
it
'finds merge requests'
,
:sidekiq_inline
do
it
'finds merge requests'
do
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
title:
'Test searching for an MR'
)
ensure_elasticsearch_index!
...
...
@@ -38,7 +38,7 @@ RSpec.describe 'Project elastic search', :js, :elastic do
expect
(
page
).
to
have_selector
(
'.results'
,
text:
'Test searching for an MR'
)
end
it
'finds milestones'
,
:sidekiq_inline
do
it
'finds milestones'
do
create
(
:milestone
,
project:
project
,
title:
'Test searching for a milestone'
)
ensure_elasticsearch_index!
...
...
@@ -48,9 +48,10 @@ RSpec.describe 'Project elastic search', :js, :elastic do
expect
(
page
).
to
have_selector
(
'.results'
,
text:
'Test searching for a milestone'
)
end
it
'finds wiki pages'
,
:sidekiq_inline
do
it
'finds wiki pages'
do
project
.
wiki
.
create_page
(
'test.md'
,
'Test searching for a wiki page'
)
project
.
wiki
.
index_wiki_blobs
ensure_elasticsearch_index!
submit_search
(
'Test'
)
select_search_scope
(
'Wiki'
)
...
...
@@ -58,7 +59,7 @@ RSpec.describe 'Project elastic search', :js, :elastic do
expect
(
page
).
to
have_selector
(
'.results'
,
text:
'Test searching for a wiki page'
)
end
it
'finds notes'
,
:sidekiq_inline
do
it
'finds notes'
do
create
(
:note
,
project:
project
,
note:
'Test searching for a comment'
)
ensure_elasticsearch_index!
...
...
@@ -68,8 +69,9 @@ RSpec.describe 'Project elastic search', :js, :elastic do
expect
(
page
).
to
have_selector
(
'.results'
,
text:
'Test searching for a comment'
)
end
it
'finds commits'
,
:sidekiq_inline
do
it
'finds commits'
do
project
.
repository
.
index_commits_and_blobs
ensure_elasticsearch_index!
submit_search
(
'initial'
)
select_search_scope
(
'Commits'
)
...
...
@@ -77,8 +79,9 @@ RSpec.describe 'Project elastic search', :js, :elastic do
expect
(
page
).
to
have_selector
(
'.results'
,
text:
'Initial commit'
)
end
it
'finds blobs'
,
:sidekiq_inline
do
it
'finds blobs'
do
project
.
repository
.
index_commits_and_blobs
ensure_elasticsearch_index!
submit_search
(
'def'
)
select_search_scope
(
'Code'
)
...
...
@@ -126,7 +129,7 @@ RSpec.describe 'Project elastic search', :js, :elastic do
end
end
RSpec
.
describe
'Project elastic search redactions'
,
:elastic
do
RSpec
.
describe
'Project elastic search redactions'
,
:elastic
,
:js
do
it_behaves_like
'a redacted search results page'
do
let
(
:search_path
)
{
project_path
(
public_restricted_project
)
}
end
...
...
ee/spec/helpers/search_helper_spec.rb
View file @
0c7c4493
...
...
@@ -286,4 +286,67 @@ RSpec.describe SearchHelper do
end
end
end
describe
'#search_nav_tabs'
do
let
(
:current_user
)
{
nil
}
subject
{
search_nav_tabs
}
context
'when @show_snippets is present'
do
before
do
@show_snippets
=
1
end
it
{
is_expected
.
to
eq
([
:snippet_titles
])
}
end
context
'when @project is present'
do
before
do
@project
=
1
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
anything
).
and_return
(
true
)
end
it
{
is_expected
.
to
eq
([
:blobs
,
:issues
,
:merge_requests
,
:milestones
,
:notes
,
:wiki_blobs
,
:commits
,
:users
])
}
end
context
'when @show_snippets and @project are not present'
do
context
'when user has access to read users'
do
before
do
allow
(
self
).
to
receive
(
:can?
).
with
(
current_user
,
:read_users_list
).
and_return
(
true
)
end
context
'when elasticsearch is enabled'
do
before
do
allow
(
self
.
search_service
).
to
receive
(
:use_elasticsearch?
).
and_return
(
true
)
end
it
{
is_expected
.
to
eq
([
:projects
,
:issues
,
:merge_requests
,
:milestones
,
:notes
,
:blobs
,
:commits
,
:wiki_blobs
,
:users
])
}
context
'when show_epics? is true'
do
before
do
allow
(
self
.
search_service
).
to
receive
(
:show_epics?
).
and_return
(
true
)
end
it
{
is_expected
.
to
eq
([
:projects
,
:issues
,
:merge_requests
,
:milestones
,
:epics
,
:notes
,
:blobs
,
:commits
,
:wiki_blobs
,
:users
])
}
end
end
context
'when elasticsearch is disabled'
do
before
do
allow
(
self
.
search_service
).
to
receive
(
:use_elasticsearch?
).
and_return
(
false
)
end
it
{
is_expected
.
to
eq
([
:projects
,
:issues
,
:merge_requests
,
:milestones
,
:users
])
}
context
'when show_epics? is true'
do
before
do
allow
(
self
.
search_service
).
to
receive
(
:show_epics?
).
and_return
(
true
)
end
it
{
is_expected
.
to
eq
([
:projects
,
:issues
,
:merge_requests
,
:milestones
,
:epics
,
:users
])
}
end
end
end
end
end
end
locale/gitlab.pot
View file @
0c7c4493
...
...
@@ -29374,6 +29374,9 @@ msgstr ""
msgid "There was an error fetching the Node's Groups"
msgstr ""
msgid "There was an error fetching the Search Counts"
msgstr ""
msgid "There was an error fetching the deploy freezes."
msgstr ""
...
...
qa/qa/page/search/results.rb
View file @
0c7c4493
...
...
@@ -4,7 +4,7 @@ module QA
module
Page
module
Search
class
Results
<
QA
::
Page
::
Base
view
'app/
views/search/_category.html.haml
'
do
view
'app/
assets/javascripts/search/topbar/constants.js
'
do
element
:code_tab
element
:projects_tab
end
...
...
spec/features/global_search_spec.rb
View file @
0c7c4493
...
...
@@ -28,7 +28,7 @@ RSpec.describe 'Global search' do
create_list
(
:issue
,
2
,
project:
project
,
title:
'initial'
)
end
it
"has a pagination"
do
it
"has a pagination"
,
:js
do
submit_search
(
'initial'
)
select_search_scope
(
'Issues'
)
...
...
spec/features/search/user_searches_for_code_spec.rb
View file @
0c7c4493
...
...
@@ -2,7 +2,7 @@
require
'spec_helper'
RSpec
.
describe
'User searches for code'
do
RSpec
.
describe
'User searches for code'
,
:js
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
,
:repository
,
namespace:
user
.
namespace
)
}
...
...
@@ -16,6 +16,7 @@ RSpec.describe 'User searches for code' do
visit
(
project_path
(
project
))
submit_search
(
'application.js'
)
select_search_scope
(
'Code'
)
expect
(
page
).
to
have_selector
(
'.results'
,
text:
'application.js'
)
...
...
@@ -24,7 +25,7 @@ RSpec.describe 'User searches for code' do
expect
(
page
).
to
have_link
(
'application.js'
,
href:
/master\/files\/js\/application.js/
)
end
context
'when on a project page'
,
:js
do
context
'when on a project page'
do
before
do
visit
(
search_path
)
find
(
'[data-testid="project-filter"]'
).
click
...
...
@@ -48,7 +49,7 @@ RSpec.describe 'User searches for code' do
expect
(
current_url
).
to
match
(
/master\/.gitignore#L3/
)
end
it
'search mutiple words with refs switching'
do
it
'search mu
l
tiple words with refs switching'
do
expected_result
=
'Use `snake_case` for naming files'
search
=
'for naming files'
...
...
@@ -67,7 +68,7 @@ RSpec.describe 'User searches for code' do
end
end
context
'search code within refs'
,
:js
do
context
'search code within refs'
do
let
(
:ref_name
)
{
'v1.0.0'
}
before
do
...
...
@@ -85,9 +86,9 @@ RSpec.describe 'User searches for code' do
expect
(
find
(
'.js-project-refs-dropdown'
)).
to
have_text
(
ref_name
)
end
# this example is use to test the des
gine
that the refs is not
# only rep
er
sent the branch as well as the tags.
it
'ref swit
her list all the branch
s and tags'
do
# this example is use to test the des
ign
that the refs is not
# only rep
re
sent the branch as well as the tags.
it
'ref swit
cher list all the branche
s 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'
)
...
...
spec/features/search/user_searches_for_comments_spec.rb
View file @
0c7c4493
...
...
@@ -2,7 +2,7 @@
require
'spec_helper'
RSpec
.
describe
'User searches for comments'
do
RSpec
.
describe
'User searches for comments'
,
:js
do
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:user
)
{
create
(
:user
)
}
...
...
spec/features/search/user_searches_for_users_spec.rb
View file @
0c7c4493
...
...
@@ -2,7 +2,7 @@
require
'spec_helper'
RSpec
.
describe
'User searches for users'
do
RSpec
.
describe
'User searches for users'
,
:js
do
let
(
:user1
)
{
create
(
:user
,
username:
'gob_bluth'
,
name:
'Gob Bluth'
)
}
let
(
:user2
)
{
create
(
:user
,
username:
'michael_bluth'
,
name:
'Michael Bluth'
)
}
let
(
:user3
)
{
create
(
:user
,
username:
'gob_2018'
,
name:
'George Oscar Bluth'
)
}
...
...
@@ -12,7 +12,7 @@ RSpec.describe 'User searches for users' do
end
context
'when on the dashboard'
do
it
'finds the user'
,
:js
do
it
'finds the user'
do
visit
dashboard_projects_path
submit_search
(
'gob'
)
...
...
spec/features/snippets/search_snippets_spec.rb
View file @
0c7c4493
...
...
@@ -2,7 +2,7 @@
require
'spec_helper'
RSpec
.
describe
'Search Snippets'
do
RSpec
.
describe
'Search Snippets'
,
:js
do
it
'user searches for snippets by title'
do
public_snippet
=
create
(
:personal_snippet
,
:public
,
title:
'Beginning and Middle'
)
private_snippet
=
create
(
:personal_snippet
,
:private
,
title:
'Middle and End'
)
...
...
spec/frontend/pages/search/show/__snapshots__/refresh_counts_spec.js.snap
deleted
100644 → 0
View file @
2c5cf14f
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`pages/search/show/refresh_counts fetches and displays search counts 1`] = `
"<div class=\\"badge\\">22</div>
<div class=\\"badge js-search-count\\" data-url=\\"http://test.host/search/count?search=lorem+ipsum&project_id=3&scope=issues\\">4</div>
<div class=\\"badge js-search-count\\" data-url=\\"http://test.host/search/count?search=lorem+ipsum&project_id=3&scope=merge_requests\\">5</div>"
`;
spec/frontend/pages/search/show/refresh_counts_spec.js
deleted
100644 → 0
View file @
2c5cf14f
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
refreshCounts
from
'
~/pages/search/show/refresh_counts
'
;
const
URL
=
`
${
TEST_HOST
}
/search/count?search=lorem+ipsum&project_id=3`
;
const
urlWithScope
=
(
scope
)
=>
`
${
URL
}
&scope=
${
scope
}
`
;
const
counts
=
[
{
scope
:
'
issues
'
,
count
:
4
},
{
scope
:
'
merge_requests
'
,
count
:
5
},
];
const
fixture
=
`<div class="badge">22</div>
<div class="badge js-search-count hidden" data-url="
${
urlWithScope
(
'
issues
'
)}
"></div>
<div class="badge js-search-count hidden" data-url="
${
urlWithScope
(
'
merge_requests
'
)}
"></div>`
;
describe
(
'
pages/search/show/refresh_counts
'
,
()
=>
{
let
mock
;
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
setFixtures
(
fixture
);
});
afterEach
(()
=>
{
mock
.
restore
();
});
it
(
'
fetches and displays search counts
'
,
()
=>
{
counts
.
forEach
(({
scope
,
count
})
=>
{
mock
.
onGet
(
urlWithScope
(
scope
)).
reply
(
200
,
{
count
});
});
// assert before act behavior
return
refreshCounts
().
then
(()
=>
{
expect
(
document
.
body
.
innerHTML
).
toMatchSnapshot
();
});
});
});
spec/frontend/search/mock_data.js
View file @
0c7c4493
...
...
@@ -61,3 +61,28 @@ export const MOCK_SORT_OPTIONS = [
},
},
];
export
const
MOCK_SEARCH_COUNTS_INPUT
=
{
scopeTabs
:
[
'
issues
'
,
'
snippet_titles
'
,
'
merge_requests
'
],
activeCount
:
'
15
'
,
};
export
const
MOCK_SEARCH_COUNT
=
{
scope
:
'
issues
'
,
count
:
'
15
'
};
export
const
MOCK_SEARCH_COUNTS_SUCCESS
=
[
{
scope
:
'
issues
'
,
count
:
'
15
'
},
{
scope
:
'
snippet_titles
'
,
count
:
'
15
'
},
{
scope
:
'
merge_requests
'
,
count
:
'
15
'
},
];
export
const
MOCK_SEARCH_COUNTS
=
[
{
scope
:
'
issues
'
,
count
:
'
15
'
},
{
scope
:
'
snippet_titles
'
,
count
:
'
5
'
},
{
scope
:
'
merge_requests
'
,
count
:
'
1
'
},
];
export
const
MOCK_SCOPE_TABS
=
[
{
scope
:
'
issues
'
,
title
:
'
Issues
'
,
count
:
'
15
'
},
{
scope
:
'
snippet_titles
'
,
title
:
'
Titles and Descriptions
'
,
count
:
'
5
'
},
{
scope
:
'
merge_requests
'
,
title
:
'
Merge requests
'
,
count
:
'
1
'
},
];
spec/frontend/search/store/actions_spec.js
View file @
0c7c4493
...
...
@@ -7,7 +7,15 @@ import * as urlUtils from '~/lib/utils/url_utility';
import
createState
from
'
~/search/store/state
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
createFlash
from
'
~/flash
'
;
import
{
MOCK_QUERY
,
MOCK_GROUPS
,
MOCK_PROJECT
,
MOCK_PROJECTS
}
from
'
../mock_data
'
;
import
{
MOCK_QUERY
,
MOCK_GROUPS
,
MOCK_PROJECT
,
MOCK_PROJECTS
,
MOCK_SEARCH_COUNT
,
MOCK_SEARCH_COUNTS_SUCCESS
,
MOCK_SEARCH_COUNTS_INPUT
,
}
from
'
../mock_data
'
;
jest
.
mock
(
'
~/flash
'
);
jest
.
mock
(
'
~/lib/utils/url_utility
'
,
()
=>
({
...
...
@@ -37,19 +45,21 @@ describe('Global Search Store Actions', () => {
});
describe
.
each
`
action | axiosMock | type | expectedMutations | callback
${
actions
.
fetchGroups
}
|
${{
method
:
'
onGet
'
,
code
:
200
,
res
:
MOCK_GROUPS
}
} |
${
'
success
'
}
|
${[{
type
:
types
.
REQUEST_GROUPS
},
{
type
:
types
.
RECEIVE_GROUPS_SUCCESS
,
payload
:
MOCK_GROUPS
}]}
|
${
noCallback
}
${
actions
.
fetchGroups
}
|
${{
method
:
'
onGet
'
,
code
:
500
,
res
:
null
}
} |
${
'
error
'
}
|
${[{
type
:
types
.
REQUEST_GROUPS
},
{
type
:
types
.
RECEIVE_GROUPS_ERROR
}]}
|
${
flashCallback
}
${
actions
.
fetchProjects
}
|
${{
method
:
'
onGet
'
,
code
:
200
,
res
:
MOCK_PROJECTS
}
} |
${
'
success
'
}
|
${[{
type
:
types
.
REQUEST_PROJECTS
},
{
type
:
types
.
RECEIVE_PROJECTS_SUCCESS
,
payload
:
MOCK_PROJECTS
}]}
|
${
noCallback
}
${
actions
.
fetchProjects
}
|
${{
method
:
'
onGet
'
,
code
:
500
,
res
:
null
}
} |
${
'
error
'
}
|
${[{
type
:
types
.
REQUEST_PROJECTS
},
{
type
:
types
.
RECEIVE_PROJECTS_ERROR
}]}
|
${
flashCallback
}
`
(
`axios calls`
,
({
action
,
axiosMock
,
type
,
expectedMutations
,
callback
})
=>
{
action | axiosMock | payload | type | expectedMutations | callback
${
actions
.
fetchGroups
}
|
${{
method
:
'
onGet
'
,
code
:
200
,
res
:
MOCK_GROUPS
}
} |
${
null
}
|
${
'
success
'
}
|
${[{
type
:
types
.
REQUEST_GROUPS
},
{
type
:
types
.
RECEIVE_GROUPS_SUCCESS
,
payload
:
MOCK_GROUPS
}]}
|
${
noCallback
}
${
actions
.
fetchGroups
}
|
${{
method
:
'
onGet
'
,
code
:
500
,
res
:
null
}
} |
${
null
}
|
${
'
error
'
}
|
${[{
type
:
types
.
REQUEST_GROUPS
},
{
type
:
types
.
RECEIVE_GROUPS_ERROR
}]}
|
${
flashCallback
}
${
actions
.
fetchProjects
}
|
${{
method
:
'
onGet
'
,
code
:
200
,
res
:
MOCK_PROJECTS
}
} |
${
null
}
|
${
'
success
'
}
|
${[{
type
:
types
.
REQUEST_PROJECTS
},
{
type
:
types
.
RECEIVE_PROJECTS_SUCCESS
,
payload
:
MOCK_PROJECTS
}]}
|
${
noCallback
}
${
actions
.
fetchProjects
}
|
${{
method
:
'
onGet
'
,
code
:
500
,
res
:
null
}
} |
${
null
}
|
${
'
error
'
}
|
${[{
type
:
types
.
REQUEST_PROJECTS
},
{
type
:
types
.
RECEIVE_PROJECTS_ERROR
}]}
|
${
flashCallback
}
${
actions
.
fetchSearchCounts
}
|
${{
method
:
'
onGet
'
,
code
:
200
,
res
:
MOCK_SEARCH_COUNT
}
} |
${
MOCK_SEARCH_COUNTS_INPUT
}
|
${
'
success
'
}
|
${[{
type
:
types
.
REQUEST_SEARCH_COUNTS
,
payload
:
MOCK_SEARCH_COUNTS_INPUT
},
{
type
:
types
.
RECEIVE_SEARCH_COUNTS_SUCCESS
,
payload
:
MOCK_SEARCH_COUNTS_SUCCESS
}]}
|
${
noCallback
}
${
actions
.
fetchSearchCounts
}
|
${{
method
:
'
onGet
'
,
code
:
500
,
res
:
null
}
} |
${
MOCK_SEARCH_COUNTS_INPUT
}
|
${
'
error
'
}
|
${[{
type
:
types
.
REQUEST_SEARCH_COUNTS
,
payload
:
MOCK_SEARCH_COUNTS_INPUT
}]}
|
${
flashCallback
}
`
(
`axios calls`
,
({
action
,
axiosMock
,
payload
,
type
,
expectedMutations
,
callback
})
=>
{
describe
(
action
.
name
,
()
=>
{
describe
(
`on
${
type
}
`
,
()
=>
{
beforeEach
(()
=>
{
mock
[
axiosMock
.
method
]().
reply
Once
(
axiosMock
.
code
,
axiosMock
.
res
);
mock
[
axiosMock
.
method
]().
reply
(
axiosMock
.
code
,
axiosMock
.
res
);
});
it
(
`should dispatch the correct mutations`
,
()
=>
{
return
testAction
({
action
,
state
,
expectedMutations
}).
then
(()
=>
callback
());
return
testAction
({
action
,
payload
,
state
,
expectedMutations
}).
then
(()
=>
callback
());
});
});
});
...
...
@@ -115,9 +125,25 @@ describe('Global Search Store Actions', () => {
page
:
null
,
state
:
null
,
confidential
:
null
,
nav_source
:
null
,
});
expect
(
urlUtils
.
visitUrl
).
toHaveBeenCalled
();
});
});
});
it
(
'
calls setUrlParams with snippets, group_id, and project_id when snippets param is true
'
,
()
=>
{
return
testAction
(
actions
.
resetQuery
,
true
,
state
,
[],
[],
()
=>
{
expect
(
urlUtils
.
setUrlParams
).
toHaveBeenCalledWith
({
...
state
.
query
,
page
:
null
,
state
:
null
,
confidential
:
null
,
nav_source
:
null
,
group_id
:
null
,
project_id
:
null
,
snippets
:
true
,
});
});
});
});
spec/frontend/search/store/mutations_spec.js
View file @
0c7c4493
import
mutations
from
'
~/search/store/mutations
'
;
import
createState
from
'
~/search/store/state
'
;
import
*
as
types
from
'
~/search/store/mutation_types
'
;
import
{
MOCK_QUERY
,
MOCK_GROUPS
,
MOCK_PROJECTS
}
from
'
../mock_data
'
;
import
{
MOCK_QUERY
,
MOCK_GROUPS
,
MOCK_PROJECTS
,
MOCK_SEARCH_COUNTS
,
MOCK_SCOPE_TABS
,
}
from
'
../mock_data
'
;
describe
(
'
Global Search Store Mutations
'
,
()
=>
{
let
state
;
...
...
@@ -71,4 +77,32 @@ describe('Global Search Store Mutations', () => {
expect
(
state
.
query
[
payload
.
key
]).
toBe
(
payload
.
value
);
});
});
describe
(
'
REQUEST_SEARCH_COUNTS
'
,
()
=>
{
it
(
'
sets the count to for the query.scope activeCount
'
,
()
=>
{
const
payload
=
{
scopeTabs
:
[
'
issues
'
],
activeCount
:
'
22
'
};
mutations
[
types
.
REQUEST_SEARCH_COUNTS
](
state
,
payload
);
expect
(
state
.
inflatedScopeTabs
).
toStrictEqual
([
{
scope
:
'
issues
'
,
title
:
'
Issues
'
,
count
:
'
22
'
},
]);
});
it
(
'
sets other scopes count to empty string
'
,
()
=>
{
const
payload
=
{
scopeTabs
:
[
'
milestones
'
],
activeCount
:
'
22
'
};
mutations
[
types
.
REQUEST_SEARCH_COUNTS
](
state
,
payload
);
expect
(
state
.
inflatedScopeTabs
).
toStrictEqual
([
{
scope
:
'
milestones
'
,
title
:
'
Milestones
'
,
count
:
''
},
]);
});
});
describe
(
'
RECEIVE_SEARCH_COUNTS_SUCCESS
'
,
()
=>
{
it
(
'
sets the count from the input for all tabs
'
,
()
=>
{
mutations
[
types
.
RECEIVE_SEARCH_COUNTS_SUCCESS
](
state
,
MOCK_SEARCH_COUNTS
);
expect
(
state
.
inflatedScopeTabs
).
toStrictEqual
(
MOCK_SCOPE_TABS
);
});
});
});
spec/frontend/search/topbar/components/app_spec.js
View file @
0c7c4493
...
...
@@ -5,6 +5,7 @@ import { MOCK_QUERY } from 'jest/search/mock_data';
import
GlobalSearchTopbar
from
'
~/search/topbar/components/app.vue
'
;
import
GroupFilter
from
'
~/search/topbar/components/group_filter.vue
'
;
import
ProjectFilter
from
'
~/search/topbar/components/project_filter.vue
'
;
import
ScopeTabs
from
'
~/search/topbar/components/scope_tabs.vue
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
...
...
@@ -42,6 +43,7 @@ describe('GlobalSearchTopbar', () => {
const
findGroupFilter
=
()
=>
wrapper
.
find
(
GroupFilter
);
const
findProjectFilter
=
()
=>
wrapper
.
find
(
ProjectFilter
);
const
findSearchButton
=
()
=>
wrapper
.
find
(
GlButton
);
const
findScopeTabs
=
()
=>
wrapper
.
find
(
ScopeTabs
);
describe
(
'
template
'
,
()
=>
{
beforeEach
(()
=>
{
...
...
@@ -52,6 +54,18 @@ describe('GlobalSearchTopbar', () => {
expect
(
findTopbarForm
().
exists
()).
toBe
(
true
);
});
describe
(
'
Scope Tabs
'
,
()
=>
{
it
(
'
renders when search param is set
'
,
()
=>
{
createComponent
({
query
:
{
search
:
'
test
'
}
});
expect
(
findScopeTabs
().
exists
()).
toBe
(
true
);
});
it
(
'
does not render search param is blank
'
,
()
=>
{
createComponent
({
query
:
{}
});
expect
(
findScopeTabs
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
Search box
'
,
()
=>
{
it
(
'
renders always
'
,
()
=>
{
expect
(
findGlSearchBox
().
exists
()).
toBe
(
true
);
...
...
spec/frontend/search/topbar/components/scope_tabs_spec.js
0 → 100644
View file @
0c7c4493
import
Vuex
from
'
vuex
'
;
import
{
createLocalVue
,
mount
}
from
'
@vue/test-utils
'
;
import
{
GlTabs
,
GlTab
,
GlBadge
}
from
'
@gitlab/ui
'
;
import
{
extendedWrapper
}
from
'
helpers/vue_test_utils_helper
'
;
import
{
MOCK_QUERY
,
MOCK_SCOPE_TABS
}
from
'
jest/search/mock_data
'
;
import
ScopeTabs
from
'
~/search/topbar/components/scope_tabs.vue
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
ScopeTabs
'
,
()
=>
{
let
wrapper
;
const
actionSpies
=
{
fetchSearchCounts
:
jest
.
fn
(),
setQuery
:
jest
.
fn
(),
resetQuery
:
jest
.
fn
(),
};
const
defaultProps
=
{
scopeTabs
:
[
'
issues
'
,
'
merge_requests
'
,
'
milestones
'
],
count
:
'
20
'
,
};
const
createComponent
=
(
props
=
{},
initialState
=
{})
=>
{
const
store
=
new
Vuex
.
Store
({
state
:
{
query
:
{
...
MOCK_QUERY
,
search
:
'
test
'
,
},
...
initialState
,
},
actions
:
actionSpies
,
});
wrapper
=
extendedWrapper
(
mount
(
ScopeTabs
,
{
localVue
,
store
,
propsData
:
{
...
defaultProps
,
...
props
,
},
}),
);
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
const
findScopeTabs
=
()
=>
wrapper
.
find
(
GlTabs
);
const
findTabs
=
()
=>
wrapper
.
findAll
(
GlTab
);
const
findBadges
=
()
=>
wrapper
.
findAll
(
GlBadge
);
const
findTabsTitle
=
()
=>
wrapper
.
findAll
(
'
[data-testid="tab-title"]
'
).
wrappers
.
map
((
w
)
=>
w
.
text
());
const
findBadgesTitle
=
()
=>
findBadges
().
wrappers
.
map
((
w
)
=>
w
.
text
());
const
findBadgeByScope
=
(
scope
)
=>
wrapper
.
findByTestId
(
`badge-
${
scope
}
`
);
const
findTabByScope
=
(
scope
)
=>
wrapper
.
findByTestId
(
`tab-
${
scope
}
`
);
describe
(
'
template
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({},
{
inflatedScopeTabs
:
MOCK_SCOPE_TABS
});
});
it
(
'
always renders Scope Tabs
'
,
()
=>
{
expect
(
findScopeTabs
().
exists
()).
toBe
(
true
);
});
describe
(
'
findTabs
'
,
()
=>
{
it
(
'
renders a tab for each scope
'
,
()
=>
{
expect
(
findTabs
()).
toHaveLength
(
defaultProps
.
scopeTabs
.
length
);
expect
(
findTabsTitle
()).
toStrictEqual
([
'
Issues
'
,
'
Titles and Descriptions
'
,
'
Merge requests
'
,
]);
});
});
describe
(
'
findBadges
'
,
()
=>
{
it
(
'
renders a badge for each scope
'
,
()
=>
{
expect
(
findBadges
()).
toHaveLength
(
defaultProps
.
scopeTabs
.
length
);
expect
(
findBadgesTitle
()).
toStrictEqual
([
'
15
'
,
'
5
'
,
'
1
'
]);
});
it
(
'
sets the variant to neutral for active tab only
'
,
()
=>
{
expect
(
findBadgeByScope
(
'
issues
'
).
classes
()).
toContain
(
'
badge-neutral
'
);
expect
(
findBadgeByScope
(
'
snippet_titles
'
).
classes
()).
toContain
(
'
badge-muted
'
);
expect
(
findBadgeByScope
(
'
merge_requests
'
).
classes
()).
toContain
(
'
badge-muted
'
);
});
});
});
describe
(
'
methods
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({},
{
inflatedScopeTabs
:
MOCK_SCOPE_TABS
});
findTabByScope
(
'
snippet_titles
'
).
vm
.
$emit
(
'
click
'
);
});
describe
(
'
handleTabChange
'
,
()
=>
{
it
(
'
calls setQuery with scope, applies any search params from ALL_SCOPE_TABS, and sends nulls for page, state, confidential, and nav_source
'
,
()
=>
{
expect
(
actionSpies
.
setQuery
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
{
key
:
'
scope
'
,
value
:
'
snippet_titles
'
,
});
});
it
(
'
calls resetQuery and sends true for snippet_titles tab
'
,
()
=>
{
expect
(
actionSpies
.
resetQuery
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
true
);
});
it
(
'
calls resetQuery and does not send true for other tabs
'
,
()
=>
{
findTabByScope
(
'
issues
'
).
vm
.
$emit
(
'
click
'
);
expect
(
actionSpies
.
resetQuery
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
),
false
);
});
});
});
});
spec/helpers/search_helper_spec.rb
View file @
0c7c4493
...
...
@@ -392,63 +392,6 @@ RSpec.describe SearchHelper do
end
end
describe
'search_filter_link'
do
it
'renders a search filter link for the current scope'
do
@scope
=
'projects'
@search_results
=
double
expect
(
@search_results
).
to
receive
(
:formatted_count
).
with
(
'projects'
).
and_return
(
'23'
)
link
=
search_filter_link
(
'projects'
,
'Projects'
)
expect
(
link
).
to
have_css
(
'li.active'
)
expect
(
link
).
to
have_link
(
'Projects'
,
href:
search_path
(
scope:
'projects'
))
expect
(
link
).
to
have_css
(
'span.badge.badge-pill:not(.js-search-count):not(.hidden):not([data-url])'
,
text:
'23'
)
end
it
'renders a search filter link for another scope'
do
link
=
search_filter_link
(
'projects'
,
'Projects'
)
count_path
=
search_count_path
(
scope:
'projects'
)
expect
(
link
).
to
have_css
(
'li:not([class="active"])'
)
expect
(
link
).
to
have_link
(
'Projects'
,
href:
search_path
(
scope:
'projects'
))
expect
(
link
).
to
have_css
(
"span.badge.badge-pill.js-search-count.hidden[data-url='
#{
count_path
}
']"
,
text:
''
)
end
it
'merges in the current search params and given params'
do
expect
(
self
).
to
receive
(
:params
).
and_return
(
ActionController
::
Parameters
.
new
(
search:
'hello'
,
scope:
'ignored'
,
other_param:
'ignored'
)
)
link
=
search_filter_link
(
'projects'
,
'Projects'
,
search:
{
project_id:
23
})
expect
(
link
).
to
have_link
(
'Projects'
,
href:
search_path
(
scope:
'projects'
,
search:
'hello'
,
project_id:
23
))
end
it
'restricts the params'
do
expect
(
self
).
to
receive
(
:params
).
and_return
(
ActionController
::
Parameters
.
new
(
search:
'hello'
,
unknown:
42
)
)
link
=
search_filter_link
(
'projects'
,
'Projects'
)
expect
(
link
).
to
have_link
(
'Projects'
,
href:
search_path
(
scope:
'projects'
,
search:
'hello'
))
end
it
'assigns given data attributes on the list container'
do
link
=
search_filter_link
(
'projects'
,
'Projects'
,
data:
{
foo:
'bar'
})
expect
(
link
).
to
have_css
(
'li[data-foo="bar"]'
)
end
end
describe
'#show_user_search_tab?'
do
subject
{
show_user_search_tab?
}
...
...
@@ -631,4 +574,86 @@ RSpec.describe SearchHelper do
expect
(
search_sort_options
).
to
eq
([
mock_created_sort
])
end
end
describe
'#search_nav_tabs'
do
subject
{
search_nav_tabs
}
let
(
:current_user
)
{
nil
}
before
do
allow
(
self
).
to
receive
(
:current_user
).
and_return
(
current_user
)
end
context
'when @show_snippets is present'
do
before
do
@show_snippets
=
1
end
it
{
is_expected
.
to
eq
([
:snippet_titles
])
}
context
'and @project is present'
do
before
do
@project
=
1
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
anything
).
and_return
(
true
)
end
it
{
is_expected
.
to
eq
([
:blobs
,
:issues
,
:merge_requests
,
:milestones
,
:notes
,
:wiki_blobs
,
:commits
,
:users
])
}
end
end
context
'when @project is present'
do
before
do
@project
=
1
end
context
'when user has access to project'
do
before
do
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
anything
).
and_return
(
true
)
end
it
{
is_expected
.
to
eq
([
:blobs
,
:issues
,
:merge_requests
,
:milestones
,
:notes
,
:wiki_blobs
,
:commits
,
:users
])
}
end
context
'when user does not have access to project'
do
before
do
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
anything
).
and_return
(
false
)
end
it
{
is_expected
.
to
eq
([])
}
end
context
'when user does not have access to read members for project'
do
before
do
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
:members
).
and_return
(
false
)
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
:merge_requests
).
and_return
(
true
)
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
:milestones
).
and_return
(
true
)
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
:wiki_blobs
).
and_return
(
true
)
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
:issues
).
and_return
(
true
)
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
:blobs
).
and_return
(
true
)
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
:notes
).
and_return
(
true
)
allow
(
self
).
to
receive
(
:project_search_tabs?
).
with
(
:commits
).
and_return
(
true
)
end
it
{
is_expected
.
to
eq
([
:blobs
,
:issues
,
:merge_requests
,
:milestones
,
:notes
,
:wiki_blobs
,
:commits
])
}
end
end
context
'when @show_snippets and @project are not present'
do
context
'when user has access to read users'
do
before
do
allow
(
self
).
to
receive
(
:can?
).
with
(
current_user
,
:read_users_list
).
and_return
(
true
)
end
it
{
is_expected
.
to
eq
([
:projects
,
:issues
,
:merge_requests
,
:milestones
,
:users
])
}
end
context
'when user does not have access to read users'
do
before
do
allow
(
self
).
to
receive
(
:can?
).
with
(
current_user
,
:read_users_list
).
and_return
(
false
)
end
it
{
is_expected
.
to
eq
([
:projects
,
:issues
,
:merge_requests
,
:milestones
])
}
end
end
end
end
spec/views/search/show.html.haml_spec.rb
View file @
0c7c4493
...
...
@@ -6,7 +6,6 @@ RSpec.describe 'search/show' do
let
(
:search_term
)
{
nil
}
before
do
stub_template
"search/_category.html.haml"
=>
'Category Partial'
stub_template
"search/_results.html.haml"
=>
'Results Partial'
@search_term
=
search_term
...
...
@@ -21,7 +20,6 @@ RSpec.describe 'search/show' do
end
it
'does not render partials'
do
expect
(
rendered
).
not_to
render_template
(
'search/_category'
)
expect
(
rendered
).
not_to
render_template
(
'search/_results'
)
end
end
...
...
@@ -30,7 +28,6 @@ RSpec.describe 'search/show' do
let
(
:search_term
)
{
'Search Foo'
}
it
'renders partials'
do
expect
(
rendered
).
to
render_template
(
'search/_category'
)
expect
(
rendered
).
to
render_template
(
'search/_results'
)
end
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment