Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
01c9488f
Commit
01c9488f
authored
Mar 30, 2017
by
Ryan Scott
Committed by
Sean McGivern
Jul 20, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added slash command to close an issue as a duplicate. Closes #26372
parent
b6555693
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
219 additions
and
1 deletion
+219
-1
app/models/system_note_metadata.rb
app/models/system_note_metadata.rb
+1
-1
app/services/issuable_base_service.rb
app/services/issuable_base_service.rb
+22
-0
app/services/quick_actions/interpret_service.rb
app/services/quick_actions/interpret_service.rb
+14
-0
app/services/system_note_service.rb
app/services/system_note_service.rb
+19
-0
changelogs/unreleased/26372-duplicate-issue-slash-command.yml
...gelogs/unreleased/26372-duplicate-issue-slash-command.yml
+4
-0
doc/user/project/quick_actions.md
doc/user/project/quick_actions.md
+1
-0
spec/features/issues/user_uses_slash_commands_spec.rb
spec/features/issues/user_uses_slash_commands_spec.rb
+41
-0
spec/services/issues/update_service_spec.rb
spec/services/issues/update_service_spec.rb
+56
-0
spec/services/quick_actions/interpret_service_spec.rb
spec/services/quick_actions/interpret_service_spec.rb
+36
-0
spec/services/system_note_service_spec.rb
spec/services/system_note_service_spec.rb
+25
-0
No files found.
app/models/system_note_metadata.rb
View file @
01c9488f
...
@@ -2,7 +2,7 @@ class SystemNoteMetadata < ActiveRecord::Base
...
@@ -2,7 +2,7 @@ class SystemNoteMetadata < ActiveRecord::Base
ICON_TYPES
=
%w[
ICON_TYPES
=
%w[
commit description merge confidential visible label assignee cross_reference
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved opened closed merged
title time_tracking branch milestone discussion task moved opened closed merged
outdated
outdated
duplicate
]
.
freeze
]
.
freeze
validates
:note
,
presence:
true
validates
:note
,
presence:
true
...
...
app/services/issuable_base_service.rb
View file @
01c9488f
...
@@ -46,6 +46,14 @@ class IssuableBaseService < BaseService
...
@@ -46,6 +46,14 @@ class IssuableBaseService < BaseService
SystemNoteService
.
change_time_spent
(
issuable
,
issuable
.
project
,
current_user
)
SystemNoteService
.
change_time_spent
(
issuable
,
issuable
.
project
,
current_user
)
end
end
def
create_issue_duplicate_note
(
issuable
,
original_issue
)
SystemNoteService
.
mark_duplicate_issue
(
issuable
,
issuable
.
project
,
current_user
,
original_issue
)
end
def
create_cross_reference_note
(
noteable
,
mentioner
)
SystemNoteService
.
cross_reference
(
noteable
,
mentioner
,
current_user
)
end
def
filter_params
(
issuable
)
def
filter_params
(
issuable
)
ability_name
=
:"admin_
#{
issuable
.
to_ability_name
}
"
ability_name
=
:"admin_
#{
issuable
.
to_ability_name
}
"
...
@@ -58,6 +66,7 @@ class IssuableBaseService < BaseService
...
@@ -58,6 +66,7 @@ class IssuableBaseService < BaseService
params
.
delete
(
:assignee_ids
)
params
.
delete
(
:assignee_ids
)
params
.
delete
(
:assignee_id
)
params
.
delete
(
:assignee_id
)
params
.
delete
(
:due_date
)
params
.
delete
(
:due_date
)
params
.
delete
(
:original_issue_id
)
end
end
filter_assignee
(
issuable
)
filter_assignee
(
issuable
)
...
@@ -209,6 +218,7 @@ class IssuableBaseService < BaseService
...
@@ -209,6 +218,7 @@ class IssuableBaseService < BaseService
change_state
(
issuable
)
change_state
(
issuable
)
change_subscription
(
issuable
)
change_subscription
(
issuable
)
change_todo
(
issuable
)
change_todo
(
issuable
)
change_issue_duplicate
(
issuable
)
toggle_award
(
issuable
)
toggle_award
(
issuable
)
filter_params
(
issuable
)
filter_params
(
issuable
)
old_labels
=
issuable
.
labels
.
to_a
old_labels
=
issuable
.
labels
.
to_a
...
@@ -291,6 +301,18 @@ class IssuableBaseService < BaseService
...
@@ -291,6 +301,18 @@ class IssuableBaseService < BaseService
end
end
end
end
def
change_issue_duplicate
(
issuable
)
original_issue_id
=
params
.
delete
(
:original_issue_id
)
return
if
original_issue_id
.
nil?
original_issue
=
IssuesFinder
.
new
(
current_user
).
find
(
original_issue_id
)
if
original_issue
.
present?
create_issue_duplicate_note
(
issuable
,
original_issue
)
close_service
.
new
(
project
,
current_user
,
{}).
execute
(
issuable
)
create_cross_reference_note
(
original_issue
,
issuable
)
end
end
def
toggle_award
(
issuable
)
def
toggle_award
(
issuable
)
award
=
params
.
delete
(
:emoji_award
)
award
=
params
.
delete
(
:emoji_award
)
if
award
if
award
...
...
app/services/quick_actions/interpret_service.rb
View file @
01c9488f
...
@@ -471,6 +471,20 @@ module QuickActions
...
@@ -471,6 +471,20 @@ module QuickActions
end
end
end
end
desc
'Mark this issue as a duplicate of another issue'
params
'#issue'
condition
do
issuable
.
is_a?
(
Issue
)
&&
issuable
.
persisted?
&&
current_user
.
can?
(
:"update_
#{
issuable
.
to_ability_name
}
"
,
issuable
)
end
command
:duplicate
do
|
duplicate_param
|
original_issue
=
extract_references
(
duplicate_param
,
:issue
).
first
if
original_issue
.
present?
&&
original_issue
!=
issuable
@updates
[
:original_issue_id
]
=
original_issue
.
id
end
end
def
extract_users
(
params
)
def
extract_users
(
params
)
return
[]
if
params
.
nil?
return
[]
if
params
.
nil?
...
...
app/services/system_note_service.rb
View file @
01c9488f
...
@@ -552,6 +552,25 @@ module SystemNoteService
...
@@ -552,6 +552,25 @@ module SystemNoteService
create_note
(
NoteSummary
.
new
(
noteable
,
project
,
author
,
body
,
action:
'moved'
))
create_note
(
NoteSummary
.
new
(
noteable
,
project
,
author
,
body
,
action:
'moved'
))
end
end
# Called when a Notable has been marked as a duplicate of another Issue
#
# noteable - Noteable object
# project - Project owning noteable
# author - User performing the change
# original_issue - Issue that this is a duplicate of
#
# Example Note text:
#
# "marked this issue as a duplicate of #1234"
#
# "marked this issue as a duplicate of other_project#5678"
#
# Returns the created Note object
def
mark_duplicate_issue
(
noteable
,
project
,
author
,
original_issue
)
body
=
"marked this issue as a duplicate of
#{
original_issue
.
to_reference
(
project
)
}
"
create_note
(
NoteSummary
.
new
(
noteable
,
project
,
author
,
body
,
action:
'duplicate'
))
end
private
private
def
notes_for_mentioner
(
mentioner
,
noteable
,
notes
)
def
notes_for_mentioner
(
mentioner
,
noteable
,
notes
)
...
...
changelogs/unreleased/26372-duplicate-issue-slash-command.yml
0 → 100644
View file @
01c9488f
---
title
:
Added /duplicate slash command to close a duplicate issue
merge_request
:
author
:
Ryan Scott
doc/user/project/quick_actions.md
View file @
01c9488f
...
@@ -37,3 +37,4 @@ do.
...
@@ -37,3 +37,4 @@ do.
|
`/target_branch <Branch Name>`
| Set target branch for current merge request |
|
`/target_branch <Branch Name>`
| Set target branch for current merge request |
|
`/award :emoji:`
| Toggle award for :emoji: |
|
`/award :emoji:`
| Toggle award for :emoji: |
|
`/board_move ~column`
| Move issue to column on the board |
|
`/board_move ~column`
| Move issue to column on the board |
|
`/duplicate #issue`
| Closes this issue and marks it as a duplicate of another issue |
spec/features/issues/user_uses_slash_commands_spec.rb
View file @
01c9488f
...
@@ -134,5 +134,46 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
...
@@ -134,5 +134,46 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
expect
(
page
).
not_to
have_content
'/wip'
expect
(
page
).
not_to
have_content
'/wip'
end
end
end
end
describe
'mark issue as duplicate'
do
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:original_issue
)
{
create
(
:issue
,
project:
project
)
}
context
'when the current user can update issues'
do
it
'does not create a note, and marks the issue as a duplicate'
do
write_note
(
"/duplicate #
#{
original_issue
.
to_reference
}
"
)
expect
(
page
).
not_to
have_content
"/duplicate
#{
original_issue
.
to_reference
}
"
expect
(
page
).
to
have_content
'Commands applied'
expect
(
page
).
to
have_content
"marked this issue as a duplicate of
#{
original_issue
.
to_reference
}
"
issue
.
reload
expect
(
issue
.
closed?
).
to
be_truthy
end
end
context
'when the current user cannot update the issue'
do
let
(
:guest
)
{
create
(
:user
)
}
before
do
project
.
team
<<
[
guest
,
:guest
]
logout
login_with
(
guest
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
end
it
'does not create a note, and does not mark the issue as a duplicate'
do
write_note
(
"/duplicate #
#{
original_issue
.
to_reference
}
"
)
expect
(
page
).
to
have_content
"/duplicate #
#{
original_issue
.
to_reference
}
"
expect
(
page
).
not_to
have_content
'Commands applied'
expect
(
page
).
not_to
have_content
"marked this issue as a duplicate of
#{
original_issue
.
to_reference
}
"
issue
.
reload
expect
(
issue
.
closed?
).
to
be_falsey
end
end
end
end
end
end
end
spec/services/issues/update_service_spec.rb
View file @
01c9488f
...
@@ -491,6 +491,62 @@ describe Issues::UpdateService, services: true do
...
@@ -491,6 +491,62 @@ describe Issues::UpdateService, services: true do
include_examples
'updating mentions'
,
Issues
::
UpdateService
include_examples
'updating mentions'
,
Issues
::
UpdateService
end
end
context
'duplicate issue'
do
let
(
:issues_finder
)
{
spy
(
:issues_finder
)
}
let
(
:close_service
)
{
spy
(
:close_service
)
}
before
do
allow
(
IssuesFinder
).
to
receive
(
:new
).
and_return
(
issues_finder
)
allow
(
Issues
::
CloseService
).
to
receive
(
:new
).
and_return
(
close_service
)
allow
(
SystemNoteService
).
to
receive
(
:cross_reference
)
allow
(
SystemNoteService
).
to
receive
(
:mark_duplicate_issue
)
end
context
'invalid original_issue_id'
do
let
(
:original_issue_id
)
{
double
}
before
{
update_issue
({
original_issue_id:
original_issue_id
})
}
it
'finds the root issue'
do
expect
(
issues_finder
).
to
have_received
(
:find
).
with
(
original_issue_id
)
end
it
'does not close the issue'
do
expect
(
close_service
).
not_to
have_received
(
:execute
)
end
it
'does not create system notes'
do
expect
(
SystemNoteService
).
not_to
have_received
(
:cross_reference
)
expect
(
SystemNoteService
).
not_to
have_received
(
:mark_duplicate_issue
)
end
end
context
'valid original_issue_id'
do
let
(
:original_issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:original_issue_id
)
{
double
}
before
do
allow
(
issues_finder
).
to
receive
(
:find
).
and_return
(
original_issue
)
update_issue
({
original_issue_id:
original_issue_id
})
end
it
'finds the root issue'
do
expect
(
issues_finder
).
to
have_received
(
:find
).
with
(
original_issue_id
)
end
it
'closes the issue'
do
expect
(
close_service
).
to
have_received
(
:execute
).
with
(
issue
)
end
it
'creates a system note that this issue is a duplicate'
do
expect
(
SystemNoteService
).
to
have_received
(
:mark_duplicate_issue
).
with
(
issue
,
project
,
user
,
original_issue
)
end
it
'creates a cross reference system note in the other issue'
do
expect
(
SystemNoteService
).
to
have_received
(
:cross_reference
).
with
(
original_issue
,
issue
,
user
)
end
end
end
include_examples
'issuable update service'
do
include_examples
'issuable update service'
do
let
(
:open_issuable
)
{
issue
}
let
(
:open_issuable
)
{
issue
}
let
(
:closed_issuable
)
{
create
(
:closed_issue
,
project:
project
)
}
let
(
:closed_issuable
)
{
create
(
:closed_issue
,
project:
project
)
}
...
...
spec/services/quick_actions/interpret_service_spec.rb
View file @
01c9488f
...
@@ -261,6 +261,17 @@ describe QuickActions::InterpretService, services: true do
...
@@ -261,6 +261,17 @@ describe QuickActions::InterpretService, services: true do
end
end
end
end
shared_examples
'duplicate command'
do
let
(
:issue_duplicate
)
{
create
(
:issue
,
project:
project
)
}
it
'fetches issue and populates original_issue_id if content contains /duplicate issue_reference'
do
issue_duplicate
# populate the issue
_
,
updates
=
service
.
execute
(
content
,
issuable
)
expect
(
updates
).
to
eq
(
original_issue_id:
issue_duplicate
.
id
)
end
end
it_behaves_like
'reopen command'
do
it_behaves_like
'reopen command'
do
let
(
:content
)
{
'/reopen'
}
let
(
:content
)
{
'/reopen'
}
let
(
:issuable
)
{
issue
}
let
(
:issuable
)
{
issue
}
...
@@ -644,6 +655,26 @@ describe QuickActions::InterpretService, services: true do
...
@@ -644,6 +655,26 @@ describe QuickActions::InterpretService, services: true do
let
(
:issuable
)
{
issue
}
let
(
:issuable
)
{
issue
}
end
end
it_behaves_like
'duplicate command'
do
let
(
:content
)
{
"/duplicate
#{
issue_duplicate
.
to_reference
}
"
}
let
(
:issuable
)
{
issue
}
end
it_behaves_like
'empty command'
do
let
(
:content
)
{
'/duplicate #{issue.to_reference}'
}
let
(
:issuable
)
{
issue
}
end
it_behaves_like
'empty command'
do
let
(
:content
)
{
'/duplicate'
}
let
(
:issuable
)
{
issue
}
end
it_behaves_like
'empty command'
do
let
(
:content
)
{
'/duplicate imaginary#1234'
}
let
(
:issuable
)
{
issue
}
end
context
'when current_user cannot :admin_issue'
do
context
'when current_user cannot :admin_issue'
do
let
(
:visitor
)
{
create
(
:user
)
}
let
(
:visitor
)
{
create
(
:user
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
visitor
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
visitor
)
}
...
@@ -693,6 +724,11 @@ describe QuickActions::InterpretService, services: true do
...
@@ -693,6 +724,11 @@ describe QuickActions::InterpretService, services: true do
let
(
:content
)
{
'/remove_due_date'
}
let
(
:content
)
{
'/remove_due_date'
}
let
(
:issuable
)
{
issue
}
let
(
:issuable
)
{
issue
}
end
end
it_behaves_like
'empty command'
do
let
(
:content
)
{
'/duplicate #{issue.to_reference}'
}
let
(
:issuable
)
{
issue
}
end
end
end
context
'/award command'
do
context
'/award command'
do
...
...
spec/services/system_note_service_spec.rb
View file @
01c9488f
...
@@ -1101,4 +1101,29 @@ describe SystemNoteService, services: true do
...
@@ -1101,4 +1101,29 @@ describe SystemNoteService, services: true do
expect
(
subject
.
note
).
to
include
(
diffs_project_merge_request_url
(
project
,
merge_request
,
diff_id:
diff_id
,
anchor:
line_code
))
expect
(
subject
.
note
).
to
include
(
diffs_project_merge_request_url
(
project
,
merge_request
,
diff_id:
diff_id
,
anchor:
line_code
))
end
end
end
end
describe
'.mark_duplicate_issue'
do
subject
{
described_class
.
mark_duplicate_issue
(
noteable
,
project
,
author
,
original_issue
)
}
context
'within the same project'
do
let
(
:original_issue
)
{
create
(
:issue
,
project:
project
)
}
it_behaves_like
'a system note'
do
let
(
:action
)
{
'duplicate'
}
end
it
{
expect
(
subject
.
note
).
to
eq
"marked this issue as a duplicate of
#{
original_issue
.
to_reference
}
"
}
end
context
'across different projects'
do
let
(
:other_project
)
{
create
(
:empty_project
)
}
let
(
:original_issue
)
{
create
(
:issue
,
project:
other_project
)
}
it_behaves_like
'a system note'
do
let
(
:action
)
{
'duplicate'
}
end
it
{
expect
(
subject
.
note
).
to
eq
"marked this issue as a duplicate of
#{
original_issue
.
to_reference
(
project
)
}
"
}
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