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
0
Merge Requests
0
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
Boxiang Sun
gitlab-ce
Commits
974a0402
Commit
974a0402
authored
Aug 02, 2017
by
Clement Ho
Committed by
Phil Hughes
Aug 02, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add filtered search to group issue dashboard
parent
6f66b19b
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
400 additions
and
45 deletions
+400
-45
app/assets/javascripts/dispatcher.js
app/assets/javascripts/dispatcher.js
+10
-2
app/assets/javascripts/droplab/plugins/ajax.js
app/assets/javascripts/droplab/plugins/ajax.js
+12
-1
app/assets/javascripts/filtered_search/dropdown_non_user.js
app/assets/javascripts/filtered_search/dropdown_non_user.js
+2
-1
app/assets/javascripts/filtered_search/dropdown_utils.js
app/assets/javascripts/filtered_search/dropdown_utils.js
+60
-0
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
...ripts/filtered_search/filtered_search_dropdown_manager.js
+1
-0
app/assets/javascripts/filtered_search/filtered_search_manager.js
...ts/javascripts/filtered_search/filtered_search_manager.js
+3
-3
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
...ascripts/filtered_search/filtered_search_visual_tokens.js
+40
-15
app/assets/javascripts/labels_select.js
app/assets/javascripts/labels_select.js
+2
-12
app/assets/javascripts/lib/utils/ajax_cache.js
app/assets/javascripts/lib/utils/ajax_cache.js
+4
-0
app/helpers/search_helper.rb
app/helpers/search_helper.rb
+12
-4
app/views/groups/issues.html.haml
app/views/groups/issues.html.haml
+5
-1
app/views/shared/issuable/_search_bar.html.haml
app/views/shared/issuable/_search_bar.html.haml
+2
-1
changelogs/unreleased/add-filtered-search-group-issues-ce.yml
...gelogs/unreleased/add-filtered-search-group-issues-ce.yml
+4
-0
spec/features/groups/issues_spec.rb
spec/features/groups/issues_spec.rb
+5
-5
spec/helpers/search_helper_spec.rb
spec/helpers/search_helper_spec.rb
+34
-0
spec/javascripts/droplab/plugins/ajax_spec.js
spec/javascripts/droplab/plugins/ajax_spec.js
+36
-0
spec/javascripts/filtered_search/dropdown_utils_spec.js
spec/javascripts/filtered_search/dropdown_utils_spec.js
+96
-0
spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
...pts/filtered_search/filtered_search_visual_tokens_spec.js
+63
-0
spec/javascripts/lib/utils/ajax_cache_spec.js
spec/javascripts/lib/utils/ajax_cache_spec.js
+9
-0
No files found.
app/assets/javascripts/dispatcher.js
View file @
974a0402
...
@@ -139,6 +139,8 @@ import GpgBadges from './gpg_badges';
...
@@ -139,6 +139,8 @@ import GpgBadges from './gpg_badges';
.
init
();
.
init
();
}
}
const
filteredSearchEnabled
=
gl
.
FilteredSearchManager
&&
document
.
querySelector
(
'
.filtered-search
'
);
switch
(
page
)
{
switch
(
page
)
{
case
'
profiles:preferences:show
'
:
case
'
profiles:preferences:show
'
:
initExperimentalFlags
();
initExperimentalFlags
();
...
@@ -155,7 +157,7 @@ import GpgBadges from './gpg_badges';
...
@@ -155,7 +157,7 @@ import GpgBadges from './gpg_badges';
break
;
break
;
case
'
projects:merge_requests:index
'
:
case
'
projects:merge_requests:index
'
:
case
'
projects:issues:index
'
:
case
'
projects:issues:index
'
:
if
(
gl
.
FilteredSearchManager
&&
document
.
querySelector
(
'
.filtered-search
'
)
)
{
if
(
filteredSearchEnabled
)
{
const
filteredSearchManager
=
new
gl
.
FilteredSearchManager
(
page
===
'
projects:issues:index
'
?
'
issues
'
:
'
merge_requests
'
);
const
filteredSearchManager
=
new
gl
.
FilteredSearchManager
(
page
===
'
projects:issues:index
'
?
'
issues
'
:
'
merge_requests
'
);
filteredSearchManager
.
setup
();
filteredSearchManager
.
setup
();
}
}
...
@@ -183,11 +185,17 @@ import GpgBadges from './gpg_badges';
...
@@ -183,11 +185,17 @@ import GpgBadges from './gpg_badges';
break
;
break
;
case
'
dashboard:issues
'
:
case
'
dashboard:issues
'
:
case
'
dashboard:merge_requests
'
:
case
'
dashboard:merge_requests
'
:
case
'
groups:issues
'
:
case
'
groups:merge_requests
'
:
case
'
groups:merge_requests
'
:
new
ProjectSelect
();
new
ProjectSelect
();
initLegacyFilters
();
initLegacyFilters
();
break
;
break
;
case
'
groups:issues
'
:
if
(
filteredSearchEnabled
)
{
const
filteredSearchManager
=
new
gl
.
FilteredSearchManager
(
'
issues
'
);
filteredSearchManager
.
setup
();
}
new
ProjectSelect
();
break
;
case
'
dashboard:todos:index
'
:
case
'
dashboard:todos:index
'
:
new
Todos
();
new
Todos
();
break
;
break
;
...
...
app/assets/javascripts/droplab/plugins/ajax.js
View file @
974a0402
...
@@ -11,6 +11,16 @@ const Ajax = {
...
@@ -11,6 +11,16 @@ const Ajax = {
if
(
!
self
.
destroyed
)
self
.
hook
.
list
[
config
.
method
].
call
(
self
.
hook
.
list
,
data
);
if
(
!
self
.
destroyed
)
self
.
hook
.
list
[
config
.
method
].
call
(
self
.
hook
.
list
,
data
);
},
},
preprocessing
:
function
preprocessing
(
config
,
data
)
{
let
results
=
data
;
if
(
config
.
preprocessing
&&
!
data
.
preprocessed
)
{
results
=
config
.
preprocessing
(
data
);
AjaxCache
.
override
(
config
.
endpoint
,
results
);
}
return
results
;
},
init
:
function
init
(
hook
)
{
init
:
function
init
(
hook
)
{
var
self
=
this
;
var
self
=
this
;
self
.
destroyed
=
false
;
self
.
destroyed
=
false
;
...
@@ -31,7 +41,8 @@ const Ajax = {
...
@@ -31,7 +41,8 @@ const Ajax = {
dynamicList
.
outerHTML
=
loadingTemplate
.
outerHTML
;
dynamicList
.
outerHTML
=
loadingTemplate
.
outerHTML
;
}
}
AjaxCache
.
retrieve
(
config
.
endpoint
)
return
AjaxCache
.
retrieve
(
config
.
endpoint
)
.
then
(
self
.
preprocessing
.
bind
(
null
,
config
))
.
then
((
data
)
=>
self
.
_loadData
(
data
,
config
,
self
))
.
then
((
data
)
=>
self
.
_loadData
(
data
,
config
,
self
))
.
catch
(
config
.
onError
);
.
catch
(
config
.
onError
);
},
},
...
...
app/assets/javascripts/filtered_search/dropdown_non_user.js
View file @
974a0402
...
@@ -6,7 +6,7 @@ import './filtered_search_dropdown';
...
@@ -6,7 +6,7 @@ import './filtered_search_dropdown';
class
DropdownNonUser
extends
gl
.
FilteredSearchDropdown
{
class
DropdownNonUser
extends
gl
.
FilteredSearchDropdown
{
constructor
(
options
=
{})
{
constructor
(
options
=
{})
{
const
{
input
,
endpoint
,
symbol
}
=
options
;
const
{
input
,
endpoint
,
symbol
,
preprocessing
}
=
options
;
super
(
options
);
super
(
options
);
this
.
symbol
=
symbol
;
this
.
symbol
=
symbol
;
this
.
config
=
{
this
.
config
=
{
...
@@ -14,6 +14,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown {
...
@@ -14,6 +14,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown {
endpoint
,
endpoint
,
method
:
'
setData
'
,
method
:
'
setData
'
,
loadingTemplate
:
this
.
loadingTemplate
,
loadingTemplate
:
this
.
loadingTemplate
,
preprocessing
,
onError
()
{
onError
()
{
/* eslint-disable no-new */
/* eslint-disable no-new */
new
Flash
(
'
An error occured fetching the dropdown data.
'
);
new
Flash
(
'
An error occured fetching the dropdown data.
'
);
...
...
app/assets/javascripts/filtered_search/dropdown_utils.js
View file @
974a0402
...
@@ -50,6 +50,66 @@ class DropdownUtils {
...
@@ -50,6 +50,66 @@ class DropdownUtils {
return
updatedItem
;
return
updatedItem
;
}
}
static
mergeDuplicateLabels
(
dataMap
,
newLabel
)
{
const
updatedMap
=
dataMap
;
const
key
=
newLabel
.
title
;
const
hasKeyProperty
=
Object
.
prototype
.
hasOwnProperty
.
call
(
updatedMap
,
key
);
if
(
!
hasKeyProperty
)
{
updatedMap
[
key
]
=
newLabel
;
}
else
{
const
existing
=
updatedMap
[
key
];
if
(
!
existing
.
multipleColors
)
{
existing
.
multipleColors
=
[
existing
.
color
];
}
existing
.
multipleColors
.
push
(
newLabel
.
color
);
}
return
updatedMap
;
}
static
duplicateLabelColor
(
labelColors
)
{
const
colors
=
labelColors
;
const
spacing
=
100
/
colors
.
length
;
// Reduce the colors to 4
colors
.
length
=
Math
.
min
(
colors
.
length
,
4
);
const
color
=
colors
.
map
((
c
,
i
)
=>
{
const
percentFirst
=
Math
.
floor
(
spacing
*
i
);
const
percentSecond
=
Math
.
floor
(
spacing
*
(
i
+
1
));
return
`
${
c
}
${
percentFirst
}
%,
${
c
}
${
percentSecond
}
%`
;
}).
join
(
'
,
'
);
return
`linear-gradient(
${
color
}
)`
;
}
static
duplicateLabelPreprocessing
(
data
)
{
const
results
=
[];
const
dataMap
=
{};
data
.
forEach
(
DropdownUtils
.
mergeDuplicateLabels
.
bind
(
null
,
dataMap
));
Object
.
keys
(
dataMap
)
.
forEach
((
key
)
=>
{
const
label
=
dataMap
[
key
];
if
(
label
.
multipleColors
)
{
label
.
color
=
DropdownUtils
.
duplicateLabelColor
(
label
.
multipleColors
);
label
.
text_color
=
'
#000000
'
;
}
results
.
push
(
label
);
});
results
.
preprocessed
=
true
;
return
results
;
}
static
filterHint
(
config
,
item
)
{
static
filterHint
(
config
,
item
)
{
const
{
input
,
allowedKeys
}
=
config
;
const
{
input
,
allowedKeys
}
=
config
;
const
updatedItem
=
item
;
const
updatedItem
=
item
;
...
...
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
View file @
974a0402
...
@@ -54,6 +54,7 @@ class FilteredSearchDropdownManager {
...
@@ -54,6 +54,7 @@ class FilteredSearchDropdownManager {
extraArguments
:
{
extraArguments
:
{
endpoint
:
`
${
this
.
baseEndpoint
}
/labels.json`
,
endpoint
:
`
${
this
.
baseEndpoint
}
/labels.json`
,
symbol
:
'
~
'
,
symbol
:
'
~
'
,
preprocessing
:
gl
.
DropdownUtils
.
duplicateLabelPreprocessing
,
},
},
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-label
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-label
'
),
},
},
...
...
app/assets/javascripts/filtered_search/filtered_search_manager.js
View file @
974a0402
...
@@ -20,13 +20,13 @@ class FilteredSearchManager {
...
@@ -20,13 +20,13 @@ class FilteredSearchManager {
allowedKeys
:
this
.
filteredSearchTokenKeys
.
getKeys
(),
allowedKeys
:
this
.
filteredSearchTokenKeys
.
getKeys
(),
});
});
this
.
searchHistoryDropdownElement
=
document
.
querySelector
(
'
.js-filtered-search-history-dropdown
'
);
this
.
searchHistoryDropdownElement
=
document
.
querySelector
(
'
.js-filtered-search-history-dropdown
'
);
const
project
Path
=
this
.
searchHistoryDropdownElement
?
const
full
Path
=
this
.
searchHistoryDropdownElement
?
this
.
searchHistoryDropdownElement
.
dataset
.
projectF
ullPath
:
'
project
'
;
this
.
searchHistoryDropdownElement
.
dataset
.
f
ullPath
:
'
project
'
;
let
recentSearchesPagePrefix
=
'
issue-recent-searches
'
;
let
recentSearchesPagePrefix
=
'
issue-recent-searches
'
;
if
(
this
.
page
===
'
merge_requests
'
)
{
if
(
this
.
page
===
'
merge_requests
'
)
{
recentSearchesPagePrefix
=
'
merge-request-recent-searches
'
;
recentSearchesPagePrefix
=
'
merge-request-recent-searches
'
;
}
}
const
recentSearchesKey
=
`
${
project
Path
}
-
${
recentSearchesPagePrefix
}
`
;
const
recentSearchesKey
=
`
${
full
Path
}
-
${
recentSearchesPagePrefix
}
`
;
this
.
recentSearchesService
=
new
RecentSearchesService
(
recentSearchesKey
);
this
.
recentSearchesService
=
new
RecentSearchesService
(
recentSearchesKey
);
}
}
...
...
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
View file @
974a0402
...
@@ -58,29 +58,54 @@ class FilteredSearchVisualTokens {
...
@@ -58,29 +58,54 @@ class FilteredSearchVisualTokens {
`
;
`
;
}
}
static
setTokenStyle
(
tokenContainer
,
backgroundColor
,
textColor
)
{
const
token
=
tokenContainer
;
// Labels with linear gradient should not override default background color
if
(
backgroundColor
.
indexOf
(
'
linear-gradient
'
)
===
-
1
)
{
token
.
style
.
backgroundColor
=
backgroundColor
;
}
token
.
style
.
color
=
textColor
;
if
(
textColor
===
'
#FFFFFF
'
)
{
const
removeToken
=
token
.
querySelector
(
'
.remove-token
'
);
removeToken
.
classList
.
add
(
'
inverted
'
);
}
return
token
;
}
static
preprocessLabel
(
labelsEndpoint
,
labels
)
{
let
processed
=
labels
;
if
(
!
labels
.
preprocessed
)
{
processed
=
gl
.
DropdownUtils
.
duplicateLabelPreprocessing
(
labels
);
AjaxCache
.
override
(
labelsEndpoint
,
processed
);
processed
.
preprocessed
=
true
;
}
return
processed
;
}
static
updateLabelTokenColor
(
tokenValueContainer
,
tokenValue
)
{
static
updateLabelTokenColor
(
tokenValueContainer
,
tokenValue
)
{
const
filteredSearchInput
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
const
filteredSearchInput
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
const
baseEndpoint
=
filteredSearchInput
.
dataset
.
baseEndpoint
;
const
baseEndpoint
=
filteredSearchInput
.
dataset
.
baseEndpoint
;
const
labelsEndpoint
=
`
${
baseEndpoint
}
/labels.json`
;
const
labelsEndpoint
=
`
${
baseEndpoint
}
/labels.json`
;
return
AjaxCache
.
retrieve
(
labelsEndpoint
)
return
AjaxCache
.
retrieve
(
labelsEndpoint
)
.
then
((
labels
)
=>
{
.
then
(
FilteredSearchVisualTokens
.
preprocessLabel
.
bind
(
null
,
labelsEndpoint
))
const
matchingLabel
=
(
labels
||
[]).
find
(
label
=>
`~
${
gl
.
DropdownUtils
.
getEscapedText
(
label
.
title
)}
`
===
tokenValue
);
.
then
((
labels
)
=>
{
const
matchingLabel
=
(
labels
||
[]).
find
(
label
=>
`~
${
gl
.
DropdownUtils
.
getEscapedText
(
label
.
title
)}
`
===
tokenValue
);
if
(
!
matchingLabel
)
{
return
;
}
const
tokenValueStyle
=
tokenValueContainer
.
style
;
if
(
!
matchingLabel
)
{
tokenValueStyle
.
backgroundColor
=
matchingLabel
.
color
;
return
;
tokenValueStyle
.
color
=
matchingLabel
.
text_color
;
}
if
(
matchingLabel
.
text_color
===
'
#FFFFFF
'
)
{
FilteredSearchVisualTokens
const
removeToken
=
tokenValueContainer
.
querySelector
(
'
.remove-token
'
);
.
setTokenStyle
(
tokenValueContainer
,
matchingLabel
.
color
,
matchingLabel
.
text_color
);
removeToken
.
classList
.
add
(
'
inverted
'
);
})
}
.
catch
(()
=>
new
Flash
(
'
An error occurred while fetching label colors.
'
));
})
.
catch
(()
=>
new
Flash
(
'
An error occurred while fetching label colors.
'
));
}
}
static
updateUserTokenAppearance
(
tokenValueContainer
,
tokenValueElement
,
tokenValue
)
{
static
updateUserTokenAppearance
(
tokenValueContainer
,
tokenValueElement
,
tokenValue
)
{
...
...
app/assets/javascripts/labels_select.js
View file @
974a0402
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
/* global ListLabel */
/* global ListLabel */
import
IssuableBulkUpdateActions
from
'
./issuable_bulk_update_actions
'
;
import
IssuableBulkUpdateActions
from
'
./issuable_bulk_update_actions
'
;
import
DropdownUtils
from
'
./filtered_search/dropdown_utils
'
;
(
function
()
{
(
function
()
{
this
.
LabelsSelect
=
(
function
()
{
this
.
LabelsSelect
=
(
function
()
{
...
@@ -218,18 +219,7 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
...
@@ -218,18 +219,7 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
}
}
}
}
if
(
label
.
duplicate
)
{
if
(
label
.
duplicate
)
{
spacing
=
100
/
label
.
color
.
length
;
color
=
gl
.
DropdownUtils
.
duplicateLabelColor
(
label
.
color
);
// Reduce the colors to 4
label
.
color
=
label
.
color
.
filter
(
function
(
color
,
i
)
{
return
i
<
4
;
});
color
=
_
.
map
(
label
.
color
,
function
(
color
,
i
)
{
var
percentFirst
,
percentSecond
;
percentFirst
=
Math
.
floor
(
spacing
*
i
);
percentSecond
=
Math
.
floor
(
spacing
*
(
i
+
1
));
return
color
+
"
"
+
percentFirst
+
"
%,
"
+
color
+
"
"
+
percentSecond
+
"
%
"
;
}).
join
(
'
,
'
);
color
=
"
linear-gradient(
"
+
color
+
"
)
"
;
}
}
else
{
else
{
if
(
label
.
color
!=
null
)
{
if
(
label
.
color
!=
null
)
{
...
...
app/assets/javascripts/lib/utils/ajax_cache.js
View file @
974a0402
...
@@ -6,6 +6,10 @@ class AjaxCache extends Cache {
...
@@ -6,6 +6,10 @@ class AjaxCache extends Cache {
this
.
pendingRequests
=
{
};
this
.
pendingRequests
=
{
};
}
}
override
(
endpoint
,
data
)
{
this
.
internalStorage
[
endpoint
]
=
data
;
}
retrieve
(
endpoint
,
forceRetrieve
)
{
retrieve
(
endpoint
,
forceRetrieve
)
{
if
(
this
.
hasData
(
endpoint
)
&&
!
forceRetrieve
)
{
if
(
this
.
hasData
(
endpoint
)
&&
!
forceRetrieve
)
{
return
Promise
.
resolve
(
this
.
get
(
endpoint
));
return
Promise
.
resolve
(
this
.
get
(
endpoint
));
...
...
app/helpers/search_helper.rb
View file @
974a0402
...
@@ -127,15 +127,23 @@ module SearchHelper
...
@@ -127,15 +127,23 @@ module SearchHelper
end
end
def
search_filter_input_options
(
type
)
def
search_filter_input_options
(
type
)
{
opts
=
{
id:
"filtered-search-
#{
type
}
"
,
id:
"filtered-search-
#{
type
}
"
,
placeholder:
'Search or filter results...'
,
placeholder:
'Search or filter results...'
,
data:
{
data:
{
'project-id'
=>
@project
.
id
,
'username-params'
=>
@users
.
to_json
(
only:
[
:id
,
:username
])
'username-params'
=>
@users
.
to_json
(
only:
[
:id
,
:username
]),
'base-endpoint'
=>
project_path
(
@project
)
}
}
}
}
if
@project
.
present?
opts
[
:data
][
'project-id'
]
=
@project
.
id
opts
[
:data
][
'base-endpoint'
]
=
project_path
(
@project
)
else
# Group context
opts
[
:data
][
'base-endpoint'
]
=
group_canonical_path
(
@group
)
end
opts
end
end
# Sanitize a HTML field for search display. Most tags are stripped out and the
# Sanitize a HTML field for search display. Most tags are stripped out and the
...
...
app/views/groups/issues.html.haml
View file @
974a0402
...
@@ -4,6 +4,10 @@
...
@@ -4,6 +4,10 @@
=
content_for
:meta_tags
do
=
content_for
:meta_tags
do
=
auto_discovery_link_tag
(
:atom
,
params
.
merge
(
rss_url_options
),
title:
"
#{
@group
.
name
}
issues"
)
=
auto_discovery_link_tag
(
:atom
,
params
.
merge
(
rss_url_options
),
title:
"
#{
@group
.
name
}
issues"
)
-
content_for
:page_specific_javascripts
do
=
webpack_bundle_tag
'common_vue'
=
webpack_bundle_tag
'filtered_search'
-
if
show_new_nav?
&&
group_issues_exists
-
if
show_new_nav?
&&
group_issues_exists
-
content_for
:breadcrumbs_extra
do
-
content_for
:breadcrumbs_extra
do
=
link_to
params
.
merge
(
rss_url_options
),
class:
'btn btn-default append-right-10'
do
=
link_to
params
.
merge
(
rss_url_options
),
class:
'btn btn-default append-right-10'
do
...
@@ -20,7 +24,7 @@
...
@@ -20,7 +24,7 @@
Subscribe
Subscribe
=
render
'shared/new_project_item_select'
,
path:
'issues/new'
,
label:
"New issue"
=
render
'shared/new_project_item_select'
,
path:
'issues/new'
,
label:
"New issue"
=
render
'shared/issuable/
filte
r'
,
type: :issues
=
render
'shared/issuable/
search_ba
r'
,
type: :issues
.row-content-block.second-block
.row-content-block.second-block
Only issues from the
Only issues from the
...
...
app/views/shared/issuable/_search_bar.html.haml
View file @
974a0402
-
type
=
local_assigns
.
fetch
(
:type
)
-
type
=
local_assigns
.
fetch
(
:type
)
-
block_css_class
=
type
!=
:boards_modal
?
'row-content-block second-block'
:
''
-
block_css_class
=
type
!=
:boards_modal
?
'row-content-block second-block'
:
''
-
full_path
=
@project
.
present?
?
@project
.
full_path
:
@group
.
full_path
.issues-filters
.issues-filters
.issues-details-filters.filtered-search-block
{
class:
block_css_class
,
"v-pre"
=>
type
==
:boards_modal
}
.issues-details-filters.filtered-search-block
{
class:
block_css_class
,
"v-pre"
=>
type
==
:boards_modal
}
...
@@ -18,7 +19,7 @@
...
@@ -18,7 +19,7 @@
dropdown_class:
"filtered-search-history-dropdown"
,
dropdown_class:
"filtered-search-history-dropdown"
,
content_class:
"filtered-search-history-dropdown-content"
,
content_class:
"filtered-search-history-dropdown-content"
,
title:
"Recent searches"
})
do
title:
"Recent searches"
})
do
.js-filtered-search-history-dropdown
{
data:
{
project_full_path:
@project
.
full_path
}
}
.js-filtered-search-history-dropdown
{
data:
{
full_path:
full_path
}
}
.filtered-search-box-input-container.droplab-dropdown
.filtered-search-box-input-container.droplab-dropdown
.scroll-container
.scroll-container
%ul
.tokens-container.list-unstyled
%ul
.tokens-container.list-unstyled
...
...
changelogs/unreleased/add-filtered-search-group-issues-ce.yml
0 → 100644
View file @
974a0402
---
title
:
Add filtered search to group issue dashboard
merge_request
:
author
:
spec/features/groups/issues_spec.rb
View file @
974a0402
require
'spec_helper'
require
'spec_helper'
feature
'Group issues page'
do
feature
'Group issues page'
do
include
FilteredSearchHelpers
let
(
:path
)
{
issues_group_path
(
group
)
}
let
(
:path
)
{
issues_group_path
(
group
)
}
let
(
:issuable
)
{
create
(
:issue
,
project:
project
,
title:
"this is my created issuable"
)}
let
(
:issuable
)
{
create
(
:issue
,
project:
project
,
title:
"this is my created issuable"
)}
...
@@ -31,12 +33,10 @@ feature 'Group issues page' do
...
@@ -31,12 +33,10 @@ feature 'Group issues page' do
let
(
:path
)
{
issues_group_path
(
group
)
}
let
(
:path
)
{
issues_group_path
(
group
)
}
it
'filters by only group users'
do
it
'filters by only group users'
do
click_button
(
'Assignee'
)
filtered_search
.
set
(
'assignee:'
)
wait_for_requests
expect
(
find
(
'
.dropdown-menu-assignee'
)).
to
have_link
(
user
.
name
)
expect
(
find
(
'
#js-dropdown-assignee .filter-dropdown'
)).
to
have_content
(
user
.
name
)
expect
(
find
(
'
.dropdown-menu-assignee'
)).
not_to
have_link
(
user2
.
name
)
expect
(
find
(
'
#js-dropdown-assignee .filter-dropdown'
)).
not_to
have_content
(
user2
.
name
)
end
end
end
end
end
end
spec/helpers/search_helper_spec.rb
View file @
974a0402
...
@@ -68,4 +68,38 @@ describe SearchHelper do
...
@@ -68,4 +68,38 @@ describe SearchHelper do
end
end
end
end
end
end
describe
'search_filter_input_options'
do
context
'project'
do
before
do
@project
=
create
(
:project
,
:repository
)
end
it
'includes id with type'
do
expect
(
search_filter_input_options
(
'type'
)[
:id
]).
to
eq
(
'filtered-search-type'
)
end
it
'includes project-id'
do
expect
(
search_filter_input_options
(
''
)[
:data
][
'project-id'
]).
to
eq
(
@project
.
id
)
end
it
'includes project base-endpoint'
do
expect
(
search_filter_input_options
(
''
)[
:data
][
'base-endpoint'
]).
to
eq
(
project_path
(
@project
))
end
end
context
'group'
do
before
do
@group
=
create
(
:group
,
name:
'group'
)
end
it
'does not includes project-id'
do
expect
(
search_filter_input_options
(
''
)[
:data
][
'project-id'
]).
to
eq
(
nil
)
end
it
'includes group base-endpoint'
do
expect
(
search_filter_input_options
(
''
)[
:data
][
'base-endpoint'
]).
to
eq
(
"/groups
#{
group_path
(
@group
)
}
"
)
end
end
end
end
end
spec/javascripts/droplab/plugins/ajax_spec.js
0 → 100644
View file @
974a0402
import
AjaxCache
from
'
~/lib/utils/ajax_cache
'
;
import
Ajax
from
'
~/droplab/plugins/ajax
'
;
describe
(
'
Ajax
'
,
()
=>
{
describe
(
'
preprocessing
'
,
()
=>
{
const
config
=
{};
describe
(
'
is not configured
'
,
()
=>
{
it
(
'
passes the data through
'
,
()
=>
{
const
data
=
[
'
data
'
];
expect
(
Ajax
.
preprocessing
(
config
,
data
)).
toEqual
(
data
);
});
});
describe
(
'
is configured
'
,
()
=>
{
const
processedArray
=
[
'
processed
'
];
beforeEach
(()
=>
{
config
.
preprocessing
=
()
=>
processedArray
;
spyOn
(
config
,
'
preprocessing
'
).
and
.
callFake
(()
=>
processedArray
);
});
it
(
'
calls preprocessing
'
,
()
=>
{
Ajax
.
preprocessing
(
config
,
[]);
expect
(
config
.
preprocessing
.
calls
.
count
()).
toBe
(
1
);
});
it
(
'
overrides AjaxCache
'
,
()
=>
{
spyOn
(
AjaxCache
,
'
override
'
).
and
.
callFake
((
endpoint
,
results
)
=>
expect
(
results
).
toEqual
(
processedArray
));
Ajax
.
preprocessing
(
config
,
[]);
expect
(
AjaxCache
.
override
.
calls
.
count
()).
toBe
(
1
);
});
});
});
});
spec/javascripts/filtered_search/dropdown_utils_spec.js
View file @
974a0402
...
@@ -191,6 +191,102 @@ describe('Dropdown Utils', () => {
...
@@ -191,6 +191,102 @@ describe('Dropdown Utils', () => {
});
});
});
});
describe
(
'
mergeDuplicateLabels
'
,
()
=>
{
const
dataMap
=
{
label
:
{
title
:
'
label
'
,
color
:
'
#FFFFFF
'
,
},
};
it
(
'
should add label to dataMap if it is not a duplicate
'
,
()
=>
{
const
newLabel
=
{
title
:
'
new-label
'
,
color
:
'
#000000
'
,
};
const
updated
=
gl
.
DropdownUtils
.
mergeDuplicateLabels
(
dataMap
,
newLabel
);
expect
(
updated
[
newLabel
.
title
]).
toEqual
(
newLabel
);
});
it
(
'
should merge colors if label is a duplicate
'
,
()
=>
{
const
duplicate
=
{
title
:
'
label
'
,
color
:
'
#000000
'
,
};
const
updated
=
gl
.
DropdownUtils
.
mergeDuplicateLabels
(
dataMap
,
duplicate
);
expect
(
updated
.
label
.
multipleColors
).
toEqual
([
dataMap
.
label
.
color
,
duplicate
.
color
]);
});
});
describe
(
'
duplicateLabelColor
'
,
()
=>
{
it
(
'
should linear-gradient 2 colors
'
,
()
=>
{
const
gradient
=
gl
.
DropdownUtils
.
duplicateLabelColor
([
'
#FFFFFF
'
,
'
#000000
'
]);
expect
(
gradient
).
toEqual
(
'
linear-gradient(#FFFFFF 0%, #FFFFFF 50%, #000000 50%, #000000 100%)
'
);
});
it
(
'
should linear-gradient 3 colors
'
,
()
=>
{
const
gradient
=
gl
.
DropdownUtils
.
duplicateLabelColor
([
'
#FFFFFF
'
,
'
#000000
'
,
'
#333333
'
]);
expect
(
gradient
).
toEqual
(
'
linear-gradient(#FFFFFF 0%, #FFFFFF 33%, #000000 33%, #000000 66%, #333333 66%, #333333 100%)
'
);
});
it
(
'
should linear-gradient 4 colors
'
,
()
=>
{
const
gradient
=
gl
.
DropdownUtils
.
duplicateLabelColor
([
'
#FFFFFF
'
,
'
#000000
'
,
'
#333333
'
,
'
#DDDDDD
'
]);
expect
(
gradient
).
toEqual
(
'
linear-gradient(#FFFFFF 0%, #FFFFFF 25%, #000000 25%, #000000 50%, #333333 50%, #333333 75%, #DDDDDD 75%, #DDDDDD 100%)
'
);
});
it
(
'
should not linear-gradient more than 4 colors
'
,
()
=>
{
const
gradient
=
gl
.
DropdownUtils
.
duplicateLabelColor
([
'
#FFFFFF
'
,
'
#000000
'
,
'
#333333
'
,
'
#DDDDDD
'
,
'
#EEEEEE
'
]);
expect
(
gradient
.
indexOf
(
'
#EEEEEE
'
)
===
-
1
).
toEqual
(
true
);
});
});
describe
(
'
duplicateLabelPreprocessing
'
,
()
=>
{
it
(
'
should set preprocessed to true
'
,
()
=>
{
const
results
=
gl
.
DropdownUtils
.
duplicateLabelPreprocessing
([]);
expect
(
results
.
preprocessed
).
toEqual
(
true
);
});
it
(
'
should not mutate existing data if there are no duplicates
'
,
()
=>
{
const
data
=
[{
title
:
'
label1
'
,
color
:
'
#FFFFFF
'
,
},
{
title
:
'
label2
'
,
color
:
'
#000000
'
,
}];
const
results
=
gl
.
DropdownUtils
.
duplicateLabelPreprocessing
(
data
);
expect
(
results
.
length
).
toEqual
(
2
);
expect
(
results
[
0
]).
toEqual
(
data
[
0
]);
expect
(
results
[
1
]).
toEqual
(
data
[
1
]);
});
describe
(
'
duplicate labels
'
,
()
=>
{
const
data
=
[{
title
:
'
label
'
,
color
:
'
#FFFFFF
'
,
},
{
title
:
'
label
'
,
color
:
'
#000000
'
,
}];
const
results
=
gl
.
DropdownUtils
.
duplicateLabelPreprocessing
(
data
);
it
(
'
should merge duplicate labels
'
,
()
=>
{
expect
(
results
.
length
).
toEqual
(
1
);
});
it
(
'
should convert multiple colored labels into linear-gradient
'
,
()
=>
{
expect
(
results
[
0
].
color
).
toEqual
(
gl
.
DropdownUtils
.
duplicateLabelColor
([
'
#FFFFFF
'
,
'
#000000
'
]));
});
it
(
'
should set multiple colored label text color to black
'
,
()
=>
{
expect
(
results
[
0
].
text_color
).
toEqual
(
'
#000000
'
);
});
});
});
describe
(
'
setDataValueIfSelected
'
,
()
=>
{
describe
(
'
setDataValueIfSelected
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
spyOn
(
gl
.
FilteredSearchDropdownManager
,
'
addWordToInput
'
)
spyOn
(
gl
.
FilteredSearchDropdownManager
,
'
addWordToInput
'
)
...
...
spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
View file @
974a0402
...
@@ -797,6 +797,69 @@ describe('Filtered Search Visual Tokens', () => {
...
@@ -797,6 +797,69 @@ describe('Filtered Search Visual Tokens', () => {
});
});
});
});
describe
(
'
setTokenStyle
'
,
()
=>
{
let
originalTextColor
;
beforeEach
(()
=>
{
originalTextColor
=
bugLabelToken
.
style
.
color
;
});
it
(
'
should set backgroundColor
'
,
()
=>
{
const
originalBackgroundColor
=
bugLabelToken
.
style
.
backgroundColor
;
const
token
=
subject
.
setTokenStyle
(
bugLabelToken
,
'
blue
'
,
'
white
'
);
expect
(
token
.
style
.
backgroundColor
).
toEqual
(
'
blue
'
);
expect
(
token
.
style
.
backgroundColor
).
not
.
toEqual
(
originalBackgroundColor
);
});
it
(
'
should not set backgroundColor when it is a linear-gradient
'
,
()
=>
{
const
token
=
subject
.
setTokenStyle
(
bugLabelToken
,
'
linear-gradient(135deg, red, blue)
'
,
'
white
'
);
expect
(
token
.
style
.
backgroundColor
).
toEqual
(
bugLabelToken
.
style
.
backgroundColor
);
});
it
(
'
should set textColor
'
,
()
=>
{
const
token
=
subject
.
setTokenStyle
(
bugLabelToken
,
'
white
'
,
'
black
'
);
expect
(
token
.
style
.
color
).
toEqual
(
'
black
'
);
expect
(
token
.
style
.
color
).
not
.
toEqual
(
originalTextColor
);
});
it
(
'
should add inverted class when textColor is #FFFFFF
'
,
()
=>
{
const
token
=
subject
.
setTokenStyle
(
bugLabelToken
,
'
black
'
,
'
#FFFFFF
'
);
expect
(
token
.
style
.
color
).
toEqual
(
'
rgb(255, 255, 255)
'
);
expect
(
token
.
style
.
color
).
not
.
toEqual
(
originalTextColor
);
expect
(
token
.
querySelector
(
'
.remove-token
'
).
classList
.
contains
(
'
inverted
'
)).
toEqual
(
true
);
});
});
describe
(
'
preprocessLabel
'
,
()
=>
{
const
endpoint
=
'
endpoint
'
;
it
(
'
does not preprocess more than once
'
,
()
=>
{
let
labels
=
[];
spyOn
(
gl
.
DropdownUtils
,
'
duplicateLabelPreprocessing
'
).
and
.
callFake
(()
=>
[]);
labels
=
gl
.
FilteredSearchVisualTokens
.
preprocessLabel
(
endpoint
,
labels
);
gl
.
FilteredSearchVisualTokens
.
preprocessLabel
(
endpoint
,
labels
);
expect
(
gl
.
DropdownUtils
.
duplicateLabelPreprocessing
.
calls
.
count
()).
toEqual
(
1
);
});
describe
(
'
not preprocessed before
'
,
()
=>
{
it
(
'
returns preprocessed labels
'
,
()
=>
{
let
labels
=
[];
expect
(
labels
.
preprocessed
).
not
.
toEqual
(
true
);
labels
=
gl
.
FilteredSearchVisualTokens
.
preprocessLabel
(
endpoint
,
labels
);
expect
(
labels
.
preprocessed
).
toEqual
(
true
);
});
it
(
'
overrides AjaxCache with preprocessed results
'
,
()
=>
{
spyOn
(
AjaxCache
,
'
override
'
).
and
.
callFake
(()
=>
{});
gl
.
FilteredSearchVisualTokens
.
preprocessLabel
(
endpoint
,
[]);
expect
(
AjaxCache
.
override
.
calls
.
count
()).
toEqual
(
1
);
});
});
});
describe
(
'
updateLabelTokenColor
'
,
()
=>
{
describe
(
'
updateLabelTokenColor
'
,
()
=>
{
const
jsonFixtureName
=
'
labels/project_labels.json
'
;
const
jsonFixtureName
=
'
labels/project_labels.json
'
;
const
dummyEndpoint
=
'
/dummy/endpoint
'
;
const
dummyEndpoint
=
'
/dummy/endpoint
'
;
...
...
spec/javascripts/lib/utils/ajax_cache_spec.js
View file @
974a0402
...
@@ -77,6 +77,15 @@ describe('AjaxCache', () => {
...
@@ -77,6 +77,15 @@ describe('AjaxCache', () => {
});
});
});
});
describe
(
'
override
'
,
()
=>
{
it
(
'
overrides existing cache
'
,
()
=>
{
AjaxCache
.
internalStorage
.
endpoint
=
'
existing-endpoint
'
;
AjaxCache
.
override
(
'
endpoint
'
,
'
new-endpoint
'
);
expect
(
AjaxCache
.
internalStorage
.
endpoint
).
toEqual
(
'
new-endpoint
'
);
});
});
describe
(
'
retrieve
'
,
()
=>
{
describe
(
'
retrieve
'
,
()
=>
{
let
ajaxSpy
;
let
ajaxSpy
;
...
...
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