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
Jun 07, 2018
by
Mario de la Ossa
Committed by
Sean McGivern
Jun 07, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Backport of "Add assignee lists to boards"
parent
d4357afd
Changes
33
Show 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 {
...
@@ -87,10 +87,46 @@ export default {
mounted
()
{
mounted
()
{
const
options
=
gl
.
issueBoards
.
getBoardSortableDefaultOptions
({
const
options
=
gl
.
issueBoards
.
getBoardSortableDefaultOptions
({
scroll
:
true
,
scroll
:
true
,
group
:
'
issues
'
,
disabled
:
this
.
disabled
,
disabled
:
this
.
disabled
,
filter
:
'
.board-list-count, .is-disabled
'
,
filter
:
'
.board-list-count, .is-disabled
'
,
dataIdAttr
:
'
data-issue-id
'
,
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
)
=>
{
onStart
:
(
e
)
=>
{
const
card
=
this
.
$refs
.
issue
[
e
.
oldIndex
];
const
card
=
this
.
$refs
.
issue
[
e
.
oldIndex
];
...
@@ -179,10 +215,11 @@ export default {
...
@@ -179,10 +215,11 @@ export default {
:list=
"list"
:list=
"list"
v-if=
"list.type !== 'closed' && showIssueForm"
/>
v-if=
"list.type !== 'closed' && showIssueForm"
/>
<ul
<ul
class=
"board-list"
class=
"board-list
js-board-list
"
v-show=
"!loading"
v-show=
"!loading"
ref=
"list"
ref=
"list"
:data-board=
"list.id"
:data-board=
"list.id"
:data-board-type=
"list.type"
:class=
"
{ 'is-smaller': showIssueForm }">
:class=
"
{ 'is-smaller': showIssueForm }">
<board-card
<board-card
v-for=
"(issue, index) in issues"
v-for=
"(issue, index) in issues"
...
...
app/assets/javascripts/boards/components/board_new_issue.vue
View file @
cf41aaba
...
@@ -49,11 +49,12 @@ export default {
...
@@ -49,11 +49,12 @@ export default {
this
.
error
=
false
;
this
.
error
=
false
;
const
labels
=
this
.
list
.
label
?
[
this
.
list
.
label
]
:
[];
const
labels
=
this
.
list
.
label
?
[
this
.
list
.
label
]
:
[];
const
assignees
=
this
.
list
.
assignee
?
[
this
.
list
.
assignee
]
:
[];
const
issue
=
new
ListIssue
({
const
issue
=
new
ListIssue
({
title
:
this
.
title
,
title
:
this
.
title
,
labels
,
labels
,
subscribed
:
true
,
subscribed
:
true
,
assignees
:
[]
,
assignees
,
project_id
:
this
.
selectedProject
.
id
,
project_id
:
this
.
selectedProject
.
id
,
});
});
...
@@ -141,4 +142,3 @@ export default {
...
@@ -141,4 +142,3 @@ export default {
</div>
</div>
</div>
</div>
</
template
>
</
template
>
app/assets/javascripts/boards/components/new_list_dropdown.js
View file @
cf41aaba
...
@@ -56,6 +56,7 @@ gl.issueBoards.newListDropdownInit = () => {
...
@@ -56,6 +56,7 @@ gl.issueBoards.newListDropdownInit = () => {
filterable
:
true
,
filterable
:
true
,
selectable
:
true
,
selectable
:
true
,
multiSelect
:
true
,
multiSelect
:
true
,
containerSelector
:
'
.js-tab-container-labels .dropdown-page-one .dropdown-content
'
,
clicked
(
options
)
{
clicked
(
options
)
{
const
{
e
}
=
options
;
const
{
e
}
=
options
;
const
label
=
options
.
selectedObj
;
const
label
=
options
.
selectedObj
;
...
...
app/assets/javascripts/boards/index.js
View file @
cf41aaba
...
@@ -7,6 +7,7 @@ import Vue from 'vue';
...
@@ -7,6 +7,7 @@ import Vue from 'vue';
import
Flash
from
'
~/flash
'
;
import
Flash
from
'
~/flash
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
FilteredSearchBoards
from
'
./filtered_search_boards
'
;
import
FilteredSearchBoards
from
'
./filtered_search_boards
'
;
import
eventHub
from
'
./eventhub
'
;
import
eventHub
from
'
./eventhub
'
;
...
@@ -15,7 +16,6 @@ import './models/issue';
...
@@ -15,7 +16,6 @@ import './models/issue';
import
'
./models/list
'
;
import
'
./models/list
'
;
import
'
./models/milestone
'
;
import
'
./models/milestone
'
;
import
'
./models/project
'
;
import
'
./models/project
'
;
import
'
./models/assignee
'
;
import
'
./stores/boards_store
'
;
import
'
./stores/boards_store
'
;
import
ModalStore
from
'
./stores/modal_store
'
;
import
ModalStore
from
'
./stores/modal_store
'
;
import
BoardService
from
'
./services/board_service
'
;
import
BoardService
from
'
./services/board_service
'
;
...
...
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
;
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 */
/* 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 ListIssue */
/* global ListLabel */
import
ListLabel
from
'
~/vue_shared/models/label
'
;
import
ListAssignee
from
'
~/vue_shared/models/assignee
'
;
import
queryData
from
'
../utils/query_data
'
;
import
queryData
from
'
../utils/query_data
'
;
const
PER_PAGE
=
20
;
const
PER_PAGE
=
20
;
class
List
{
class
List
{
constructor
(
obj
,
defaultAvatar
)
{
constructor
(
obj
,
defaultAvatar
)
{
this
.
id
=
obj
.
id
;
this
.
id
=
obj
.
id
;
this
.
_uid
=
this
.
guid
();
this
.
_uid
=
this
.
guid
();
this
.
position
=
obj
.
position
;
this
.
position
=
obj
.
position
;
...
@@ -24,6 +26,9 @@ class List {
...
@@ -24,6 +26,9 @@ class List {
if
(
obj
.
label
)
{
if
(
obj
.
label
)
{
this
.
label
=
new
ListLabel
(
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
)
{
if
(
this
.
type
!==
'
blank
'
&&
this
.
id
)
{
...
@@ -34,14 +39,25 @@ class List {
...
@@ -34,14 +39,25 @@ class List {
}
}
guid
()
{
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
()}
`
;
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
)
return
gl
.
boardService
.
createList
(
this
.
label
.
id
)
.
then
(
res
=>
res
.
data
)
.
then
(
res
=>
res
.
data
)
.
then
(
(
data
)
=>
{
.
then
(
data
=>
{
this
.
id
=
data
.
id
;
this
.
id
=
data
.
id
;
this
.
type
=
data
.
list_type
;
this
.
type
=
data
.
list_type
;
this
.
position
=
data
.
position
;
this
.
position
=
data
.
position
;
...
@@ -50,25 +66,23 @@ class List {
...
@@ -50,25 +66,23 @@ class List {
});
});
}
}
destroy
()
{
destroy
()
{
const
index
=
gl
.
issueBoards
.
BoardsStore
.
state
.
lists
.
indexOf
(
this
);
const
index
=
gl
.
issueBoards
.
BoardsStore
.
state
.
lists
.
indexOf
(
this
);
gl
.
issueBoards
.
BoardsStore
.
state
.
lists
.
splice
(
index
,
1
);
gl
.
issueBoards
.
BoardsStore
.
state
.
lists
.
splice
(
index
,
1
);
gl
.
issueBoards
.
BoardsStore
.
updateNewListDropdown
(
this
.
id
);
gl
.
issueBoards
.
BoardsStore
.
updateNewListDropdown
(
this
.
id
);
gl
.
boardService
.
destroyList
(
this
.
id
)
gl
.
boardService
.
destroyList
(
this
.
id
).
catch
(()
=>
{
.
catch
(()
=>
{
// TODO: handle request error
// TODO: handle request error
});
});
}
}
update
()
{
update
()
{
gl
.
boardService
.
updateList
(
this
.
id
,
this
.
position
)
gl
.
boardService
.
updateList
(
this
.
id
,
this
.
position
).
catch
(()
=>
{
.
catch
(()
=>
{
// TODO: handle request error
// TODO: handle request error
});
});
}
}
nextPage
()
{
nextPage
()
{
if
(
this
.
issuesSize
>
this
.
issues
.
length
)
{
if
(
this
.
issuesSize
>
this
.
issues
.
length
)
{
if
(
this
.
issues
.
length
/
PER_PAGE
>=
1
)
{
if
(
this
.
issues
.
length
/
PER_PAGE
>=
1
)
{
this
.
page
+=
1
;
this
.
page
+=
1
;
...
@@ -78,7 +92,7 @@ class List {
...
@@ -78,7 +92,7 @@ class List {
}
}
}
}
getIssues
(
emptyIssues
=
true
)
{
getIssues
(
emptyIssues
=
true
)
{
const
data
=
queryData
(
gl
.
issueBoards
.
BoardsStore
.
filter
.
path
,
{
page
:
this
.
page
});
const
data
=
queryData
(
gl
.
issueBoards
.
BoardsStore
.
filter
.
path
,
{
page
:
this
.
page
});
if
(
this
.
label
&&
data
.
label_name
)
{
if
(
this
.
label
&&
data
.
label_name
)
{
...
@@ -89,7 +103,8 @@ class List {
...
@@ -89,7 +103,8 @@ class List {
this
.
loading
=
true
;
this
.
loading
=
true
;
}
}
return
gl
.
boardService
.
getIssuesForList
(
this
.
id
,
data
)
return
gl
.
boardService
.
getIssuesForList
(
this
.
id
,
data
)
.
then
(
res
=>
res
.
data
)
.
then
(
res
=>
res
.
data
)
.
then
((
data
)
=>
{
.
then
((
data
)
=>
{
this
.
loading
=
false
;
this
.
loading
=
false
;
...
@@ -103,11 +118,12 @@ class List {
...
@@ -103,11 +118,12 @@ class List {
});
});
}
}
newIssue
(
issue
)
{
newIssue
(
issue
)
{
this
.
addIssue
(
issue
,
null
,
0
);
this
.
addIssue
(
issue
,
null
,
0
);
this
.
issuesSize
+=
1
;
this
.
issuesSize
+=
1
;
return
gl
.
boardService
.
newIssue
(
this
.
id
,
issue
)
return
gl
.
boardService
.
newIssue
(
this
.
id
,
issue
)
.
then
(
res
=>
res
.
data
)
.
then
(
res
=>
res
.
data
)
.
then
((
data
)
=>
{
.
then
((
data
)
=>
{
issue
.
id
=
data
.
id
;
issue
.
id
=
data
.
id
;
...
@@ -123,13 +139,13 @@ class List {
...
@@ -123,13 +139,13 @@ class List {
});
});
}
}
createIssues
(
data
)
{
createIssues
(
data
)
{
data
.
forEach
(
(
issueObj
)
=>
{
data
.
forEach
(
issueObj
=>
{
this
.
addIssue
(
new
ListIssue
(
issueObj
,
this
.
defaultAvatar
));
this
.
addIssue
(
new
ListIssue
(
issueObj
,
this
.
defaultAvatar
));
});
});
}
}
addIssue
(
issue
,
listFrom
,
newIndex
)
{
addIssue
(
issue
,
listFrom
,
newIndex
)
{
let
moveBeforeId
=
null
;
let
moveBeforeId
=
null
;
let
moveAfterId
=
null
;
let
moveAfterId
=
null
;
...
@@ -152,6 +168,13 @@ class List {
...
@@ -152,6 +168,13 @@ class List {
issue
.
addLabel
(
this
.
label
);
issue
.
addLabel
(
this
.
label
);
}
}
if
(
this
.
assignee
)
{
if
(
listFrom
&&
listFrom
.
type
===
'
assignee
'
)
{
issue
.
removeAssignee
(
listFrom
.
assignee
);
}
issue
.
addAssignee
(
this
.
assignee
);
}
if
(
listFrom
)
{
if
(
listFrom
)
{
this
.
issuesSize
+=
1
;
this
.
issuesSize
+=
1
;
...
@@ -160,29 +183,29 @@ class List {
...
@@ -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
(
oldIndex
,
1
);
this
.
issues
.
splice
(
newIndex
,
0
,
issue
);
this
.
issues
.
splice
(
newIndex
,
0
,
issue
);
gl
.
boardService
.
moveIssue
(
issue
.
id
,
null
,
null
,
moveBeforeId
,
moveAfterId
)
gl
.
boardService
.
moveIssue
(
issue
.
id
,
null
,
null
,
moveBeforeId
,
moveAfterId
).
catch
(()
=>
{
.
catch
(()
=>
{
// TODO: handle request error
// TODO: handle request error
});
});
}
}
updateIssueLabel
(
issue
,
listFrom
,
moveBeforeId
,
moveAfterId
)
{
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
(()
=>
{
.
catch
(()
=>
{
// TODO: handle request error
// TODO: handle request error
});
});
}
}
findIssue
(
id
)
{
findIssue
(
id
)
{
return
this
.
issues
.
find
(
issue
=>
issue
.
id
===
id
);
return
this
.
issues
.
find
(
issue
=>
issue
.
id
===
id
);
}
}
removeIssue
(
removeIssue
)
{
removeIssue
(
removeIssue
)
{
this
.
issues
=
this
.
issues
.
filter
(
(
issue
)
=>
{
this
.
issues
=
this
.
issues
.
filter
(
issue
=>
{
const
matchesRemove
=
removeIssue
.
id
===
issue
.
id
;
const
matchesRemove
=
removeIssue
.
id
===
issue
.
id
;
if
(
matchesRemove
)
{
if
(
matchesRemove
)
{
...
...
app/assets/javascripts/boards/services/board_service.js
View file @
cf41aaba
...
@@ -30,11 +30,13 @@ export default class BoardService {
...
@@ -30,11 +30,13 @@ export default class BoardService {
return
axios
.
post
(
this
.
listsEndpointGenerate
,
{});
return
axios
.
post
(
this
.
listsEndpointGenerate
,
{});
}
}
createList
(
labelId
)
{
createList
(
entityId
,
entityType
)
{
const
list
=
{
[
entityType
]:
entityId
,
};
return
axios
.
post
(
this
.
listsEndpoint
,
{
return
axios
.
post
(
this
.
listsEndpoint
,
{
list
:
{
list
,
label_id
:
labelId
,
},
});
});
}
}
...
...
app/assets/javascripts/boards/stores/boards_store.js
View file @
cf41aaba
...
@@ -103,8 +103,15 @@ gl.issueBoards.BoardsStore = {
...
@@ -103,8 +103,15 @@ gl.issueBoards.BoardsStore = {
const
listLabels
=
issueLists
.
map
(
listIssue
=>
listIssue
.
label
);
const
listLabels
=
issueLists
.
map
(
listIssue
=>
listIssue
.
label
);
if
(
!
issueTo
)
{
if
(
!
issueTo
)
{
// 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
// Add to new lists issues if it doesn't already exist
listTo
.
addIssue
(
issue
,
listFrom
,
newIndex
);
listTo
.
addIssue
(
issue
,
listFrom
,
newIndex
);
}
}
else
{
}
else
{
listTo
.
updateIssueLabel
(
issue
,
listFrom
);
listTo
.
updateIssueLabel
(
issue
,
listFrom
);
issueTo
.
removeLabel
(
listFrom
.
label
);
issueTo
.
removeLabel
(
listFrom
.
label
);
...
@@ -115,7 +122,11 @@ gl.issueBoards.BoardsStore = {
...
@@ -115,7 +122,11 @@ gl.issueBoards.BoardsStore = {
list
.
removeIssue
(
issue
);
list
.
removeIssue
(
issue
);
});
});
issue
.
removeLabels
(
listLabels
);
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
);
listFrom
.
removeIssue
(
issue
);
}
}
},
},
...
@@ -126,11 +137,12 @@ gl.issueBoards.BoardsStore = {
...
@@ -126,11 +137,12 @@ gl.issueBoards.BoardsStore = {
list
.
moveIssue
(
issue
,
oldIndex
,
newIndex
,
beforeId
,
afterId
);
list
.
moveIssue
(
issue
,
oldIndex
,
newIndex
,
beforeId
,
afterId
);
},
},
findList
(
key
,
val
,
type
=
'
label
'
)
{
findList
(
key
,
val
,
type
=
'
label
'
)
{
return
this
.
state
.
lists
.
filter
((
list
)
=>
{
const
filteredList
=
this
.
state
.
lists
.
filter
((
list
)
=>
{
const
byType
=
type
?
list
[
'
type
'
]
===
type
:
true
;
const
byType
=
type
?
(
list
.
type
===
type
)
||
(
list
.
type
===
'
assignee
'
)
:
true
;
return
list
[
key
]
===
val
&&
byType
;
return
list
[
key
]
===
val
&&
byType
;
})[
0
];
});
return
filteredList
[
0
];
},
},
updateFiltersUrl
()
{
updateFiltersUrl
()
{
history
.
pushState
(
null
,
null
,
`?
${
this
.
filter
.
path
}
`
);
history
.
pushState
(
null
,
null
,
`?
${
this
.
filter
.
path
}
`
);
...
...
app/assets/javascripts/gl_dropdown.js
View file @
cf41aaba
...
@@ -602,7 +602,11 @@ GitLabDropdown = (function() {
...
@@ -602,7 +602,11 @@ GitLabDropdown = (function() {
var
selector
;
var
selector
;
selector
=
'
.dropdown-content
'
;
selector
=
'
.dropdown-content
'
;
if
(
this
.
dropdown
.
find
(
"
.dropdown-toggle-page
"
).
length
)
{
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
();
return
$
(
selector
,
this
.
dropdown
).
empty
();
...
...
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
;
app/controllers/boards/lists_controller.rb
View file @
cf41aaba
...
@@ -56,8 +56,12 @@ module Boards
...
@@ -56,8 +56,12 @@ module Boards
private
private
def
list_creation_attrs
%i[label_id]
end
def
list_params
def
list_params
params
.
require
(
:list
).
permit
(
:label_id
)
params
.
require
(
:list
).
permit
(
list_creation_attrs
)
end
end
def
move_params
def
move_params
...
@@ -65,11 +69,15 @@ module Boards
...
@@ -65,11 +69,15 @@ module Boards
end
end
def
serialize_as_json
(
resource
)
def
serialize_as_json
(
resource
)
resource
.
as_json
(
resource
.
as_json
(
serialization_attrs
)
end
def
serialization_attrs
{
only:
[
:id
,
:list_type
,
:position
],
only:
[
:id
,
:list_type
,
:position
],
methods:
[
:title
],
methods:
[
:title
],
label:
true
label:
true
)
}
end
end
end
end
end
end
app/finders/group_members_finder.rb
View file @
cf41aaba
...
@@ -3,17 +3,29 @@ class GroupMembersFinder
...
@@ -3,17 +3,29 @@ class GroupMembersFinder
@group
=
group
@group
=
group
end
end
def
execute
def
execute
(
include_descendants:
false
)
group_members
=
@group
.
members
group_members
=
@group
.
members
wheres
=
[]
return
group_members
unless
@group
.
parent
return
group_members
unless
@group
.
parent
||
include_descendants
wheres
<<
"members.id IN (
#{
group_members
.
select
(
:id
).
to_sql
}
)"
if
@group
.
parent
parents_members
=
GroupMember
.
non_request
parents_members
=
GroupMember
.
non_request
.
where
(
source_id:
@group
.
ancestors
.
select
(
:id
))
.
where
(
source_id:
@group
.
ancestors
.
select
(
:id
))
.
where
.
not
(
user_id:
@group
.
users
.
select
(
:id
))
.
where
.
not
(
user_id:
@group
.
users
.
select
(
:id
))
wheres
=
[
"members.id IN (
#{
group_members
.
select
(
:id
).
to_sql
}
)"
]
wheres
<<
"members.id IN (
#{
parents_members
.
select
(
:id
).
to_sql
}
)"
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 '
))
GroupMember
.
where
(
wheres
.
join
(
' OR '
))
end
end
...
...
app/finders/members_finder.rb
View file @
cf41aaba
...
@@ -7,12 +7,12 @@ class MembersFinder
...
@@ -7,12 +7,12 @@ class MembersFinder
@group
=
project
.
group
@group
=
project
.
group
end
end
def
execute
def
execute
(
include_descendants:
false
)
project_members
=
project
.
project_members
project_members
=
project
.
project_members
project_members
=
project_members
.
non_invite
unless
can?
(
current_user
,
:admin_project
,
project
)
project_members
=
project_members
.
non_invite
unless
can?
(
current_user
,
:admin_project
,
project
)
if
group
if
group
group_members
=
GroupMembersFinder
.
new
(
group
).
execute
group_members
=
GroupMembersFinder
.
new
(
group
).
execute
(
include_descendants:
include_descendants
)
group_members
=
group_members
.
non_invite
group_members
=
group_members
.
non_invite
union
=
Gitlab
::
SQL
::
Union
.
new
([
project_members
,
group_members
],
remove_duplicates:
false
)
union
=
Gitlab
::
SQL
::
Union
.
new
([
project_members
,
group_members
],
remove_duplicates:
false
)
...
...
app/models/list.rb
View file @
cf41aaba
...
@@ -2,17 +2,27 @@ class List < ActiveRecord::Base
...
@@ -2,17 +2,27 @@ class List < ActiveRecord::Base
belongs_to
:board
belongs_to
:board
belongs_to
:label
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
:board
,
:list_type
,
presence:
true
validates
:label
,
:position
,
presence:
true
,
if: :label?
validates
:label
,
:position
,
presence:
true
,
if: :label?
validates
:label_id
,
uniqueness:
{
scope: :board_id
},
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
before_destroy
:can_be_destroyed
scope
:destroyable
,
->
{
where
(
list_type:
list_types
[
:label
])
}
scope
:destroyable
,
->
{
where
(
list_type:
list_types
.
slice
(
*
destroyable_types
).
values
)
}
scope
:movable
,
->
{
where
(
list_type:
list_types
[
:label
])
}
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?
def
destroyable?
label?
label?
...
...
app/services/boards/issues/list_service.rb
View file @
cf41aaba
...
@@ -3,13 +3,18 @@ module Boards
...
@@ -3,13 +3,18 @@ module Boards
class
ListService
<
Boards
::
BaseService
class
ListService
<
Boards
::
BaseService
def
execute
def
execute
issues
=
IssuesFinder
.
new
(
current_user
,
filter_params
).
execute
issues
=
IssuesFinder
.
new
(
current_user
,
filter_params
).
execute
issues
=
without_board_labels
(
issues
)
unless
movable_list?
||
closed_list?
issues
=
filter
(
issues
)
issues
=
with_list_label
(
issues
)
if
movable_list?
issues
.
order_by_position_and_priority
issues
.
order_by_position_and_priority
end
end
private
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
def
board
@board
||=
parent
.
boards
.
find
(
params
[
:board_id
])
@board
||=
parent
.
boards
.
find
(
params
[
:board_id
])
end
end
...
@@ -20,18 +25,6 @@ module Boards
...
@@ -20,18 +25,6 @@ module Boards
@list
=
board
.
lists
.
find
(
params
[
:id
])
if
params
.
key?
(
:id
)
@list
=
board
.
lists
.
find
(
params
[
:id
])
if
params
.
key?
(
:id
)
end
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
def
filter_params
set_parent
set_parent
set_state
set_state
...
...
app/services/boards/issues/move_service.rb
View file @
cf41aaba
...
@@ -3,7 +3,7 @@ module Boards
...
@@ -3,7 +3,7 @@ module Boards
class
MoveService
<
Boards
::
BaseService
class
MoveService
<
Boards
::
BaseService
def
execute
(
issue
)
def
execute
(
issue
)
return
false
unless
can?
(
current_user
,
:update_issue
,
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
)
update
(
issue
)
end
end
...
@@ -28,10 +28,10 @@ module Boards
...
@@ -28,10 +28,10 @@ module Boards
end
end
def
update
(
issue
)
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
end
def
issue_params
def
issue_params
(
issue
)
attrs
=
{}
attrs
=
{}
if
move_between_lists?
if
move_between_lists?
...
...
app/services/boards/lists/create_service.rb
View file @
cf41aaba
module
Boards
module
Boards
module
Lists
module
Lists
class
CreateService
<
Boards
::
BaseService
class
CreateService
<
Boards
::
BaseService
include
Gitlab
::
Utils
::
StrongMemoize
def
execute
(
board
)
def
execute
(
board
)
List
.
transaction
do
List
.
transaction
do
label
=
available_labels_for
(
board
).
find
(
params
[
:label_id
]
)
target
=
target
(
board
)
position
=
next_position
(
board
)
position
=
next_position
(
board
)
create_list
(
board
,
label
,
position
)
create_list
(
board
,
type
,
target
,
position
)
end
end
end
end
private
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
)
def
available_labels_for
(
board
)
options
=
{
include_ancestor_groups:
true
}
options
=
{
include_ancestor_groups:
true
}
...
@@ -28,8 +40,8 @@ module Boards
...
@@ -28,8 +40,8 @@ module Boards
max_position
.
nil?
?
0
:
max_position
.
succ
max_position
.
nil?
?
0
:
max_position
.
succ
end
end
def
create_list
(
board
,
label
,
position
)
def
create_list
(
board
,
type
,
target
,
position
)
board
.
lists
.
create
(
label:
label
,
list_type: :label
,
position:
position
)
board
.
lists
.
create
(
type
=>
target
,
list_type:
type
,
position:
position
)
end
end
end
end
end
end
...
...
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"
}
":data-id"
=>
"list.id"
}
.board-inner
.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)"
}
%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 @@
...
@@ -7,10 +7,18 @@
":class"
:
"{
\"
fa-caret-down
\"
: list.isExpanded,
\"
fa-caret-right
\"
: !list.isExpanded }"
,
":class"
:
"{
\"
fa-caret-down
\"
: list.isExpanded,
\"
fa-caret-right
\"
: !list.isExpanded }"
,
"aria-hidden"
:
"true"
}
"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
\"
"
,
%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 }}
{{ 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
\"
"
,
%span
.has-tooltip
{
"v-if"
:
"list.type ===
\"
label
\"
"
,
":title"
=>
'(list.label ? list.label.description : "")'
,
":title"
=>
'(list.label ? list.label.description : "")'
,
data:
{
container:
"body"
,
placement:
"bottom"
},
data:
{
container:
"body"
,
placement:
"bottom"
},
...
...
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
app/views/shared/issuable/_label_page_create.html.haml
View file @
cf41aaba
-
show_close
=
local_assigns
.
fetch
(
:show_close
,
true
)
-
subject
=
@project
||
@group
-
subject
=
@project
||
@group
.dropdown-page-two.dropdown-new-label
.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_content
do
.dropdown-labels-error.js-label-error
.dropdown-labels-error.js-label-error
%input
#new_label_name
.default-dropdown-input
{
type:
"text"
,
placeholder:
_
(
'Name new label'
)
}
%input
#new_label_name
.default-dropdown-input
{
type:
"text"
,
placeholder:
_
(
'Name new label'
)
}
...
...
app/views/shared/issuable/_label_page_default.html.haml
View file @
cf41aaba
-
title
=
local_assigns
.
fetch
(
:title
,
_
(
'Assign labels'
))
-
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_create
=
local_assigns
.
fetch
(
:show_create
,
true
)
-
show_footer
=
local_assigns
.
fetch
(
:show_footer
,
true
)
-
show_footer
=
local_assigns
.
fetch
(
:show_footer
,
true
)
-
filter_placeholder
=
local_assigns
.
fetch
(
:filter_placeholder
,
'Search'
)
-
filter_placeholder
=
local_assigns
.
fetch
(
:filter_placeholder
,
'Search'
)
-
show_boards_content
=
local_assigns
.
fetch
(
:show_boards_content
,
false
)
-
show_boards_content
=
local_assigns
.
fetch
(
:show_boards_content
,
false
)
-
subject
=
@project
||
@group
-
subject
=
@project
||
@group
.dropdown-page-one
.dropdown-page-one
-
if
show_title
=
dropdown_title
(
title
)
=
dropdown_title
(
title
)
-
if
show_boards_content
-
if
show_boards_content
.issue-board-dropdown-content
.issue-board-dropdown-content
%p
%p
=
_
(
'Create lists from labels. Issues with that label appear in that list.'
)
=
content_title
=
dropdown_filter
(
filter_placeholder
)
=
dropdown_filter
(
filter_placeholder
)
=
dropdown_content
=
dropdown_content
-
if
current_board_parent
&&
show_footer
-
if
current_board_parent
&&
show_footer
...
...
app/views/shared/issuable/_search_bar.html.haml
View file @
cf41aaba
...
@@ -104,14 +104,7 @@
...
@@ -104,14 +104,7 @@
.filter-dropdown-container
.filter-dropdown-container
-
if
type
==
:boards
-
if
type
==
:boards
-
if
can?
(
current_user
,
:admin_list
,
board
.
parent
)
-
if
can?
(
current_user
,
:admin_list
,
board
.
parent
)
.dropdown.prepend-left-10
#js-add-list
=
render_if_exists
'shared/issuable/board_create_list_dropdown'
,
board:
board
%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
-
if
@project
-
if
@project
#js-add-issues-btn
.prepend-left-10
{
data:
{
can_admin_list:
can?
(
current_user
,
:admin_list
,
@project
)
}
}
#js-add-issues-btn
.prepend-left-10
{
data:
{
can_admin_list:
can?
(
current_user
,
:admin_list
,
@project
)
}
}
-
elsif
type
!=
:boards_modal
-
elsif
type
!=
:boards_modal
...
...
spec/features/boards/boards_spec.rb
View file @
cf41aaba
...
@@ -150,7 +150,7 @@ describe 'Issue Boards', :js do
...
@@ -150,7 +150,7 @@ describe 'Issue Boards', :js do
click_button
'Add list'
click_button
'Add list'
wait_for_requests
wait_for_requests
find
(
'.
dropdown-menu-close
'
).
click
find
(
'.
js-new-board-list
'
).
click
page
.
within
(
find
(
'.board:nth-child(2)'
))
do
page
.
within
(
find
(
'.board:nth-child(2)'
))
do
accept_confirm
{
find
(
'.board-delete'
).
click
}
accept_confirm
{
find
(
'.board-delete'
).
click
}
...
...
spec/features/issues/form_spec.rb
View file @
cf41aaba
...
@@ -143,6 +143,9 @@ describe 'New/edit issue', :js do
...
@@ -143,6 +143,9 @@ describe 'New/edit issue', :js do
click_link
label
.
title
click_link
label
.
title
click_link
label2
.
title
click_link
label2
.
title
end
end
find
(
'.js-issuable-form-dropdown.js-label-select'
).
click
page
.
within
'.js-label-select'
do
page
.
within
'.js-label-select'
do
expect
(
page
).
to
have_content
label
.
title
expect
(
page
).
to
have_content
label
.
title
end
end
...
...
spec/finders/group_members_finder_spec.rb
View file @
cf41aaba
...
@@ -29,4 +29,16 @@ describe GroupMembersFinder, '#execute' do
...
@@ -29,4 +29,16 @@ describe GroupMembersFinder, '#execute' do
expect
(
result
.
to_a
).
to
match_array
([
member1
,
member3
,
member4
])
expect
(
result
.
to_a
).
to
match_array
([
member1
,
member3
,
member4
])
end
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
end
spec/finders/members_finder_spec.rb
View file @
cf41aaba
...
@@ -19,4 +19,16 @@ describe MembersFinder, '#execute' do
...
@@ -19,4 +19,16 @@ describe MembersFinder, '#execute' do
expect
(
result
.
to_a
).
to
match_array
([
member1
,
member2
,
member3
])
expect
(
result
.
to_a
).
to
match_array
([
member1
,
member2
,
member3
])
end
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
end
spec/fixtures/api/schemas/list.json
View file @
cf41aaba
...
@@ -37,5 +37,5 @@
...
@@ -37,5 +37,5 @@
"title"
:
{
"type"
:
"string"
},
"title"
:
{
"type"
:
"string"
},
"position"
:
{
"type"
:
[
"integer"
,
"null"
]
}
"position"
:
{
"type"
:
[
"integer"
,
"null"
]
}
},
},
"additionalProperties"
:
fals
e
"additionalProperties"
:
tru
e
}
}
spec/javascripts/boards/board_card_spec.js
View file @
cf41aaba
...
@@ -5,10 +5,10 @@
...
@@ -5,10 +5,10 @@
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
'
~/boards/models/assignee
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/stores/boards_store
'
;
import
'
~/boards/stores/boards_store
'
;
import
boardCard
from
'
~/boards/components/board_card.vue
'
;
import
boardCard
from
'
~/boards/components/board_card.vue
'
;
...
...
spec/javascripts/boards/boards_store_spec.js
View file @
cf41aaba
...
@@ -7,9 +7,9 @@ import axios from '~/lib/utils/axios_utils';
...
@@ -7,9 +7,9 @@ import axios from '~/lib/utils/axios_utils';
import
Cookies
from
'
js-cookie
'
;
import
Cookies
from
'
js-cookie
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/stores/boards_store
'
;
import
'
~/boards/stores/boards_store
'
;
import
{
listObj
,
listObjDuplicate
,
boardsMockInterceptor
,
mockBoardService
}
from
'
./mock_data
'
;
import
{
listObj
,
listObjDuplicate
,
boardsMockInterceptor
,
mockBoardService
}
from
'
./mock_data
'
;
...
...
spec/javascripts/boards/issue_card_spec.js
View file @
cf41aaba
...
@@ -5,9 +5,9 @@
...
@@ -5,9 +5,9 @@
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/stores/boards_store
'
;
import
'
~/boards/stores/boards_store
'
;
import
'
~/boards/components/issue_card_inner
'
;
import
'
~/boards/components/issue_card_inner
'
;
import
{
listObj
}
from
'
./mock_data
'
;
import
{
listObj
}
from
'
./mock_data
'
;
...
...
spec/javascripts/boards/issue_spec.js
View file @
cf41aaba
...
@@ -3,9 +3,9 @@
...
@@ -3,9 +3,9 @@
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/stores/boards_store
'
;
import
'
~/boards/stores/boards_store
'
;
import
{
mockBoardService
}
from
'
./mock_data
'
;
import
{
mockBoardService
}
from
'
./mock_data
'
;
...
...
spec/javascripts/boards/list_spec.js
View file @
cf41aaba
...
@@ -6,9 +6,9 @@ import MockAdapter from 'axios-mock-adapter';
...
@@ -6,9 +6,9 @@ import MockAdapter from 'axios-mock-adapter';
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/stores/boards_store
'
;
import
'
~/boards/stores/boards_store
'
;
import
{
listObj
,
listObjDuplicate
,
boardsMockInterceptor
,
mockBoardService
}
from
'
./mock_data
'
;
import
{
listObj
,
listObjDuplicate
,
boardsMockInterceptor
,
mockBoardService
}
from
'
./mock_data
'
;
...
...
spec/javascripts/boards/modal_store_spec.js
View file @
cf41aaba
/* global ListIssue */
/* global ListIssue */
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/label
'
;
import
'
~/vue_shared/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/assignee
'
;
import
Store
from
'
~/boards/stores/modal_store
'
;
import
Store
from
'
~/boards/stores/modal_store
'
;
describe
(
'
Modal store
'
,
()
=>
{
describe
(
'
Modal store
'
,
()
=>
{
...
...
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