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
Jérome Perrin
gitlab-ce
Commits
6bfff2fa
Commit
6bfff2fa
authored
Mar 10, 2016
by
Jacob Vosmaer
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
https://gitlab.com/gitlab-org/gitlab-ce
into expiring-lock
parents
261214ef
8ecdc0a7
Changes
22
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
852 additions
and
44 deletions
+852
-44
CHANGELOG
CHANGELOG
+1
-0
app/assets/javascripts/api.js.coffee
app/assets/javascripts/api.js.coffee
+14
-0
app/assets/javascripts/gl_dropdown.js.coffee
app/assets/javascripts/gl_dropdown.js.coffee
+270
-0
app/assets/javascripts/issue_status_select.js.coffee
app/assets/javascripts/issue_status_select.js.coffee
+11
-0
app/assets/javascripts/labels_select.js.coffee
app/assets/javascripts/labels_select.js.coffee
+92
-0
app/assets/javascripts/milestone_select.js.coffee
app/assets/javascripts/milestone_select.js.coffee
+60
-0
app/assets/javascripts/users_select.js.coffee
app/assets/javascripts/users_select.js.coffee
+75
-0
app/assets/stylesheets/framework/blocks.scss
app/assets/stylesheets/framework/blocks.scss
+4
-0
app/assets/stylesheets/framework/dropdowns.scss
app/assets/stylesheets/framework/dropdowns.scss
+30
-6
app/assets/stylesheets/framework/filters.scss
app/assets/stylesheets/framework/filters.scss
+1
-0
app/assets/stylesheets/pages/labels.scss
app/assets/stylesheets/pages/labels.scss
+22
-0
app/controllers/dashboard/projects_controller.rb
app/controllers/dashboard/projects_controller.rb
+2
-2
app/controllers/explore/projects_controller.rb
app/controllers/explore/projects_controller.rb
+3
-3
app/helpers/dropdowns_helper.rb
app/helpers/dropdowns_helper.rb
+100
-0
app/views/help/ui.html.haml
app/views/help/ui.html.haml
+45
-0
app/views/shared/issuable/_filter.html.haml
app/views/shared/issuable/_filter.html.haml
+78
-13
features/steps/dashboard/issues.rb
features/steps/dashboard/issues.rb
+13
-4
features/steps/dashboard/merge_requests.rb
features/steps/dashboard/merge_requests.rb
+13
-4
features/steps/project/issues/filter_labels.rb
features/steps/project/issues/filter_labels.rb
+4
-1
features/steps/project/issues/issues.rb
features/steps/project/issues/issues.rb
+6
-5
spec/features/issues/filter_by_milestone_spec.rb
spec/features/issues/filter_by_milestone_spec.rb
+4
-3
spec/features/merge_requests/filter_by_milestone_spec.rb
spec/features/merge_requests/filter_by_milestone_spec.rb
+4
-3
No files found.
CHANGELOG
View file @
6bfff2fa
...
...
@@ -25,6 +25,7 @@ v 8.6.0 (unreleased)
- Show labels in dashboard and group milestone views
- Add main language of a project in the list of projects (Tiago Botelho)
- Add ability to show archived projects on dashboard, explore and group pages
- Fix pagination for filtered dashboard and explore pages
v 8.5.5
- Ensure removing a project removes associated Todo entries.
...
...
app/assets/javascripts/api.js.coffee
View file @
6bfff2fa
...
...
@@ -4,6 +4,7 @@
namespaces_path
:
"/api/:version/namespaces.json"
group_projects_path
:
"/api/:version/groups/:id/projects.json"
projects_path
:
"/api/:version/projects.json"
labels_path
:
"/api/:version/projects/:id/labels"
group
:
(
group_id
,
callback
)
->
url
=
Api
.
buildUrl
(
Api
.
group_path
)
...
...
@@ -61,6 +62,19 @@
).
done
(
projects
)
->
callback
(
projects
)
newLabel
:
(
project_id
,
data
,
callback
)
->
url
=
Api
.
buildUrl
(
Api
.
labels_path
)
url
=
url
.
replace
(
':id'
,
project_id
)
data
.
private_token
=
gon
.
api_token
$
.
ajax
(
url
:
url
type
:
"POST"
data
:
data
dataType
:
"json"
).
done
(
label
)
->
callback
(
label
)
# Return group projects list. Filtered by query
groupProjects
:
(
group_id
,
query
,
callback
)
->
url
=
Api
.
buildUrl
(
Api
.
group_projects_path
)
...
...
app/assets/javascripts/gl_dropdown.js.coffee
0 → 100644
View file @
6bfff2fa
class
GitLabDropdownFilter
BLUR_KEYCODES
=
[
27
,
40
]
constructor
:
(
@
dropdown
,
@
options
)
->
@
input
=
@
dropdown
.
find
(
".dropdown-input .dropdown-input-field"
)
# Key events
timeout
=
""
@
input
.
on
"keyup"
,
(
e
)
=>
if
e
.
keyCode
is
13
&&
@
input
.
val
()
isnt
""
if
@
options
.
enterCallback
@
options
.
enterCallback
()
return
clearTimeout
timeout
timeout
=
setTimeout
=>
blur_field
=
@
shouldBlur
e
.
keyCode
search_text
=
@
input
.
val
()
if
blur_field
@
input
.
blur
()
if
@
options
.
remote
@
options
.
query
search_text
,
(
data
)
=>
@
options
.
callback
(
data
)
else
@
filter
search_text
,
250
shouldBlur
:
(
keyCode
)
->
return
BLUR_KEYCODES
.
indexOf
(
keyCode
)
>=
0
filter
:
(
search_text
)
->
data
=
@
options
.
data
()
results
=
data
if
search_text
isnt
""
results
=
fuzzaldrinPlus
.
filter
(
data
,
search_text
,
key
:
@
options
.
keys
)
@
options
.
callback
results
class
GitLabDropdownRemote
constructor
:
(
@
dataEndpoint
,
@
options
)
->
execute
:
->
if
typeof
@
dataEndpoint
is
"string"
@
fetchData
()
else
if
typeof
@
dataEndpoint
is
"function"
if
@
options
.
beforeSend
@
options
.
beforeSend
()
# Fetch the data by calling the data funcfion
@
dataEndpoint
""
,
(
data
)
=>
if
@
options
.
success
@
options
.
success
(
data
)
if
@
options
.
beforeSend
@
options
.
beforeSend
()
# Fetch the data through ajax if the data is a string
fetchData
:
->
$
.
ajax
(
url
:
@
dataEndpoint
,
dataType
:
@
options
.
dataType
,
beforeSend
:
=>
if
@
options
.
beforeSend
@
options
.
beforeSend
()
success
:
(
data
)
=>
if
@
options
.
success
@
options
.
success
(
data
)
)
class
GitLabDropdown
LOADING_CLASS
=
"is-loading"
PAGE_TWO_CLASS
=
"is-page-two"
ACTIVE_CLASS
=
"is-active"
constructor
:
(
@
el
,
@
options
)
->
self
=
@
@
dropdown
=
$
(
@
el
).
parent
()
search_fields
=
if
@
options
.
search
then
@
options
.
search
.
fields
else
[];
if
@
options
.
data
# Remote data
@
remote
=
new
GitLabDropdownRemote
@
options
.
data
,
{
dataType
:
@
options
.
dataType
,
beforeSend
:
@
toggleLoading
.
bind
(
@
)
success
:
(
data
)
=>
@
fullData
=
data
@
parseData
@
fullData
}
# Init filiterable
if
@
options
.
filterable
@
filter
=
new
GitLabDropdownFilter
@
dropdown
,
remote
:
@
options
.
filterRemote
query
:
@
options
.
data
keys
:
@
options
.
search
.
fields
data
:
=>
return
@
fullData
callback
:
(
data
)
=>
@
parseData
data
enterCallback
:
=>
@
selectFirstRow
()
# Event listeners
@
dropdown
.
on
"shown.bs.dropdown"
,
@
opened
@
dropdown
.
on
"hidden.bs.dropdown"
,
@
hidden
if
@
dropdown
.
find
(
".dropdown-toggle-page"
).
length
@
dropdown
.
find
(
".dropdown-toggle-page, .dropdown-menu-back"
).
on
"click"
,
(
e
)
=>
e
.
preventDefault
()
e
.
stopPropagation
()
@
togglePage
()
if
@
options
.
selectable
selector
=
".dropdown-content a"
if
@
dropdown
.
find
(
".dropdown-toggle-page"
).
length
selector
=
".dropdown-page-one .dropdown-content a"
@
dropdown
.
on
"click"
,
selector
,
(
e
)
->
self
.
rowClicked
$
(
@
)
if
self
.
options
.
clicked
self
.
options
.
clicked
()
toggleLoading
:
->
$
(
'.dropdown-menu'
,
@
dropdown
).
toggleClass
LOADING_CLASS
togglePage
:
->
menu
=
$
(
'.dropdown-menu'
,
@
dropdown
)
if
menu
.
hasClass
(
PAGE_TWO_CLASS
)
if
@
remote
@
remote
.
execute
()
menu
.
toggleClass
PAGE_TWO_CLASS
parseData
:
(
data
)
->
@
renderedData
=
data
# Render each row
html
=
$
.
map
data
,
(
obj
)
=>
return
@
renderItem
(
obj
)
if
@
options
.
filterable
and
data
.
length
is
0
# render no matching results
html
=
[
@
noResults
()]
# Render the full menu
full_html
=
@
renderMenu
(
html
.
join
(
""
))
@
appendMenu
(
full_html
)
opened
:
=>
contentHtml
=
$
(
'.dropdown-content'
,
@
dropdown
).
html
()
if
@
remote
&&
contentHtml
is
""
@
remote
.
execute
()
if
@
options
.
filterable
@
dropdown
.
find
(
".dropdown-input-field"
).
focus
()
hidden
:
=>
if
@
options
.
filterable
@
dropdown
.
find
(
".dropdown-input-field"
).
blur
().
val
(
""
)
if
@
dropdown
.
find
(
".dropdown-toggle-page"
).
length
$
(
'.dropdown-menu'
,
@
dropdown
).
removeClass
PAGE_TWO_CLASS
# Render the full menu
renderMenu
:
(
html
)
->
menu_html
=
""
if
@
options
.
renderMenu
menu_html
=
@
options
.
renderMenu
(
html
)
else
menu_html
=
"<ul>
#{
html
}
</ul>"
return
menu_html
# Append the menu into the dropdown
appendMenu
:
(
html
)
->
selector
=
'.dropdown-content'
if
@
dropdown
.
find
(
".dropdown-toggle-page"
).
length
selector
=
".dropdown-page-one .dropdown-content"
$
(
selector
,
@
dropdown
).
html
html
# Render the row
renderItem
:
(
data
)
->
html
=
""
return
"<li class='divider'></li>"
if
data
is
"divider"
if
@
options
.
renderRow
# Call the render function
html
=
@
options
.
renderRow
(
data
)
else
selected
=
if
@
options
.
isSelected
then
@
options
.
isSelected
(
data
)
else
false
url
=
if
@
options
.
url
then
@
options
.
url
(
data
)
else
"#"
text
=
if
@
options
.
text
then
@
options
.
text
(
data
)
else
""
cssClass
=
""
;
if
selected
cssClass
=
"is-active"
html
=
"<li>"
html
+=
"<a href='
#{
url
}
' class='
#{
cssClass
}
'>"
html
+=
text
html
+=
"</a>"
html
+=
"</li>"
return
html
noResults
:
->
html
=
"<li>"
html
+=
"<a href='#' class='is-focused'>"
html
+=
"No matching results."
html
+=
"</a>"
html
+=
"</li>"
rowClicked
:
(
el
)
->
fieldName
=
@
options
.
fieldName
field
=
@
dropdown
.
parent
().
find
(
"input[name='
#{
fieldName
}
']"
)
if
el
.
hasClass
(
ACTIVE_CLASS
)
field
.
remove
()
else
fieldName
=
@
options
.
fieldName
selectedIndex
=
el
.
parent
().
index
()
if
@
renderedData
selectedObject
=
@
renderedData
[
selectedIndex
]
value
=
if
@
options
.
id
then
@
options
.
id
(
selectedObject
,
el
)
else
selectedObject
.
id
if
@
options
.
multiSelect
oldValue
=
field
.
val
()
if
oldValue
value
=
"
#{
oldValue
}
,
#{
value
}
"
else
@
dropdown
.
find
(
ACTIVE_CLASS
).
removeClass
ACTIVE_CLASS
field
.
remove
()
# Toggle active class for the tick mark
el
.
toggleClass
"is-active"
if
value
if
!
field
.
length
# Create hidden input for form
input
=
"<input type='hidden' name='
#{
fieldName
}
' />"
@
dropdown
.
before
input
@
dropdown
.
parent
().
find
(
"input[name='
#{
fieldName
}
']"
).
val
value
selectFirstRow
:
->
selector
=
'.dropdown-content li:first-child a'
if
@
dropdown
.
find
(
".dropdown-toggle-page"
).
length
selector
=
".dropdown-page-one .dropdown-content li:first-child a"
# similute a click on the first link
$
(
selector
).
trigger
"click"
$
.
fn
.
glDropdown
=
(
opts
)
->
return
@
.
each
->
new
GitLabDropdown
@
,
opts
app/assets/javascripts/issue_status_select.js.coffee
0 → 100644
View file @
6bfff2fa
class
@
IssueStatusSelect
constructor
:
->
$
(
'.js-issue-status'
).
each
(
i
,
el
)
->
fieldName
=
$
(
el
).
data
(
"field-name"
)
$
(
el
).
glDropdown
(
selectable
:
true
fieldName
:
fieldName
id
:
(
obj
,
el
)
->
$
(
el
).
data
(
"id"
)
)
app/assets/javascripts/labels_select.js.coffee
0 → 100644
View file @
6bfff2fa
class
@
LabelsSelect
constructor
:
->
$
(
'.js-label-select'
).
each
(
i
,
dropdown
)
->
projectId
=
$
(
dropdown
).
data
(
'project-id'
)
labelUrl
=
$
(
dropdown
).
data
(
"labels"
)
selectedLabel
=
$
(
dropdown
).
data
(
'selected'
)
if
selectedLabel
selectedLabel
=
selectedLabel
.
split
(
","
)
newLabelField
=
$
(
'#new_label_name'
)
newColorField
=
$
(
'#new_label_color'
)
showNo
=
$
(
dropdown
).
data
(
'show-no'
)
showAny
=
$
(
dropdown
).
data
(
'show-any'
)
if
newLabelField
.
length
$
(
'.suggest-colors-dropdown a'
).
on
"click"
,
(
e
)
->
e
.
preventDefault
()
e
.
stopPropagation
()
newColorField
.
val
$
(
this
).
data
(
"color"
)
$
(
'.js-dropdown-label-color-preview'
)
.
css
'background-color'
,
$
(
this
).
data
(
"color"
)
.
addClass
'is-active'
$
(
'.js-new-label-btn'
).
on
"click"
,
(
e
)
->
e
.
preventDefault
()
e
.
stopPropagation
()
if
newLabelField
.
val
()
isnt
""
&&
newColorField
.
val
()
isnt
""
$
(
'.js-new-label-btn'
).
disable
()
# Create new label with API
Api
.
newLabel
projectId
,
{
name
:
newLabelField
.
val
()
color
:
newColorField
.
val
()
},
(
label
)
->
$
(
'.js-new-label-btn'
).
enable
()
$
(
'.dropdown-menu-back'
,
$
(
dropdown
).
parent
()).
trigger
"click"
$
(
dropdown
).
glDropdown
(
data
:
(
term
,
callback
)
->
# We have to fetch the JS version of the labels list because there is no
# public facing JSON url for labels
$
.
ajax
(
url
:
labelUrl
).
done
(
data
)
->
html
=
$
(
data
)
data
=
[]
html
.
find
(
'.label-row a'
).
each
->
data
.
push
(
title
:
$
(
@
).
text
().
trim
()
)
if
showNo
data
.
unshift
(
id
:
"0"
title
:
'No label'
)
if
showAny
data
.
unshift
(
title
:
'Any label'
)
if
data
.
length
>
2
data
.
splice
2
,
0
,
"divider"
callback
data
renderRow
:
(
label
)
->
if
$
.
isArray
(
selectedLabel
)
selected
=
""
$
.
each
selectedLabel
,
(
i
,
selectedLbl
)
->
selectedLbl
=
selectedLbl
.
trim
()
if
selected
is
""
&&
label
.
title
is
selectedLbl
selected
=
"is-active"
else
selected
=
if
label
.
title
is
selectedLabel
then
"is-active"
else
""
"<li>
<a href='#' class='
#{
selected
}
'>
#{
label
.
title
}
</a>
</li>"
filterable
:
true
search
:
fields
:
[
'title'
]
selectable
:
true
fieldName
:
$
(
dropdown
).
data
(
'field-name'
)
id
:
(
label
)
->
label
.
title
clicked
:
->
if
$
(
dropdown
).
hasClass
"js-filter-submit"
$
(
dropdown
).
parents
(
'form'
).
submit
()
)
app/assets/javascripts/milestone_select.js.coffee
0 → 100644
View file @
6bfff2fa
class
@
MilestoneSelect
constructor
:
->
$
(
'.js-milestone-select'
).
each
(
i
,
dropdown
)
->
projectId
=
$
(
dropdown
).
data
(
'project-id'
)
milestonesUrl
=
$
(
dropdown
).
data
(
'milestones'
)
selectedMilestone
=
$
(
dropdown
).
data
(
'selected'
)
showNo
=
$
(
dropdown
).
data
(
'show-no'
)
showAny
=
$
(
dropdown
).
data
(
'show-any'
)
useId
=
$
(
dropdown
).
data
(
'use-id'
)
$
(
dropdown
).
glDropdown
(
data
:
(
term
,
callback
)
->
$
.
ajax
(
url
:
milestonesUrl
).
done
(
data
)
->
html
=
$
(
data
)
data
=
[]
html
.
find
(
'.milestone strong a'
).
each
->
link
=
$
(
@
).
attr
(
"href"
).
split
(
"/"
)
data
.
push
(
id
:
link
[
link
.
length
-
1
]
title
:
$
(
@
).
text
().
trim
()
)
if
showNo
data
.
unshift
(
id
:
"0"
title
:
'No Milestone'
)
if
showAny
data
.
unshift
(
title
:
'Any Milestone'
)
if
data
.
length
>
2
data
.
splice
2
,
0
,
"divider"
callback
(
data
)
filterable
:
true
search
:
fields
:
[
'title'
]
selectable
:
true
fieldName
:
$
(
dropdown
).
data
(
'field-name'
)
text
:
(
milestone
)
->
milestone
.
title
id
:
(
milestone
)
->
if
!
useId
if
milestone
.
title
isnt
"Any milestone"
milestone
.
title
else
""
else
milestone
.
id
isSelected
:
(
milestone
)
->
milestone
.
title
is
selectedMilestone
clicked
:
->
if
$
(
dropdown
).
hasClass
"js-filter-submit"
$
(
dropdown
).
parents
(
'form'
).
submit
()
)
app/assets/javascripts/users_select.js.coffee
View file @
6bfff2fa
...
...
@@ -3,6 +3,81 @@ class @UsersSelect
@
usersPath
=
"/autocomplete/users.json"
@
userPath
=
"/autocomplete/users/:id.json"
$
(
'.js-user-search'
).
each
(
i
,
dropdown
)
=>
@
projectId
=
$
(
dropdown
).
data
(
'project-id'
)
@
showCurrentUser
=
$
(
dropdown
).
data
(
'current-user'
)
showNullUser
=
$
(
dropdown
).
data
(
'null-user'
)
showAnyUser
=
$
(
dropdown
).
data
(
'any-user'
)
firstUser
=
$
(
dropdown
).
data
(
'first-user'
)
selectedId
=
$
(
dropdown
).
data
(
'selected'
)
$
(
dropdown
).
glDropdown
(
data
:
(
term
,
callback
)
=>
@
users
term
,
(
users
)
=>
if
term
.
length
is
0
showDivider
=
0
if
firstUser
# Move current user to the front of the list
for
obj
,
index
in
users
if
obj
.
username
==
firstUser
users
.
splice
(
index
,
1
)
users
.
unshift
(
obj
)
break
if
showNullUser
showDivider
+=
1
users
.
unshift
(
name
:
'Unassigned'
,
id
:
0
)
if
showAnyUser
showDivider
+=
1
name
=
showAnyUser
name
=
'Any User'
if
name
==
true
anyUser
=
{
name
:
name
,
id
:
null
}
users
.
unshift
(
anyUser
)
if
showDivider
users
.
splice
(
showDivider
,
0
,
"divider"
)
# Send the data back
callback
users
filterable
:
true
filterRemote
:
true
search
:
fields
:
[
'name'
,
'username'
]
selectable
:
true
fieldName
:
$
(
dropdown
).
data
(
'field-name'
)
clicked
:
->
if
$
(
dropdown
).
hasClass
"js-filter-submit"
$
(
dropdown
).
parents
(
'form'
).
submit
()
renderRow
:
(
user
)
->
username
=
if
user
.
username
then
"@
#{
user
.
username
}
"
else
""
avatar
=
if
user
.
avatar_url
then
user
.
avatar_url
else
false
selected
=
if
user
.
id
is
selectedId
then
"is-active"
else
""
img
=
""
if
avatar
img
=
"<img src='
#{
avatar
}
' class='avatar avatar-inline' width='30' />"
"<li>
<a href='#' class='dropdown-menu-user-link
#{
selected
}
'>
#{
img
}
<strong class='dropdown-menu-user-full-name'>
#{
user
.
name
}
</strong>
<span class='dropdown-menu-user-username'>
#{
username
}
</span>
</a>
</li>"
)
$
(
'.ajax-users-select'
).
each
(
i
,
select
)
=>
@
projectId
=
$
(
select
).
data
(
'project-id'
)
@
groupId
=
$
(
select
).
data
(
'group-id'
)
...
...
app/assets/stylesheets/framework/blocks.scss
View file @
6bfff2fa
...
...
@@ -28,6 +28,10 @@
border-bottom
:
1px
solid
$border-color
;
color
:
$gl-gray
;
a
{
color
:
$md-link-color
;
}
&
.oneline-block
{
line-height
:
42px
;
}
...
...
app/assets/stylesheets/framework/dropdowns.scss
View file @
6bfff2fa
...
...
@@ -29,8 +29,8 @@
.dropdown-menu-toggle
{
position
:
relative
;
min-
width
:
160px
;
padding
:
5px
20px
5
px
10px
;
width
:
160px
;
padding
:
6px
20px
6
px
10px
;
background-color
:
$dropdown-toggle-bg
;
color
:
$dropdown-toggle-color
;
font-size
:
15px
;
...
...
@@ -65,7 +65,7 @@
position
:
absolute
;
top
:
100%
;
left
:
0
;
z-index
:
9
999
;
z-index
:
9
;
width
:
240px
;
margin-top
:
2px
;
margin-bottom
:
0
;
...
...
@@ -117,15 +117,19 @@
white-space
:
nowrap
;
overflow
:
hidden
;
&
:hover
{
&
:hover
,
&
:focus
,
&
.is-focused
{
background-color
:
$dropdown-link-hover-bg
;
text-decoration
:
none
;
outline
:
0
;
}
}
}
.dropdown-menu-paging
{
.dropdown-page-two
{
.dropdown-page-two
,
.dropdown-menu-back
{
display
:
none
;
}
...
...
@@ -134,7 +138,8 @@
display
:
none
;
}
.dropdown-page-two
{
.dropdown-page-two
,
.dropdown-menu-back
{
display
:
block
;
}
}
...
...
@@ -157,6 +162,7 @@
.dropdown-menu-user-full-name
{
display
:
block
;
margin-bottom
:
2px
;
font-weight
:
600
;
line-height
:
1
;
text-overflow
:
ellipsis
;
overflow
:
hidden
;
...
...
@@ -232,6 +238,7 @@
font-size
:
14px
;
border
:
0
;
background
:
none
;
outline
:
0
;
&
:hover
{
color
:
darken
(
$dropdown-title-btn-color
,
15%
);
...
...
@@ -298,6 +305,14 @@
border-top
:
1px
solid
$dropdown-divider-color
;
}
.dropdown-footer-list
{
font-size
:
14px
;
a
{
padding-left
:
10px
;
}
}
.dropdown-loading
{
position
:
absolute
;
top
:
0
;
...
...
@@ -317,3 +332,12 @@
margin-left
:
-14px
;
}
}
.dropdown-menu-labels
{
.label
{
position
:
relative
;
width
:
30px
;
margin-right
:
5px
;
text-indent
:
-99999px
;
}
}
app/assets/stylesheets/framework/filters.scss
View file @
6bfff2fa
.filter-item
{
margin-right
:
6px
;
vertical-align
:
top
;
}
@media
(
min-width
:
800px
)
{
...
...
app/assets/stylesheets/pages/labels.scss
View file @
6bfff2fa
...
...
@@ -7,6 +7,28 @@
display
:
inline-block
;
margin-right
:
10px
;
}
&
.suggest-colors-dropdown
{
margin-bottom
:
5px
;
a
{
@include
border-radius
(
0
);
width
:
36
.7px
;
margin-right
:
0
;
margin-bottom
:
-5px
;
}
}
}
.dropdown-label-color-preview
{
display
:
none
;
margin-top
:
5px
;
width
:
100%
;
height
:
25px
;
&
.is-active
{
display
:
block
;
}
}
.label-row
{
...
...
app/controllers/dashboard/projects_controller.rb
View file @
6bfff2fa
...
...
@@ -8,7 +8,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects
=
filter_projects
(
@projects
)
@projects
=
@projects
.
includes
(
:namespace
)
@projects
=
@projects
.
sort
(
@sort
=
params
[
:sort
])
@projects
=
@projects
.
page
(
params
[
:page
]).
per
(
PER_PAGE
)
if
params
[
:filter_projects
].
blank?
@projects
=
@projects
.
page
(
params
[
:page
]).
per
(
PER_PAGE
)
@last_push
=
current_user
.
recent_push
...
...
@@ -32,7 +32,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects
=
filter_projects
(
@projects
)
@projects
=
@projects
.
includes
(
:namespace
,
:forked_from_project
,
:tags
)
@projects
=
@projects
.
sort
(
@sort
=
params
[
:sort
])
@projects
=
@projects
.
page
(
params
[
:page
]).
per
(
PER_PAGE
)
if
params
[
:filter_projects
].
blank?
@projects
=
@projects
.
page
(
params
[
:page
]).
per
(
PER_PAGE
)
@last_push
=
current_user
.
recent_push
@groups
=
[]
...
...
app/controllers/explore/projects_controller.rb
View file @
6bfff2fa
...
...
@@ -8,7 +8,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects
=
@projects
.
where
(
visibility_level:
params
[
:visibility_level
])
if
params
[
:visibility_level
].
present?
@projects
=
filter_projects
(
@projects
)
@projects
=
@projects
.
sort
(
@sort
=
params
[
:sort
])
@projects
=
@projects
.
includes
(
:namespace
).
page
(
params
[
:page
]).
per
(
PER_PAGE
)
if
params
[
:filter_projects
].
blank?
@projects
=
@projects
.
includes
(
:namespace
).
page
(
params
[
:page
]).
per
(
PER_PAGE
)
respond_to
do
|
format
|
format
.
html
...
...
@@ -23,7 +23,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def
trending
@projects
=
TrendingProjectsFinder
.
new
.
execute
(
current_user
)
@projects
=
filter_projects
(
@projects
)
@projects
=
@projects
.
page
(
params
[
:page
]).
per
(
PER_PAGE
)
if
params
[
:filter_projects
].
blank?
@projects
=
@projects
.
page
(
params
[
:page
]).
per
(
PER_PAGE
)
respond_to
do
|
format
|
format
.
html
...
...
@@ -39,7 +39,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects
=
ProjectsFinder
.
new
.
execute
(
current_user
)
@projects
=
filter_projects
(
@projects
)
@projects
=
@projects
.
reorder
(
'star_count DESC'
)
@projects
=
@projects
.
page
(
params
[
:page
]).
per
(
PER_PAGE
)
if
params
[
:filter_projects
].
blank?
@projects
=
@projects
.
page
(
params
[
:page
]).
per
(
PER_PAGE
)
respond_to
do
|
format
|
format
.
html
...
...
app/helpers/dropdowns_helper.rb
0 → 100644
View file @
6bfff2fa
module
DropdownsHelper
def
dropdown_tag
(
toggle_text
,
options:
{},
&
block
)
content_tag
:div
,
class:
"dropdown"
do
data_attr
=
{
toggle:
"dropdown"
}
if
options
.
has_key?
(
:data
)
data_attr
=
options
[
:data
].
merge
(
data_attr
)
end
dropdown_output
=
dropdown_toggle
(
toggle_text
,
data_attr
,
options
)
dropdown_output
<<
content_tag
(
:div
,
class:
"dropdown-menu dropdown-select
#{
options
[
:dropdown_class
]
if
options
.
has_key?
(
:dropdown_class
)
}
"
)
do
output
=
""
if
options
.
has_key?
(
:title
)
output
<<
dropdown_title
(
options
[
:title
])
end
if
options
.
has_key?
(
:filter
)
output
<<
dropdown_filter
(
options
[
:placeholder
])
end
output
<<
content_tag
(
:div
,
class:
"dropdown-content"
)
do
capture
(
&
block
)
if
block
&&
!
options
.
has_key?
(
:footer_content
)
end
if
block
&&
options
.
has_key?
(
:footer_content
)
output
<<
content_tag
(
:div
,
class:
"dropdown-footer"
)
do
capture
(
&
block
)
end
end
output
<<
dropdown_loading
output
.
html_safe
end
dropdown_output
.
html_safe
end
end
def
dropdown_toggle
(
toggle_text
,
data_attr
,
options
)
content_tag
(
:button
,
class:
"dropdown-menu-toggle
#{
options
[
:toggle_class
]
if
options
.
has_key?
(
:toggle_class
)
}
"
,
id:
(
options
[
:id
]
if
options
.
has_key?
(
:id
)),
type:
"button"
,
data:
data_attr
)
do
output
=
content_tag
(
:span
,
toggle_text
,
class:
"dropdown-toggle-text"
)
output
<<
icon
(
'chevron-down'
)
output
.
html_safe
end
end
def
dropdown_title
(
title
,
back:
false
)
content_tag
:div
,
class:
"dropdown-title"
do
title_output
=
""
if
back
title_output
<<
content_tag
(
:button
,
class:
"dropdown-title-button dropdown-menu-back"
,
aria:
{
label:
"Go back"
},
type:
"button"
)
do
icon
(
'arrow-left'
)
end
end
title_output
<<
content_tag
(
:span
,
title
)
title_output
<<
content_tag
(
:button
,
class:
"dropdown-title-button dropdown-menu-close"
,
aria:
{
label:
"Close"
},
type:
"button"
)
do
icon
(
'times'
)
end
title_output
.
html_safe
end
end
def
dropdown_filter
(
placeholder
)
content_tag
:div
,
class:
"dropdown-input"
do
filter_output
=
search_field_tag
nil
,
nil
,
class:
"dropdown-input-field"
,
placeholder:
placeholder
filter_output
<<
icon
(
'search'
)
filter_output
.
html_safe
end
end
def
dropdown_content
(
&
block
)
content_tag
(
:div
,
class:
"dropdown-content"
)
do
if
block
capture
(
&
block
)
end
end
end
def
dropdown_footer
(
&
block
)
content_tag
(
:div
,
class:
"dropdown-footer"
)
do
if
block
capture
(
&
block
)
end
end
end
def
dropdown_loading
content_tag
:div
,
class:
"dropdown-loading"
do
icon
(
'spinner spin'
)
end
end
end
app/views/help/ui.html.haml
View file @
6bfff2fa
...
...
@@ -390,6 +390,51 @@
%button
.btn.btn-primary
Create
.example
%div
.dropdown.inline
%button
#js-project-dropdown
.dropdown-menu-toggle
{
type:
'button'
,
data:
{
toggle:
'dropdown'
}}
Projects
=
icon
(
'chevron-down'
)
.dropdown-menu.dropdown-select.dropdown-menu-selectable
.dropdown-title
%span
Go to project
%button
.dropdown-title-button.dropdown-menu-close
{
aria:
{
label:
"Close"
}}
=
icon
(
'times'
)
.dropdown-input
%input
.dropdown-input-field
{
type:
"search"
,
placeholder:
"Filter results"
}
=
icon
(
'search'
)
.dropdown-content
.dropdown-loading
=
icon
(
'spinner spin'
)
:javascript
$
(
'
#js-project-dropdown
'
).
glDropdown
({
data
:
function
(
term
,
callback
)
{
Api
.
projects
(
term
,
"
last_activity_at
"
,
function
(
data
)
{
callback
(
data
);
});
},
text
:
function
(
project
)
{
return
project
.
name_with_namespace
||
project
.
name
;
},
selectable
:
true
,
fieldName
:
"
author_id
"
,
filterable
:
true
,
search
:
{
fields
:
[
'
name_with_namespace
'
]
},
id
:
function
(
data
)
{
return
data
.
id
;
},
isSelected
:
function
(
data
)
{
return
data
.
id
===
2
;
}
})
.example
%div
=
dropdown_tag
(
"Projects"
,
options:
{
title:
"Go to project"
,
filter:
true
,
placeholder:
"Filter projects"
})
%h2
#panels
Panels
.row
...
...
app/views/shared/issuable/_filter.html.haml
View file @
6bfff2fa
...
...
@@ -7,22 +7,77 @@
class:
"check_all_issues left"
.issues-other-filters
.filter-item.inline
=
users_select_tag
(
:author_id
,
selected:
params
[
:author_id
],
placeholder:
'Author'
,
class:
'trigger-submit'
,
any_user:
"Any Author"
,
first_user:
true
,
current_user:
true
)
-
if
params
[
:author_id
]
=
hidden_field_tag
(
:author_id
,
params
[
:author_id
])
=
dropdown_tag
(
"Author"
,
options:
{
toggle_class:
"js-user-search js-filter-submit js-author-search"
,
title:
"Filter by author"
,
filter:
true
,
dropdown_class:
"dropdown-menu-user dropdown-menu-selectable dropdown-menu-author"
,
placeholder:
"Search authors"
,
data:
{
any_user:
"Any Author"
,
first_user:
(
current_user
.
username
if
current_user
),
current_user:
true
,
project_id:
(
@project
.
id
if
@project
),
selected:
params
[
:author_id
],
field_name:
"author_id"
}
})
.filter-item.inline
=
users_select_tag
(
:assignee_id
,
selected:
params
[
:assignee_id
],
placeholder:
'Assignee'
,
class:
'trigger-submit'
,
any_user:
"Any Assignee"
,
null_user:
true
,
first_user:
true
,
current_user:
true
)
-
if
params
[
:assignee_id
]
=
hidden_field_tag
(
:assignee_id
,
params
[
:assignee_id
])
=
dropdown_tag
(
"Assignee"
,
options:
{
toggle_class:
"js-user-search js-filter-submit"
,
title:
"Filter by assignee"
,
filter:
true
,
dropdown_class:
"dropdown-menu-user dropdown-menu-selectable"
,
placeholder:
"Search assignee"
,
data:
{
any_user:
"Any Author"
,
first_user:
(
current_user
.
username
if
current_user
),
null_user:
true
,
current_user:
true
,
project_id:
(
@project
.
id
if
@project
),
selected:
params
[
:assignee_id
],
field_name:
"assignee_id"
}
})
.filter-item.inline.milestone-filter
=
select_tag
(
'milestone_title'
,
projects_milestones_options
,
class:
'select2 trigger-submit'
,
include_blank:
true
,
data:
{
placeholder:
'Milestone'
})
-
if
params
[
:milestone_title
]
=
hidden_field_tag
(
:milestone_title
,
params
[
:milestone_title
])
=
dropdown_tag
(
"Milestone"
,
options:
{
title:
"Filter by milestone"
,
toggle_class:
'js-milestone-select js-filter-submit'
,
filter:
true
,
dropdown_class:
"dropdown-menu-selectable"
,
placeholder:
"Search milestones"
,
footer_content:
true
,
data:
{
show_no:
true
,
show_any:
true
,
field_name:
"milestone_title"
,
selected:
params
[
:milestone_title
],
project_id:
(
@project
.
id
if
@project
),
milestones:
(
namespace_project_milestones_path
(
@project
.
namespace
,
@project
,
:js
)
if
@project
)
}
})
do
-
if
@project
%ul
.dropdown-footer-list
-
if
can?
current_user
,
:admin_milestone
,
@project
%li
=
link_to
new_namespace_project_milestone_path
(
@project
.
namespace
,
@project
),
title:
"New Milestone"
do
Create new
%li
=
link_to
namespace_project_milestones_path
(
@project
.
namespace
,
@project
)
do
-
if
can?
current_user
,
:admin_milestone
,
@project
Manage milestones
-
else
View milestones
.filter-item.inline.labels-filter
=
select_tag
(
'label_name'
,
projects_labels_options
,
class:
'select2 trigger-submit'
,
include_blank:
true
,
data:
{
placeholder:
'Label'
})
-
if
params
[
:label_name
]
=
hidden_field_tag
(
:label_name
,
params
[
:label_name
])
.dropdown
%button
.dropdown-menu-toggle.js-label-select.js-filter-submit
{
type:
"button"
,
data:
{
toggle:
"dropdown"
,
field_name:
"label_name"
,
show_no:
"true"
,
show_any:
"true"
,
selected:
params
[
:label_name
],
project_id:
(
@project
.
id
if
@project
),
labels:
(
namespace_project_labels_path
(
@project
.
namespace
,
@project
,
:js
)
if
@project
)}}
%span
.dropdown-toggle-text
Label
=
icon
(
'chevron-down'
)
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
.dropdown-page-one
=
dropdown_title
(
"Filter by label"
)
=
dropdown_filter
(
"Search labels"
)
=
dropdown_content
-
if
@project
=
dropdown_footer
do
%ul
.dropdown-footer-list
-
if
can?
current_user
,
:admin_label
,
@project
%li
%a
.dropdown-toggle-page
{
href:
"#"
}
Create new
%li
=
link_to
namespace_project_labels_path
(
@project
.
namespace
,
@project
)
do
-
if
can?
current_user
,
:admin_label
,
@project
Manage labels
-
else
View labels
-
if
can?
current_user
,
:admin_label
,
@project
.dropdown-page-two
=
dropdown_title
(
"Create new label"
,
back:
true
)
=
dropdown_content
do
%input
#new_label_color
{
type:
"hidden"
}
%input
#new_label_name
.dropdown-input-field
{
type:
"text"
,
placeholder:
"Name new label"
}
.dropdown-label-color-preview.js-dropdown-label-color-preview
.suggest-colors.suggest-colors-dropdown
-
suggested_colors
.
each
do
|
color
|
=
link_to
'#'
,
style:
"background-color:
#{
color
}
"
,
data:
{
color:
color
}
do
&
nbsp
%button
.btn.btn-primary.js-new-label-btn
{
type:
"button"
}
Create
=
dropdown_loading
.dropdown-loading
=
icon
(
'spinner spin'
)
.pull-right
=
render
'shared/sort_dropdown'
...
...
@@ -31,11 +86,18 @@
.issues_bulk_update.hide
=
form_tag
bulk_update_namespace_project_issues_path
(
@project
.
namespace
,
@project
),
method: :post
do
.filter-item.inline
=
select_tag
(
'update[state_event]'
,
options_for_select
([[
'Open'
,
'reopen'
],
[
'Closed'
,
'close'
]]),
include_blank:
true
,
data:
{
placeholder:
"Status"
})
=
dropdown_tag
(
"Status"
,
options:
{
toggle_class:
"js-issue-status"
,
title:
"Change status"
,
dropdown_class:
"dropdown-menu-selectable"
,
data:
{
field_name:
"update[state_event]"
}
}
)
do
%ul
%li
%a
{
href:
"#"
,
data:
{
id:
"reopen"
}}
Open
%li
%a
{
href:
"#"
,
data:
{
id:
"close"
}}
Closed
.filter-item.inline
=
users_select_tag
(
'update[assignee_id]'
,
placeholder:
'Assignee'
,
null_user:
true
,
first_user:
true
,
current_user:
true
)
=
dropdown_tag
(
"Assignee"
,
options:
{
toggle_class:
"js-user-search"
,
title:
"Assign to"
,
filter:
true
,
dropdown_class:
"dropdown-menu-user dropdown-menu-selectable"
,
placeholder:
"Search authors"
,
data:
{
first_user:
(
current_user
.
username
if
current_user
),
current_user:
true
,
project_id:
@project
.
id
,
field_name:
"update[assignee_id]"
}
})
.filter-item.inline
=
select_tag
(
'update[milestone_id]'
,
bulk_update_milestone_options
,
include_blank:
true
,
data:
{
placeholder:
"Milestone"
})
=
dropdown_tag
(
"Milestone"
,
options:
{
title:
"Assign milestone"
,
toggle_class:
'js-milestone-select'
,
filter:
true
,
dropdown_class:
"dropdown-menu-selectable"
,
placeholder:
"Search milestones"
,
data:
{
show_no:
true
,
field_name:
"update[milestone_id]"
,
project_id:
@project
.
id
,
milestones:
namespace_project_milestones_path
(
@project
.
namespace
,
@project
,
:js
),
use_id:
true
}
})
=
hidden_field_tag
'update[issues_ids]'
,
[]
=
hidden_field_tag
:state_event
,
params
[
:state_event
]
.filter-item.inline
...
...
@@ -47,6 +109,9 @@
:javascript
new
UsersSelect
();
new
LabelsSelect
();
new
MilestoneSelect
();
new
IssueStatusSelect
();
$
(
'
form.filter-form
'
).
on
(
'
submit
'
,
function
(
event
)
{
event
.
preventDefault
();
Turbolinks
.
visit
(
this
.
action
+
'
&
'
+
$
(
this
).
serialize
());
...
...
features/steps/dashboard/issues.rb
View file @
6bfff2fa
...
...
@@ -36,13 +36,22 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
end
step
'I click "Authored by me" link'
do
select2
(
current_user
.
id
,
from:
"#author_id"
)
select2
(
nil
,
from:
"#assignee_id"
)
execute_script
(
'$("#assignee_id").val("")'
)
execute_script
(
'$(".js-user-search").first().click()'
)
sleep
1
execute_script
(
"$('.dropdown-content li:contains(
\"
#{
current_user
.
to_reference
}
\"
) a').click()"
)
sleep
1
end
step
'I click "All" link'
do
select2
(
nil
,
from:
"#author_id"
)
select2
(
nil
,
from:
"#assignee_id"
)
execute_script
(
'$(".js-user-search").first().click()'
)
sleep
1
execute_script
(
'$(".js-user-search").first().parent().find("li a").first().click()'
)
sleep
1
execute_script
(
'$(".js-user-search").eq(1).click()'
)
sleep
1
execute_script
(
'$(".js-user-search").eq(1).parent().find("li a").first().click()'
)
sleep
1
end
def
should_see
(
issue
)
...
...
features/steps/dashboard/merge_requests.rb
View file @
6bfff2fa
...
...
@@ -40,13 +40,22 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
end
step
'I click "Authored by me" link'
do
select2
(
current_user
.
id
,
from:
"#author_id"
)
select2
(
nil
,
from:
"#assignee_id"
)
execute_script
(
'$("#assignee_id").val("")'
)
execute_script
(
'$(".js-user-search").first().click()'
)
sleep
0.5
execute_script
(
"$('.dropdown-content li:contains(
\"
#{
current_user
.
to_reference
}
\"
) a').click()"
)
sleep
2
end
step
'I click "All" link'
do
select2
(
nil
,
from:
"#author_id"
)
select2
(
nil
,
from:
"#assignee_id"
)
execute_script
(
'$(".js-user-search").first().click()'
)
sleep
0.5
execute_script
(
'$(".js-user-search").first().parent().find("li a").first().click()'
)
sleep
2
execute_script
(
'$(".js-user-search").eq(1).click()'
)
sleep
0.5
execute_script
(
'$(".js-user-search").eq(1).parent().find("li a").first().click()'
)
sleep
2
end
def
should_see
(
merge_request
)
...
...
features/steps/project/issues/filter_labels.rb
View file @
6bfff2fa
...
...
@@ -29,7 +29,10 @@ class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps
end
step
'I click link "bug"'
do
select2
(
'bug'
,
from:
"#label_name"
)
page
.
find
(
'.js-label-select'
).
click
sleep
0.5
execute_script
(
"$('.dropdown-menu-labels li:contains(
\"
bug
\"
) a').click()"
)
sleep
2
end
step
'I click link "feature"'
do
...
...
features/steps/project/issues/issues.rb
View file @
6bfff2fa
...
...
@@ -27,7 +27,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step
'I click link "Closed"'
do
click_link
"Closed"
find
(
'.issues-state-filters a'
,
text:
"Closed"
).
click
end
step
'I click button "Unsubscribe"'
do
...
...
@@ -63,14 +63,15 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step
'I click "author" dropdown'
do
first
(
'#s2id_author_id'
).
click
page
.
find
(
'.js-author-search'
).
click
sleep
1
end
step
'I see current user as the first user'
do
expect
(
page
).
to
have_selector
(
'.
user-result'
,
visible:
true
,
count:
3
)
users
=
page
.
all
(
'.
user-name
'
)
expect
(
page
).
to
have_selector
(
'.
dropdown-content'
,
visible:
true
)
users
=
page
.
all
(
'.
dropdown-menu-author .dropdown-content li a
'
)
expect
(
users
[
0
].
text
).
to
eq
'Any Author'
expect
(
users
[
1
].
text
).
to
eq
current_user
.
name
expect
(
users
[
1
].
text
).
to
eq
"
#{
current_user
.
name
}
#{
current_user
.
to_reference
}
"
end
step
'I submit new issue "500 error on profile"'
do
...
...
spec/features/issues/filter_by_milestone_spec.rb
View file @
6bfff2fa
require
'rails_helper'
feature
'Issue filtering by Milestone'
,
feature:
true
do
include
Select2Helper
let
(
:project
)
{
create
(
:project
,
:public
)
}
let
(
:milestone
)
{
create
(
:milestone
,
project:
project
)
}
...
...
@@ -31,6 +29,9 @@ feature 'Issue filtering by Milestone', feature: true do
end
def
filter_by_milestone
(
title
)
select2
(
title
,
from:
'#milestone_title'
)
find
(
".js-milestone-select"
).
click
sleep
0.5
find
(
".milestone-filter a"
,
text:
title
).
click
sleep
1
end
end
spec/features/merge_requests/filter_by_milestone_spec.rb
View file @
6bfff2fa
require
'rails_helper'
feature
'Merge Request filtering by Milestone'
,
feature:
true
do
include
Select2Helper
let
(
:project
)
{
create
(
:project
,
:public
)
}
let
(
:milestone
)
{
create
(
:milestone
,
project:
project
)
}
...
...
@@ -31,6 +29,9 @@ feature 'Merge Request filtering by Milestone', feature: true do
end
def
filter_by_milestone
(
title
)
select2
(
title
,
from:
'#milestone_title'
)
find
(
".js-milestone-select"
).
click
sleep
0.5
find
(
".milestone-filter a"
,
text:
title
).
click
sleep
1
end
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