Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Jérome Perrin
gitlab-ce
Commits
57a44f2d
Commit
57a44f2d
authored
Jun 13, 2018
by
Jarka Kadlecová
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support todos for epics backport
parent
275fbf24
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
170 additions
and
29 deletions
+170
-29
app/finders/todos_finder.rb
app/finders/todos_finder.rb
+38
-5
app/helpers/todos_helper.rb
app/helpers/todos_helper.rb
+1
-1
app/models/group.rb
app/models/group.rb
+9
-0
app/models/todo.rb
app/models/todo.rb
+9
-2
app/services/todo_service.rb
app/services/todo_service.rb
+16
-15
db/migrate/20180608091413_add_group_to_todos.rb
db/migrate/20180608091413_add_group_to_todos.rb
+32
-0
db/schema.rb
db/schema.rb
+4
-1
lib/api/entities.rb
lib/api/entities.rb
+4
-3
spec/factories/todos.rb
spec/factories/todos.rb
+3
-2
spec/finders/todos_finder_spec.rb
spec/finders/todos_finder_spec.rb
+53
-0
spec/models/todo_spec.rb
spec/models/todo_spec.rb
+1
-0
No files found.
app/finders/todos_finder.rb
View file @
57a44f2d
...
...
@@ -15,6 +15,7 @@
class
TodosFinder
prepend
FinderWithCrossProjectAccess
include
FinderMethods
include
Gitlab
::
Utils
::
StrongMemoize
requires_cross_project_access
unless:
->
{
project?
}
...
...
@@ -34,9 +35,11 @@ class TodosFinder
items
=
by_author
(
items
)
items
=
by_state
(
items
)
items
=
by_type
(
items
)
items
=
by_group
(
items
)
# Filtering by project HAS TO be the last because we use
# the project IDs yielded by the todos query thus far
items
=
by_project
(
items
)
items
=
visible_to_user
(
items
)
sort
(
items
)
end
...
...
@@ -82,6 +85,10 @@ class TodosFinder
params
[
:project_id
].
present?
end
def
group?
params
[
:group_id
].
present?
end
def
project
return
@project
if
defined?
(
@project
)
...
...
@@ -100,6 +107,12 @@ class TodosFinder
@project
end
def
group
strong_memoize
(
:group
)
do
Group
.
find
(
params
[
:group_id
])
end
end
def
project_ids
(
items
)
ids
=
items
.
except
(
:order
).
select
(
:project_id
)
if
Gitlab
::
Database
.
mysql?
...
...
@@ -111,7 +124,7 @@ class TodosFinder
end
def
type?
type
.
present?
&&
%w(Issue MergeRequest)
.
include?
(
type
)
type
.
present?
&&
%w(Issue MergeRequest
Epic
)
.
include?
(
type
)
end
def
type
...
...
@@ -148,12 +161,32 @@ class TodosFinder
def
by_project
(
items
)
if
project?
items
.
where
(
project:
project
)
else
projects
=
Project
.
public_or_visible_to_user
(
current_user
)
items
=
items
.
where
(
project:
project
)
end
items
end
items
.
joins
(
:project
).
merge
(
projects
)
def
by_group
(
items
)
if
group?
items
=
items
.
where
(
group:
group
)
end
items
end
def
visible_to_user
(
items
)
projects
=
Project
.
public_or_visible_to_user
(
current_user
)
groups
=
Group
.
public_or_visible_to_user
(
current_user
)
items
.
joins
(
'LEFT JOIN namespaces ON namespaces.id = todos.group_id'
)
.
joins
(
'LEFT JOIN projects ON projects.id = todos.project_id'
)
.
where
(
'project_id IN (?) OR group_id IN (?)'
,
projects
.
map
(
&
:id
),
groups
.
map
(
&
:id
)
)
end
def
by_state
(
items
)
...
...
app/helpers/todos_helper.rb
View file @
57a44f2d
...
...
@@ -43,7 +43,7 @@ module TodosHelper
project_commit_path
(
todo
.
project
,
todo
.
target
,
anchor:
anchor
)
else
path
=
[
todo
.
p
roject
.
namespace
.
becomes
(
Namespace
),
todo
.
projec
t
,
todo
.
target
]
path
=
[
todo
.
p
aren
t
,
todo
.
target
]
path
.
unshift
(
:pipelines
)
if
todo
.
build_failed?
...
...
app/models/group.rb
View file @
57a44f2d
...
...
@@ -39,6 +39,8 @@ class Group < Namespace
has_many
:boards
has_many
:badges
,
class_name:
'GroupBadge'
has_many
:todos
accepts_nested_attributes_for
:variables
,
allow_destroy:
true
validate
:visibility_level_allowed_by_projects
...
...
@@ -82,6 +84,13 @@ class Group < Namespace
where
(
id:
user
.
authorized_groups
.
select
(
:id
).
reorder
(
nil
))
end
def
public_or_visible_to_user
(
user
)
where
(
'id IN (?) OR namespaces.visibility_level IN (?)'
,
user
.
authorized_groups
.
select
(
:id
),
Gitlab
::
VisibilityLevel
.
levels_for_user
(
user
)
)
end
def
select_for_project_authorization
if
current_scope
.
joins_values
.
include?
(
:shared_projects
)
joins
(
'INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id'
)
...
...
app/models/todo.rb
View file @
57a44f2d
...
...
@@ -22,15 +22,18 @@ class Todo < ActiveRecord::Base
belongs_to
:author
,
class_name:
"User"
belongs_to
:note
belongs_to
:project
belongs_to
:group
belongs_to
:target
,
polymorphic:
true
,
touch:
true
# rubocop:disable Cop/PolymorphicAssociations
belongs_to
:user
delegate
:name
,
:email
,
to: :author
,
prefix:
true
,
allow_nil:
true
validates
:action
,
:
project
,
:
target_type
,
:user
,
presence:
true
validates
:action
,
:target_type
,
:user
,
presence:
true
validates
:author
,
presence:
true
validates
:target_id
,
presence:
true
,
unless: :for_commit?
validates
:commit_id
,
presence:
true
,
if: :for_commit?
validates
:project
,
presence:
true
,
unless: :group
validates
:group
,
presence:
true
,
unless: :project
scope
:pending
,
->
{
with_state
(
:pending
)
}
scope
:done
,
->
{
with_state
(
:done
)
}
...
...
@@ -44,7 +47,7 @@ class Todo < ActiveRecord::Base
state
:done
end
after_save
:keep_around_commit
after_save
:keep_around_commit
,
if: :commit_id
class
<<
self
# Priority sorting isn't displayed in the dropdown, because we don't show
...
...
@@ -79,6 +82,10 @@ class Todo < ActiveRecord::Base
end
end
def
parent
project
||
group
end
def
unmergeable?
action
==
UNMERGEABLE
end
...
...
app/services/todo_service.rb
View file @
57a44f2d
...
...
@@ -260,15 +260,15 @@ class TodoService
end
end
def
create_mention_todos
(
p
rojec
t
,
target
,
author
,
note
=
nil
,
skip_users
=
[])
def
create_mention_todos
(
p
aren
t
,
target
,
author
,
note
=
nil
,
skip_users
=
[])
# Create Todos for directly addressed users
directly_addressed_users
=
filter_directly_addressed_users
(
p
rojec
t
,
note
||
target
,
author
,
skip_users
)
attributes
=
attributes_for_todo
(
p
rojec
t
,
target
,
author
,
Todo
::
DIRECTLY_ADDRESSED
,
note
)
directly_addressed_users
=
filter_directly_addressed_users
(
p
aren
t
,
note
||
target
,
author
,
skip_users
)
attributes
=
attributes_for_todo
(
p
aren
t
,
target
,
author
,
Todo
::
DIRECTLY_ADDRESSED
,
note
)
create_todos
(
directly_addressed_users
,
attributes
)
# Create Todos for mentioned users
mentioned_users
=
filter_mentioned_users
(
p
rojec
t
,
note
||
target
,
author
,
skip_users
)
attributes
=
attributes_for_todo
(
p
rojec
t
,
target
,
author
,
Todo
::
MENTIONED
,
note
)
mentioned_users
=
filter_mentioned_users
(
p
aren
t
,
note
||
target
,
author
,
skip_users
)
attributes
=
attributes_for_todo
(
p
aren
t
,
target
,
author
,
Todo
::
MENTIONED
,
note
)
create_todos
(
mentioned_users
,
attributes
)
end
...
...
@@ -299,36 +299,37 @@ class TodoService
def
attributes_for_todo
(
project
,
target
,
author
,
action
,
note
=
nil
)
attributes_for_target
(
target
).
merge!
(
project_id:
project
.
id
,
project_id:
project
&
.
id
,
group_id:
target
.
respond_to?
(
:group
)
?
target
.
group
.
id
:
nil
,
author_id:
author
.
id
,
action:
action
,
note:
note
)
end
def
filter_todo_users
(
users
,
p
rojec
t
,
target
)
reject_users_without_access
(
users
,
p
rojec
t
,
target
).
uniq
def
filter_todo_users
(
users
,
p
aren
t
,
target
)
reject_users_without_access
(
users
,
p
aren
t
,
target
).
uniq
end
def
filter_mentioned_users
(
p
rojec
t
,
target
,
author
,
skip_users
=
[])
def
filter_mentioned_users
(
p
aren
t
,
target
,
author
,
skip_users
=
[])
mentioned_users
=
target
.
mentioned_users
(
author
)
-
skip_users
filter_todo_users
(
mentioned_users
,
p
rojec
t
,
target
)
filter_todo_users
(
mentioned_users
,
p
aren
t
,
target
)
end
def
filter_directly_addressed_users
(
p
rojec
t
,
target
,
author
,
skip_users
=
[])
def
filter_directly_addressed_users
(
p
aren
t
,
target
,
author
,
skip_users
=
[])
directly_addressed_users
=
target
.
directly_addressed_users
(
author
)
-
skip_users
filter_todo_users
(
directly_addressed_users
,
p
rojec
t
,
target
)
filter_todo_users
(
directly_addressed_users
,
p
aren
t
,
target
)
end
def
reject_users_without_access
(
users
,
p
rojec
t
,
target
)
if
target
.
is_a?
(
Note
)
&&
(
target
.
for_issue?
||
target
.
for_merge_request?
)
def
reject_users_without_access
(
users
,
p
aren
t
,
target
)
if
target
.
is_a?
(
Note
)
&&
(
target
.
for_issue?
||
target
.
for_merge_request?
||
target
.
for_epic?
)
target
=
target
.
noteable
end
if
target
.
is_a?
(
Issuable
)
select_users
(
users
,
:"read_
#{
target
.
to_ability_name
}
"
,
target
)
else
select_users
(
users
,
:read_project
,
p
rojec
t
)
select_users
(
users
,
:read_project
,
p
aren
t
)
end
end
...
...
db/migrate/20180608091413_add_group_to_todos.rb
0 → 100644
View file @
57a44f2d
class
AddGroupToTodos
<
ActiveRecord
::
Migration
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
disable_ddl_transaction!
def
up
add_column
:todos
,
:group_id
,
:integer
add_foreign_key
:todos
,
:namespaces
,
column: :group_id
,
on_delete: :cascade
add_concurrent_index
:todos
,
:group_id
change_column_null
:todos
,
:project_id
,
true
end
def
down
return
unless
group_id_exists?
remove_foreign_key
:todos
,
column: :group_id
remove_index
:todos
,
:group_id
if
index_exists?
(
:todos
,
:group_id
)
remove_column
:todos
,
:group_id
execute
"DELETE FROM todos WHERE project_id IS NULL"
change_column_null
:todos
,
:project_id
,
false
end
private
def
group_id_exists?
column_exists?
(
:todos
,
:group_id
)
end
end
db/schema.rb
View file @
57a44f2d
...
...
@@ -1929,7 +1929,7 @@ ActiveRecord::Schema.define(version: 20180626125654) do
create_table
"todos"
,
force: :cascade
do
|
t
|
t
.
integer
"user_id"
,
null:
false
t
.
integer
"project_id"
,
null:
false
t
.
integer
"project_id"
t
.
integer
"target_id"
t
.
string
"target_type"
,
null:
false
t
.
integer
"author_id"
,
null:
false
...
...
@@ -1939,10 +1939,12 @@ ActiveRecord::Schema.define(version: 20180626125654) do
t
.
datetime
"updated_at"
t
.
integer
"note_id"
t
.
string
"commit_id"
t
.
integer
"group_id"
end
add_index
"todos"
,
[
"author_id"
],
name:
"index_todos_on_author_id"
,
using: :btree
add_index
"todos"
,
[
"commit_id"
],
name:
"index_todos_on_commit_id"
,
using: :btree
add_index
"todos"
,
[
"group_id"
],
name:
"index_todos_on_group_id"
,
using: :btree
add_index
"todos"
,
[
"note_id"
],
name:
"index_todos_on_note_id"
,
using: :btree
add_index
"todos"
,
[
"project_id"
],
name:
"index_todos_on_project_id"
,
using: :btree
add_index
"todos"
,
[
"target_type"
,
"target_id"
],
name:
"index_todos_on_target_type_and_target_id"
,
using: :btree
...
...
@@ -2313,6 +2315,7 @@ ActiveRecord::Schema.define(version: 20180626125654) do
add_foreign_key
"term_agreements"
,
"users"
,
on_delete: :cascade
add_foreign_key
"timelogs"
,
"issues"
,
name:
"fk_timelogs_issues_issue_id"
,
on_delete: :cascade
add_foreign_key
"timelogs"
,
"merge_requests"
,
name:
"fk_timelogs_merge_requests_merge_request_id"
,
on_delete: :cascade
add_foreign_key
"todos"
,
"namespaces"
,
column:
"group_id"
,
on_delete: :cascade
add_foreign_key
"todos"
,
"notes"
,
name:
"fk_91d1f47b13"
,
on_delete: :cascade
add_foreign_key
"todos"
,
"projects"
,
name:
"fk_45054f9c45"
,
on_delete: :cascade
add_foreign_key
"todos"
,
"users"
,
column:
"author_id"
,
name:
"fk_ccf0373936"
,
on_delete: :cascade
...
...
lib/api/entities.rb
View file @
57a44f2d
...
...
@@ -769,7 +769,8 @@ module API
class
Todo
<
Grape
::
Entity
expose
:id
expose
:project
,
using:
Entities
::
BasicProjectDetails
expose
:project
,
using:
Entities
::
ProjectIdentity
,
if:
->
(
todo
,
_
)
{
todo
.
project
}
expose
:group
,
using:
'API::Entities::NamespaceBasic'
,
if:
->
(
todo
,
_
)
{
todo
.
group
}
expose
:author
,
using:
Entities
::
UserBasic
expose
:action_name
expose
:target_type
...
...
@@ -780,12 +781,12 @@ module API
expose
:target_url
do
|
todo
,
options
|
target_type
=
todo
.
target_type
.
underscore
target_url
=
"
namespace_project
_
#{
target_type
}
_url"
target_url
=
"
#{
todo
.
parent
.
class
.
to_s
.
underscore
}
_
#{
target_type
}
_url"
target_anchor
=
"note_
#{
todo
.
note_id
}
"
if
todo
.
note_id?
Gitlab
::
Routing
.
url_helpers
.
public_send
(
target_url
,
todo
.
p
roject
.
namespace
,
todo
.
projec
t
,
todo
.
target
,
anchor:
target_anchor
)
# rubocop:disable GitlabSecurity/PublicSend
.
public_send
(
target_url
,
todo
.
p
aren
t
,
todo
.
target
,
anchor:
target_anchor
)
# rubocop:disable GitlabSecurity/PublicSend
end
expose
:body
...
...
spec/factories/todos.rb
View file @
57a44f2d
FactoryBot
.
define
do
factory
:todo
do
project
author
{
project
.
creator
}
user
{
project
.
creator
}
group
author
{
project
&
.
creator
||
user
}
user
{
project
&
.
creator
||
user
}
target
factory: :issue
action
{
Todo
::
ASSIGNED
}
...
...
spec/finders/todos_finder_spec.rb
View file @
57a44f2d
...
...
@@ -5,12 +5,65 @@ describe TodosFinder do
let
(
:user
)
{
create
(
:user
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:project
)
{
create
(
:project
,
namespace:
group
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
)
}
let
(
:finder
)
{
described_class
}
before
do
group
.
add_developer
(
user
)
end
describe
'#execute'
do
context
'visibility'
do
let
(
:private_group_access
)
{
create
(
:group
,
:private
)
}
let
(
:private_group_hidden
)
{
create
(
:group
,
:private
)
}
let
(
:public_project
)
{
create
(
:project
,
:public
)
}
let
(
:private_project_hidden
)
{
create
(
:project
)
}
let
(
:public_group
)
{
create
(
:group
)
}
let!
(
:todo1
)
{
create
(
:todo
,
user:
user
,
project:
project
,
group:
nil
)
}
let!
(
:todo2
)
{
create
(
:todo
,
user:
user
,
project:
public_project
,
group:
nil
)
}
let!
(
:todo3
)
{
create
(
:todo
,
user:
user
,
project:
private_project_hidden
,
group:
nil
)
}
let!
(
:todo4
)
{
create
(
:todo
,
user:
user
,
project:
nil
,
group:
group
)
}
let!
(
:todo5
)
{
create
(
:todo
,
user:
user
,
project:
nil
,
group:
private_group_access
)
}
let!
(
:todo6
)
{
create
(
:todo
,
user:
user
,
project:
nil
,
group:
private_group_hidden
)
}
let!
(
:todo7
)
{
create
(
:todo
,
user:
user
,
project:
nil
,
group:
public_group
)
}
before
do
private_group_access
.
add_developer
(
user
)
end
it
'returns only todos with a target a user has access to'
do
todos
=
finder
.
new
(
user
).
execute
expect
(
todos
).
to
match_array
([
todo1
,
todo2
,
todo4
,
todo5
,
todo7
])
end
end
context
'filtering'
do
let!
(
:todo1
)
{
create
(
:todo
,
user:
user
,
project:
project
,
target:
issue
)
}
let!
(
:todo2
)
{
create
(
:todo
,
user:
user
,
group:
group
,
target:
merge_request
)
}
it
'returns correct todos when filtered by a project'
do
todos
=
finder
.
new
(
user
,
{
project_id:
project
.
id
}).
execute
expect
(
todos
).
to
match_array
([
todo1
])
end
it
'returns correct todos when filtered by a group'
do
todos
=
finder
.
new
(
user
,
{
group_id:
group
.
id
}).
execute
expect
(
todos
).
to
match_array
([
todo2
])
end
it
'returns correct todos when filtered by a type'
do
todos
=
finder
.
new
(
user
,
{
type:
'Issue'
}).
execute
expect
(
todos
).
to
match_array
([
todo1
])
end
end
end
describe
'#sort'
do
context
'by date'
do
let!
(
:todo1
)
{
create
(
:todo
,
user:
user
,
project:
project
)
}
...
...
spec/models/todo_spec.rb
View file @
57a44f2d
...
...
@@ -7,6 +7,7 @@ describe Todo do
it
{
is_expected
.
to
belong_to
(
:author
).
class_name
(
"User"
)
}
it
{
is_expected
.
to
belong_to
(
:note
)
}
it
{
is_expected
.
to
belong_to
(
:project
)
}
it
{
is_expected
.
to
belong_to
(
:group
)
}
it
{
is_expected
.
to
belong_to
(
:target
).
touch
(
true
)
}
it
{
is_expected
.
to
belong_to
(
:user
)
}
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