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
cf41aaba
Commit
cf41aaba
authored
6 years ago
by
Mario de la Ossa
Committed by
Sean McGivern
6 years ago
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Backport of "Add assignee lists to boards"
parent
d4357afd
Changes
33
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
273 additions
and
118 deletions
+273
-118
app/assets/javascripts/boards/components/board_list.vue
app/assets/javascripts/boards/components/board_list.vue
+39
-2
app/assets/javascripts/boards/components/board_new_issue.vue
app/assets/javascripts/boards/components/board_new_issue.vue
+2
-2
app/assets/javascripts/boards/components/new_list_dropdown.js
...assets/javascripts/boards/components/new_list_dropdown.js
+1
-0
app/assets/javascripts/boards/index.js
app/assets/javascripts/boards/index.js
+1
-1
app/assets/javascripts/boards/models/assignee.js
app/assets/javascripts/boards/models/assignee.js
+0
-12
app/assets/javascripts/boards/models/list.js
app/assets/javascripts/boards/models/list.js
+55
-32
app/assets/javascripts/boards/services/board_service.js
app/assets/javascripts/boards/services/board_service.js
+6
-4
app/assets/javascripts/boards/stores/boards_store.js
app/assets/javascripts/boards/stores/boards_store.js
+18
-6
app/assets/javascripts/gl_dropdown.js
app/assets/javascripts/gl_dropdown.js
+5
-1
app/assets/javascripts/vue_shared/models/assignee.js
app/assets/javascripts/vue_shared/models/assignee.js
+13
-0
app/controllers/boards/lists_controller.rb
app/controllers/boards/lists_controller.rb
+11
-3
app/finders/group_members_finder.rb
app/finders/group_members_finder.rb
+19
-7
app/finders/members_finder.rb
app/finders/members_finder.rb
+2
-2
app/models/list.rb
app/models/list.rb
+14
-4
app/services/boards/issues/list_service.rb
app/services/boards/issues/list_service.rb
+7
-14
app/services/boards/issues/move_service.rb
app/services/boards/issues/move_service.rb
+3
-3
app/services/boards/lists/create_service.rb
app/services/boards/lists/create_service.rb
+16
-4
app/views/shared/boards/components/_board.html.haml
app/views/shared/boards/components/_board.html.haml
+10
-2
app/views/shared/issuable/_board_create_list_dropdown.html.haml
...ews/shared/issuable/_board_create_list_dropdown.html.haml
+8
-0
app/views/shared/issuable/_label_page_create.html.haml
app/views/shared/issuable/_label_page_create.html.haml
+2
-1
app/views/shared/issuable/_label_page_default.html.haml
app/views/shared/issuable/_label_page_default.html.haml
+5
-2
app/views/shared/issuable/_search_bar.html.haml
app/views/shared/issuable/_search_bar.html.haml
+1
-8
spec/features/boards/boards_spec.rb
spec/features/boards/boards_spec.rb
+1
-1
spec/features/issues/form_spec.rb
spec/features/issues/form_spec.rb
+3
-0
spec/finders/group_members_finder_spec.rb
spec/finders/group_members_finder_spec.rb
+12
-0
spec/finders/members_finder_spec.rb
spec/finders/members_finder_spec.rb
+12
-0
spec/fixtures/api/schemas/list.json
spec/fixtures/api/schemas/list.json
+1
-1
spec/javascripts/boards/board_card_spec.js
spec/javascripts/boards/board_card_spec.js
+1
-1
spec/javascripts/boards/boards_store_spec.js
spec/javascripts/boards/boards_store_spec.js
+1
-1
spec/javascripts/boards/issue_card_spec.js
spec/javascripts/boards/issue_card_spec.js
+1
-1
spec/javascripts/boards/issue_spec.js
spec/javascripts/boards/issue_spec.js
+1
-1
spec/javascripts/boards/list_spec.js
spec/javascripts/boards/list_spec.js
+1
-1
spec/javascripts/boards/modal_store_spec.js
spec/javascripts/boards/modal_store_spec.js
+1
-1
No files found.
app/assets/javascripts/boards/components/board_list.vue
View file @
cf41aaba
...
...
@@ -87,10 +87,46 @@ export default {
mounted
()
{
const
options
=
gl
.
issueBoards
.
getBoardSortableDefaultOptions
({
scroll
:
true
,
group
:
'
issues
'
,
disabled
:
this
.
disabled
,
filter
:
'
.board-list-count, .is-disabled
'
,
dataIdAttr
:
'
data-issue-id
'
,
group
:
{
name
:
'
issues
'
,
/**
* Dynamically determine between which containers
* items can be moved or copied as
* Assignee lists (EE feature) require this behavior
*/
pull
:
(
to
,
from
,
dragEl
,
e
)
=>
{
// As per Sortable's docs, `to` should provide
// reference to exact sortable container on which
// we're trying to drag element, but either it is
// a library's bug or our markup structure is too complex
// that `to` never points to correct container
// See https://github.com/RubaXa/Sortable/issues/1037
//
// So we use `e.target` which is always accurate about
// which element we're currently dragging our card upon
// So from there, we can get reference to actual container
// and thus the container type to enable Copy or Move
if
(
e
.
target
)
{
const
containerEl
=
e
.
target
.
closest
(
'
.js-board-list
'
)
||
e
.
target
.
querySelector
(
'
.js-board-list
'
);
const
toBoardType
=
containerEl
.
dataset
.
boardType
;
if
(
toBoardType
)
{
const
fromBoardType
=
this
.
list
.
type
;
if
((
fromBoardType
===
'
assignee
'
&&
toBoardType
===
'
label
'
)
||
(
fromBoardType
===
'
label
'
&&
toBoardType
===
'
assignee
'
))
{
return
'
clone
'
;
}
}
}
return
true
;
},
revertClone
:
true
,
},
onStart
:
(
e
)
=>
{
const
card
=
this
.
$refs
.
issue
[
e
.
oldIndex
];
...
...
@@ -179,10 +215,11 @@ export default {
:list=
"list"
v-if=
"list.type !== 'closed' && showIssueForm"
/>
<ul
class=
"board-list"
class=
"board-list
js-board-list
"
v-show=
"!loading"
ref=
"list"
:data-board=
"list.id"
:data-board-type=
"list.type"
:class=
"
{ 'is-smaller': showIssueForm }">
<board-card
v-for=
"(issue, index) in issues"
...
...
This diff is collapsed.
Click to expand it.
app/assets/javascripts/boards/components/board_new_issue.vue
View file @
cf41aaba
...
...
@@ -49,11 +49,12 @@ export default {
this
.
error
=
false
;
const
labels
=
this
.
list
.
label
?
[
this
.
list
.
label
]
:
[];
const
assignees
=
this
.
list
.
assignee
?
[
this
.
list
.
assignee
]
:
[];
const
issue
=
new
ListIssue
({
title
:
this
.
title
,
labels
,
subscribed
:
true
,
assignees
:
[]
,
assignees
,
project_id
:
this
.
selectedProject
.
id
,
});
...
...
@@ -141,4 +142,3 @@ export default {
</div>
</div>
</
template
>
This diff is collapsed.
Click to expand it.
app/assets/javascripts/boards/components/new_list_dropdown.js
View file @
cf41aaba
...
...
@@ -56,6 +56,7 @@ gl.issueBoards.newListDropdownInit = () => {
filterable
:
true
,
selectable
:
true
,
multiSelect
:
true
,
containerSelector
:
'
.js-tab-container-labels .dropdown-page-one .dropdown-content
'
,
clicked
(
options
)
{
const
{
e
}
=
options
;
const
label
=
options
.
selectedObj
;
...
...
This diff is collapsed.
Click to expand it.
app/assets/javascripts/boards/index.js
View file @
cf41aaba
...
...
@@ -7,6 +7,7 @@ import Vue from 'vue';
import
Flash
from
'
~/flash
'
;
import
{
__
}
from
'
~/locale
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
FilteredSearchBoards
from
'
./filtered_search_boards
'
;
import
eventHub
from
'
./eventhub
'
;
...
...
@@ -15,7 +16,6 @@ import './models/issue';
import
'
./models/list
'
;
import
'
./models/milestone
'
;
import
'
./models/project
'
;
import
'
./models/assignee
'
;
import
'
./stores/boards_store
'
;
import
ModalStore
from
'
./stores/modal_store
'
;
import
BoardService
from
'
./services/board_service
'
;
...
...
This diff is collapsed.
Click to expand it.
app/assets/javascripts/boards/models/assignee.js
deleted
100644 → 0
View file @
d4357afd
/* eslint-disable no-unused-vars */
class
ListAssignee
{
constructor
(
user
,
defaultAvatar
)
{
this
.
id
=
user
.
id
;
this
.
name
=
user
.
name
;
this
.
username
=
user
.
username
;
this
.
avatar
=
user
.
avatar_url
||
defaultAvatar
;
}
}
window
.
ListAssignee
=
ListAssignee
;
This diff is collapsed.
Click to expand it.
app/assets/javascripts/boards/models/list.js
View file @
cf41aaba
/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */
/* global ListIssue */
/* global ListLabel */
import
ListLabel
from
'
~/vue_shared/models/label
'
;
import
ListAssignee
from
'
~/vue_shared/models/assignee
'
;
import
queryData
from
'
../utils/query_data
'
;
const
PER_PAGE
=
20
;
class
List
{
constructor
(
obj
,
defaultAvatar
)
{
constructor
(
obj
,
defaultAvatar
)
{
this
.
id
=
obj
.
id
;
this
.
_uid
=
this
.
guid
();
this
.
position
=
obj
.
position
;
...
...
@@ -24,6 +26,9 @@ class List {
if
(
obj
.
label
)
{
this
.
label
=
new
ListLabel
(
obj
.
label
);
}
else
if
(
obj
.
user
)
{
this
.
assignee
=
new
ListAssignee
(
obj
.
user
);
this
.
title
=
this
.
assignee
.
name
;
}
if
(
this
.
type
!==
'
blank
'
&&
this
.
id
)
{
...
...
@@ -34,14 +39,25 @@ class List {
}
guid
()
{
const
s4
=
()
=>
Math
.
floor
((
1
+
Math
.
random
())
*
0x10000
).
toString
(
16
).
substring
(
1
);
const
s4
=
()
=>
Math
.
floor
((
1
+
Math
.
random
())
*
0x10000
)
.
toString
(
16
)
.
substring
(
1
);
return
`
${
s4
()}${
s4
()}
-
${
s4
()}
-
${
s4
()}
-
${
s4
()}
-
${
s4
()}${
s4
()}${
s4
()}
`
;
}
save
()
{
save
()
{
const
entity
=
this
.
label
||
this
.
assignee
;
let
entityType
=
''
;
if
(
this
.
label
)
{
entityType
=
'
label_id
'
;
}
else
{
entityType
=
'
assignee_id
'
;
}
return
gl
.
boardService
.
createList
(
this
.
label
.
id
)
.
then
(
res
=>
res
.
data
)
.
then
(
(
data
)
=>
{
.
then
(
data
=>
{
this
.
id
=
data
.
id
;
this
.
type
=
data
.
list_type
;
this
.
position
=
data
.
position
;
...
...
@@ -50,25 +66,23 @@ class List {
});
}
destroy
()
{
destroy
()
{
const
index
=
gl
.
issueBoards
.
BoardsStore
.
state
.
lists
.
indexOf
(
this
);
gl
.
issueBoards
.
BoardsStore
.
state
.
lists
.
splice
(
index
,
1
);
gl
.
issueBoards
.
BoardsStore
.
updateNewListDropdown
(
this
.
id
);
gl
.
boardService
.
destroyList
(
this
.
id
)
.
catch
(()
=>
{
// TODO: handle request error
});
gl
.
boardService
.
destroyList
(
this
.
id
).
catch
(()
=>
{
// TODO: handle request error
});
}
update
()
{
gl
.
boardService
.
updateList
(
this
.
id
,
this
.
position
)
.
catch
(()
=>
{
// TODO: handle request error
});
update
()
{
gl
.
boardService
.
updateList
(
this
.
id
,
this
.
position
).
catch
(()
=>
{
// TODO: handle request error
});
}
nextPage
()
{
nextPage
()
{
if
(
this
.
issuesSize
>
this
.
issues
.
length
)
{
if
(
this
.
issues
.
length
/
PER_PAGE
>=
1
)
{
this
.
page
+=
1
;
...
...
@@ -78,7 +92,7 @@ class List {
}
}
getIssues
(
emptyIssues
=
true
)
{
getIssues
(
emptyIssues
=
true
)
{
const
data
=
queryData
(
gl
.
issueBoards
.
BoardsStore
.
filter
.
path
,
{
page
:
this
.
page
});
if
(
this
.
label
&&
data
.
label_name
)
{
...
...
@@ -89,7 +103,8 @@ class List {
this
.
loading
=
true
;
}
return
gl
.
boardService
.
getIssuesForList
(
this
.
id
,
data
)
return
gl
.
boardService
.
getIssuesForList
(
this
.
id
,
data
)
.
then
(
res
=>
res
.
data
)
.
then
((
data
)
=>
{
this
.
loading
=
false
;
...
...
@@ -103,11 +118,12 @@ class List {
});
}
newIssue
(
issue
)
{
newIssue
(
issue
)
{
this
.
addIssue
(
issue
,
null
,
0
);
this
.
issuesSize
+=
1
;
return
gl
.
boardService
.
newIssue
(
this
.
id
,
issue
)
return
gl
.
boardService
.
newIssue
(
this
.
id
,
issue
)
.
then
(
res
=>
res
.
data
)
.
then
((
data
)
=>
{
issue
.
id
=
data
.
id
;
...
...
@@ -123,13 +139,13 @@ class List {
});
}
createIssues
(
data
)
{
data
.
forEach
(
(
issueObj
)
=>
{
createIssues
(
data
)
{
data
.
forEach
(
issueObj
=>
{
this
.
addIssue
(
new
ListIssue
(
issueObj
,
this
.
defaultAvatar
));
});
}
addIssue
(
issue
,
listFrom
,
newIndex
)
{
addIssue
(
issue
,
listFrom
,
newIndex
)
{
let
moveBeforeId
=
null
;
let
moveAfterId
=
null
;
...
...
@@ -152,6 +168,13 @@ class List {
issue
.
addLabel
(
this
.
label
);
}
if
(
this
.
assignee
)
{
if
(
listFrom
&&
listFrom
.
type
===
'
assignee
'
)
{
issue
.
removeAssignee
(
listFrom
.
assignee
);
}
issue
.
addAssignee
(
this
.
assignee
);
}
if
(
listFrom
)
{
this
.
issuesSize
+=
1
;
...
...
@@ -160,29 +183,29 @@ class List {
}
}
moveIssue
(
issue
,
oldIndex
,
newIndex
,
moveBeforeId
,
moveAfterId
)
{
moveIssue
(
issue
,
oldIndex
,
newIndex
,
moveBeforeId
,
moveAfterId
)
{
this
.
issues
.
splice
(
oldIndex
,
1
);
this
.
issues
.
splice
(
newIndex
,
0
,
issue
);
gl
.
boardService
.
moveIssue
(
issue
.
id
,
null
,
null
,
moveBeforeId
,
moveAfterId
)
.
catch
(()
=>
{
// TODO: handle request error
});
gl
.
boardService
.
moveIssue
(
issue
.
id
,
null
,
null
,
moveBeforeId
,
moveAfterId
).
catch
(()
=>
{
// TODO: handle request error
});
}
updateIssueLabel
(
issue
,
listFrom
,
moveBeforeId
,
moveAfterId
)
{
gl
.
boardService
.
moveIssue
(
issue
.
id
,
listFrom
.
id
,
this
.
id
,
moveBeforeId
,
moveAfterId
)
gl
.
boardService
.
moveIssue
(
issue
.
id
,
listFrom
.
id
,
this
.
id
,
moveBeforeId
,
moveAfterId
)
.
catch
(()
=>
{
// TODO: handle request error
});
}
findIssue
(
id
)
{
findIssue
(
id
)
{
return
this
.
issues
.
find
(
issue
=>
issue
.
id
===
id
);
}
removeIssue
(
removeIssue
)
{
this
.
issues
=
this
.
issues
.
filter
(
(
issue
)
=>
{
removeIssue
(
removeIssue
)
{
this
.
issues
=
this
.
issues
.
filter
(
issue
=>
{
const
matchesRemove
=
removeIssue
.
id
===
issue
.
id
;
if
(
matchesRemove
)
{
...
...
This diff is collapsed.
Click to expand it.
app/assets/javascripts/boards/services/board_service.js
View file @
cf41aaba
...
...
@@ -30,11 +30,13 @@ export default class BoardService {
return
axios
.
post
(
this
.
listsEndpointGenerate
,
{});
}
createList
(
labelId
)
{
createList
(
entityId
,
entityType
)
{
const
list
=
{
[
entityType
]:
entityId
,
};
return
axios
.
post
(
this
.
listsEndpoint
,
{
list
:
{
label_id
:
labelId
,
},
list
,
});
}
...
...
This diff is collapsed.
Click to expand it.
app/assets/javascripts/boards/stores/boards_store.js
View file @
cf41aaba
...
...
@@ -103,8 +103,15 @@ gl.issueBoards.BoardsStore = {
const
listLabels
=
issueLists
.
map
(
listIssue
=>
listIssue
.
label
);
if
(
!
issueTo
)
{
// Add to new lists issues if it doesn't already exist
listTo
.
addIssue
(
issue
,
listFrom
,
newIndex
);
// Check if target list assignee is already present in this issue
if
((
listTo
.
type
===
'
assignee
'
&&
listFrom
.
type
===
'
assignee
'
)
&&
issue
.
findAssignee
(
listTo
.
assignee
))
{
const
targetIssue
=
listTo
.
findIssue
(
issue
.
id
);
targetIssue
.
removeAssignee
(
listFrom
.
assignee
);
}
else
{
// Add to new lists issues if it doesn't already exist
listTo
.
addIssue
(
issue
,
listFrom
,
newIndex
);
}
}
else
{
listTo
.
updateIssueLabel
(
issue
,
listFrom
);
issueTo
.
removeLabel
(
listFrom
.
label
);
...
...
@@ -115,7 +122,11 @@ gl.issueBoards.BoardsStore = {
list
.
removeIssue
(
issue
);
});
issue
.
removeLabels
(
listLabels
);
}
else
{
}
else
if
(
listTo
.
type
===
'
backlog
'
&&
listFrom
.
type
===
'
assignee
'
)
{
issue
.
removeAssignee
(
listFrom
.
assignee
);
listFrom
.
removeIssue
(
issue
);
}
else
if
((
listTo
.
type
!==
'
label
'
&&
listFrom
.
type
===
'
assignee
'
)
||
(
listTo
.
type
!==
'
assignee
'
&&
listFrom
.
type
===
'
label
'
))
{
listFrom
.
removeIssue
(
issue
);
}
},
...
...
@@ -126,11 +137,12 @@ gl.issueBoards.BoardsStore = {
list
.
moveIssue
(
issue
,
oldIndex
,
newIndex
,
beforeId
,
afterId
);
},
findList
(
key
,
val
,
type
=
'
label
'
)
{
return
this
.
state
.
lists
.
filter
((
list
)
=>
{
const
byType
=
type
?
list
[
'
type
'
]
===
type
:
true
;
const
filteredList
=
this
.
state
.
lists
.
filter
((
list
)
=>
{
const
byType
=
type
?
(
list
.
type
===
type
)
||
(
list
.
type
===
'
assignee
'
)
:
true
;
return
list
[
key
]
===
val
&&
byType
;
})[
0
];
});
return
filteredList
[
0
];
},
updateFiltersUrl
()
{
history
.
pushState
(
null
,
null
,
`?
${
this
.
filter
.
path
}
`
);
...
...
This diff is collapsed.
Click to expand it.
app/assets/javascripts/gl_dropdown.js
View file @
cf41aaba
...
...
@@ -602,7 +602,11 @@ GitLabDropdown = (function() {
var
selector
;
selector
=
'
.dropdown-content
'
;
if
(
this
.
dropdown
.
find
(
"
.dropdown-toggle-page
"
).
length
)
{
selector
=
"
.dropdown-page-one .dropdown-content
"
;
if
(
this
.
options
.
containerSelector
)
{
selector
=
this
.
options
.
containerSelector
;
}
else
{
selector
=
'
.dropdown-page-one .dropdown-content
'
;
}
}
return
$
(
selector
,
this
.
dropdown
).
empty
();
...
...
This diff is collapsed.
Click to expand it.
app/assets/javascripts/vue_shared/models/assignee.js
0 → 100644
View file @
cf41aaba
export
default
class
ListAssignee
{
constructor
(
obj
,
defaultAvatar
)
{
this
.
id
=
obj
.
id
;
this
.
name
=
obj
.
name
;
this
.
username
=
obj
.
username
;
this
.
avatar
=
obj
.
avatar_url
||
obj
.
avatar
||
defaultAvatar
;
this
.
path
=
obj
.
path
;
this
.
state
=
obj
.
state
;
this
.
webUrl
=
obj
.
web_url
||
obj
.
webUrl
;
}
}
window
.
ListAssignee
=
ListAssignee
;
This diff is collapsed.
Click to expand it.
app/controllers/boards/lists_controller.rb
View file @
cf41aaba
...
...
@@ -56,8 +56,12 @@ module Boards
private
def
list_creation_attrs
%i[label_id]
end
def
list_params
params
.
require
(
:list
).
permit
(
:label_id
)
params
.
require
(
:list
).
permit
(
list_creation_attrs
)
end
def
move_params
...
...
@@ -65,11 +69,15 @@ module Boards
end
def
serialize_as_json
(
resource
)
resource
.
as_json
(
resource
.
as_json
(
serialization_attrs
)
end
def
serialization_attrs
{
only:
[
:id
,
:list_type
,
:position
],
methods:
[
:title
],
label:
true
)
}
end
end
end
This diff is collapsed.
Click to expand it.
app/finders/group_members_finder.rb
View file @
cf41aaba
...
...
@@ -3,17 +3,29 @@ class GroupMembersFinder
@group
=
group
end
def
execute
def
execute
(
include_descendants:
false
)
group_members
=
@group
.
members
wheres
=
[]
return
group_members
unless
@group
.
parent
return
group_members
unless
@group
.
parent
||
include_descendants
parents_members
=
GroupMember
.
non_request
.
where
(
source_id:
@group
.
ancestors
.
select
(
:id
))
.
where
.
not
(
user_id:
@group
.
users
.
select
(
:id
))
wheres
<<
"members.id IN (
#{
group_members
.
select
(
:id
).
to_sql
}
)"
wheres
=
[
"members.id IN (
#{
group_members
.
select
(
:id
).
to_sql
}
)"
]
wheres
<<
"members.id IN (
#{
parents_members
.
select
(
:id
).
to_sql
}
)"
if
@group
.
parent
parents_members
=
GroupMember
.
non_request
.
where
(
source_id:
@group
.
ancestors
.
select
(
:id
))
.
where
.
not
(
user_id:
@group
.
users
.
select
(
:id
))
wheres
<<
"members.id IN (
#{
parents_members
.
select
(
:id
).
to_sql
}
)"
end
if
include_descendants
descendant_members
=
GroupMember
.
non_request
.
where
(
source_id:
@group
.
descendants
.
select
(
:id
))
.
where
.
not
(
user_id:
@group
.
users
.
select
(
:id
))
wheres
<<
"members.id IN (
#{
descendant_members
.
select
(
:id
).
to_sql
}
)"
end
GroupMember
.
where
(
wheres
.
join
(
' OR '
))
end
...
...
This diff is collapsed.
Click to expand it.
app/finders/members_finder.rb
View file @
cf41aaba
...
...
@@ -7,12 +7,12 @@ class MembersFinder
@group
=
project
.
group
end
def
execute
def
execute
(
include_descendants:
false
)
project_members
=
project
.
project_members
project_members
=
project_members
.
non_invite
unless
can?
(
current_user
,
:admin_project
,
project
)
if
group
group_members
=
GroupMembersFinder
.
new
(
group
).
execute
group_members
=
GroupMembersFinder
.
new
(
group
).
execute
(
include_descendants:
include_descendants
)
group_members
=
group_members
.
non_invite
union
=
Gitlab
::
SQL
::
Union
.
new
([
project_members
,
group_members
],
remove_duplicates:
false
)
...
...
This diff is collapsed.
Click to expand it.
app/models/list.rb
View file @
cf41aaba
...
...
@@ -2,17 +2,27 @@ class List < ActiveRecord::Base
belongs_to
:board
belongs_to
:label
enum
list_type:
{
backlog:
0
,
label:
1
,
closed:
2
}
enum
list_type:
{
backlog:
0
,
label:
1
,
closed:
2
,
assignee:
3
}
validates
:board
,
:list_type
,
presence:
true
validates
:label
,
:position
,
presence:
true
,
if: :label?
validates
:label_id
,
uniqueness:
{
scope: :board_id
},
if: :label?
validates
:position
,
numericality:
{
only_integer:
true
,
greater_than_or_equal_to:
0
},
if: :
label
?
validates
:position
,
numericality:
{
only_integer:
true
,
greater_than_or_equal_to:
0
},
if: :
movable
?
before_destroy
:can_be_destroyed
scope
:destroyable
,
->
{
where
(
list_type:
list_types
[
:label
])
}
scope
:movable
,
->
{
where
(
list_type:
list_types
[
:label
])
}
scope
:destroyable
,
->
{
where
(
list_type:
list_types
.
slice
(
*
destroyable_types
).
values
)
}
scope
:movable
,
->
{
where
(
list_type:
list_types
.
slice
(
*
movable_types
).
values
)
}
class
<<
self
def
destroyable_types
[
:label
]
end
def
movable_types
[
:label
]
end
end
def
destroyable?
label?
...
...
This diff is collapsed.
Click to expand it.
app/services/boards/issues/list_service.rb
View file @
cf41aaba
...
...
@@ -3,13 +3,18 @@ module Boards
class
ListService
<
Boards
::
BaseService
def
execute
issues
=
IssuesFinder
.
new
(
current_user
,
filter_params
).
execute
issues
=
without_board_labels
(
issues
)
unless
movable_list?
||
closed_list?
issues
=
with_list_label
(
issues
)
if
movable_list?
issues
=
filter
(
issues
)
issues
.
order_by_position_and_priority
end
private
def
filter
(
issues
)
issues
=
without_board_labels
(
issues
)
unless
list
&
.
movable?
||
list
&
.
closed?
issues
=
with_list_label
(
issues
)
if
list
&
.
label?
issues
end
def
board
@board
||=
parent
.
boards
.
find
(
params
[
:board_id
])
end
...
...
@@ -20,18 +25,6 @@ module Boards
@list
=
board
.
lists
.
find
(
params
[
:id
])
if
params
.
key?
(
:id
)
end
def
movable_list?
return
@movable_list
if
defined?
(
@movable_list
)
@movable_list
=
list
.
present?
&&
list
.
movable?
end
def
closed_list?
return
@closed_list
if
defined?
(
@closed_list
)
@closed_list
=
list
.
present?
&&
list
.
closed?
end
def
filter_params
set_parent
set_state
...
...
This diff is collapsed.
Click to expand it.
app/services/boards/issues/move_service.rb
View file @
cf41aaba
...
...
@@ -3,7 +3,7 @@ module Boards
class
MoveService
<
Boards
::
BaseService
def
execute
(
issue
)
return
false
unless
can?
(
current_user
,
:update_issue
,
issue
)
return
false
if
issue_params
.
empty?
return
false
if
issue_params
(
issue
)
.
empty?
update
(
issue
)
end
...
...
@@ -28,10 +28,10 @@ module Boards
end
def
update
(
issue
)
::
Issues
::
UpdateService
.
new
(
issue
.
project
,
current_user
,
issue_params
).
execute
(
issue
)
::
Issues
::
UpdateService
.
new
(
issue
.
project
,
current_user
,
issue_params
(
issue
)
).
execute
(
issue
)
end
def
issue_params
def
issue_params
(
issue
)
attrs
=
{}
if
move_between_lists?
...
...
This diff is collapsed.
Click to expand it.
app/services/boards/lists/create_service.rb
View file @
cf41aaba
module
Boards
module
Lists
class
CreateService
<
Boards
::
BaseService
include
Gitlab
::
Utils
::
StrongMemoize
def
execute
(
board
)
List
.
transaction
do
label
=
available_labels_for
(
board
).
find
(
params
[
:label_id
]
)
target
=
target
(
board
)
position
=
next_position
(
board
)
create_list
(
board
,
label
,
position
)
create_list
(
board
,
type
,
target
,
position
)
end
end
private
def
type
:label
end
def
target
(
board
)
strong_memoize
(
:target
)
do
available_labels_for
(
board
).
find
(
params
[
:label_id
])
end
end
def
available_labels_for
(
board
)
options
=
{
include_ancestor_groups:
true
}
...
...
@@ -28,8 +40,8 @@ module Boards
max_position
.
nil?
?
0
:
max_position
.
succ
end
def
create_list
(
board
,
label
,
position
)
board
.
lists
.
create
(
label:
label
,
list_type: :label
,
position:
position
)
def
create_list
(
board
,
type
,
target
,
position
)
board
.
lists
.
create
(
type
=>
target
,
list_type:
type
,
position:
position
)
end
end
end
...
...
This diff is collapsed.
Click to expand it.
app/views/shared/boards/components/_board.html.haml
View file @
cf41aaba
.board
{
":class"
=>
'
{
"is-draggable"
:
!
list
.
preset
,
"is-expandable"
:
list
.
isExpandable
,
"is-collapsed"
:
!
list
.
isExpanded
}
'
,
.board
{
":class"
=>
'
{
"is-draggable"
:
!
list
.
preset
,
"is-expandable"
:
list
.
isExpandable
,
"is-collapsed"
:
!
list
.
isExpanded
,
"board-type-assignee"
:
list
.
type
===
"assignee"
}
'
,
":data-id"
=>
"list.id"
}
.board-inner
%header
.board-header
{
":class"
=>
'
{
"has-border"
:
list
.
label
&&
list
.
label
.
color
}
'
,
":style"
=>
"{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }"
,
"@click"
=>
"toggleExpanded($event)"
}
...
...
@@ -7,10 +7,18 @@
":class"
:
"{
\"
fa-caret-down
\"
: list.isExpanded,
\"
fa-caret-right
\"
: !list.isExpanded }"
,
"aria-hidden"
:
"true"
}
%a
.user-avatar-link.js-no-trigger
{
"v-if"
:
"list.type ===
\"
assignee
\"
"
,
":href"
:
"list.assignee.path"
}
-# haml-lint:disable AltText
%img
.avatar.s20.has-tooltip
{
height:
"20"
,
width:
"20"
,
":src"
:
"list.assignee.avatar"
,
":alt"
:
"list.assignee.name"
}
%span
.board-title-text.has-tooltip
{
"v-if"
:
"list.type !==
\"
label
\"
"
,
":title"
=>
'(
list.label ? list.label.description :
"")'
,
data:
{
container:
"body"
}
}
":title"
=>
'(
(list.label && list.label.description) || list.title ||
"")'
,
data:
{
container:
"body"
}
}
{{ list.title }}
%span
.board-title-sub-text.prepend-left-5.has-tooltip
{
"v-if"
:
"list.type ===
\"
assignee
\"
"
,
":title"
=>
'(list.assignee && list.assignee.username || "")'
}
@{{ list.assignee.username }}
%span
.has-tooltip
{
"v-if"
:
"list.type ===
\"
label
\"
"
,
":title"
=>
'(list.label ? list.label.description : "")'
,
data:
{
container:
"body"
,
placement:
"bottom"
},
...
...
This diff is collapsed.
Click to expand it.
app/views/shared/issuable/_board_create_list_dropdown.html.haml
0 → 100644
View file @
cf41aaba
.dropdown.prepend-left-10
#js-add-list
%button
.btn.btn-create.btn-inverted.js-new-board-list
{
type:
"button"
,
data:
board_list_data
}
Add list
.dropdown-menu.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
=
render
partial:
"shared/issuable/label_page_default"
,
locals:
{
show_footer:
true
,
show_create:
true
,
show_boards_content:
true
,
title:
"Add list"
}
-
if
can?
(
current_user
,
:admin_label
,
board
.
parent
)
=
render
partial:
"shared/issuable/label_page_create"
=
dropdown_loading
This diff is collapsed.
Click to expand it.
app/views/shared/issuable/_label_page_create.html.haml
View file @
cf41aaba
-
show_close
=
local_assigns
.
fetch
(
:show_close
,
true
)
-
subject
=
@project
||
@group
.dropdown-page-two.dropdown-new-label
=
dropdown_title
(
create_label_title
(
subject
),
options:
{
back:
true
})
=
dropdown_title
(
create_label_title
(
subject
),
options:
{
back:
true
,
close:
show_close
})
=
dropdown_content
do
.dropdown-labels-error.js-label-error
%input
#new_label_name
.default-dropdown-input
{
type:
"text"
,
placeholder:
_
(
'Name new label'
)
}
...
...
This diff is collapsed.
Click to expand it.
app/views/shared/issuable/_label_page_default.html.haml
View file @
cf41aaba
-
title
=
local_assigns
.
fetch
(
:title
,
_
(
'Assign labels'
))
-
content_title
=
local_assigns
.
fetch
(
:content_title
,
_
(
'Create lists from labels. Issues with that label appear in that list.'
))
-
show_title
=
local_assigns
.
fetch
(
:show_title
,
true
)
-
show_create
=
local_assigns
.
fetch
(
:show_create
,
true
)
-
show_footer
=
local_assigns
.
fetch
(
:show_footer
,
true
)
-
filter_placeholder
=
local_assigns
.
fetch
(
:filter_placeholder
,
'Search'
)
-
show_boards_content
=
local_assigns
.
fetch
(
:show_boards_content
,
false
)
-
subject
=
@project
||
@group
.dropdown-page-one
=
dropdown_title
(
title
)
-
if
show_title
=
dropdown_title
(
title
)
-
if
show_boards_content
.issue-board-dropdown-content
%p
=
_
(
'Create lists from labels. Issues with that label appear in that list.'
)
=
content_title
=
dropdown_filter
(
filter_placeholder
)
=
dropdown_content
-
if
current_board_parent
&&
show_footer
...
...
This diff is collapsed.
Click to expand it.
app/views/shared/issuable/_search_bar.html.haml
View file @
cf41aaba
...
...
@@ -104,14 +104,7 @@
.filter-dropdown-container
-
if
type
==
:boards
-
if
can?
(
current_user
,
:admin_list
,
board
.
parent
)
.dropdown.prepend-left-10
#js-add-list
%button
.btn.btn-create.btn-inverted.js-new-board-list
{
type:
"button"
,
data:
board_list_data
}
Add list
.dropdown-menu.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
=
render
partial:
"shared/issuable/label_page_default"
,
locals:
{
show_footer:
true
,
show_create:
true
,
show_boards_content:
true
,
title:
"Add list"
}
-
if
can?
(
current_user
,
:admin_label
,
board
.
parent
)
=
render
partial:
"shared/issuable/label_page_create"
=
dropdown_loading
=
render_if_exists
'shared/issuable/board_create_list_dropdown'
,
board:
board
-
if
@project
#js-add-issues-btn
.prepend-left-10
{
data:
{
can_admin_list:
can?
(
current_user
,
:admin_list
,
@project
)
}
}
-
elsif
type
!=
:boards_modal
...
...
This diff is collapsed.
Click to expand it.
spec/features/boards/boards_spec.rb
View file @
cf41aaba
...
...
@@ -150,7 +150,7 @@ describe 'Issue Boards', :js do
click_button
'Add list'
wait_for_requests
find
(
'.
dropdown-menu-close
'
).
click
find
(
'.
js-new-board-list
'
).
click
page
.
within
(
find
(
'.board:nth-child(2)'
))
do
accept_confirm
{
find
(
'.board-delete'
).
click
}
...
...
This diff is collapsed.
Click to expand it.
spec/features/issues/form_spec.rb
View file @
cf41aaba
...
...
@@ -143,6 +143,9 @@ describe 'New/edit issue', :js do
click_link
label
.
title
click_link
label2
.
title
end
find
(
'.js-issuable-form-dropdown.js-label-select'
).
click
page
.
within
'.js-label-select'
do
expect
(
page
).
to
have_content
label
.
title
end
...
...
This diff is collapsed.
Click to expand it.
spec/finders/group_members_finder_spec.rb
View file @
cf41aaba
...
...
@@ -29,4 +29,16 @@ describe GroupMembersFinder, '#execute' do
expect
(
result
.
to_a
).
to
match_array
([
member1
,
member3
,
member4
])
end
it
'returns members for descendant groups if requested'
,
:nested_groups
do
member1
=
group
.
add_master
(
user2
)
member2
=
group
.
add_master
(
user1
)
nested_group
.
add_master
(
user2
)
member3
=
nested_group
.
add_master
(
user3
)
member4
=
nested_group
.
add_master
(
user4
)
result
=
described_class
.
new
(
group
).
execute
(
include_descendants:
true
)
expect
(
result
.
to_a
).
to
match_array
([
member1
,
member2
,
member3
,
member4
])
end
end
This diff is collapsed.
Click to expand it.
spec/finders/members_finder_spec.rb
View file @
cf41aaba
...
...
@@ -19,4 +19,16 @@ describe MembersFinder, '#execute' do
expect
(
result
.
to_a
).
to
match_array
([
member1
,
member2
,
member3
])
end
it
'includes nested group members if asked'
,
:nested_groups
do
project
=
create
(
:project
,
namespace:
group
)
nested_group
.
request_access
(
user1
)
member1
=
group
.
add_master
(
user2
)
member2
=
nested_group
.
add_master
(
user3
)
member3
=
project
.
add_master
(
user4
)
result
=
described_class
.
new
(
project
,
user2
).
execute
(
include_descendants:
true
)
expect
(
result
.
to_a
).
to
match_array
([
member1
,
member2
,
member3
])
end
end
This diff is collapsed.
Click to expand it.
spec/fixtures/api/schemas/list.json
View file @
cf41aaba
...
...
@@ -37,5 +37,5 @@
"title"
:
{
"type"
:
"string"
},
"position"
:
{
"type"
:
[
"integer"
,
"null"
]
}
},
"additionalProperties"
:
fals
e
"additionalProperties"
:
tru
e
}
This diff is collapsed.
Click to expand it.
spec/javascripts/boards/board_card_spec.js
View file @
cf41aaba
...
...
@@ -5,10 +5,10 @@
import
Vue
from
'
vue
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
'
~/boards/models/assignee
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/stores/boards_store
'
;
import
boardCard
from
'
~/boards/components/board_card.vue
'
;
...
...
This diff is collapsed.
Click to expand it.
spec/javascripts/boards/boards_store_spec.js
View file @
cf41aaba
...
...
@@ -7,9 +7,9 @@ import axios from '~/lib/utils/axios_utils';
import
Cookies
from
'
js-cookie
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/stores/boards_store
'
;
import
{
listObj
,
listObjDuplicate
,
boardsMockInterceptor
,
mockBoardService
}
from
'
./mock_data
'
;
...
...
This diff is collapsed.
Click to expand it.
spec/javascripts/boards/issue_card_spec.js
View file @
cf41aaba
...
...
@@ -5,9 +5,9 @@
import
Vue
from
'
vue
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/stores/boards_store
'
;
import
'
~/boards/components/issue_card_inner
'
;
import
{
listObj
}
from
'
./mock_data
'
;
...
...
This diff is collapsed.
Click to expand it.
spec/javascripts/boards/issue_spec.js
View file @
cf41aaba
...
...
@@ -3,9 +3,9 @@
import
Vue
from
'
vue
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/stores/boards_store
'
;
import
{
mockBoardService
}
from
'
./mock_data
'
;
...
...
This diff is collapsed.
Click to expand it.
spec/javascripts/boards/list_spec.js
View file @
cf41aaba
...
...
@@ -6,9 +6,9 @@ import MockAdapter from 'axios-mock-adapter';
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
_
from
'
underscore
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/stores/boards_store
'
;
import
{
listObj
,
listObjDuplicate
,
boardsMockInterceptor
,
mockBoardService
}
from
'
./mock_data
'
;
...
...
This diff is collapsed.
Click to expand it.
spec/javascripts/boards/modal_store_spec.js
View file @
cf41aaba
/* global ListIssue */
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/assignee
'
;
import
Store
from
'
~/boards/stores/modal_store
'
;
describe
(
'
Modal store
'
,
()
=>
{
...
...
This diff is collapsed.
Click to expand it.
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