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
Léo-Paul Géneau
gitlab-ce
Commits
5bc32b65
Commit
5bc32b65
authored
Nov 25, 2017
by
Vitaliy @blackst0ne Klachkov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add an ability to use a custom branch name on creation from issues
parent
acae8ddb
Changes
15
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
831 additions
and
271 deletions
+831
-271
app/assets/javascripts/create_merge_request_dropdown.js
app/assets/javascripts/create_merge_request_dropdown.js
+366
-88
app/assets/javascripts/droplab/constants.js
app/assets/javascripts/droplab/constants.js
+2
-0
app/assets/javascripts/droplab/drop_down.js
app/assets/javascripts/droplab/drop_down.js
+25
-4
app/assets/javascripts/droplab/hook.js
app/assets/javascripts/droplab/hook.js
+1
-1
app/assets/stylesheets/pages/issues.scss
app/assets/stylesheets/pages/issues.scss
+59
-10
app/controllers/projects/issues_controller.rb
app/controllers/projects/issues_controller.rb
+2
-1
app/services/merge_requests/build_service.rb
app/services/merge_requests/build_service.rb
+42
-24
app/services/merge_requests/create_from_issue_service.rb
app/services/merge_requests/create_from_issue_service.rb
+16
-8
app/views/projects/issues/_new_branch.html.haml
app/views/projects/issues/_new_branch.html.haml
+38
-22
changelogs/unreleased/21143-customize-branch-name-when-using-create-branch-in-an-issue.yml
...mize-branch-name-when-using-create-branch-in-an-issue.yml
+5
-0
spec/features/issues/create_branch_merge_request_spec.rb
spec/features/issues/create_branch_merge_request_spec.rb
+0
-106
spec/features/issues/user_creates_branch_and_merge_request_spec.rb
...ures/issues/user_creates_branch_and_merge_request_spec.rb
+248
-0
spec/javascripts/droplab/drop_down_spec.js
spec/javascripts/droplab/drop_down_spec.js
+7
-1
spec/javascripts/droplab/hook_spec.js
spec/javascripts/droplab/hook_spec.js
+1
-1
spec/services/merge_requests/create_from_issue_service_spec.rb
...services/merge_requests/create_from_issue_service_spec.rb
+19
-5
No files found.
app/assets/javascripts/create_merge_request_dropdown.js
View file @
5bc32b65
This diff is collapsed.
Click to expand it.
app/assets/javascripts/droplab/constants.js
View file @
5bc32b65
...
@@ -3,6 +3,7 @@ const DATA_DROPDOWN = 'data-dropdown';
...
@@ -3,6 +3,7 @@ const DATA_DROPDOWN = 'data-dropdown';
const
SELECTED_CLASS
=
'
droplab-item-selected
'
;
const
SELECTED_CLASS
=
'
droplab-item-selected
'
;
const
ACTIVE_CLASS
=
'
droplab-item-active
'
;
const
ACTIVE_CLASS
=
'
droplab-item-active
'
;
const
IGNORE_CLASS
=
'
droplab-item-ignore
'
;
const
IGNORE_CLASS
=
'
droplab-item-ignore
'
;
const
IGNORE_HIDING_CLASS
=
'
droplab-item-ignore-hiding
'
;
// Matches `{{anything}}` and `{{ everything }}`.
// Matches `{{anything}}` and `{{ everything }}`.
const
TEMPLATE_REGEX
=
/
\{\{(
.+
?)\}\}
/g
;
const
TEMPLATE_REGEX
=
/
\{\{(
.+
?)\}\}
/g
;
...
@@ -13,4 +14,5 @@ export {
...
@@ -13,4 +14,5 @@ export {
ACTIVE_CLASS
,
ACTIVE_CLASS
,
TEMPLATE_REGEX
,
TEMPLATE_REGEX
,
IGNORE_CLASS
,
IGNORE_CLASS
,
IGNORE_HIDING_CLASS
,
};
};
app/assets/javascripts/droplab/drop_down.js
View file @
5bc32b65
import
utils
from
'
./utils
'
;
import
utils
from
'
./utils
'
;
import
{
SELECTED_CLASS
,
IGNORE_CLASS
}
from
'
./constants
'
;
import
{
SELECTED_CLASS
,
IGNORE_CLASS
,
IGNORE_HIDING_CLASS
}
from
'
./constants
'
;
class
DropDown
{
class
DropDown
{
constructor
(
list
)
{
constructor
(
list
,
config
=
{}
)
{
this
.
currentIndex
=
0
;
this
.
currentIndex
=
0
;
this
.
hidden
=
true
;
this
.
hidden
=
true
;
this
.
list
=
typeof
list
===
'
string
'
?
document
.
querySelector
(
list
)
:
list
;
this
.
list
=
typeof
list
===
'
string
'
?
document
.
querySelector
(
list
)
:
list
;
this
.
items
=
[];
this
.
items
=
[];
this
.
eventWrapper
=
{};
this
.
eventWrapper
=
{};
if
(
config
.
addActiveClassToDropdownButton
)
{
this
.
dropdownToggle
=
this
.
list
.
parentNode
.
querySelector
(
'
.js-dropdown-toggle
'
);
}
this
.
getItems
();
this
.
getItems
();
this
.
initTemplateString
();
this
.
initTemplateString
();
this
.
addEvents
();
this
.
addEvents
();
...
@@ -42,7 +45,7 @@ class DropDown {
...
@@ -42,7 +45,7 @@ class DropDown {
this
.
addSelectedClass
(
selected
);
this
.
addSelectedClass
(
selected
);
e
.
preventDefault
();
e
.
preventDefault
();
this
.
hide
();
if
(
!
e
.
target
.
classList
.
contains
(
IGNORE_HIDING_CLASS
))
this
.
hide
();
const
listEvent
=
new
CustomEvent
(
'
click.dl
'
,
{
const
listEvent
=
new
CustomEvent
(
'
click.dl
'
,
{
detail
:
{
detail
:
{
...
@@ -67,7 +70,20 @@ class DropDown {
...
@@ -67,7 +70,20 @@ class DropDown {
addEvents
()
{
addEvents
()
{
this
.
eventWrapper
.
clickEvent
=
this
.
clickEvent
.
bind
(
this
);
this
.
eventWrapper
.
clickEvent
=
this
.
clickEvent
.
bind
(
this
);
this
.
eventWrapper
.
closeDropdown
=
this
.
closeDropdown
.
bind
(
this
);
this
.
list
.
addEventListener
(
'
click
'
,
this
.
eventWrapper
.
clickEvent
);
this
.
list
.
addEventListener
(
'
click
'
,
this
.
eventWrapper
.
clickEvent
);
this
.
list
.
addEventListener
(
'
keyup
'
,
this
.
eventWrapper
.
closeDropdown
);
}
closeDropdown
(
event
)
{
// `ESC` key closes the dropdown.
if
(
event
.
keyCode
===
27
)
{
event
.
preventDefault
();
return
this
.
toggle
();
}
return
true
;
}
}
setData
(
data
)
{
setData
(
data
)
{
...
@@ -110,6 +126,8 @@ class DropDown {
...
@@ -110,6 +126,8 @@ class DropDown {
this
.
list
.
style
.
display
=
'
block
'
;
this
.
list
.
style
.
display
=
'
block
'
;
this
.
currentIndex
=
0
;
this
.
currentIndex
=
0
;
this
.
hidden
=
false
;
this
.
hidden
=
false
;
if
(
this
.
dropdownToggle
)
this
.
dropdownToggle
.
classList
.
add
(
'
active
'
);
}
}
hide
()
{
hide
()
{
...
@@ -117,6 +135,8 @@ class DropDown {
...
@@ -117,6 +135,8 @@ class DropDown {
this
.
list
.
style
.
display
=
'
none
'
;
this
.
list
.
style
.
display
=
'
none
'
;
this
.
currentIndex
=
0
;
this
.
currentIndex
=
0
;
this
.
hidden
=
true
;
this
.
hidden
=
true
;
if
(
this
.
dropdownToggle
)
this
.
dropdownToggle
.
classList
.
remove
(
'
active
'
);
}
}
toggle
()
{
toggle
()
{
...
@@ -128,6 +148,7 @@ class DropDown {
...
@@ -128,6 +148,7 @@ class DropDown {
destroy
()
{
destroy
()
{
this
.
hide
();
this
.
hide
();
this
.
list
.
removeEventListener
(
'
click
'
,
this
.
eventWrapper
.
clickEvent
);
this
.
list
.
removeEventListener
(
'
click
'
,
this
.
eventWrapper
.
clickEvent
);
this
.
list
.
removeEventListener
(
'
keyup
'
,
this
.
eventWrapper
.
closeDropdown
);
}
}
static
setImagesSrc
(
template
)
{
static
setImagesSrc
(
template
)
{
...
...
app/assets/javascripts/droplab/hook.js
View file @
5bc32b65
...
@@ -3,7 +3,7 @@ import DropDown from './drop_down';
...
@@ -3,7 +3,7 @@ import DropDown from './drop_down';
class
Hook
{
class
Hook
{
constructor
(
trigger
,
list
,
plugins
,
config
)
{
constructor
(
trigger
,
list
,
plugins
,
config
)
{
this
.
trigger
=
trigger
;
this
.
trigger
=
trigger
;
this
.
list
=
new
DropDown
(
list
);
this
.
list
=
new
DropDown
(
list
,
config
);
this
.
type
=
'
Hook
'
;
this
.
type
=
'
Hook
'
;
this
.
event
=
'
click
'
;
this
.
event
=
'
click
'
;
this
.
plugins
=
plugins
||
[];
this
.
plugins
=
plugins
||
[];
...
...
app/assets/stylesheets/pages/issues.scss
View file @
5bc32b65
...
@@ -203,7 +203,24 @@ ul.related-merge-requests > li {
...
@@ -203,7 +203,24 @@ ul.related-merge-requests > li {
}
}
.create-mr-dropdown-wrap
{
.create-mr-dropdown-wrap
{
@include
new-style-dropdown
;
.branch-message
,
.ref-message
{
display
:
none
;
}
.
ref
:
:
selection
{
color
:
$placeholder-text-color
;
}
.dropdown
{
.dropdown-menu-toggle
{
min-width
:
285px
;
}
.dropdown-select
{
width
:
285px
;
}
}
.btn-group
:not
(
.hide
)
{
.btn-group
:not
(
.hide
)
{
display
:
flex
;
display
:
flex
;
...
@@ -214,15 +231,16 @@ ul.related-merge-requests > li {
...
@@ -214,15 +231,16 @@ ul.related-merge-requests > li {
flex-shrink
:
0
;
flex-shrink
:
0
;
}
}
.dropdown-menu
{
.
create-merge-request-
dropdown-menu
{
width
:
300px
;
width
:
300px
;
opacity
:
1
;
opacity
:
1
;
visibility
:
visible
;
visibility
:
visible
;
transform
:
translateY
(
0
);
transform
:
translateY
(
0
);
display
:
none
;
display
:
none
;
margin-top
:
4px
;
}
}
.dropdown-toggle
{
.
create-merge-request-
dropdown-toggle
{
.fa-caret-down
{
.fa-caret-down
{
pointer-events
:
none
;
pointer-events
:
none
;
color
:
inherit
;
color
:
inherit
;
...
@@ -230,18 +248,50 @@ ul.related-merge-requests > li {
...
@@ -230,18 +248,50 @@ ul.related-merge-requests > li {
}
}
}
}
.droplab-item-ignore
{
pointer-events
:
auto
;
}
.create-item
{
cursor
:
pointer
;
margin
:
0
1px
;
&
:hover
,
&
:focus
{
background-color
:
$dropdown-item-hover-bg
;
color
:
$gl-text-color
;
}
}
li
.divider
{
margin
:
8px
10px
;
}
li
:not
(
.divider
)
{
li
:not
(
.divider
)
{
padding
:
8px
9px
;
&
:last-child
{
padding-bottom
:
8px
;
}
&
.droplab-item-selected
{
&
.droplab-item-selected
{
.icon-container
{
.icon-container
{
i
{
i
{
visibility
:
visible
;
visibility
:
visible
;
}
}
}
}
.description
{
display
:
block
;
}
}
&
.droplab-item-ignore
{
padding-top
:
8px
;
}
}
.icon-container
{
.icon-container
{
float
:
left
;
float
:
left
;
padding-left
:
6px
;
i
{
i
{
visibility
:
hidden
;
visibility
:
hidden
;
...
@@ -249,13 +299,12 @@ ul.related-merge-requests > li {
...
@@ -249,13 +299,12 @@ ul.related-merge-requests > li {
}
}
.description
{
.description
{
padding-left
:
30
px
;
padding-left
:
22
px
;
font-size
:
13px
;
}
strong
{
input
,
display
:
block
;
span
{
font-weight
:
$gl-font-weight-bold
;
margin
:
4px
0
0
;
}
}
}
}
}
}
}
...
...
app/controllers/projects/issues_controller.rb
View file @
5bc32b65
...
@@ -158,7 +158,8 @@ class Projects::IssuesController < Projects::ApplicationController
...
@@ -158,7 +158,8 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
def
create_merge_request
def
create_merge_request
result
=
::
MergeRequests
::
CreateFromIssueService
.
new
(
project
,
current_user
,
issue_iid:
issue
.
iid
).
execute
create_params
=
params
.
slice
(
:branch_name
,
:ref
).
merge
(
issue_iid:
issue
.
iid
)
result
=
::
MergeRequests
::
CreateFromIssueService
.
new
(
project
,
current_user
,
create_params
).
execute
if
result
[
:status
]
==
:success
if
result
[
:status
]
==
:success
render
json:
MergeRequestCreateSerializer
.
new
.
represent
(
result
[
:merge_request
])
render
json:
MergeRequestCreateSerializer
.
new
.
represent
(
result
[
:merge_request
])
...
...
app/services/merge_requests/build_service.rb
View file @
5bc32b65
module
MergeRequests
module
MergeRequests
class
BuildService
<
MergeRequests
::
BaseService
class
BuildService
<
MergeRequests
::
BaseService
def
execute
def
execute
@issue_iid
=
params
.
delete
(
:issue_iid
)
self
.
merge_request
=
MergeRequest
.
new
(
params
)
self
.
merge_request
=
MergeRequest
.
new
(
params
)
merge_request
.
compare_commits
=
[]
merge_request
.
compare_commits
=
[]
merge_request
.
source_project
=
find_source_project
merge_request
.
source_project
=
find_source_project
...
@@ -106,37 +108,53 @@ module MergeRequests
...
@@ -106,37 +108,53 @@ module MergeRequests
# more than one commit in the MR
# more than one commit in the MR
#
#
def
assign_title_and_description
def
assign_title_and_description
if
match
=
source_branch
.
match
(
/\A(\d+)-/
)
assign_title_and_description_from_single_commit
iid
=
match
[
1
]
assign_title_from_issue
end
commits
=
compare_commits
merge_request
.
title
||=
source_branch
.
titleize
.
humanize
if
commits
&&
commits
.
count
==
1
merge_request
.
title
=
wip_title
if
compare_commits
.
empty?
commit
=
commits
.
first
merge_request
.
title
=
commit
.
title
append_closes_description
merge_request
.
description
||=
commit
.
description
.
try
(
:strip
)
end
elsif
iid
&&
issue
=
target_project
.
get_issue
(
iid
,
current_user
)
case
issue
def
append_closes_description
when
Issue
return
unless
issue_iid
merge_request
.
title
=
"Resolve
\"
#{
issue
.
title
}
\"
"
when
ExternalIssue
closes_issue
=
"Closes #
#{
issue_iid
}
"
merge_request
.
title
=
"Resolve
#{
issue
.
title
}
"
end
if
description
.
present?
merge_request
.
description
+=
closes_issue
.
prepend
(
"
\n\n
"
)
else
else
merge_request
.
title
=
source_branch
.
titleize
.
humaniz
e
merge_request
.
description
=
closes_issu
e
end
end
end
if
iid
def
assign_title_and_description_from_single_commit
closes_issue
=
"Closes #
#{
iid
}
"
commits
=
compare_commits
return
unless
commits
&
.
count
==
1
commit
=
commits
.
first
merge_request
.
title
||=
commit
.
title
merge_request
.
description
||=
commit
.
description
.
try
(
:strip
)
end
def
assign_title_from_issue
return
unless
issue
if
description
.
present?
merge_request
.
title
=
merge_request
.
description
+=
closes_issue
.
prepend
(
"
\n\n
"
)
case
issue
else
when
Issue
then
"Resolve
\"
#{
issue
.
title
}
\"
"
merge_request
.
description
=
closes_issue
when
ExternalIssue
then
"Resolve
#{
issue
.
title
}
"
end
end
end
end
def
issue_iid
@issue_iid
||=
source_branch
.
match
(
/\A(\d+)-/
).
try
(
:[]
,
1
)
end
merge_request
.
title
=
wip_title
if
commits
.
empty?
def
issue
@issue
||=
target_project
.
get_issue
(
issue_iid
,
current_user
)
end
end
end
end
end
end
app/services/merge_requests/create_from_issue_service.rb
View file @
5bc32b65
module
MergeRequests
module
MergeRequests
class
CreateFromIssueService
<
MergeRequests
::
CreateService
class
CreateFromIssueService
<
MergeRequests
::
CreateService
def
initialize
(
project
,
user
,
params
)
# branch - the name of new branch
# ref - the source of new branch.
@branch_name
=
params
[
:branch_name
]
@issue_iid
=
params
[
:issue_iid
]
@ref
=
params
[
:ref
]
super
(
project
,
user
)
end
def
execute
def
execute
return
error
(
'Invalid issue iid'
)
unless
issue_iid
.
present?
&&
issue
.
present?
return
error
(
'Invalid issue iid'
)
unless
@
issue_iid
.
present?
&&
issue
.
present?
params
[
:label_ids
]
=
issue
.
label_ids
if
issue
.
label_ids
.
any?
params
[
:label_ids
]
=
issue
.
label_ids
if
issue
.
label_ids
.
any?
...
@@ -21,20 +32,16 @@ module MergeRequests
...
@@ -21,20 +32,16 @@ module MergeRequests
private
private
def
issue_iid
@isssue_iid
||=
params
.
delete
(
:issue_iid
)
end
def
issue
def
issue
@issue
||=
IssuesFinder
.
new
(
current_user
,
project_id:
project
.
id
).
find_by
(
iid:
issue_iid
)
@issue
||=
IssuesFinder
.
new
(
current_user
,
project_id:
project
.
id
).
find_by
(
iid:
@
issue_iid
)
end
end
def
branch_name
def
branch_name
@branch
_name
||=
issue
.
to_branch_name
@branch
||=
@branch_name
||
issue
.
to_branch_name
end
end
def
ref
def
ref
project
.
default_branch
||
'master'
@ref
||
project
.
default_branch
||
'master'
end
end
def
merge_request
def
merge_request
...
@@ -43,6 +50,7 @@ module MergeRequests
...
@@ -43,6 +50,7 @@ module MergeRequests
def
merge_request_params
def
merge_request_params
{
{
issue_iid:
@issue_iid
,
source_project_id:
project
.
id
,
source_project_id:
project
.
id
,
source_branch:
branch_name
,
source_branch:
branch_name
,
target_project_id:
project
.
id
,
target_project_id:
project
.
id
,
...
...
app/views/projects/issues/_new_branch.html.haml
View file @
5bc32b65
-
can_create_merge_request
=
can?
(
current_user
,
:create_merge_request
,
@project
)
-
can_create_merge_request
=
can?
(
current_user
,
:create_merge_request
,
@project
)
-
data_action
=
can_create_merge_request
?
'create-mr'
:
'create-branch'
-
data_action
=
can_create_merge_request
?
'create-mr'
:
'create-branch'
-
value
=
can_create_merge_request
?
'Create
a merge request'
:
'Create a
branch'
-
value
=
can_create_merge_request
?
'Create
merge request'
:
'Create
branch'
-
if
can?
(
current_user
,
:push_code
,
@project
)
-
if
can?
(
current_user
,
:push_code
,
@project
)
.create-mr-dropdown-wrap
{
data:
{
can_create_path:
can_create_branch_project_issue_path
(
@project
,
@issue
),
create_mr_path:
create_merge_request_project_issue_path
(
@project
,
@issue
),
create_branch_path:
project_branches_path
(
@project
,
branch_name:
@issue
.
to_branch_name
,
issue_iid:
@issue
.
iid
)
}
}
-
can_create_path
=
can_create_branch_project_issue_path
(
@project
,
@issue
)
-
create_mr_path
=
create_merge_request_project_issue_path
(
@project
,
@issue
,
branch_name:
@issue
.
to_branch_name
,
ref:
@project
.
default_branch
)
-
create_branch_path
=
project_branches_path
(
@project
,
branch_name:
@issue
.
to_branch_name
,
ref:
@project
.
default_branch
,
issue_iid:
@issue
.
iid
)
-
refs_path
=
refs_namespace_project_path
(
@project
.
namespace
,
@project
,
search:
''
)
.create-mr-dropdown-wrap
{
data:
{
can_create_path:
can_create_path
,
create_mr_path:
create_mr_path
,
create_branch_path:
create_branch_path
,
refs_path:
refs_path
}
}
.btn-group.unavailable
.btn-group.unavailable
%button
.btn.btn-grouped
{
type:
'button'
,
disabled:
'disabled'
}
%button
.btn.btn-grouped
{
type:
'button'
,
disabled:
'disabled'
}
=
icon
(
'spinner'
,
class:
'fa-spin'
)
=
icon
(
'spinner'
,
class:
'fa-spin'
)
%span
.text
%span
.text
Checking branch availability…
Checking branch availability…
.btn-group.available.hide
.btn-group.available.hide
%input
.btn.js-create-merge-request.btn-inverted.btn-success
{
type:
'button'
,
value:
value
,
data:
{
action:
data_action
}
}
%button
.btn.js-create-merge-request.btn-default
{
type:
'button'
,
data:
{
action:
data_action
}
}
%button
.btn.btn-inverted.dropdown-toggle.btn-inverted.btn-success.js-dropdown-toggle
{
type:
'button'
,
data:
{
'dropdown-trigger'
=>
'#create-merge-request-dropdown'
}
}
=
value
%button
.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-default.js-dropdown-toggle
{
type:
'button'
,
data:
{
dropdown:
{
trigger:
'#create-merge-request-dropdown'
}
}
}
=
icon
(
'caret-down'
)
=
icon
(
'caret-down'
)
%ul
#create-merge-request-dropdown
.dropdown-menu.dropdown-menu-align-right
{
data:
{
dropdown:
true
}
}
%ul
#create-merge-request-dropdown
.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-align-right.gl-show-field-errors
{
data:
{
dropdown:
true
}
}
-
if
can_create_merge_request
-
if
can_create_merge_request
%li
.droplab-item-selected
{
role:
'button'
,
data:
{
value:
'create-mr'
,
'text'
=>
'Create a merge request'
}
}
%li
.create-item.droplab-item-selected.droplab-item-ignore-hiding
{
role:
'button'
,
data:
{
value:
'create-mr'
,
text:
'Create merge request'
}
}
.menu-item
.menu-item.droplab-item-ignore-hiding
.icon-container
.icon-container.droplab-item-ignore-hiding
=
icon
(
'check'
)
=
icon
(
'check'
)
.description.droplab-item-ignore-hiding
Create merge request and branch
.description
%strong
Create a merge request
%li
.create-item.droplab-item-ignore-hiding
{
class:
[
!
can_create_merge_request
&&
'droplab-item-selected'
],
role:
'button'
,
data:
{
value:
'create-branch'
,
text:
'Create branch'
}
}
%span
.menu-item.droplab-item-ignore-hiding
Creates a merge request named after this issue, with source branch created from '
#{
@project
.
default_branch
}
'.
.icon-container.droplab-item-ignore-hiding
=
icon
(
'check'
)
%li
.divider.droplab-item-ignore
.description.droplab-item-ignore-hiding
Create branch
%li
{
class:
[
!
can_create_merge_request
&&
'droplab-item-selected'
],
role:
'button'
,
data:
{
value:
'create-branch'
,
'text'
=>
'Create a branch'
}
}
%li
.divider
.menu-item
.icon-container
%li
.droplab-item-ignore
=
icon
(
'check'
)
Branch name
.description
%input
.js-branch-name.form-control.droplab-item-ignore
{
type:
'text'
,
placeholder:
"#{@issue.to_branch_name}"
,
value:
"#{@issue.to_branch_name}"
}
%strong
Create a branch
%span
.js-branch-message.branch-message.droplab-item-ignore
%span
Creates a branch named after this issue, from '
#{
@project
.
default_branch
}
'.
%li
.droplab-item-ignore
Source (branch or tag)
%input
.js-ref.ref.form-control.droplab-item-ignore
{
type:
'text'
,
placeholder:
"#{@project.default_branch}"
,
value:
"#{@project.default_branch}"
,
data:
{
value:
"#{@project.default_branch}"
}
}
%span
.js-ref-message.ref-message.droplab-item-ignore
%li
.droplab-item-ignore
%button
.btn.btn-success.js-create-target.droplab-item-ignore
{
type:
'button'
,
data:
{
action:
'create-mr'
}
}
Create merge request
changelogs/unreleased/21143-customize-branch-name-when-using-create-branch-in-an-issue.yml
0 → 100644
View file @
5bc32b65
---
title
:
Add an ability to use a custom branch name on creation from issues
merge_request
:
13884
author
:
Vitaliy @blackst0ne Klachkov
type
:
added
spec/features/issues/create_branch_merge_request_spec.rb
deleted
100644 → 0
View file @
acae8ddb
require
'rails_helper'
feature
'Create Branch/Merge Request Dropdown on issue page'
,
:feature
,
:js
do
let
(
:user
)
{
create
(
:user
)
}
let!
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
title:
'Cherry-Coloured Funk'
)
}
context
'for team members'
do
before
do
project
.
team
<<
[
user
,
:developer
]
sign_in
(
user
)
end
it
'allows creating a merge request from the issue page'
do
visit
project_issue_path
(
project
,
issue
)
perform_enqueued_jobs
do
select_dropdown_option
(
'create-mr'
)
expect
(
page
).
to
have_content
(
'WIP: Resolve "Cherry-Coloured Funk"'
)
expect
(
current_path
).
to
eq
(
project_merge_request_path
(
project
,
MergeRequest
.
first
))
wait_for_requests
end
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
to
have_content
(
"created branch 1-cherry-coloured-funk"
)
expect
(
page
).
to
have_content
(
"mentioned in merge request !1"
)
end
it
'allows creating a branch from the issue page'
do
visit
project_issue_path
(
project
,
issue
)
select_dropdown_option
(
'create-branch'
)
wait_for_requests
expect
(
page
).
to
have_selector
(
'.dropdown-toggle-text '
,
text:
'1-cherry-coloured-funk'
)
expect
(
current_path
).
to
eq
project_tree_path
(
project
,
'1-cherry-coloured-funk'
)
end
context
"when there is a referenced merge request"
do
let!
(
:note
)
do
create
(
:note
,
:on_issue
,
:system
,
project:
project
,
noteable:
issue
,
note:
"mentioned in
#{
referenced_mr
.
to_reference
}
"
)
end
let
(
:referenced_mr
)
do
create
(
:merge_request
,
:simple
,
source_project:
project
,
target_project:
project
,
description:
"Fixes
#{
issue
.
to_reference
}
"
,
author:
user
)
end
before
do
referenced_mr
.
cache_merge_request_closes_issues!
(
user
)
visit
project_issue_path
(
project
,
issue
)
end
it
'disables the create branch button'
do
expect
(
page
).
to
have_css
(
'.create-mr-dropdown-wrap .unavailable:not(.hide)'
)
expect
(
page
).
to
have_css
(
'.create-mr-dropdown-wrap .available.hide'
,
visible:
false
)
expect
(
page
).
to
have_content
/1 Related Merge Request/
end
end
context
'when merge requests are disabled'
do
before
do
project
.
project_feature
.
update
(
merge_requests_access_level:
0
)
visit
project_issue_path
(
project
,
issue
)
end
it
'shows only create branch button'
do
expect
(
page
).
not_to
have_button
(
'Create a merge request'
)
expect
(
page
).
to
have_button
(
'Create a branch'
)
end
end
context
'when issue is confidential'
do
it
'disables the create branch button'
do
issue
=
create
(
:issue
,
:confidential
,
project:
project
)
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
not_to
have_css
(
'.create-mr-dropdown-wrap'
)
end
end
end
context
'for visitors'
do
before
do
visit
project_issue_path
(
project
,
issue
)
end
it
'shows no buttons'
do
expect
(
page
).
not_to
have_selector
(
'.create-mr-dropdown-wrap'
)
end
end
def
select_dropdown_option
(
option
)
find
(
'.create-mr-dropdown-wrap .dropdown-toggle'
).
click
find
(
"li[data-value='
#{
option
}
']"
).
click
find
(
'.js-create-merge-request'
).
click
end
end
spec/features/issues/user_creates_branch_and_merge_request_spec.rb
0 → 100644
View file @
5bc32b65
require
'rails_helper'
describe
'User creates branch and merge request on issue page'
,
:js
do
let
(
:user
)
{
create
(
:user
)
}
let!
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
title:
'Cherry-Coloured Funk'
)
}
context
'when signed out'
do
before
do
visit
project_issue_path
(
project
,
issue
)
end
it
"doesn't show 'Create merge request' button"
do
expect
(
page
).
not_to
have_selector
(
'.create-mr-dropdown-wrap'
)
end
end
context
'when signed in'
do
before
do
project
.
add_developer
(
user
)
sign_in
(
user
)
end
context
'when interacting with the dropdown'
do
before
do
visit
project_issue_path
(
project
,
issue
)
end
# In order to improve tests performance, all UI checks are placed in this test.
it
'shows elements'
do
button_create_merge_request
=
find
(
'.js-create-merge-request'
)
button_toggle_dropdown
=
find
(
'.create-mr-dropdown-wrap .dropdown-toggle'
)
button_toggle_dropdown
.
click
dropdown
=
find
(
'.create-merge-request-dropdown-menu'
)
page
.
within
(
dropdown
)
do
button_create_target
=
find
(
'.js-create-target'
)
input_branch_name
=
find
(
'.js-branch-name'
)
input_source
=
find
(
'.js-ref'
)
li_create_branch
=
find
(
"li[data-value='create-branch']"
)
li_create_merge_request
=
find
(
"li[data-value='create-mr']"
)
# Test that all elements are presented.
expect
(
page
).
to
have_content
(
'Create merge request and branch'
)
expect
(
page
).
to
have_content
(
'Create branch'
)
expect
(
page
).
to
have_content
(
'Branch name'
)
expect
(
page
).
to
have_content
(
'Source (branch or tag)'
)
expect
(
page
).
to
have_button
(
'Create merge request'
)
expect
(
page
).
to
have_selector
(
'.js-branch-name:focus'
)
test_selection_mark
(
li_create_branch
,
li_create_merge_request
,
button_create_target
,
button_create_merge_request
)
test_branch_name_checking
(
input_branch_name
)
test_source_checking
(
input_source
)
# The button inside dropdown should be disabled if any errors occured.
expect
(
page
).
to
have_button
(
'Create branch'
,
disabled:
true
)
end
# The top level button should be disabled if any errors occured.
expect
(
page
).
to
have_button
(
'Create branch'
,
disabled:
true
)
end
context
'when branch name is auto-generated'
do
it
'creates a merge request'
do
perform_enqueued_jobs
do
select_dropdown_option
(
'create-mr'
)
expect
(
page
).
to
have_content
(
'WIP: Resolve "Cherry-Coloured Funk"'
)
expect
(
current_path
).
to
eq
(
project_merge_request_path
(
project
,
MergeRequest
.
first
))
wait_for_requests
end
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
to
have_content
(
'created branch 1-cherry-coloured-funk'
)
expect
(
page
).
to
have_content
(
'mentioned in merge request !1'
)
end
it
'creates a branch'
do
select_dropdown_option
(
'create-branch'
)
wait_for_requests
expect
(
page
).
to
have_selector
(
'.dropdown-toggle-text '
,
text:
'1-cherry-coloured-funk'
)
expect
(
current_path
).
to
eq
project_tree_path
(
project
,
'1-cherry-coloured-funk'
)
end
end
context
'when branch name is custom'
do
let
(
:branch_name
)
{
'custom-branch-name'
}
it
'creates a merge request'
do
perform_enqueued_jobs
do
select_dropdown_option
(
'create-mr'
,
branch_name
)
expect
(
page
).
to
have_content
(
'WIP: Resolve "Cherry-Coloured Funk"'
)
expect
(
page
).
to
have_content
(
'Request to merge custom-branch-name into'
)
expect
(
current_path
).
to
eq
(
project_merge_request_path
(
project
,
MergeRequest
.
first
))
wait_for_requests
end
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
to
have_content
(
'created branch custom-branch-name'
)
expect
(
page
).
to
have_content
(
'mentioned in merge request !1'
)
end
it
'creates a branch'
do
select_dropdown_option
(
'create-branch'
,
branch_name
)
wait_for_requests
expect
(
page
).
to
have_selector
(
'.dropdown-toggle-text '
,
text:
branch_name
)
expect
(
current_path
).
to
eq
project_tree_path
(
project
,
branch_name
)
end
end
end
context
"when there is a referenced merge request"
do
let!
(
:note
)
do
create
(
:note
,
:on_issue
,
:system
,
project:
project
,
noteable:
issue
,
note:
"mentioned in
#{
referenced_mr
.
to_reference
}
"
)
end
let
(
:referenced_mr
)
do
create
(
:merge_request
,
:simple
,
source_project:
project
,
target_project:
project
,
description:
"Fixes
#{
issue
.
to_reference
}
"
,
author:
user
)
end
before
do
referenced_mr
.
cache_merge_request_closes_issues!
(
user
)
visit
project_issue_path
(
project
,
issue
)
end
it
'disables the create branch button'
do
expect
(
page
).
to
have_css
(
'.create-mr-dropdown-wrap .unavailable:not(.hide)'
)
expect
(
page
).
to
have_css
(
'.create-mr-dropdown-wrap .available.hide'
,
visible:
false
)
expect
(
page
).
to
have_content
/1 Related Merge Request/
end
end
context
'when merge requests are disabled'
do
before
do
project
.
project_feature
.
update
(
merge_requests_access_level:
0
)
visit
project_issue_path
(
project
,
issue
)
end
it
'shows only create branch button'
do
expect
(
page
).
not_to
have_button
(
'Create merge request'
)
expect
(
page
).
to
have_button
(
'Create branch'
)
end
end
context
'when issue is confidential'
do
let
(
:issue
)
{
create
(
:issue
,
:confidential
,
project:
project
)
}
it
'disables the create branch button'
do
visit
project_issue_path
(
project
,
issue
)
expect
(
page
).
not_to
have_css
(
'.create-mr-dropdown-wrap'
)
end
end
end
private
def
select_dropdown_option
(
option
,
branch_name
=
nil
)
find
(
'.create-mr-dropdown-wrap .dropdown-toggle'
).
click
find
(
"li[data-value='
#{
option
}
']"
).
click
if
branch_name
find
(
'.js-branch-name'
).
set
(
branch_name
)
# Javascript debounces AJAX calls.
# So we have to wait until AJAX requests are started.
# Details are in app/assets/javascripts/create_merge_request_dropdown.js
# this.refDebounce = _.debounce(...)
sleep
0.5
wait_for_requests
end
find
(
'.js-create-merge-request'
).
click
end
def
test_branch_name_checking
(
input_branch_name
)
expect
(
input_branch_name
.
value
).
to
eq
(
issue
.
to_branch_name
)
input_branch_name
.
set
(
'new-branch-name'
)
branch_name_message
=
find
(
'.js-branch-message'
)
expect
(
branch_name_message
).
to
have_text
(
'Checking branch name availability…'
)
wait_for_requests
expect
(
branch_name_message
).
to
have_text
(
'Branch name is available'
)
input_branch_name
.
set
(
project
.
default_branch
)
expect
(
branch_name_message
).
to
have_text
(
'Checking branch name availability…'
)
wait_for_requests
expect
(
branch_name_message
).
to
have_text
(
'Branch is already taken'
)
end
def
test_selection_mark
(
li_create_branch
,
li_create_merge_request
,
button_create_target
,
button_create_merge_request
)
page
.
within
(
li_create_merge_request
)
do
expect
(
page
).
to
have_css
(
'i.fa.fa-check'
)
expect
(
button_create_target
).
to
have_text
(
'Create merge request'
)
expect
(
button_create_merge_request
).
to
have_text
(
'Create merge request'
)
end
li_create_branch
.
click
page
.
within
(
li_create_branch
)
do
expect
(
page
).
to
have_css
(
'i.fa.fa-check'
)
expect
(
button_create_target
).
to
have_text
(
'Create branch'
)
expect
(
button_create_merge_request
).
to
have_text
(
'Create branch'
)
end
end
def
test_source_checking
(
input_source
)
expect
(
input_source
.
value
).
to
eq
(
project
.
default_branch
)
input_source
.
set
(
'mas'
)
# Intentionally entered first 3 letters of `master` to check autocomplete feature later.
source_message
=
find
(
'.js-ref-message'
)
expect
(
source_message
).
to
have_text
(
'Checking source availability…'
)
wait_for_requests
expect
(
source_message
).
to
have_text
(
'Source is not available'
)
# JavaScript gets refs started with `mas` (entered above) and places the first match.
# User sees `mas` in black color (the part he entered) and the `ter` in gray color (a hint).
# Since hinting is implemented via text selection and rspec/capybara doesn't have matchers for it,
# we just checking the whole source name.
expect
(
input_source
.
value
).
to
eq
(
project
.
default_branch
)
end
end
spec/javascripts/droplab/drop_down_spec.js
View file @
5bc32b65
...
@@ -279,7 +279,12 @@ describe('DropDown', function () {
...
@@ -279,7 +279,12 @@ describe('DropDown', function () {
describe
(
'
addEvents
'
,
function
()
{
describe
(
'
addEvents
'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
this
.
list
=
{
addEventListener
:
()
=>
{}
};
this
.
list
=
{
addEventListener
:
()
=>
{}
};
this
.
dropdown
=
{
list
:
this
.
list
,
clickEvent
:
()
=>
{},
eventWrapper
:
{}
};
this
.
dropdown
=
{
list
:
this
.
list
,
clickEvent
:
()
=>
{},
closeDropdown
:
()
=>
{},
eventWrapper
:
{},
};
spyOn
(
this
.
list
,
'
addEventListener
'
);
spyOn
(
this
.
list
,
'
addEventListener
'
);
...
@@ -288,6 +293,7 @@ describe('DropDown', function () {
...
@@ -288,6 +293,7 @@ describe('DropDown', function () {
it
(
'
should call .addEventListener
'
,
function
()
{
it
(
'
should call .addEventListener
'
,
function
()
{
expect
(
this
.
list
.
addEventListener
).
toHaveBeenCalledWith
(
'
click
'
,
jasmine
.
any
(
Function
));
expect
(
this
.
list
.
addEventListener
).
toHaveBeenCalledWith
(
'
click
'
,
jasmine
.
any
(
Function
));
expect
(
this
.
list
.
addEventListener
).
toHaveBeenCalledWith
(
'
keyup
'
,
jasmine
.
any
(
Function
));
});
});
});
});
...
...
spec/javascripts/droplab/hook_spec.js
View file @
5bc32b65
...
@@ -24,7 +24,7 @@ describe('Hook', function () {
...
@@ -24,7 +24,7 @@ describe('Hook', function () {
});
});
it
(
'
should call DropDown constructor
'
,
function
()
{
it
(
'
should call DropDown constructor
'
,
function
()
{
expect
(
dropdownSrc
.
default
).
toHaveBeenCalledWith
(
this
.
list
);
expect
(
dropdownSrc
.
default
).
toHaveBeenCalledWith
(
this
.
list
,
this
.
config
);
});
});
it
(
'
should set .type
'
,
function
()
{
it
(
'
should set .type
'
,
function
()
{
...
...
spec/services/merge_requests/create_from_issue_service_spec.rb
View file @
5bc32b65
...
@@ -6,8 +6,10 @@ describe MergeRequests::CreateFromIssueService do
...
@@ -6,8 +6,10 @@ describe MergeRequests::CreateFromIssueService do
let
(
:label_ids
)
{
create_pair
(
:label
,
project:
project
).
map
(
&
:id
)
}
let
(
:label_ids
)
{
create_pair
(
:label
,
project:
project
).
map
(
&
:id
)
}
let
(
:milestone_id
)
{
create
(
:milestone
,
project:
project
).
id
}
let
(
:milestone_id
)
{
create
(
:milestone
,
project:
project
).
id
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
milestone_id:
milestone_id
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
milestone_id:
milestone_id
)
}
let
(
:custom_source_branch
)
{
'custom-source-branch'
}
subject
(
:service
)
{
described_class
.
new
(
project
,
user
,
issue_iid:
issue
.
iid
)
}
subject
(
:service
)
{
described_class
.
new
(
project
,
user
,
issue_iid:
issue
.
iid
)
}
subject
(
:service_with_custom_source_branch
)
{
described_class
.
new
(
project
,
user
,
issue_iid:
issue
.
iid
,
branch_name:
custom_source_branch
)
}
before
do
before
do
project
.
add_developer
(
user
)
project
.
add_developer
(
user
)
...
@@ -17,8 +19,8 @@ describe MergeRequests::CreateFromIssueService do
...
@@ -17,8 +19,8 @@ describe MergeRequests::CreateFromIssueService do
it
'returns an error with invalid issue iid'
do
it
'returns an error with invalid issue iid'
do
result
=
described_class
.
new
(
project
,
user
,
issue_iid:
-
1
).
execute
result
=
described_class
.
new
(
project
,
user
,
issue_iid:
-
1
).
execute
expect
(
result
[
:status
]).
to
eq
:error
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
]).
to
eq
'Invalid issue iid'
expect
(
result
[
:message
]).
to
eq
(
'Invalid issue iid'
)
end
end
it
'delegates issue search to IssuesFinder'
do
it
'delegates issue search to IssuesFinder'
do
...
@@ -53,6 +55,12 @@ describe MergeRequests::CreateFromIssueService do
...
@@ -53,6 +55,12 @@ describe MergeRequests::CreateFromIssueService do
expect
(
project
.
repository
.
branch_exists?
(
issue
.
to_branch_name
)).
to
be_truthy
expect
(
project
.
repository
.
branch_exists?
(
issue
.
to_branch_name
)).
to
be_truthy
end
end
it
'creates a branch using passed name'
do
service_with_custom_source_branch
.
execute
expect
(
project
.
repository
.
branch_exists?
(
custom_source_branch
)).
to
be_truthy
end
it
'creates a system note'
do
it
'creates a system note'
do
expect
(
SystemNoteService
).
to
receive
(
:new_issue_branch
).
with
(
issue
,
project
,
user
,
issue
.
to_branch_name
)
expect
(
SystemNoteService
).
to
receive
(
:new_issue_branch
).
with
(
issue
,
project
,
user
,
issue
.
to_branch_name
)
...
@@ -72,19 +80,25 @@ describe MergeRequests::CreateFromIssueService do
...
@@ -72,19 +80,25 @@ describe MergeRequests::CreateFromIssueService do
it
'sets the merge request author to current user'
do
it
'sets the merge request author to current user'
do
result
=
service
.
execute
result
=
service
.
execute
expect
(
result
[
:merge_request
].
author
).
to
eq
user
expect
(
result
[
:merge_request
].
author
).
to
eq
(
user
)
end
end
it
'sets the merge request source branch to the new issue branch'
do
it
'sets the merge request source branch to the new issue branch'
do
result
=
service
.
execute
result
=
service
.
execute
expect
(
result
[
:merge_request
].
source_branch
).
to
eq
issue
.
to_branch_name
expect
(
result
[
:merge_request
].
source_branch
).
to
eq
(
issue
.
to_branch_name
)
end
it
'sets the merge request source branch to the passed branch name'
do
result
=
service_with_custom_source_branch
.
execute
expect
(
result
[
:merge_request
].
source_branch
).
to
eq
(
custom_source_branch
)
end
end
it
'sets the merge request target branch to the project default branch'
do
it
'sets the merge request target branch to the project default branch'
do
result
=
service
.
execute
result
=
service
.
execute
expect
(
result
[
:merge_request
].
target_branch
).
to
eq
project
.
default_branch
expect
(
result
[
:merge_request
].
target_branch
).
to
eq
(
project
.
default_branch
)
end
end
end
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