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
f328f38e
Commit
f328f38e
authored
May 03, 2017
by
Sam Rose
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Disallow multiselect for Milestone dropdown
parent
f59a44db
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
97 additions
and
13 deletions
+97
-13
app/assets/javascripts/boards/components/board_sidebar.js
app/assets/javascripts/boards/components/board_sidebar.js
+3
-0
app/assets/javascripts/milestone_select.js
app/assets/javascripts/milestone_select.js
+27
-10
app/views/projects/boards/components/sidebar/_milestone.html.haml
...s/projects/boards/components/sidebar/_milestone.html.haml
+2
-1
app/views/shared/issuable/_milestone_dropdown.html.haml
app/views/shared/issuable/_milestone_dropdown.html.haml
+1
-1
app/views/shared/issuable/_sidebar.html.haml
app/views/shared/issuable/_sidebar.html.haml
+1
-1
changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml
...sidebar-milestone-dropdown-should-not-be-multi-select.yml
+4
-0
spec/features/dashboard/milestone_filter_spec.rb
spec/features/dashboard/milestone_filter_spec.rb
+59
-0
No files found.
app/assets/javascripts/boards/components/board_sidebar.js
View file @
f328f38e
...
@@ -36,6 +36,9 @@ gl.issueBoards.BoardSidebar = Vue.extend({
...
@@ -36,6 +36,9 @@ gl.issueBoards.BoardSidebar = Vue.extend({
},
},
assigneeId
()
{
assigneeId
()
{
return
this
.
issue
.
assignee
?
this
.
issue
.
assignee
.
id
:
0
;
return
this
.
issue
.
assignee
?
this
.
issue
.
assignee
.
id
:
0
;
},
milestoneTitle
()
{
return
this
.
issue
.
milestone
?
this
.
issue
.
milestone
.
title
:
'
No Milestone
'
;
}
}
},
},
watch
:
{
watch
:
{
...
...
app/assets/javascripts/milestone_select.js
View file @
f328f38e
...
@@ -18,12 +18,11 @@
...
@@ -18,12 +18,11 @@
}
}
$els
.
each
(
function
(
i
,
dropdown
)
{
$els
.
each
(
function
(
i
,
dropdown
)
{
var
$block
,
$dropdown
,
$loading
,
$selectbox
,
$sidebarCollapsedValue
,
$value
,
abilityName
,
collapsedSidebarLabelTemplate
,
defaultLabel
,
issuableId
,
issueUpdateURL
,
milestoneLinkNoneTemplate
,
milestoneLinkTemplate
,
milestonesUrl
,
projectId
,
selectedMilestone
,
showAny
,
showNo
,
showUpcoming
,
showStarted
,
useId
,
showMenuAbove
;
var
$block
,
$dropdown
,
$loading
,
$selectbox
,
$sidebarCollapsedValue
,
$value
,
abilityName
,
collapsedSidebarLabelTemplate
,
defaultLabel
,
defaultNo
,
issuableId
,
issueUpdateURL
,
milestoneLinkNoneTemplate
,
milestoneLinkTemplate
,
milestonesUrl
,
projectId
,
selectedMilestone
,
selectedMilestoneDefault
,
showAny
,
showNo
,
showUpcoming
,
showStarted
,
useId
,
showMenuAbove
;
$dropdown
=
$
(
dropdown
);
$dropdown
=
$
(
dropdown
);
projectId
=
$dropdown
.
data
(
'
project-id
'
);
projectId
=
$dropdown
.
data
(
'
project-id
'
);
milestonesUrl
=
$dropdown
.
data
(
'
milestones
'
);
milestonesUrl
=
$dropdown
.
data
(
'
milestones
'
);
issueUpdateURL
=
$dropdown
.
data
(
'
issueUpdate
'
);
issueUpdateURL
=
$dropdown
.
data
(
'
issueUpdate
'
);
selectedMilestone
=
$dropdown
.
data
(
'
selected
'
);
showNo
=
$dropdown
.
data
(
'
show-no
'
);
showNo
=
$dropdown
.
data
(
'
show-no
'
);
showAny
=
$dropdown
.
data
(
'
show-any
'
);
showAny
=
$dropdown
.
data
(
'
show-any
'
);
showMenuAbove
=
$dropdown
.
data
(
'
showMenuAbove
'
);
showMenuAbove
=
$dropdown
.
data
(
'
showMenuAbove
'
);
...
@@ -31,6 +30,7 @@
...
@@ -31,6 +30,7 @@
showStarted
=
$dropdown
.
data
(
'
show-started
'
);
showStarted
=
$dropdown
.
data
(
'
show-started
'
);
useId
=
$dropdown
.
data
(
'
use-id
'
);
useId
=
$dropdown
.
data
(
'
use-id
'
);
defaultLabel
=
$dropdown
.
data
(
'
default-label
'
);
defaultLabel
=
$dropdown
.
data
(
'
default-label
'
);
defaultNo
=
$dropdown
.
data
(
'
default-no
'
);
issuableId
=
$dropdown
.
data
(
'
issuable-id
'
);
issuableId
=
$dropdown
.
data
(
'
issuable-id
'
);
abilityName
=
$dropdown
.
data
(
'
ability-name
'
);
abilityName
=
$dropdown
.
data
(
'
ability-name
'
);
$selectbox
=
$dropdown
.
closest
(
'
.selectbox
'
);
$selectbox
=
$dropdown
.
closest
(
'
.selectbox
'
);
...
@@ -38,6 +38,9 @@
...
@@ -38,6 +38,9 @@
$sidebarCollapsedValue
=
$block
.
find
(
'
.sidebar-collapsed-icon
'
);
$sidebarCollapsedValue
=
$block
.
find
(
'
.sidebar-collapsed-icon
'
);
$value
=
$block
.
find
(
'
.value
'
);
$value
=
$block
.
find
(
'
.value
'
);
$loading
=
$block
.
find
(
'
.block-loading
'
).
fadeOut
();
$loading
=
$block
.
find
(
'
.block-loading
'
).
fadeOut
();
selectedMilestoneDefault
=
(
showAny
?
''
:
null
);
selectedMilestoneDefault
=
(
showNo
&&
defaultNo
?
'
No Milestone
'
:
selectedMilestoneDefault
);
selectedMilestone
=
$dropdown
.
data
(
'
selected
'
)
||
selectedMilestoneDefault
;
if
(
issueUpdateURL
)
{
if
(
issueUpdateURL
)
{
milestoneLinkTemplate
=
_
.
template
(
'
<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>
'
);
milestoneLinkTemplate
=
_
.
template
(
'
<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>
'
);
milestoneLinkNoneTemplate
=
'
<span class="no-value">None</span>
'
;
milestoneLinkNoneTemplate
=
'
<span class="no-value">None</span>
'
;
...
@@ -86,8 +89,18 @@
...
@@ -86,8 +89,18 @@
if
(
showMenuAbove
)
{
if
(
showMenuAbove
)
{
$dropdown
.
data
(
'
glDropdown
'
).
positionMenuAbove
();
$dropdown
.
data
(
'
glDropdown
'
).
positionMenuAbove
();
}
}
$
(
`[data-milestone-id="
${
selectedMilestone
}
"] > a`
).
addClass
(
'
is-active
'
);
});
});
},
},
renderRow
:
function
(
milestone
)
{
return
`
<li data-milestone-id="
${
milestone
.
name
}
">
<a href='#' class='dropdown-menu-milestone-link'>
${
_
.
escape
(
milestone
.
title
)}
</a>
</li>
`
;
},
filterable
:
true
,
filterable
:
true
,
search
:
{
search
:
{
fields
:
[
'
title
'
]
fields
:
[
'
title
'
]
...
@@ -120,15 +133,24 @@
...
@@ -120,15 +133,24 @@
// display:block overrides the hide-collapse rule
// display:block overrides the hide-collapse rule
return
$value
.
css
(
'
display
'
,
''
);
return
$value
.
css
(
'
display
'
,
''
);
},
},
opened
:
function
(
e
)
{
const
$el
=
$
(
e
.
currentTarget
);
if
(
$dropdown
.
hasClass
(
'
js-issue-board-sidebar
'
))
{
selectedMilestone
=
$dropdown
[
0
].
dataset
.
selected
||
selectedMilestoneDefault
;
}
$
(
'
a.is-active
'
,
$el
).
removeClass
(
'
is-active
'
);
$
(
`[data-milestone-id="
${
selectedMilestone
}
"] > a`
,
$el
).
addClass
(
'
is-active
'
);
},
vue
:
$dropdown
.
hasClass
(
'
js-issue-board-sidebar
'
),
vue
:
$dropdown
.
hasClass
(
'
js-issue-board-sidebar
'
),
clicked
:
function
(
options
)
{
clicked
:
function
(
options
)
{
const
{
$el
,
e
}
=
options
;
const
{
$el
,
e
}
=
options
;
let
selected
=
options
.
selectedObj
;
let
selected
=
options
.
selectedObj
;
var
data
,
isIssueIndex
,
isMRIndex
,
isSelecting
,
page
,
boardsStore
;
var
data
,
isIssueIndex
,
isMRIndex
,
page
,
boardsStore
;
page
=
$
(
'
body
'
).
data
(
'
page
'
);
page
=
$
(
'
body
'
).
data
(
'
page
'
);
isIssueIndex
=
page
===
'
projects:issues:index
'
;
isIssueIndex
=
page
===
'
projects:issues:index
'
;
isMRIndex
=
(
page
===
page
&&
page
===
'
projects:merge_requests:index
'
);
isMRIndex
=
(
page
===
page
&&
page
===
'
projects:merge_requests:index
'
);
isSelecting
=
(
selected
.
name
!==
selectedMilestone
);
selectedMilestone
=
isSelecting
?
selected
.
name
:
selectedMilestoneDefault
;
if
(
$dropdown
.
hasClass
(
'
js-filter-bulk-update
'
)
||
$dropdown
.
hasClass
(
'
js-issuable-form-dropdown
'
))
{
if
(
$dropdown
.
hasClass
(
'
js-filter-bulk-update
'
)
||
$dropdown
.
hasClass
(
'
js-issuable-form-dropdown
'
))
{
e
.
preventDefault
();
e
.
preventDefault
();
return
;
return
;
...
@@ -142,16 +164,11 @@
...
@@ -142,16 +164,11 @@
boardsStore
[
$dropdown
.
data
(
'
field-name
'
)]
=
selected
.
name
;
boardsStore
[
$dropdown
.
data
(
'
field-name
'
)]
=
selected
.
name
;
e
.
preventDefault
();
e
.
preventDefault
();
}
else
if
(
$dropdown
.
hasClass
(
'
js-filter-submit
'
)
&&
(
isIssueIndex
||
isMRIndex
))
{
}
else
if
(
$dropdown
.
hasClass
(
'
js-filter-submit
'
)
&&
(
isIssueIndex
||
isMRIndex
))
{
if
(
selected
.
name
!=
null
)
{
selectedMilestone
=
selected
.
name
;
}
else
{
selectedMilestone
=
''
;
}
return
Issuable
.
filterResults
(
$dropdown
.
closest
(
'
form
'
));
return
Issuable
.
filterResults
(
$dropdown
.
closest
(
'
form
'
));
}
else
if
(
$dropdown
.
hasClass
(
'
js-filter-submit
'
))
{
}
else
if
(
$dropdown
.
hasClass
(
'
js-filter-submit
'
))
{
return
$dropdown
.
closest
(
'
form
'
).
submit
();
return
$dropdown
.
closest
(
'
form
'
).
submit
();
}
else
if
(
$dropdown
.
hasClass
(
'
js-issue-board-sidebar
'
))
{
}
else
if
(
$dropdown
.
hasClass
(
'
js-issue-board-sidebar
'
))
{
if
(
selected
.
id
!==
-
1
)
{
if
(
selected
.
id
!==
-
1
&&
isSelecting
)
{
gl
.
issueBoards
.
boardStoreIssueSet
(
'
milestone
'
,
new
ListMilestone
({
gl
.
issueBoards
.
boardStoreIssueSet
(
'
milestone
'
,
new
ListMilestone
({
id
:
selected
.
id
,
id
:
selected
.
id
,
title
:
selected
.
name
title
:
selected
.
name
...
...
app/views/projects/boards/components/sidebar/_milestone.html.haml
View file @
f328f38e
...
@@ -16,7 +16,8 @@
...
@@ -16,7 +16,8 @@
name:
"issue[milestone_id]"
,
name:
"issue[milestone_id]"
,
"v-if"
=>
"issue.milestone"
}
"v-if"
=>
"issue.milestone"
}
.dropdown
.dropdown
%button
.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar
{
type:
"button"
,
data:
{
toggle:
"dropdown"
,
show_no:
"true"
,
field_name:
"issue[milestone_id]"
,
project_id:
@project
.
id
,
milestones:
namespace_project_milestones_path
(
@project
.
namespace
,
@project
,
:json
),
ability_name:
"issue"
,
use_id:
"true"
},
%button
.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar
{
type:
"button"
,
data:
{
toggle:
"dropdown"
,
show_no:
"true"
,
field_name:
"issue[milestone_id]"
,
project_id:
@project
.
id
,
milestones:
namespace_project_milestones_path
(
@project
.
namespace
,
@project
,
:json
),
ability_name:
"issue"
,
use_id:
"true"
,
default_no:
"true"
},
":data-selected"
=>
"milestoneTitle"
,
":data-issuable-id"
=>
"issue.id"
,
":data-issuable-id"
=>
"issue.id"
,
":data-issue-update"
=>
"'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'"
}
":data-issue-update"
=>
"'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'"
}
Milestone
Milestone
...
...
app/views/shared/issuable/_milestone_dropdown.html.haml
View file @
f328f38e
...
@@ -6,7 +6,7 @@
...
@@ -6,7 +6,7 @@
-
if
selected
.
present?
||
params
[
:milestone_title
].
present?
-
if
selected
.
present?
||
params
[
:milestone_title
].
present?
=
hidden_field_tag
(
name
,
name
==
:milestone_title
?
selected_text
:
selected
.
id
)
=
hidden_field_tag
(
name
,
name
==
:milestone_title
?
selected_text
:
selected
.
id
)
=
dropdown_tag
(
milestone_dropdown_label
(
selected_text
),
options:
{
title:
dropdown_title
,
toggle_class:
"js-milestone-select js-filter-submit
#{
extra_class
}
"
,
filter:
true
,
dropdown_class:
"dropdown-menu-selectable dropdown-menu-milestone"
,
=
dropdown_tag
(
milestone_dropdown_label
(
selected_text
),
options:
{
title:
dropdown_title
,
toggle_class:
"js-milestone-select js-filter-submit
#{
extra_class
}
"
,
filter:
true
,
dropdown_class:
"dropdown-menu-selectable dropdown-menu-milestone"
,
placeholder:
"Search milestones"
,
footer_content:
project
.
present?
,
data:
{
show_no:
true
,
show_menu_above:
show_menu_above
,
show_any:
show_any
,
show_upcoming:
show_upcoming
,
show_started:
show_started
,
field_name:
name
,
selected:
selected
.
try
(
:title
)
,
project_id:
project
.
try
(
:id
),
milestones:
milestones_filter_dropdown_path
,
default_label:
"Milestone"
}
})
do
placeholder:
"Search milestones"
,
footer_content:
project
.
present?
,
data:
{
show_no:
true
,
show_menu_above:
show_menu_above
,
show_any:
show_any
,
show_upcoming:
show_upcoming
,
show_started:
show_started
,
field_name:
name
,
selected:
selected
_text
,
project_id:
project
.
try
(
:id
),
milestones:
milestones_filter_dropdown_path
,
default_label:
"Milestone"
}
})
do
-
if
project
-
if
project
%ul
.dropdown-footer-list
%ul
.dropdown-footer-list
-
if
can?
current_user
,
:admin_milestone
,
project
-
if
can?
current_user
,
:admin_milestone
,
project
...
...
app/views/shared/issuable/_sidebar.html.haml
View file @
f328f38e
...
@@ -91,7 +91,7 @@
...
@@ -91,7 +91,7 @@
.selectbox.hide-collapsed
.selectbox.hide-collapsed
=
f
.
hidden_field
'milestone_id'
,
value:
issuable
.
milestone_id
,
id:
nil
=
f
.
hidden_field
'milestone_id'
,
value:
issuable
.
milestone_id
,
id:
nil
=
dropdown_tag
(
'Milestone'
,
options:
{
title:
'Assign milestone'
,
toggle_class:
'js-milestone-select js-extra-options'
,
filter:
true
,
dropdown_class:
'dropdown-menu-selectable'
,
placeholder:
'Search milestones'
,
data:
{
show_no:
true
,
field_name:
"
#{
issuable
.
to_ability_name
}
[milestone_id]"
,
project_id:
@project
.
id
,
issuable_id:
issuable
.
id
,
milestones:
namespace_project_milestones_path
(
@project
.
namespace
,
@project
,
:json
),
ability_name:
issuable
.
to_ability_name
,
issue_update:
issuable_json_path
(
issuable
),
use_id:
true
}})
=
dropdown_tag
(
'Milestone'
,
options:
{
title:
'Assign milestone'
,
toggle_class:
'js-milestone-select js-extra-options'
,
filter:
true
,
dropdown_class:
'dropdown-menu-selectable'
,
placeholder:
'Search milestones'
,
data:
{
show_no:
true
,
field_name:
"
#{
issuable
.
to_ability_name
}
[milestone_id]"
,
project_id:
@project
.
id
,
issuable_id:
issuable
.
id
,
milestones:
namespace_project_milestones_path
(
@project
.
namespace
,
@project
,
:json
),
ability_name:
issuable
.
to_ability_name
,
issue_update:
issuable_json_path
(
issuable
),
use_id:
true
,
default_no:
true
,
selected:
(
issuable
.
milestone
.
name
if
issuable
.
milestone
),
null_default:
true
}})
-
if
issuable
.
has_attribute?
(
:time_estimate
)
-
if
issuable
.
has_attribute?
(
:time_estimate
)
#issuable-time-tracker
.block
#issuable-time-tracker
.block
// Fallback while content is loading
// Fallback while content is loading
...
...
changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml
0 → 100644
View file @
f328f38e
---
title
:
Disallow multiple selections for Milestone dropdown
merge_request
:
11084
author
:
spec/features/dashboard/milestone_filter_spec.rb
0 → 100644
View file @
f328f38e
require
'spec_helper'
describe
'Dashboard > milestone filter'
,
feature:
true
,
js:
true
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
,
name:
'test'
,
namespace:
user
.
namespace
)
}
let
(
:milestone
)
{
create
(
:milestone
,
title:
"v1.0"
,
project:
project
)
}
let
(
:milestone2
)
{
create
(
:milestone
,
title:
"v2.0"
,
project:
project
)
}
let!
(
:issue
)
{
create
:issue
,
author:
user
,
project:
project
,
milestone:
milestone
}
let!
(
:issue2
)
{
create
:issue
,
author:
user
,
project:
project
,
milestone:
milestone2
}
before
do
login_as
(
user
)
visit
issues_dashboard_path
(
author_id:
user
.
id
)
end
context
'default state'
do
it
'shows issues with Any Milestone'
do
page
.
all
(
'.issue-info'
).
each
do
|
issue_info
|
expect
(
issue_info
.
text
).
to
match
(
/v\d.0/
)
end
end
end
context
'filtering by milestone'
do
milestone_select
=
'.js-milestone-select'
before
do
find
(
milestone_select
).
click
page
.
within
(
'.dropdown-content'
)
do
click_link
'v1.0'
end
find
(
milestone_select
).
click
end
it
'shows issues with Milestone v1.0'
do
expect
(
find
(
'.issues-list'
)).
to
have_selector
(
'.issue'
,
count:
1
)
find
(
milestone_select
).
click
expect
(
find
(
'.dropdown-content'
)).
to
have_selector
(
'a.is-active'
,
count:
1
)
end
it
'should not change active Milestone unless clicked'
do
find
(
milestone_select
).
click
expect
(
find
(
'.dropdown-content'
)).
to
have_selector
(
'a.is-active'
,
count:
1
)
# open & close dropdown
find
(
'.dropdown-menu-close'
).
click
expect
(
find
(
'.milestone-filter'
)).
not_to
have_selector
(
'.dropdown.open'
)
find
(
milestone_select
).
click
expect
(
find
(
'.dropdown-content'
)).
to
have_selector
(
'a.is-active'
,
count:
1
)
expect
(
find
(
'.dropdown-content a.is-active'
)).
to
have_content
(
'v1.0'
)
end
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