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
0d3ecf58
Commit
0d3ecf58
authored
Apr 26, 2017
by
Clement Ho
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'multiple_assignees_review' into multiple-assignees-fe-sidebar
parents
a0f6bf9d
d525c9a8
Changes
47
Hide whitespace changes
Inline
Side-by-side
Showing
47 changed files
with
472 additions
and
261 deletions
+472
-261
app/assets/javascripts/boards/components/issue_card_inner.js
app/assets/javascripts/boards/components/issue_card_inner.js
+110
-29
app/assets/javascripts/boards/models/issue.js
app/assets/javascripts/boards/models/issue.js
+2
-5
app/assets/stylesheets/pages/boards.scss
app/assets/stylesheets/pages/boards.scss
+92
-12
app/finders/issuable_finder.rb
app/finders/issuable_finder.rb
+11
-0
app/finders/issues_finder.rb
app/finders/issues_finder.rb
+8
-22
app/finders/merge_requests_finder.rb
app/finders/merge_requests_finder.rb
+0
-11
app/models/concerns/milestoneish.rb
app/models/concerns/milestoneish.rb
+1
-1
app/models/global_milestone.rb
app/models/global_milestone.rb
+2
-2
app/models/issue.rb
app/models/issue.rb
+3
-5
app/models/user.rb
app/models/user.rb
+1
-1
app/services/issues/base_service.rb
app/services/issues/base_service.rb
+2
-2
app/services/issues/export_csv_service.rb
app/services/issues/export_csv_service.rb
+3
-3
app/services/issues/update_service.rb
app/services/issues/update_service.rb
+1
-1
app/services/merge_requests/assign_issues_service.rb
app/services/merge_requests/assign_issues_service.rb
+1
-1
app/services/notification_recipient_service.rb
app/services/notification_recipient_service.rb
+1
-1
app/services/slash_commands/interpret_service.rb
app/services/slash_commands/interpret_service.rb
+8
-5
app/services/system_note_service.rb
app/services/system_note_service.rb
+1
-1
app/views/notify/_reassigned_issuable_email.text.erb
app/views/notify/_reassigned_issuable_email.text.erb
+0
-6
app/views/notify/new_mention_in_issue_email.html.haml
app/views/notify/new_mention_in_issue_email.html.haml
+1
-1
app/views/notify/reassigned_issue_email.html.haml
app/views/notify/reassigned_issue_email.html.haml
+0
-1
app/views/notify/reassigned_issue_email.text.erb
app/views/notify/reassigned_issue_email.text.erb
+6
-1
app/views/notify/reassigned_merge_request_email.html.haml
app/views/notify/reassigned_merge_request_email.html.haml
+9
-1
changelogs/unreleased/update-issue-board-cards-design.yml
changelogs/unreleased/update-issue-board-cards-design.yml
+4
-0
db/migrate/20170320171632_create_issue_assignees_table.rb
db/migrate/20170320171632_create_issue_assignees_table.rb
+1
-5
db/migrate/20170320173259_migrate_assignees.rb
db/migrate/20170320173259_migrate_assignees.rb
+15
-1
db/schema.rb
db/schema.rb
+1
-1
doc/api/issues.md
doc/api/issues.md
+18
-18
doc/user/project/integrations/webhooks.md
doc/user/project/integrations/webhooks.md
+4
-4
lib/api/v3/entities.rb
lib/api/v3/entities.rb
+7
-0
lib/api/v3/helpers.rb
lib/api/v3/helpers.rb
+8
-0
lib/api/v3/issues.rb
lib/api/v3/issues.rb
+17
-14
lib/api/v3/merge_requests.rb
lib/api/v3/merge_requests.rb
+1
-1
lib/api/v3/milestones.rb
lib/api/v3/milestones.rb
+2
-2
spec/controllers/projects/merge_requests_controller_spec.rb
spec/controllers/projects/merge_requests_controller_spec.rb
+1
-1
spec/features/atom/dashboard_issues_spec.rb
spec/features/atom/dashboard_issues_spec.rb
+2
-2
spec/features/atom/issues_spec.rb
spec/features/atom/issues_spec.rb
+2
-2
spec/features/boards/add_issues_modal_spec.rb
spec/features/boards/add_issues_modal_spec.rb
+9
-9
spec/features/issues/csv_spec.rb
spec/features/issues/csv_spec.rb
+1
-1
spec/features/unsubscribe_links_spec.rb
spec/features/unsubscribe_links_spec.rb
+1
-1
spec/models/concerns/elastic/issue_spec.rb
spec/models/concerns/elastic/issue_spec.rb
+3
-2
spec/models/concerns/issuable_spec.rb
spec/models/concerns/issuable_spec.rb
+0
-78
spec/models/issue_spec.rb
spec/models/issue_spec.rb
+40
-0
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+42
-0
spec/services/merge_requests/assign_issues_service_spec.rb
spec/services/merge_requests/assign_issues_service_spec.rb
+4
-4
spec/services/notification_service_spec.rb
spec/services/notification_service_spec.rb
+1
-1
spec/services/slash_commands/interpret_service_spec.rb
spec/services/slash_commands/interpret_service_spec.rb
+23
-0
spec/support/features/issuable_slash_commands_shared_examples.rb
...pport/features/issuable_slash_commands_shared_examples.rb
+2
-2
No files found.
app/assets/javascripts/boards/components/issue_card_inner.js
View file @
0d3ecf58
...
...
@@ -20,6 +20,7 @@ import eventHub from '../eventhub';
list
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
rootPath
:
{
type
:
String
,
...
...
@@ -31,7 +32,67 @@ import eventHub from '../eventhub';
default
:
false
,
},
},
data
()
{
return
{
limitBeforeCounter
:
3
,
maxRender
:
4
,
maxCounter
:
99
,
};
},
computed
:
{
numberOverLimit
()
{
return
this
.
issue
.
assignees
.
length
-
this
.
limitBeforeCounter
;
},
assigneeCounterTooltip
()
{
return
`
${
this
.
assigneeCounterLabel
}
more`
;
},
assigneeCounterLabel
()
{
if
(
this
.
numberOverLimit
>
this
.
maxCounter
)
{
return
`
${
this
.
maxCounter
}
+`
;
}
return
`+
${
this
.
numberOverLimit
}
`
;
},
shouldRenderCounter
()
{
if
(
this
.
issue
.
assignees
.
length
<=
this
.
maxRender
)
{
return
false
;
}
return
this
.
issue
.
assignees
.
length
>
this
.
numberOverLimit
;
},
cardUrl
()
{
return
`
${
this
.
issueLinkBase
}
/
${
this
.
issue
.
id
}
`
;
},
issueId
()
{
return
`#
${
this
.
issue
.
id
}
`
;
},
showLabelFooter
()
{
return
this
.
issue
.
labels
.
find
(
l
=>
this
.
showLabel
(
l
))
!==
undefined
;
},
},
methods
:
{
isIndexLessThanlimit
(
index
)
{
return
index
<
this
.
limitBeforeCounter
;
},
shouldRenderAssignee
(
index
)
{
// Eg. maxRender is 4,
// Render up to all 4 assignees if there are only 4 assigness
// Otherwise render up to the limitBeforeCounter
if
(
this
.
issue
.
assignees
.
length
<=
this
.
maxRender
)
{
return
index
<
this
.
maxRender
;
}
return
index
<
this
.
limitBeforeCounter
;
},
assigneeUrl
(
assignee
)
{
return
`
${
this
.
rootPath
}${
assignee
.
username
}
`
;
},
assigneeUrlTitle
(
assignee
)
{
return
`Assigned to
${
assignee
.
name
}
`
;
},
avatarUrlTitle
(
assignee
)
{
return
`Avatar for
${
assignee
.
name
}
`
;
},
showLabel
(
label
)
{
if
(
!
this
.
list
)
return
true
;
...
...
@@ -67,35 +128,55 @@ import eventHub from '../eventhub';
},
template
:
`
<div>
<h4 class="card-title">
<i
class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"></i>
<a
:href="issueLinkBase + '/' + issue.id"
:title="issue.title">
{{ issue.title }}
</a>
</h4>
<div class="card-footer">
<span
class="card-number"
v-if="issue.id">
#{{ issue.id }}
</span>
<a
class="card-assignee has-tooltip"
:href="rootPath + issue.assignee.username"
:title="'Assigned to ' + issue.assignee.name"
v-if="issue.assignee"
data-container="body">
<img
class="avatar avatar-inline s20"
:src="issue.assignee.avatar"
width="20"
height="20"
:alt="'Avatar for ' + issue.assignee.name" />
</a>
<div class="card-header">
<h4 class="card-title">
<i
class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"
aria-hidden="true"
/>
<a
class="js-no-trigger"
:href="cardUrl"
:title="issue.title">{{ issue.title }}</a>
<span
class="card-number"
v-if="issue.id"
>
{{ issueId }}
</span>
</h4>
<div class="card-assignee">
<a
class="has-tooltip js-no-trigger"
:href="assigneeUrl(assignee)"
:title="assigneeUrlTitle(assignee)"
v-for="(assignee, index) in issue.assignees"
v-if="shouldRenderAssignee(index)"
data-container="body"
data-placement="bottom"
>
<img
class="avatar avatar-inline s20"
:src="assignee.avatar"
width="20"
height="20"
:alt="avatarUrlTitle(assignee)"
/>
</a>
<span
class="avatar-counter has-tooltip"
:title="assigneeCounterTooltip"
v-if="shouldRenderCounter"
>
{{ assigneeCounterLabel }}
</span>
</div>
</div>
<div
class="card-footer"
v-if="showLabelFooter"
>
<button
class="label color-label has-tooltip"
v-for="label in issue.labels"
...
...
app/assets/javascripts/boards/models/issue.js
View file @
0d3ecf58
...
...
@@ -15,14 +15,9 @@ class ListIssue {
this
.
subscribed
=
obj
.
subscribed
;
this
.
labels
=
[];
this
.
selected
=
false
;
this
.
assignee
=
false
;
this
.
position
=
obj
.
relative_position
||
Infinity
;
this
.
milestone_id
=
obj
.
milestone_id
;
if
(
obj
.
assignee
)
{
this
.
assignee
=
new
ListUser
(
obj
.
assignee
);
}
if
(
obj
.
milestone
)
{
this
.
milestone
=
new
ListMilestone
(
obj
.
milestone
);
}
...
...
@@ -30,6 +25,8 @@ class ListIssue {
obj
.
labels
.
forEach
((
label
)
=>
{
this
.
labels
.
push
(
new
ListLabel
(
label
));
});
this
.
assignees
=
obj
.
assignees
.
map
(
a
=>
new
ListUser
(
a
));
}
addLabel
(
label
)
{
...
...
app/assets/stylesheets/pages/boards.scss
View file @
0d3ecf58
...
...
@@ -226,7 +226,7 @@
.card
{
position
:
relative
;
padding
:
1
0
px
$gl-padding
;
padding
:
1
1px
10px
11
px
$gl-padding
;
background
:
$white-light
;
border-radius
:
$border-radius-default
;
box-shadow
:
0
1px
2px
$issue-boards-card-shadow
;
...
...
@@ -236,8 +236,13 @@
margin-bottom
:
5px
;
}
&
.is-active
{
&
.is-active
,
&
.is-active
.card-assignee
:hover
a
{
background-color
:
$row-hover
;
&
:first-child:not
(
:only-child
)
{
box-shadow
:
-10px
0
10px
1px
$row-hover
;
}
}
.label
{
...
...
@@ -246,36 +251,111 @@
}
.confidential-icon
{
position
:
relative
;
top
:
1px
;
margin-right
:
5px
;
}
}
.card-title
{
margin
:
0
;
margin
:
0
30px
0
0
;
font-size
:
1em
;
line-height
:
inherit
;
a
{
color
:
inherit
;
color
:
$gl-text-color
;
word-wrap
:
break-word
;
margin-right
:
2px
;
}
}
.card-
foot
er
{
margin-top
:
5p
x
;
line-height
:
25
px
;
.card-
head
er
{
display
:
fle
x
;
min-height
:
20
px
;
.label
{
margin-right
:
5px
;
font-size
:
(
14px
/
$issue-boards-font-size
)
*
1em
;
.card-assignee
{
display
:
flex
;
justify-content
:
flex-end
;
position
:
absolute
;
right
:
15px
;
height
:
20px
;
width
:
20px
;
.avatar-counter
{
display
:
none
;
vertical-align
:
middle
;
min-width
:
20px
;
line-height
:
19px
;
height
:
20px
;
padding-left
:
2px
;
padding-right
:
2px
;
border-radius
:
2em
;
}
img
{
vertical-align
:
top
;
}
a
{
position
:
relative
;
margin-left
:
-15px
;
}
a
:nth-child
(
1
)
{
z-index
:
3
;
}
a
:nth-child
(
2
)
{
z-index
:
2
;
}
a
:nth-child
(
3
)
{
z-index
:
1
;
}
a
:nth-child
(
4
)
{
display
:
none
;
}
&
:hover
{
.avatar-counter
{
display
:
inline-block
;
}
a
{
position
:
static
;
background-color
:
$white-light
;
transition
:
background-color
0s
;
margin-left
:
auto
;
&
:nth-child
(
4
)
{
display
:
block
;
}
&
:first-child:not
(
:only-child
)
{
box-shadow
:
-10px
0
10px
1px
$white-light
;
}
}
}
}
.avatar
{
margin-left
:
0
;
margin
:
0
;
}
}
.card-footer
{
margin
:
0
0
5px
;
.label
{
margin-top
:
5px
;
margin-right
:
6px
;
}
}
.card-number
{
margin-right
:
5px
;
font-size
:
12px
;
color
:
$gl-text-color-secondary
;
}
.issue-boards-search
{
...
...
app/finders/issuable_finder.rb
View file @
0d3ecf58
...
...
@@ -231,6 +231,17 @@ class IssuableFinder
klass
.
all
end
def
by_scope
(
items
)
case
params
[
:scope
]
when
'created-by-me'
,
'authored'
items
.
where
(
author_id:
current_user
.
id
)
when
'assigned-to-me'
items
.
assigned_to
(
current_user
)
else
items
end
end
def
by_state
(
items
)
case
params
[
:state
].
to_s
when
'closed'
...
...
app/finders/issues_finder.rb
View file @
0d3ecf58
...
...
@@ -28,40 +28,26 @@ class IssuesFinder < IssuableFinder
def
by_assignee
(
items
)
if
assignee
items
=
items
.
where
(
"issue_assignees.user_id = ?"
,
assignee
.
id
)
items
.
assigned_to
(
assignee
)
elsif
no_assignee?
items
=
items
.
where
(
"issue_assignees.user_id is NULL"
)
items
.
unassigned
elsif
assignee_id?
||
assignee_username?
# assignee not found
items
=
items
.
none
end
items
end
def
by_scope
(
items
)
case
params
[
:scope
]
when
'created-by-me'
,
'authored'
items
.
where
(
author_id:
current_user
.
id
)
when
'assigned-to-me'
items
.
where
(
"issue_assignees.user_id = ?"
,
current_user
.
id
)
items
.
none
else
items
end
end
def
self
.
not_restricted_by_confidentiality
(
user
)
issues
=
Issue
.
with_assignees
return
issues
.
where
(
'issues.confidential IS NULL OR issues.confidential IS FALSE'
)
if
user
.
blank?
return
Issue
.
where
(
'issues.confidential IS NOT TRUE'
)
if
user
.
blank?
return
issues
.
all
if
user
.
admin_or_auditor?
return
Issue
.
all
if
user
.
admin_or_auditor?
issues
.
where
(
'
issues.confidential IS NULL
OR issues.confidential IS FALSE
Issue
.
where
(
'
issues.confidential IS NOT TRUE
OR (issues.confidential = TRUE
AND (issues.author_id = :user_id
OR
issue_assignees.user_id = :user_id
OR
EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = :user_id AND issue_id = issues.id)
OR issues.project_id IN(:project_ids)))'
,
user_id:
user
.
id
,
project_ids:
user
.
authorized_projects
(
Gitlab
::
Access
::
REPORTER
).
select
(
:id
))
...
...
app/finders/merge_requests_finder.rb
View file @
0d3ecf58
...
...
@@ -23,17 +23,6 @@ class MergeRequestsFinder < IssuableFinder
private
def
by_scope
(
items
)
case
params
[
:scope
]
when
'created-by-me'
,
'authored'
items
.
where
(
author_id:
current_user
.
id
)
when
'assigned-to-me'
items
.
where
(
assignee_id:
current_user
.
id
)
else
items
end
end
def
item_project_ids
(
items
)
items
&
.
reorder
(
nil
)
&
.
select
(
:target_project_id
)
end
...
...
app/models/concerns/milestoneish.rb
View file @
0d3ecf58
...
...
@@ -40,7 +40,7 @@ module Milestoneish
def
issues_visible_to_user
(
user
)
memoize_per_user
(
user
,
:issues_visible_to_user
)
do
IssuesFinder
.
new
(
user
,
issues_finder_params
)
.
execute
.
where
(
milestone_id:
milestoneish_ids
)
.
execute
.
includes
(
:assignees
).
where
(
milestone_id:
milestoneish_ids
)
end
end
...
...
app/models/global_milestone.rb
View file @
0d3ecf58
...
...
@@ -36,7 +36,7 @@ class GlobalMilestone
closed
=
count_by_state
(
milestones_by_state_and_title
,
'closed'
)
all
=
milestones_by_state_and_title
.
map
{
|
(
_
,
title
),
_
|
title
}.
uniq
.
count
{
{
opened:
opened
,
closed:
closed
,
all:
all
...
...
@@ -94,7 +94,7 @@ class GlobalMilestone
end
def
participants
@participants
||=
milestones
.
includes
(
:participants
).
map
(
&
:participants
).
flatten
.
compact
.
uniq
@participants
||=
milestones
.
map
(
&
:participants
).
flatten
.
uniq
end
def
labels
...
...
app/models/issue.rb
View file @
0d3ecf58
...
...
@@ -34,13 +34,11 @@ class Issue < ActiveRecord::Base
validates
:project
,
presence:
true
scope
:cared
,
->
(
user
)
{
with_assignees
.
where
(
"issue_assignees.user_id IN(?)"
,
user
.
id
)
}
scope
:open_for
,
->
(
user
)
{
opened
.
assigned_to
(
user
)
}
scope
:in_projects
,
->
(
project_ids
)
{
where
(
project_id:
project_ids
)
}
scope
:with_assignees
,
->
{
joins
(
"LEFT JOIN issue_assignees ON issue_id = issues.id"
)
}
scope
:assigned
,
->
{
with_assignees
.
where
(
'issue_assignees.user_id IS NOT NULL'
)
}
scope
:unassigned
,
->
{
with_assignees
.
where
(
'issue_assignees.user_id IS NULL'
)
}
scope
:assigned_to
,
->
(
u
)
{
with_assignees
.
where
(
'issue_assignees.user_id = ?'
,
u
.
id
)}
scope
:assigned
,
->
{
where
(
'EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)'
)
}
scope
:unassigned
,
->
{
where
(
'NOT EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)'
)
}
scope
:assigned_to
,
->
(
u
)
{
where
(
'EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = ? AND issue_id = issues.id)'
,
u
.
id
)}
scope
:without_due_date
,
->
{
where
(
due_date:
nil
)
}
scope
:due_before
,
->
(
date
)
{
where
(
'issues.due_date < ?'
,
date
)
}
...
...
app/models/user.rb
View file @
0d3ecf58
...
...
@@ -106,7 +106,7 @@ class User < ActiveRecord::Base
has_many
:protected_branch_push_access_levels
,
dependent: :destroy
,
class_name:
ProtectedBranch
::
PushAccessLevel
has_many
:triggers
,
dependent: :destroy
,
class_name:
'Ci::Trigger'
,
foreign_key: :owner_id
has_many
:issue_assignees
,
dependent: :destroy
has_many
:issue_assignees
has_many
:assigned_issues
,
class_name:
"Issue"
,
through: :issue_assignees
,
source: :issue
has_many
:assigned_merge_requests
,
dependent: :nullify
,
foreign_key: :assignee_id
,
class_name:
"MergeRequest"
...
...
app/services/issues/base_service.rb
View file @
0d3ecf58
...
...
@@ -22,9 +22,9 @@ module Issues
end
def
filter_assignee
(
issuable
)
return
if
params
[
:assignee_ids
].
to_a
.
empty
?
return
if
params
[
:assignee_ids
].
blank
?
assignee_ids
=
params
[
:assignee_ids
].
select
{
|
assignee_id
|
assignee_can_read?
(
issuable
,
assignee_id
)
}
assignee_ids
=
params
[
:assignee_ids
].
select
{
|
assignee_id
|
assignee_can_read?
(
issuable
,
assignee_id
)
}
if
params
[
:assignee_ids
].
map
(
&
:to_s
)
==
[
IssuableFinder
::
NONE
]
params
[
:assignee_ids
]
=
[]
...
...
app/services/issues/export_csv_service.rb
View file @
0d3ecf58
...
...
@@ -21,7 +21,7 @@ module Issues
def
csv_builder
@csv_builder
||=
CsvBuilder
.
new
(
@issues
.
includes
(
:author
),
header_to_value_hash
)
CsvBuilder
.
new
(
@issues
.
includes
(
:author
,
:assignees
),
header_to_value_hash
)
end
private
...
...
@@ -35,8 +35,8 @@ module Issues
'Description'
=>
'description'
,
'Author'
=>
'author_name'
,
'Author Username'
=>
->
(
issue
)
{
issue
.
author
&
.
username
},
'Assignee'
=>
->
(
issue
)
{
issue
.
assignees
.
pluck
(
:name
).
join
(
', '
)
},
'Assignee Username'
=>
->
(
issue
)
{
issue
.
assignees
.
pluck
(
:username
).
join
(
', '
)
},
'Assignee'
=>
->
(
issue
)
{
issue
.
assignees
.
map
(
&
:name
).
join
(
', '
)
},
'Assignee Username'
=>
->
(
issue
)
{
issue
.
assignees
.
map
(
&
:username
).
join
(
', '
)
},
'Confidential'
=>
->
(
issue
)
{
issue
.
confidential?
?
'Yes'
:
'No'
},
'Due Date'
=>
->
(
issue
)
{
issue
.
due_date
&
.
to_s
(
:csv
)
},
'Created At (UTC)'
=>
->
(
issue
)
{
issue
.
created_at
&
.
to_s
(
:csv
)
},
...
...
app/services/issues/update_service.rb
View file @
0d3ecf58
...
...
@@ -22,7 +22,7 @@ module Issues
end
if
issue
.
previous_changes
.
include?
(
'title'
)
||
issue
.
previous_changes
.
include?
(
'description'
)
issue
.
previous_changes
.
include?
(
'description'
)
todo_service
.
update_issue
(
issue
,
current_user
)
end
...
...
app/services/merge_requests/assign_issues_service.rb
View file @
0d3ecf58
...
...
@@ -4,7 +4,7 @@ module MergeRequests
@assignable_issues
||=
begin
if
current_user
==
merge_request
.
author
closes_issues
.
select
do
|
issue
|
!
issue
.
is_a?
(
ExternalIssue
)
&&
!
issue
.
assignees
.
any
?
&&
can?
(
current_user
,
:admin_issue
,
issue
)
!
issue
.
is_a?
(
ExternalIssue
)
&&
!
issue
.
assignees
.
present
?
&&
can?
(
current_user
,
:admin_issue
,
issue
)
end
else
[]
...
...
app/services/notification_recipient_service.rb
View file @
0d3ecf58
...
...
@@ -29,7 +29,7 @@ class NotificationRecipientService
recipients
<<
target
.
assignee
when
:reassign_issue
previous_assignees
=
Array
(
previous_assignee
)
recipients
.
concat
(
previous_assignees
)
if
previous_assignees
.
any?
recipients
.
concat
(
previous_assignees
)
recipients
.
concat
(
target
.
assignees
)
end
...
...
app/services/slash_commands/interpret_service.rb
View file @
0d3ecf58
...
...
@@ -86,15 +86,18 @@ module SlashCommands
current_user
.
can?
(
:"admin_
#{
issuable
.
to_ability_name
}
"
,
project
)
end
command
:assign
do
|
assignee_param
|
user
=
extract_references
(
assignee_param
,
:user
).
first
user
||=
User
.
find_by
(
username:
assignee_param
)
user_ids
=
extract_references
(
assignee_param
,
:user
).
map
(
&
:id
)
next
unless
user
if
user_ids
.
empty?
user_ids
=
User
.
where
(
username:
assignee_param
.
split
(
' '
).
map
(
&
:strip
)).
pluck
(
:id
)
end
next
if
user_ids
.
empty?
if
issuable
.
is_a?
(
Issue
)
@updates
[
:assignee_ids
]
=
[
user
.
id
]
@updates
[
:assignee_ids
]
=
user_ids
else
@updates
[
:assignee_id
]
=
user
.
id
@updates
[
:assignee_id
]
=
user
_ids
.
last
end
end
...
...
app/services/system_note_service.rb
View file @
0d3ecf58
...
...
@@ -54,7 +54,7 @@ module SystemNoteService
# issue - Issue object
# project - Project owning noteable
# author - User performing the change
# assignees - User being assigned, or nil
# assignees - User
s
being assigned, or nil
#
# Example Note text:
#
...
...
app/views/notify/_reassigned_issuable_email.text.erb
deleted
100644 → 0
View file @
a0f6bf9d
Reassigned Issue
<%=
@issue
.
iid
%>
<%=
url_for
([
@issue
.
project
.
namespace
.
becomes
(
Namespace
),
@issue
.
project
,
@issue
,
{
only_path:
false
}])
%>
Assignee changed
<%=
"from
#{
@previous_assignees
.
map
(
&
:name
).
to_sentence
}
"
if
@previous_assignees
.
any?
-%>
to
<%=
"
#{
@issue
.
assignees
.
any?
?
@issue
.
assignee_list
:
'Unassigned'
}
"
%>
app/views/notify/new_mention_in_issue_email.html.haml
View file @
0d3ecf58
...
...
@@ -7,6 +7,6 @@
-
if
@issue
.
description
=
markdown
(
@issue
.
description
,
pipeline: :email
,
author:
@issue
.
author
)
-
if
@issue
.
assignees
.
any
?
-
if
@issue
.
assignees
.
present
?
%p
Assignee:
#{
@issue
.
assignee_list
}
app/views/notify/reassigned_issue_email.html.haml
View file @
0d3ecf58
...
...
@@ -8,4 +8,3 @@
%strong
=
@issue
.
assignee_list
-
else
%strong
Unassigned
app/views/notify/reassigned_issue_email.text.erb
View file @
0d3ecf58
<%=
render
'reassigned_issuable_email'
,
issuable:
@issue
%>
Reassigned Issue
<%=
@issue
.
iid
%>
<%=
url_for
([
@issue
.
project
.
namespace
.
becomes
(
Namespace
),
@issue
.
project
,
@issue
,
{
only_path:
false
}])
%>
Assignee changed
<%=
"from
#{
@previous_assignees
.
map
(
&
:name
).
to_sentence
}
"
if
@previous_assignees
.
any?
-%>
to
<%=
"
#{
@issue
.
assignees
.
any?
?
@issue
.
assignee_list
:
'Unassigned'
}
"
%>
app/views/notify/reassigned_merge_request_email.html.haml
View file @
0d3ecf58
=
render
'reassigned_issuable_email'
,
issuable:
@merge_request
Reassigned Merge Request
#{
@merge_request
.
iid
}
=
url_for
([
@merge_request
.
project
.
namespace
.
becomes
(
Namespace
),
@merge_request
.
project
,
@merge_request
,
{
only_path:
false
}])
Assignee changed
-
if
@previous_assignee
from
#{
@previous_assignee
.
name
}
to
=
@merge_request
.
assignee_id
?
@merge_request
.
assignee_name
:
'Unassigned'
changelogs/unreleased/update-issue-board-cards-design.yml
0 → 100644
View file @
0d3ecf58
---
title
:
Update issue board cards design
merge_request
:
10353
author
:
db/migrate/20170320171632_create_issue_assignees_table.rb
View file @
0d3ecf58
...
...
@@ -26,7 +26,7 @@ class CreateIssueAssigneesTable < ActiveRecord::Migration
# disable_ddl_transaction!
def
up
create_table
:issue_assignees
do
|
t
|
create_table
:issue_assignees
,
id:
false
do
|
t
|
t
.
references
:user
,
foreign_key:
{
on_delete: :cascade
},
index:
true
,
null:
false
t
.
references
:issue
,
foreign_key:
{
on_delete: :cascade
},
null:
false
end
...
...
@@ -35,10 +35,6 @@ class CreateIssueAssigneesTable < ActiveRecord::Migration
end
def
down
if
index_exists?
(
:issue_assignees
,
name:
INDEX_NAME
)
remove_index
:issue_assignees
,
name:
INDEX_NAME
end
drop_table
:issue_assignees
end
end
db/migrate/20170320173259_migrate_assignees.rb
View file @
0d3ecf58
...
...
@@ -21,9 +21,23 @@ class MigrateAssignees < ActiveRecord::Migration
#
# To disable transactions uncomment the following line and remove these
# comments:
#
disable_ddl_transaction!
disable_ddl_transaction!
def
up
# Optimisation: this accounts for most of the invalid assignee IDs on GitLab.com
update_column_in_batches
(
:issues
,
:assignee_id
,
nil
)
do
|
table
,
query
|
query
.
where
(
table
[
:assignee_id
].
eq
(
0
))
end
users
=
Arel
::
Table
.
new
(
:users
)
update_column_in_batches
(
:issues
,
:assignee_id
,
nil
)
do
|
table
,
query
|
query
.
where
(
table
[
:assignee_id
].
not_eq
(
nil
)\
.
and
(
users
.
project
(
"true"
).
where
(
users
[
:id
].
eq
(
table
[
:assignee_id
])).
exists
.
not
))
end
execute
<<-
EOF
INSERT INTO issue_assignees(issue_id, user_id)
SELECT id, assignee_id FROM issues WHERE assignee_id IS NOT NULL
...
...
db/schema.rb
View file @
0d3ecf58
...
...
@@ -495,7 +495,7 @@ ActiveRecord::Schema.define(version: 20170320173259) do
add_index
"index_statuses"
,
[
"project_id"
],
name:
"index_index_statuses_on_project_id"
,
unique:
true
,
using: :btree
create_table
"issue_assignees"
,
force: :cascade
do
|
t
|
create_table
"issue_assignees"
,
id:
false
,
force: :cascade
do
|
t
|
t
.
integer
"user_id"
,
null:
false
t
.
integer
"issue_id"
,
null:
false
end
...
...
doc/api/issues.md
View file @
0d3ecf58
...
...
@@ -68,14 +68,14 @@ Example response:
"updated_at"
:
"2016-01-04T15:31:39.996Z"
},
"project_id"
:
1
,
"assignee
"
:
{
"assignee
s"
:
[
{
"state"
:
"active"
,
"id"
:
1
,
"name"
:
"Administrator"
,
"web_url"
:
"https://gitlab.example.com/root"
,
"avatar_url"
:
null
,
"username"
:
"root"
},
}
]
,
"updated_at"
:
"2016-01-04T15:31:51.081Z"
,
"id"
:
76
,
"title"
:
"Consequatur vero maxime deserunt laboriosam est voluptas dolorem."
,
...
...
@@ -150,14 +150,14 @@ Example response:
"description"
:
"Omnis vero earum sunt corporis dolor et placeat."
,
"state"
:
"closed"
,
"iid"
:
1
,
"assignee
"
:
{
"assignee
s"
:
[
{
"avatar_url"
:
null
,
"web_url"
:
"https://gitlab.example.com/lennie"
,
"state"
:
"active"
,
"username"
:
"lennie"
,
"id"
:
9
,
"name"
:
"Dr. Luella Kovacek"
},
}
]
,
"labels"
:
[],
"id"
:
41
,
"title"
:
"Ut commodi ullam eos dolores perferendis nihil sunt."
,
...
...
@@ -231,14 +231,14 @@ Example response:
"description"
:
"Omnis vero earum sunt corporis dolor et placeat."
,
"state"
:
"closed"
,
"iid"
:
1
,
"assignee
"
:
{
"assignee
s"
:
[
{
"avatar_url"
:
null
,
"web_url"
:
"https://gitlab.example.com/lennie"
,
"state"
:
"active"
,
"username"
:
"lennie"
,
"id"
:
9
,
"name"
:
"Dr. Luella Kovacek"
},
}
]
,
"labels"
:
[],
"id"
:
41
,
"title"
:
"Ut commodi ullam eos dolores perferendis nihil sunt."
,
...
...
@@ -297,14 +297,14 @@ Example response:
"description"
:
"Omnis vero earum sunt corporis dolor et placeat."
,
"state"
:
"closed"
,
"iid"
:
1
,
"assignee
"
:
{
"assignee
s"
:
[
{
"avatar_url"
:
null
,
"web_url"
:
"https://gitlab.example.com/lennie"
,
"state"
:
"active"
,
"username"
:
"lennie"
,
"id"
:
9
,
"name"
:
"Dr. Luella Kovacek"
},
}
]
,
"labels"
:
[],
"id"
:
41
,
"title"
:
"Ut commodi ullam eos dolores perferendis nihil sunt."
,
...
...
@@ -333,7 +333,7 @@ POST /projects/:id/issues
|
`title`
| string | yes | The title of an issue |
|
`description`
| string | no | The description of an issue |
|
`confidential`
| boolean | no | Set an issue to be confidential. Default is
`false`
. |
|
`assignee_id
`
| integer
| no | The ID of a user to assign issue |
|
`assignee_id
s`
| Array[integer]
| no | The ID of a user to assign issue |
|
`milestone_id`
| integer | no | The ID of a milestone to assign issue |
|
`labels`
| string | no | Comma-separated label names for an issue |
|
`created_at`
| string | no | Date time string, ISO 8601 formatted, e.g.
`2016-03-11T03:45:40Z`
(requires admin or project owner rights) |
...
...
@@ -356,7 +356,7 @@ Example response:
"iid"
:
14
,
"title"
:
"Issues with auth"
,
"state"
:
"opened"
,
"assignee
"
:
null
,
"assignee
s"
:
[]
,
"labels"
:
[
"bug"
],
...
...
@@ -396,7 +396,7 @@ PUT /projects/:id/issues/:issue_iid
|
`title`
| string | no | The title of an issue |
|
`description`
| string | no | The description of an issue |
|
`confidential`
| boolean | no | Updates an issue to be confidential |
|
`assignee_id
`
| integer
| no | The ID of a user to assign the issue to |
|
`assignee_id
s`
| Array[integer]
| no | The ID of a user to assign the issue to |
|
`milestone_id`
| integer | no | The ID of a milestone to assign the issue to |
|
`labels`
| string | no | Comma-separated label names for an issue |
|
`state_event`
| string | no | The state event of an issue. Set
`close`
to close the issue and
`reopen`
to reopen it |
...
...
@@ -431,7 +431,7 @@ Example response:
"bug"
],
"id"
:
85
,
"assignee
"
:
null
,
"assignee
s"
:
[]
,
"milestone"
:
null
,
"subscribed"
:
true
,
"user_notes_count"
:
0
,
...
...
@@ -496,14 +496,14 @@ Example response:
"updated_at"
:
"2016-04-07T12:20:17.596Z"
,
"labels"
:
[],
"milestone"
:
null
,
"assignee
"
:
{
"assignee
s"
:
[
{
"name"
:
"Miss Monserrate Beier"
,
"username"
:
"axel.block"
,
"id"
:
12
,
"state"
:
"active"
,
"avatar_url"
:
"http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon"
,
"web_url"
:
"https://gitlab.example.com/axel.block"
},
}
]
,
"author"
:
{
"name"
:
"Kris Steuber"
,
"username"
:
"solon.cremin"
,
...
...
@@ -552,14 +552,14 @@ Example response:
"updated_at"
:
"2016-04-07T12:20:17.596Z"
,
"labels"
:
[],
"milestone"
:
null
,
"assignee
"
:
{
"assignee
s"
:
[
{
"name"
:
"Miss Monserrate Beier"
,
"username"
:
"axel.block"
,
"id"
:
12
,
"state"
:
"active"
,
"avatar_url"
:
"http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon"
,
"web_url"
:
"https://gitlab.example.com/axel.block"
},
}
]
,
"author"
:
{
"name"
:
"Kris Steuber"
,
"username"
:
"solon.cremin"
,
...
...
@@ -656,14 +656,14 @@ Example response:
"updated_at"
:
"2016-06-17T07:47:33.832Z"
,
"due_date"
:
null
},
"assignee
"
:
{
"assignee
s"
:
[
{
"name"
:
"Jarret O'Keefe"
,
"username"
:
"francisca"
,
"id"
:
14
,
"state"
:
"active"
,
"avatar_url"
:
"http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon"
,
"web_url"
:
"https://gitlab.example.com/francisca"
},
}
]
,
"author"
:
{
"name"
:
"Maxie Medhurst"
,
"username"
:
"craig_rutherford"
,
...
...
doc/user/project/integrations/webhooks.md
View file @
0d3ecf58
...
...
@@ -232,7 +232,7 @@ X-Gitlab-Event: Issue Hook
"object_attributes"
:
{
"id"
:
301
,
"title"
:
"New API: create/update/delete file"
,
"assignee_id
"
:
51
,
"assignee_id
s"
:
[
51
]
,
"author_id"
:
51
,
"project_id"
:
14
,
"created_at"
:
"2013-12-03T17:15:43Z"
,
...
...
@@ -246,11 +246,11 @@ X-Gitlab-Event: Issue Hook
"url"
:
"http://example.com/diaspora/issues/23"
,
"action"
:
"open"
},
"assignee
"
:
{
"assignee
s"
:
[
{
"name"
:
"User1"
,
"username"
:
"user1"
,
"avatar_url"
:
"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40
\u
0026d=identicon"
},
}
]
,
"labels"
:
[{
"id"
:
206
,
"title"
:
"API"
,
...
...
@@ -544,7 +544,7 @@ X-Gitlab-Event: Note Hook
"issue"
:
{
"id"
:
92
,
"title"
:
"test"
,
"assignee_id
"
:
null
,
"assignee_id
s"
:
[]
,
"author_id"
:
1
,
"project_id"
:
5
,
"created_at"
:
"2015-04-12 14:53:17 UTC"
,
...
...
lib/api/v3/entities.rb
View file @
0d3ecf58
...
...
@@ -295,6 +295,13 @@ module API
expose
:project_id
,
:issues_events
,
:merge_requests_events
expose
:note_events
,
:build_events
,
:pipeline_events
,
:wiki_page_events
end
class
Issue
<
::
API
::
Entities
::
Issue
unexpose
:assignees
expose
:assignee
do
|
issue
,
options
|
::
API
::
Entities
::
UserBasic
.
represent
(
issue
.
assignees
.
first
,
options
)
end
end
end
end
end
lib/api/v3/helpers.rb
View file @
0d3ecf58
...
...
@@ -14,6 +14,14 @@ module API
authorize!
access_level
,
merge_request
merge_request
end
def
convert_parameters_from_legacy_format
(
params
)
if
params
[
:assignee_id
].
present?
params
[
:assignee_ids
]
=
[
params
.
delete
(
:assignee_id
)]
end
params
end
end
end
end
lib/api/v3/issues.rb
View file @
0d3ecf58
...
...
@@ -8,6 +8,7 @@ module API
helpers
do
def
find_issues
(
args
=
{})
args
=
params
.
merge
(
args
)
args
=
convert_parameters_from_legacy_format
(
args
)
args
.
delete
(
:id
)
args
[
:milestone_title
]
=
args
.
delete
(
:milestone
)
...
...
@@ -53,7 +54,7 @@ module API
resource
:issues
do
desc
"Get currently authenticated user's issues"
do
success
::
API
::
Entities
::
Issue
success
::
API
::
V3
::
Entities
::
Issue
end
params
do
optional
:state
,
type:
String
,
values:
%w[opened closed all]
,
default:
'all'
,
...
...
@@ -62,7 +63,7 @@ module API
end
get
do
issues
=
find_issues
(
scope:
'authored'
)
present
paginate
(
issues
),
with:
::
API
::
Entities
::
Issue
,
current_user:
current_user
present
paginate
(
issues
),
with:
::
API
::
V3
::
Entities
::
Issue
,
current_user:
current_user
end
end
...
...
@@ -71,7 +72,7 @@ module API
end
resource
:groups
,
requirements:
{
id:
%r{[^/]+}
}
do
desc
'Get a list of group issues'
do
success
::
API
::
Entities
::
Issue
success
::
API
::
V3
::
Entities
::
Issue
end
params
do
optional
:state
,
type:
String
,
values:
%w[opened closed all]
,
default:
'opened'
,
...
...
@@ -83,7 +84,7 @@ module API
issues
=
find_issues
(
group_id:
group
.
id
,
state:
params
[
:state
]
||
'opened'
,
match_all_labels:
true
)
present
paginate
(
issues
),
with:
::
API
::
Entities
::
Issue
,
current_user:
current_user
present
paginate
(
issues
),
with:
::
API
::
V3
::
Entities
::
Issue
,
current_user:
current_user
end
end
...
...
@@ -95,7 +96,7 @@ module API
desc
'Get a list of project issues'
do
detail
'iid filter is deprecated have been removed on V4'
success
::
API
::
Entities
::
Issue
success
::
API
::
V3
::
Entities
::
Issue
end
params
do
optional
:state
,
type:
String
,
values:
%w[opened closed all]
,
default:
'all'
,
...
...
@@ -108,22 +109,22 @@ module API
issues
=
find_issues
(
project_id:
project
.
id
)
present
paginate
(
issues
),
with:
::
API
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
present
paginate
(
issues
),
with:
::
API
::
V3
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
end
desc
'Get a single project issue'
do
success
::
API
::
Entities
::
Issue
success
::
API
::
V3
::
Entities
::
Issue
end
params
do
requires
:issue_id
,
type:
Integer
,
desc:
'The ID of a project issue'
end
get
":id/issues/:issue_id"
do
issue
=
find_project_issue
(
params
[
:issue_id
])
present
issue
,
with:
::
API
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
present
issue
,
with:
::
API
::
V3
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
end
desc
'Create a new project issue'
do
success
::
API
::
Entities
::
Issue
success
::
API
::
V3
::
Entities
::
Issue
end
params
do
requires
:title
,
type:
String
,
desc:
'The title of an issue'
...
...
@@ -141,6 +142,7 @@ module API
issue_params
=
declared_params
(
include_missing:
false
)
issue_params
=
issue_params
.
merge
(
merge_request_to_resolve_discussions_of:
issue_params
.
delete
(
:merge_request_for_resolving_discussions
))
issue_params
=
convert_parameters_from_legacy_format
(
issue_params
)
issue
=
::
Issues
::
CreateService
.
new
(
user_project
,
current_user
,
...
...
@@ -148,14 +150,14 @@ module API
render_spam_error!
if
issue
.
spam?
if
issue
.
valid?
present
issue
,
with:
::
API
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
present
issue
,
with:
::
API
::
V3
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
else
render_validation_error!
(
issue
)
end
end
desc
'Update an existing issue'
do
success
::
API
::
Entities
::
Issue
success
::
API
::
V3
::
Entities
::
Issue
end
params
do
requires
:issue_id
,
type:
Integer
,
desc:
'The ID of a project issue'
...
...
@@ -178,6 +180,7 @@ module API
end
update_params
=
declared_params
(
include_missing:
false
).
merge
(
request:
request
,
api:
true
)
update_params
=
convert_parameters_from_legacy_format
(
update_params
)
issue
=
::
Issues
::
UpdateService
.
new
(
user_project
,
current_user
,
...
...
@@ -186,14 +189,14 @@ module API
render_spam_error!
if
issue
.
spam?
if
issue
.
valid?
present
issue
,
with:
::
API
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
present
issue
,
with:
::
API
::
V3
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
else
render_validation_error!
(
issue
)
end
end
desc
'Move an existing issue'
do
success
::
API
::
Entities
::
Issue
success
::
API
::
V3
::
Entities
::
Issue
end
params
do
requires
:issue_id
,
type:
Integer
,
desc:
'The ID of a project issue'
...
...
@@ -208,7 +211,7 @@ module API
begin
issue
=
::
Issues
::
MoveService
.
new
(
user_project
,
current_user
).
execute
(
issue
,
new_project
)
present
issue
,
with:
::
API
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
present
issue
,
with:
::
API
::
V3
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
rescue
::
Issues
::
MoveService
::
MoveError
=>
error
render_api_error!
(
error
.
message
,
400
)
end
...
...
lib/api/v3/merge_requests.rb
View file @
0d3ecf58
...
...
@@ -32,7 +32,7 @@ module API
if
project
.
has_external_issue_tracker?
::
API
::
Entities
::
ExternalIssue
else
::
API
::
Entities
::
Issue
::
API
::
V3
::
Entities
::
Issue
end
end
...
...
lib/api/v3/milestones.rb
View file @
0d3ecf58
...
...
@@ -39,7 +39,7 @@ module API
end
desc
'Get all issues for a single project milestone'
do
success
::
API
::
Entities
::
Issue
success
::
API
::
V3
::
Entities
::
Issue
end
params
do
requires
:milestone_id
,
type:
Integer
,
desc:
'The ID of a project milestone'
...
...
@@ -56,7 +56,7 @@ module API
}
issues
=
IssuesFinder
.
new
(
current_user
,
finder_params
).
execute
present
paginate
(
issues
),
with:
::
API
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
present
paginate
(
issues
),
with:
::
API
::
V3
::
Entities
::
Issue
,
current_user:
current_user
,
project:
user_project
end
end
end
...
...
spec/controllers/projects/merge_requests_controller_spec.rb
View file @
0d3ecf58
...
...
@@ -1329,7 +1329,7 @@ describe Projects::MergeRequestsController do
end
it
'correctly pluralizes flash message on success'
do
issue2
.
update!
(
assignees:
[
user
])
issue2
.
assignees
=
[
user
]
post_assign_issues
...
...
spec/features/atom/dashboard_issues_spec.rb
View file @
0d3ecf58
...
...
@@ -41,7 +41,7 @@ describe "Dashboard Issues Feed", feature: true do
expect
(
entry
).
to
be_present
expect
(
entry
).
to
have_selector
(
'author email'
,
text:
issue2
.
author_public_email
)
expect
(
entry
).
to
have_selector
(
'assignees email'
,
text:
issue2
.
assignees
.
first
.
public_email
)
expect
(
entry
).
to
have_selector
(
'assignees email'
,
text:
assignee
.
public_email
)
expect
(
entry
).
not_to
have_selector
(
'labels'
)
expect
(
entry
).
not_to
have_selector
(
'milestone'
)
expect
(
entry
).
to
have_selector
(
'description'
,
text:
issue2
.
description
)
...
...
@@ -64,7 +64,7 @@ describe "Dashboard Issues Feed", feature: true do
expect
(
entry
).
to
be_present
expect
(
entry
).
to
have_selector
(
'author email'
,
text:
issue1
.
author_public_email
)
expect
(
entry
).
to
have_selector
(
'assignees email'
,
text:
issue1
.
assignees
.
first
.
public_email
)
expect
(
entry
).
to
have_selector
(
'assignees email'
,
text:
assignee
.
public_email
)
expect
(
entry
).
to
have_selector
(
'labels label'
,
text:
label1
.
title
)
expect
(
entry
).
to
have_selector
(
'milestone'
,
text:
milestone1
.
title
)
expect
(
entry
).
not_to
have_selector
(
'description'
)
...
...
spec/features/atom/issues_spec.rb
View file @
0d3ecf58
...
...
@@ -22,7 +22,7 @@ describe 'Issues Feed', feature: true do
to
have_content
(
'application/atom+xml'
)
expect
(
body
).
to
have_selector
(
'title'
,
text:
"
#{
project
.
name
}
issues"
)
expect
(
body
).
to
have_selector
(
'author email'
,
text:
issue
.
author_public_email
)
expect
(
body
).
to
have_selector
(
'assignee email'
,
text:
issue
.
author_public_email
)
expect
(
body
).
to
have_selector
(
'assignee
s
email'
,
text:
issue
.
author_public_email
)
expect
(
body
).
to
have_selector
(
'entry summary'
,
text:
issue
.
title
)
end
end
...
...
@@ -36,7 +36,7 @@ describe 'Issues Feed', feature: true do
to
have_content
(
'application/atom+xml'
)
expect
(
body
).
to
have_selector
(
'title'
,
text:
"
#{
project
.
name
}
issues"
)
expect
(
body
).
to
have_selector
(
'author email'
,
text:
issue
.
author_public_email
)
expect
(
body
).
to
have_selector
(
'assignee email'
,
text:
issue
.
author_public_email
)
expect
(
body
).
to
have_selector
(
'assignee
s
email'
,
text:
issue
.
author_public_email
)
expect
(
body
).
to
have_selector
(
'entry summary'
,
text:
issue
.
title
)
end
end
...
...
spec/features/boards/add_issues_modal_spec.rb
View file @
0d3ecf58
...
...
@@ -131,7 +131,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
context
'selecing issues'
do
it
'selects single issue'
do
page
.
within
(
'.add-issues-modal'
)
do
first
(
'.card'
).
click
first
(
'.card
.card-number
'
).
click
page
.
within
(
'.nav-links'
)
do
expect
(
page
).
to
have_content
(
'Selected issues 1'
)
...
...
@@ -141,7 +141,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
it
'changes button text'
do
page
.
within
(
'.add-issues-modal'
)
do
first
(
'.card'
).
click
first
(
'.card
.card-number
'
).
click
expect
(
first
(
'.add-issues-footer .btn'
)).
to
have_content
(
'Add 1 issue'
)
end
...
...
@@ -149,7 +149,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
it
'changes button text with plural'
do
page
.
within
(
'.add-issues-modal'
)
do
all
(
'.card'
).
each
do
|
el
|
all
(
'.card
.card-number
'
).
each
do
|
el
|
el
.
click
end
...
...
@@ -159,7 +159,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
it
'shows only selected issues on selected tab'
do
page
.
within
(
'.add-issues-modal'
)
do
first
(
'.card'
).
click
first
(
'.card
.card-number
'
).
click
click_link
'Selected issues'
...
...
@@ -189,7 +189,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
it
'selects all that arent already selected'
do
page
.
within
(
'.add-issues-modal'
)
do
first
(
'.card'
).
click
first
(
'.card
.card-number
'
).
click
expect
(
page
).
to
have_selector
(
'.is-active'
,
count:
1
)
...
...
@@ -201,11 +201,11 @@ describe 'Issue Boards add issue modal', :feature, :js do
it
'unselects from selected tab'
do
page
.
within
(
'.add-issues-modal'
)
do
first
(
'.card'
).
click
first
(
'.card
.card-number
'
).
click
click_link
'Selected issues'
first
(
'.card'
).
click
first
(
'.card
.card-number
'
).
click
expect
(
page
).
not_to
have_selector
(
'.is-active'
)
end
...
...
@@ -215,7 +215,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
context
'adding issues'
do
it
'adds to board'
do
page
.
within
(
'.add-issues-modal'
)
do
first
(
'.card'
).
click
first
(
'.card
.card-number
'
).
click
click_button
'Add 1 issue'
end
...
...
@@ -227,7 +227,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
it
'adds to second list'
do
page
.
within
(
'.add-issues-modal'
)
do
first
(
'.card'
).
click
first
(
'.card
.card-number
'
).
click
click_button
planning
.
title
...
...
spec/features/issues/csv_spec.rb
View file @
0d3ecf58
...
...
@@ -80,6 +80,6 @@ describe 'Issues csv', feature: true do
author:
user
,
milestone:
milestone
,
labels:
[
feature_label
,
idea_label
])
expect
{
request_csv
}.
not_to
exceed_query_limit
(
control_count
+
23
)
expect
{
request_csv
}.
not_to
exceed_query_limit
(
control_count
+
5
)
end
end
spec/features/unsubscribe_links_spec.rb
View file @
0d3ecf58
...
...
@@ -6,7 +6,7 @@ describe 'Unsubscribe links', feature: true do
let
(
:recipient
)
{
create
(
:user
)
}
let
(
:author
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:empty_project
,
:public
)
}
let
(
:params
)
{
{
title:
'A bug!'
,
description:
'Fix it!'
,
assignee
:
recipient
}
}
let
(
:params
)
{
{
title:
'A bug!'
,
description:
'Fix it!'
,
assignee
s:
[
recipient
]
}
}
let
(
:issue
)
{
Issues
::
CreateService
.
new
(
project
,
author
,
params
).
execute
}
let
(
:mail
)
{
ActionMailer
::
Base
.
deliveries
.
last
}
...
...
spec/models/concerns/elastic/issue_spec.rb
View file @
0d3ecf58
...
...
@@ -31,13 +31,14 @@ describe Issue, elastic: true do
end
it
"returns json with all needed elements"
do
issue
=
create
:issue
,
project:
project
assignee
=
create
(
:user
)
issue
=
create
:issue
,
project:
project
,
assignees:
[
assignee
]
expected_hash
=
issue
.
attributes
.
extract!
(
'id'
,
'iid'
,
'title'
,
'description'
,
'created_at'
,
'updated_at'
,
'state'
,
'project_id'
,
'author_id'
,
'confidential'
)
expected_hash
[
'assignee_id'
]
=
[]
expected_hash
[
'assignee_id'
]
=
[
assignee
.
id
]
expect
(
issue
.
as_indexed_json
).
to
eq
(
expected_hash
)
end
...
...
spec/models/concerns/issuable_spec.rb
View file @
0d3ecf58
...
...
@@ -56,84 +56,6 @@ describe Issue, "Issuable" do
end
end
describe
"before_save"
do
describe
"#update_cache_counts when an issue is reassigned"
do
context
"when previous assignee exists"
do
before
do
assignee
=
create
(
:user
)
issue
.
project
.
team
<<
[
assignee
,
:developer
]
issue
.
assignees
<<
assignee
end
it
"updates cache counts for new assignee"
do
user
=
create
(
:user
)
expect
(
user
).
to
receive
(
:update_cache_counts
)
issue
.
assignees
<<
user
end
it
"updates cache counts for previous assignee"
do
old_assignee
=
issue
.
assignees
.
first
expect_any_instance_of
(
User
).
to
receive
(
:update_cache_counts
)
issue
.
assignees
.
destroy_all
end
end
context
"when previous assignee does not exist"
do
before
{
issue
.
assignees
=
[]
}
it
"updates cache count for the new assignee"
do
expect_any_instance_of
(
User
).
to
receive
(
:update_cache_counts
)
issue
.
assignees
<<
user
end
end
end
describe
"#update_cache_counts when a merge request is reassigned"
do
let
(
:project
)
{
create
:project
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
}
context
"when previous assignee exists"
do
before
do
assignee
=
create
(
:user
)
project
.
team
<<
[
assignee
,
:developer
]
merge_request
.
update
(
assignee:
assignee
)
end
it
"updates cache counts for new assignee"
do
user
=
create
(
:user
)
expect
(
user
).
to
receive
(
:update_cache_counts
)
merge_request
.
update
(
assignee:
user
)
end
it
"updates cache counts for previous assignee"
do
old_assignee
=
merge_request
.
assignee
allow
(
User
).
to
receive
(
:find_by_id
).
with
(
old_assignee
.
id
).
and_return
(
old_assignee
)
expect
(
old_assignee
).
to
receive
(
:update_cache_counts
)
merge_request
.
update
(
assignee:
nil
)
end
end
context
"when previous assignee does not exist"
do
before
{
merge_request
.
update
(
assignee:
nil
)
}
it
"updates cache count for the new assignee"
do
expect_any_instance_of
(
User
).
to
receive
(
:update_cache_counts
)
merge_request
.
update
(
assignee:
user
)
end
end
end
end
describe
".search"
do
let!
(
:searchable_issue
)
{
create
(
:issue
,
title:
"Searchable issue"
)
}
...
...
spec/models/issue_spec.rb
View file @
0d3ecf58
...
...
@@ -38,6 +38,46 @@ describe Issue, models: true do
end
end
describe
"before_save"
do
describe
"#update_cache_counts when an issue is reassigned"
do
let
(
:issue
)
{
create
(
:issue
)
}
let
(
:assignee
)
{
create
(
:user
)
}
context
"when previous assignee exists"
do
before
do
issue
.
project
.
team
<<
[
assignee
,
:developer
]
issue
.
assignees
<<
assignee
end
it
"updates cache counts for new assignee"
do
user
=
create
(
:user
)
expect
(
user
).
to
receive
(
:update_cache_counts
)
issue
.
assignees
<<
user
end
it
"updates cache counts for previous assignee"
do
issue
.
assignees
.
first
expect_any_instance_of
(
User
).
to
receive
(
:update_cache_counts
)
issue
.
assignees
.
destroy_all
end
end
context
"when previous assignee does not exist"
do
it
"updates cache count for the new assignee"
do
issue
.
assignees
=
[]
expect_any_instance_of
(
User
).
to
receive
(
:update_cache_counts
)
issue
.
assignees
<<
assignee
end
end
end
end
describe
'#card_attributes'
do
it
'includes the author name'
do
allow
(
subject
).
to
receive
(
:author
).
and_return
(
double
(
name:
'Robert'
))
...
...
spec/models/merge_request_spec.rb
View file @
0d3ecf58
...
...
@@ -88,6 +88,48 @@ describe MergeRequest, models: true do
end
end
describe
"before_save"
do
describe
"#update_cache_counts when a merge request is reassigned"
do
let
(
:project
)
{
create
:project
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
}
let
(
:assignee
)
{
create
:user
}
context
"when previous assignee exists"
do
before
do
project
.
team
<<
[
assignee
,
:developer
]
merge_request
.
update
(
assignee:
assignee
)
end
it
"updates cache counts for new assignee"
do
user
=
create
(
:user
)
expect
(
user
).
to
receive
(
:update_cache_counts
)
merge_request
.
update
(
assignee:
user
)
end
it
"updates cache counts for previous assignee"
do
old_assignee
=
merge_request
.
assignee
allow
(
User
).
to
receive
(
:find_by_id
).
with
(
old_assignee
.
id
).
and_return
(
old_assignee
)
expect
(
old_assignee
).
to
receive
(
:update_cache_counts
)
merge_request
.
update
(
assignee:
nil
)
end
end
context
"when previous assignee does not exist"
do
it
"updates cache count for the new assignee"
do
merge_request
.
update
(
assignee:
nil
)
expect_any_instance_of
(
User
).
to
receive
(
:update_cache_counts
)
merge_request
.
update
(
assignee:
assignee
)
end
end
end
end
describe
'#card_attributes'
do
it
'includes the author name'
do
allow
(
subject
).
to
receive
(
:author
).
and_return
(
double
(
name:
'Robert'
))
...
...
spec/services/merge_requests/assign_issues_service_spec.rb
View file @
0d3ecf58
...
...
@@ -15,14 +15,14 @@ describe MergeRequests::AssignIssuesService, services: true do
expect
(
service
.
assignable_issues
.
map
(
&
:id
)).
to
include
(
issue
.
id
)
end
it
'ignores issues
already assigned to any user
'
do
issue
.
assignees
=
[
create
(
:user
)]
it
'ignores issues
the user cannot update assignee on
'
do
project
.
team
.
truncate
expect
(
service
.
assignable_issues
).
to
be_empty
end
it
'ignores issues
the user cannot update assignee on
'
do
project
.
team
.
truncate
it
'ignores issues
already assigned to any user
'
do
issue
.
assignees
=
[
create
(
:user
)]
expect
(
service
.
assignable_issues
).
to
be_empty
end
...
...
spec/services/notification_service_spec.rb
View file @
0d3ecf58
...
...
@@ -460,7 +460,7 @@ describe NotificationService, services: true do
it
do
notification
.
new_issue
(
issue
,
@u_disabled
)
should_email
(
issue
.
assignees
.
first
)
should_email
(
assignee
)
should_email
(
@u_watcher
)
should_email
(
@u_guest_watcher
)
should_email
(
@u_guest_custom
)
...
...
spec/services/slash_commands/interpret_service_spec.rb
View file @
0d3ecf58
...
...
@@ -3,6 +3,7 @@ require 'spec_helper'
describe
SlashCommands
::
InterpretService
,
services:
true
do
let
(
:project
)
{
create
(
:project
,
:public
)
}
let
(
:developer
)
{
create
(
:user
)
}
let
(
:developer2
)
{
create
(
:user
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:milestone
)
{
create
(
:milestone
,
project:
project
,
title:
'9.10'
)
}
let
(
:inprogress
)
{
create
(
:label
,
project:
project
,
title:
'In Progress'
)
}
...
...
@@ -388,6 +389,28 @@ describe SlashCommands::InterpretService, services: true do
end
end
context
'assign command with multiple assignees'
do
let
(
:content
)
{
"/assign @
#{
developer
.
username
}
@
#{
developer2
.
username
}
"
}
before
{
project
.
team
<<
[
developer2
,
:developer
]
}
context
'Issue'
do
it
'fetches assignee and populates assignee_id if content contains /assign'
do
_
,
updates
=
service
.
execute
(
content
,
issue
)
expect
(
updates
[
:assignee_ids
]).
to
match_array
([
developer
.
id
,
developer2
.
id
])
end
end
context
'Merge Request'
do
it
'fetches assignee and populates assignee_id if content contains /assign'
do
_
,
updates
=
service
.
execute
(
content
,
merge_request
)
expect
(
updates
).
to
eq
(
assignee_id:
developer
.
id
)
end
end
end
it_behaves_like
'empty command'
do
let
(
:content
)
{
'/assign @abcd1234'
}
let
(
:issuable
)
{
issue
}
...
...
spec/support/features/issuable_slash_commands_shared_examples.rb
View file @
0d3ecf58
...
...
@@ -63,7 +63,7 @@ shared_examples 'issuable record that supports slash commands in its description
note
=
issuable
.
notes
.
user
.
first
expect
(
note
.
note
).
to
eq
"Awesome!"
expect
(
issuable
.
assignee
).
to
eq
assignee
expect
(
issuable
.
assignee
s
).
to
eq
[
assignee
]
expect
(
issuable
.
labels
).
to
eq
[
label_bug
]
expect
(
issuable
.
milestone
).
to
eq
milestone
end
...
...
@@ -81,7 +81,7 @@ shared_examples 'issuable record that supports slash commands in its description
issuable
.
reload
expect
(
issuable
.
notes
.
user
).
to
be_empty
expect
(
issuable
.
assignee
).
to
eq
assignee
expect
(
issuable
.
assignee
s
).
to
eq
[
assignee
]
expect
(
issuable
.
labels
).
to
eq
[
label_bug
]
expect
(
issuable
.
milestone
).
to
eq
milestone
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