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
b4778a58
Commit
b4778a58
authored
Sep 07, 2017
by
Sean McGivern
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'ee_issue_928_backport' into 'master'
Group boards CE backport See merge request !13883
parents
cb555da1
a1a839c9
Changes
78
Hide whitespace changes
Inline
Side-by-side
Showing
78 changed files
with
774 additions
and
527 deletions
+774
-527
app/assets/javascripts/api.js
app/assets/javascripts/api.js
+12
-4
app/assets/javascripts/boards/boards_bundle.js
app/assets/javascripts/boards/boards_bundle.js
+33
-18
app/assets/javascripts/boards/components/board_list.js
app/assets/javascripts/boards/components/board_list.js
+4
-6
app/assets/javascripts/boards/components/board_new_issue.js
app/assets/javascripts/boards/components/board_new_issue.js
+4
-1
app/assets/javascripts/boards/components/issue_card_inner.js
app/assets/javascripts/boards/components/issue_card_inner.js
+6
-3
app/assets/javascripts/boards/components/modal/footer.js
app/assets/javascripts/boards/components/modal/footer.js
+1
-1
app/assets/javascripts/boards/components/new_list_dropdown.js
...assets/javascripts/boards/components/new_list_dropdown.js
+1
-1
app/assets/javascripts/boards/components/sidebar/remove_issue.js
...ets/javascripts/boards/components/sidebar/remove_issue.js
+22
-6
app/assets/javascripts/boards/models/issue.js
app/assets/javascripts/boards/models/issue.js
+2
-2
app/assets/javascripts/boards/models/label.js
app/assets/javascripts/boards/models/label.js
+1
-0
app/assets/javascripts/boards/models/list.js
app/assets/javascripts/boards/models/list.js
+15
-13
app/assets/javascripts/boards/services/board_service.js
app/assets/javascripts/boards/services/board_service.js
+10
-10
app/assets/stylesheets/framework/dropdowns.scss
app/assets/stylesheets/framework/dropdowns.scss
+1
-1
app/assets/stylesheets/pages/boards.scss
app/assets/stylesheets/pages/boards.scss
+20
-43
app/controllers/boards/application_controller.rb
app/controllers/boards/application_controller.rb
+21
-0
app/controllers/boards/issues_controller.rb
app/controllers/boards/issues_controller.rb
+90
-0
app/controllers/boards/lists_controller.rb
app/controllers/boards/lists_controller.rb
+75
-0
app/controllers/concerns/boards_responses.rb
app/controllers/concerns/boards_responses.rb
+42
-0
app/controllers/projects/boards/application_controller.rb
app/controllers/projects/boards/application_controller.rb
+0
-15
app/controllers/projects/boards/issues_controller.rb
app/controllers/projects/boards/issues_controller.rb
+0
-94
app/controllers/projects/boards/lists_controller.rb
app/controllers/projects/boards/lists_controller.rb
+0
-86
app/controllers/projects/boards_controller.rb
app/controllers/projects/boards_controller.rb
+13
-14
app/helpers/boards_helper.rb
app/helpers/boards_helper.rb
+71
-6
app/helpers/issuables_helper.rb
app/helpers/issuables_helper.rb
+8
-0
app/helpers/labels_helper.rb
app/helpers/labels_helper.rb
+4
-3
app/helpers/search_helper.rb
app/helpers/search_helper.rb
+8
-6
app/models/board.rb
app/models/board.rb
+13
-1
app/models/concerns/relative_positioning.rb
app/models/concerns/relative_positioning.rb
+9
-5
app/models/label.rb
app/models/label.rb
+3
-1
app/models/project.rb
app/models/project.rb
+8
-0
app/services/boards/base_service.rb
app/services/boards/base_service.rb
+10
-0
app/services/boards/create_service.rb
app/services/boards/create_service.rb
+3
-3
app/services/boards/issues/create_service.rb
app/services/boards/issues/create_service.rb
+10
-2
app/services/boards/issues/list_service.rb
app/services/boards/issues/list_service.rb
+5
-5
app/services/boards/issues/move_service.rb
app/services/boards/issues/move_service.rb
+10
-10
app/services/boards/list_service.rb
app/services/boards/list_service.rb
+4
-4
app/services/boards/lists/create_service.rb
app/services/boards/lists/create_service.rb
+4
-5
app/services/boards/lists/destroy_service.rb
app/services/boards/lists/destroy_service.rb
+1
-1
app/services/boards/lists/generate_service.rb
app/services/boards/lists/generate_service.rb
+3
-3
app/services/boards/lists/list_service.rb
app/services/boards/lists/list_service.rb
+1
-1
app/services/boards/lists/move_service.rb
app/services/boards/lists/move_service.rb
+1
-1
app/services/issues/update_service.rb
app/services/issues/update_service.rb
+8
-8
app/views/projects/boards/index.html.haml
app/views/projects/boards/index.html.haml
+1
-1
app/views/projects/boards/show.html.haml
app/views/projects/boards/show.html.haml
+1
-1
app/views/shared/boards/_show.html.haml
app/views/shared/boards/_show.html.haml
+2
-2
app/views/shared/boards/components/_board.html.haml
app/views/shared/boards/components/_board.html.haml
+12
-11
app/views/shared/boards/components/_sidebar.html.haml
app/views/shared/boards/components/_sidebar.html.haml
+7
-6
app/views/shared/boards/components/sidebar/_assignee.html.haml
...iews/shared/boards/components/sidebar/_assignee.html.haml
+6
-6
app/views/shared/boards/components/sidebar/_due_date.html.haml
...iews/shared/boards/components/sidebar/_due_date.html.haml
+4
-4
app/views/shared/boards/components/sidebar/_labels.html.haml
app/views/shared/boards/components/sidebar/_labels.html.haml
+12
-5
app/views/shared/boards/components/sidebar/_milestone.html.haml
...ews/shared/boards/components/sidebar/_milestone.html.haml
+5
-5
app/views/shared/boards/components/sidebar/_notifications.html.haml
...shared/boards/components/sidebar/_notifications.html.haml
+1
-1
app/views/shared/boards/index.html.haml
app/views/shared/boards/index.html.haml
+1
-0
app/views/shared/boards/show.html.haml
app/views/shared/boards/show.html.haml
+1
-0
app/views/shared/issuable/_label_page_default.html.haml
app/views/shared/issuable/_label_page_default.html.haml
+5
-6
app/views/shared/issuable/_search_bar.html.haml
app/views/shared/issuable/_search_bar.html.haml
+3
-3
config/routes.rb
config/routes.rb
+13
-0
config/routes/project.rb
config/routes/project.rb
+1
-13
doc/README.md
doc/README.md
+1
-1
lib/gitlab/path_regex.rb
lib/gitlab/path_regex.rb
+1
-0
spec/controllers/boards/issues_controller_spec.rb
spec/controllers/boards/issues_controller_spec.rb
+23
-9
spec/controllers/boards/lists_controller_spec.rb
spec/controllers/boards/lists_controller_spec.rb
+1
-1
spec/factories/milestones.rb
spec/factories/milestones.rb
+4
-0
spec/fixtures/api/schemas/issue.json
spec/fixtures/api/schemas/issue.json
+6
-0
spec/javascripts/boards/board_blank_state_spec.js
spec/javascripts/boards/board_blank_state_spec.js
+2
-1
spec/javascripts/boards/board_card_spec.js
spec/javascripts/boards/board_card_spec.js
+3
-2
spec/javascripts/boards/board_list_spec.js
spec/javascripts/boards/board_list_spec.js
+3
-1
spec/javascripts/boards/board_new_issue_spec.js
spec/javascripts/boards/board_new_issue_spec.js
+2
-1
spec/javascripts/boards/boards_store_spec.js
spec/javascripts/boards/boards_store_spec.js
+4
-2
spec/javascripts/boards/components/board_spec.js
spec/javascripts/boards/components/board_spec.js
+8
-2
spec/javascripts/boards/issue_card_spec.js
spec/javascripts/boards/issue_card_spec.js
+47
-48
spec/javascripts/boards/issue_spec.js
spec/javascripts/boards/issue_spec.js
+3
-1
spec/javascripts/boards/list_spec.js
spec/javascripts/boards/list_spec.js
+10
-5
spec/javascripts/boards/mock_data.js
spec/javascripts/boards/mock_data.js
+19
-3
spec/javascripts/boards/modal_store_spec.js
spec/javascripts/boards/modal_store_spec.js
+2
-0
spec/services/boards/issues/create_service_spec.rb
spec/services/boards/issues/create_service_spec.rb
+1
-1
spec/services/boards/issues/move_service_spec.rb
spec/services/boards/issues/move_service_spec.rb
+1
-1
spec/services/issues/update_service_spec.rb
spec/services/issues/update_service_spec.rb
+1
-1
No files found.
app/assets/javascripts/api.js
View file @
b4778a58
...
@@ -6,7 +6,8 @@ const Api = {
...
@@ -6,7 +6,8 @@ const Api = {
namespacesPath
:
'
/api/:version/namespaces.json
'
,
namespacesPath
:
'
/api/:version/namespaces.json
'
,
groupProjectsPath
:
'
/api/:version/groups/:id/projects.json
'
,
groupProjectsPath
:
'
/api/:version/groups/:id/projects.json
'
,
projectsPath
:
'
/api/:version/projects.json
'
,
projectsPath
:
'
/api/:version/projects.json
'
,
labelsPath
:
'
/:namespace_path/:project_path/labels
'
,
projectLabelsPath
:
'
/:namespace_path/:project_path/labels
'
,
groupLabelsPath
:
'
/groups/:namespace_path/labels
'
,
licensePath
:
'
/api/:version/templates/licenses/:key
'
,
licensePath
:
'
/api/:version/templates/licenses/:key
'
,
gitignorePath
:
'
/api/:version/templates/gitignores/:key
'
,
gitignorePath
:
'
/api/:version/templates/gitignores/:key
'
,
gitlabCiYmlPath
:
'
/api/:version/templates/gitlab_ci_ymls/:key
'
,
gitlabCiYmlPath
:
'
/api/:version/templates/gitlab_ci_ymls/:key
'
,
...
@@ -74,9 +75,16 @@ const Api = {
...
@@ -74,9 +75,16 @@ const Api = {
},
},
newLabel
(
namespacePath
,
projectPath
,
data
,
callback
)
{
newLabel
(
namespacePath
,
projectPath
,
data
,
callback
)
{
const
url
=
Api
.
buildUrl
(
Api
.
labelsPath
)
let
url
;
.
replace
(
'
:namespace_path
'
,
namespacePath
)
.
replace
(
'
:project_path
'
,
projectPath
);
if
(
projectPath
)
{
url
=
Api
.
buildUrl
(
Api
.
projectLabelsPath
)
.
replace
(
'
:namespace_path
'
,
namespacePath
)
.
replace
(
'
:project_path
'
,
projectPath
);
}
else
{
url
=
Api
.
buildUrl
(
Api
.
groupLabelsPath
).
replace
(
'
:namespace_path
'
,
namespacePath
);
}
return
$
.
ajax
({
return
$
.
ajax
({
url
,
url
,
type
:
'
POST
'
,
type
:
'
POST
'
,
...
...
app/assets/javascripts/boards/boards_bundle.js
View file @
b4778a58
...
@@ -53,7 +53,8 @@ $(() => {
...
@@ -53,7 +53,8 @@ $(() => {
data
:
{
data
:
{
state
:
Store
.
state
,
state
:
Store
.
state
,
loading
:
true
,
loading
:
true
,
endpoint
:
$boardApp
.
dataset
.
endpoint
,
boardsEndpoint
:
$boardApp
.
dataset
.
boardsEndpoint
,
listsEndpoint
:
$boardApp
.
dataset
.
listsEndpoint
,
boardId
:
$boardApp
.
dataset
.
boardId
,
boardId
:
$boardApp
.
dataset
.
boardId
,
disabled
:
$boardApp
.
dataset
.
disabled
===
'
true
'
,
disabled
:
$boardApp
.
dataset
.
disabled
===
'
true
'
,
issueLinkBase
:
$boardApp
.
dataset
.
issueLinkBase
,
issueLinkBase
:
$boardApp
.
dataset
.
issueLinkBase
,
...
@@ -68,7 +69,13 @@ $(() => {
...
@@ -68,7 +69,13 @@ $(() => {
},
},
},
},
created
()
{
created
()
{
gl
.
boardService
=
new
BoardService
(
this
.
endpoint
,
this
.
bulkUpdatePath
,
this
.
boardId
);
gl
.
boardService
=
new
BoardService
({
boardsEndpoint
:
this
.
boardsEndpoint
,
listsEndpoint
:
this
.
listsEndpoint
,
bulkUpdatePath
:
this
.
bulkUpdatePath
,
boardId
:
this
.
boardId
,
});
Store
.
rootPath
=
this
.
boardsEndpoint
;
this
.
filterManager
=
new
FilteredSearchBoards
(
Store
.
filter
,
true
);
this
.
filterManager
=
new
FilteredSearchBoards
(
Store
.
filter
,
true
);
this
.
filterManager
.
setup
();
this
.
filterManager
.
setup
();
...
@@ -112,19 +119,21 @@ $(() => {
...
@@ -112,19 +119,21 @@ $(() => {
gl
.
IssueBoardsSearch
=
new
Vue
({
gl
.
IssueBoardsSearch
=
new
Vue
({
el
:
document
.
getElementById
(
'
js-add-list
'
),
el
:
document
.
getElementById
(
'
js-add-list
'
),
data
:
{
data
:
{
filters
:
Store
.
state
.
filters
filters
:
Store
.
state
.
filters
,
},
},
mounted
()
{
mounted
()
{
gl
.
issueBoards
.
newListDropdownInit
();
gl
.
issueBoards
.
newListDropdownInit
();
}
}
,
});
});
gl
.
IssueBoardsModalAddBtn
=
new
Vue
({
gl
.
IssueBoardsModalAddBtn
=
new
Vue
({
mixins
:
[
gl
.
issueBoards
.
ModalMixins
],
mixins
:
[
gl
.
issueBoards
.
ModalMixins
],
el
:
document
.
getElementById
(
'
js-add-issues-btn
'
),
el
:
document
.
getElementById
(
'
js-add-issues-btn
'
),
data
:
{
data
()
{
modal
:
ModalStore
.
store
,
return
{
store
:
Store
.
state
,
modal
:
ModalStore
.
store
,
store
:
Store
.
state
,
};
},
},
watch
:
{
watch
:
{
disabled
()
{
disabled
()
{
...
@@ -133,6 +142,9 @@ $(() => {
...
@@ -133,6 +142,9 @@ $(() => {
},
},
computed
:
{
computed
:
{
disabled
()
{
disabled
()
{
if
(
!
this
.
store
)
{
return
true
;
}
return
!
this
.
store
.
lists
.
filter
(
list
=>
!
list
.
preset
).
length
;
return
!
this
.
store
.
lists
.
filter
(
list
=>
!
list
.
preset
).
length
;
},
},
tooltipTitle
()
{
tooltipTitle
()
{
...
@@ -145,7 +157,7 @@ $(() => {
...
@@ -145,7 +157,7 @@ $(() => {
},
},
methods
:
{
methods
:
{
updateTooltip
()
{
updateTooltip
()
{
const
$tooltip
=
$
(
this
.
$
el
);
const
$tooltip
=
$
(
this
.
$
refs
.
addIssuesButton
);
this
.
$nextTick
(()
=>
{
this
.
$nextTick
(()
=>
{
if
(
this
.
disabled
)
{
if
(
this
.
disabled
)
{
...
@@ -165,16 +177,19 @@ $(() => {
...
@@ -165,16 +177,19 @@ $(() => {
this
.
updateTooltip
();
this
.
updateTooltip
();
},
},
template
:
`
template
:
`
<button
<div class="board-extra-actions">
class="btn btn-create pull-right prepend-left-10"
<button
type="button"
class="btn btn-create prepend-left-10"
data-placement="bottom"
type="button"
:class="{ 'disabled': disabled }"
data-placement="bottom"
:title="tooltipTitle"
ref="addIssuesButton"
:aria-disabled="disabled"
:class="{ 'disabled': disabled }"
@click="openModal">
:title="tooltipTitle"
Add issues
:aria-disabled="disabled"
</button>
@click="openModal">
Add issues
</button>
</div>
`
,
`
,
});
});
});
});
app/assets/javascripts/boards/components/board_list.js
View file @
b4778a58
...
@@ -77,7 +77,7 @@ export default {
...
@@ -77,7 +77,7 @@ export default {
this
.
showIssueForm
=
!
this
.
showIssueForm
;
this
.
showIssueForm
=
!
this
.
showIssueForm
;
},
},
onScroll
()
{
onScroll
()
{
if
(
(
this
.
scrollTop
()
>
this
.
scrollHeight
()
-
this
.
scrollOffset
)
&&
!
this
.
list
.
loadingMore
)
{
if
(
!
this
.
loadingMore
&&
(
this
.
scrollTop
()
>
this
.
scrollHeight
()
-
this
.
scrollOffset
)
)
{
this
.
loadNextPage
();
this
.
loadNextPage
();
}
}
},
},
...
@@ -165,11 +165,9 @@ export default {
...
@@ -165,11 +165,9 @@ export default {
v-if="loading">
v-if="loading">
<loading-icon />
<loading-icon />
</div>
</div>
<transition name="slide-down">
<board-new-issue
<board-new-issue
:list="list"
:list="list"
v-if="list.type !== 'closed' && showIssueForm"/>
v-if="list.type !== 'closed' && showIssueForm"/>
</transition>
<ul
<ul
class="board-list"
class="board-list"
v-show="!loading"
v-show="!loading"
...
...
app/assets/javascripts/boards/components/board_new_issue.js
View file @
b4778a58
...
@@ -6,7 +6,10 @@ const Store = gl.issueBoards.BoardsStore;
...
@@ -6,7 +6,10 @@ const Store = gl.issueBoards.BoardsStore;
export
default
{
export
default
{
name
:
'
BoardNewIssue
'
,
name
:
'
BoardNewIssue
'
,
props
:
{
props
:
{
list
:
Object
,
list
:
{
type
:
Object
,
required
:
true
,
},
},
},
data
()
{
data
()
{
return
{
return
{
...
...
app/assets/javascripts/boards/components/issue_card_inner.js
View file @
b4778a58
...
@@ -64,10 +64,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({
...
@@ -64,10 +64,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({
return
this
.
issue
.
assignees
.
length
>
this
.
numberOverLimit
;
return
this
.
issue
.
assignees
.
length
>
this
.
numberOverLimit
;
},
},
cardUrl
()
{
cardUrl
()
{
return
`
${
this
.
issueLinkBase
}
/
${
this
.
issue
.
id
}
`
;
return
`
${
this
.
issueLinkBase
}
/
${
this
.
issue
.
i
i
d
}
`
;
},
},
issueId
()
{
issueId
()
{
return
`#
${
this
.
issue
.
id
}
`
;
if
(
this
.
issue
.
iid
)
{
return
`#
${
this
.
issue
.
iid
}
`
;
}
return
false
;
},
},
showLabelFooter
()
{
showLabelFooter
()
{
return
this
.
issue
.
labels
.
find
(
l
=>
this
.
showLabel
(
l
))
!==
undefined
;
return
this
.
issue
.
labels
.
find
(
l
=>
this
.
showLabel
(
l
))
!==
undefined
;
...
@@ -143,7 +146,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({
...
@@ -143,7 +146,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({
:title="issue.title">{{ issue.title }}</a>
:title="issue.title">{{ issue.title }}</a>
<span
<span
class="card-number"
class="card-number"
v-if="issue
.i
d"
v-if="issue
I
d"
>
>
{{ issueId }}
{{ issueId }}
</span>
</span>
...
...
app/assets/javascripts/boards/components/modal/footer.js
View file @
b4778a58
...
@@ -29,7 +29,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
...
@@ -29,7 +29,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
const
firstListIndex
=
1
;
const
firstListIndex
=
1
;
const
list
=
this
.
modal
.
selectedList
||
this
.
state
.
lists
[
firstListIndex
];
const
list
=
this
.
modal
.
selectedList
||
this
.
state
.
lists
[
firstListIndex
];
const
selectedIssues
=
ModalStore
.
getSelectedIssues
();
const
selectedIssues
=
ModalStore
.
getSelectedIssues
();
const
issueIds
=
selectedIssues
.
map
(
issue
=>
issue
.
globalI
d
);
const
issueIds
=
selectedIssues
.
map
(
issue
=>
issue
.
i
d
);
// Post the data to the backend
// Post the data to the backend
gl
.
boardService
.
bulkUpdate
(
issueIds
,
{
gl
.
boardService
.
bulkUpdate
(
issueIds
,
{
...
...
app/assets/javascripts/boards/components/new_list_dropdown.js
View file @
b4778a58
...
@@ -27,7 +27,7 @@ gl.issueBoards.newListDropdownInit = () => {
...
@@ -27,7 +27,7 @@ gl.issueBoards.newListDropdownInit = () => {
$this
.
glDropdown
({
$this
.
glDropdown
({
data
(
term
,
callback
)
{
data
(
term
,
callback
)
{
$
.
get
(
$this
.
attr
(
'
data-l
abels
'
))
$
.
get
(
$this
.
attr
(
'
data-l
ist-labels-path
'
))
.
then
((
resp
)
=>
{
.
then
((
resp
)
=>
{
callback
(
resp
);
callback
(
resp
);
});
});
...
...
app/assets/javascripts/boards/components/sidebar/remove_issue.js
View file @
b4778a58
...
@@ -18,17 +18,33 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
...
@@ -18,17 +18,33 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
type
:
Object
,
type
:
Object
,
required
:
true
,
required
:
true
,
},
},
issueUpdate
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
updateUrl
()
{
return
this
.
issueUpdate
;
},
},
},
methods
:
{
methods
:
{
removeIssue
()
{
removeIssue
()
{
const
issue
=
this
.
issue
;
const
issue
=
this
.
issue
;
const
lists
=
issue
.
getLists
();
const
lists
=
issue
.
getLists
();
const
labelIds
=
lists
.
map
(
list
=>
list
.
label
.
id
);
const
listLabelIds
=
lists
.
map
(
list
=>
list
.
label
.
id
);
let
labelIds
=
this
.
issue
.
labels
// Post the remove data
.
map
(
label
=>
label
.
id
)
gl
.
boardService
.
bulkUpdate
([
issue
.
globalId
],
{
.
filter
(
id
=>
!
listLabelIds
.
includes
(
id
));
remove_label_ids
:
labelIds
,
if
(
labelIds
.
length
===
0
)
{
}).
catch
(()
=>
{
labelIds
=
[
''
];
}
const
data
=
{
issue
:
{
label_ids
:
labelIds
,
},
};
Vue
.
http
.
patch
(
this
.
updateUrl
,
data
).
catch
(()
=>
{
new
Flash
(
'
Failed to remove issue from board, please try again.
'
,
'
alert
'
);
new
Flash
(
'
Failed to remove issue from board, please try again.
'
,
'
alert
'
);
lists
.
forEach
((
list
)
=>
{
lists
.
forEach
((
list
)
=>
{
...
...
app/assets/javascripts/boards/models/issue.js
View file @
b4778a58
...
@@ -7,8 +7,8 @@ import Vue from 'vue';
...
@@ -7,8 +7,8 @@ import Vue from 'vue';
class
ListIssue
{
class
ListIssue
{
constructor
(
obj
,
defaultAvatar
)
{
constructor
(
obj
,
defaultAvatar
)
{
this
.
globalI
d
=
obj
.
id
;
this
.
i
d
=
obj
.
id
;
this
.
id
=
obj
.
iid
;
this
.
i
i
d
=
obj
.
iid
;
this
.
title
=
obj
.
title
;
this
.
title
=
obj
.
title
;
this
.
confidential
=
obj
.
confidential
;
this
.
confidential
=
obj
.
confidential
;
this
.
dueDate
=
obj
.
due_date
;
this
.
dueDate
=
obj
.
due_date
;
...
...
app/assets/javascripts/boards/models/label.js
View file @
b4778a58
...
@@ -4,6 +4,7 @@ class ListLabel {
...
@@ -4,6 +4,7 @@ class ListLabel {
constructor
(
obj
)
{
constructor
(
obj
)
{
this
.
id
=
obj
.
id
;
this
.
id
=
obj
.
id
;
this
.
title
=
obj
.
title
;
this
.
title
=
obj
.
title
;
this
.
type
=
obj
.
type
;
this
.
color
=
obj
.
color
;
this
.
color
=
obj
.
color
;
this
.
textColor
=
obj
.
text_color
;
this
.
textColor
=
obj
.
text_color
;
this
.
description
=
obj
.
description
;
this
.
description
=
obj
.
description
;
...
...
app/assets/javascripts/boards/models/list.js
View file @
b4778a58
...
@@ -110,11 +110,13 @@ class List {
...
@@ -110,11 +110,13 @@ class List {
return
gl
.
boardService
.
newIssue
(
this
.
id
,
issue
)
return
gl
.
boardService
.
newIssue
(
this
.
id
,
issue
)
.
then
(
resp
=>
resp
.
json
())
.
then
(
resp
=>
resp
.
json
())
.
then
((
data
)
=>
{
.
then
((
data
)
=>
{
issue
.
id
=
data
.
iid
;
issue
.
id
=
data
.
id
;
issue
.
iid
=
data
.
iid
;
issue
.
project
=
data
.
project
;
if
(
this
.
issuesSize
>
1
)
{
if
(
this
.
issuesSize
>
1
)
{
const
moveBeforeI
i
d
=
this
.
issues
[
1
].
id
;
const
moveBeforeId
=
this
.
issues
[
1
].
id
;
gl
.
boardService
.
moveIssue
(
issue
.
id
,
null
,
null
,
null
,
moveBeforeI
i
d
);
gl
.
boardService
.
moveIssue
(
issue
.
id
,
null
,
null
,
null
,
moveBeforeId
);
}
}
});
});
}
}
...
@@ -126,19 +128,19 @@ class List {
...
@@ -126,19 +128,19 @@ class List {
}
}
addIssue
(
issue
,
listFrom
,
newIndex
)
{
addIssue
(
issue
,
listFrom
,
newIndex
)
{
let
moveBeforeI
i
d
=
null
;
let
moveBeforeId
=
null
;
let
moveAfterI
i
d
=
null
;
let
moveAfterId
=
null
;
if
(
!
this
.
findIssue
(
issue
.
id
))
{
if
(
!
this
.
findIssue
(
issue
.
id
))
{
if
(
newIndex
!==
undefined
)
{
if
(
newIndex
!==
undefined
)
{
this
.
issues
.
splice
(
newIndex
,
0
,
issue
);
this
.
issues
.
splice
(
newIndex
,
0
,
issue
);
if
(
this
.
issues
[
newIndex
-
1
])
{
if
(
this
.
issues
[
newIndex
-
1
])
{
moveBeforeI
i
d
=
this
.
issues
[
newIndex
-
1
].
id
;
moveBeforeId
=
this
.
issues
[
newIndex
-
1
].
id
;
}
}
if
(
this
.
issues
[
newIndex
+
1
])
{
if
(
this
.
issues
[
newIndex
+
1
])
{
moveAfterI
i
d
=
this
.
issues
[
newIndex
+
1
].
id
;
moveAfterId
=
this
.
issues
[
newIndex
+
1
].
id
;
}
}
}
else
{
}
else
{
this
.
issues
.
push
(
issue
);
this
.
issues
.
push
(
issue
);
...
@@ -151,30 +153,30 @@ class List {
...
@@ -151,30 +153,30 @@ class List {
if
(
listFrom
)
{
if
(
listFrom
)
{
this
.
issuesSize
+=
1
;
this
.
issuesSize
+=
1
;
this
.
updateIssueLabel
(
issue
,
listFrom
,
moveBeforeI
id
,
moveAfterIi
d
);
this
.
updateIssueLabel
(
issue
,
listFrom
,
moveBeforeI
d
,
moveAfterI
d
);
}
}
}
}
}
}
moveIssue
(
issue
,
oldIndex
,
newIndex
,
moveBeforeI
id
,
moveAfterIi
d
)
{
moveIssue
(
issue
,
oldIndex
,
newIndex
,
moveBeforeI
d
,
moveAfterI
d
)
{
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
,
moveBeforeI
id
,
moveAfterIi
d
)
gl
.
boardService
.
moveIssue
(
issue
.
id
,
null
,
null
,
moveBeforeI
d
,
moveAfterI
d
)
.
catch
(()
=>
{
.
catch
(()
=>
{
// TODO: handle request error
// TODO: handle request error
});
});
}
}
updateIssueLabel
(
issue
,
listFrom
,
moveBeforeI
id
,
moveAfterIi
d
)
{
updateIssueLabel
(
issue
,
listFrom
,
moveBeforeI
d
,
moveAfterI
d
)
{
gl
.
boardService
.
moveIssue
(
issue
.
id
,
listFrom
.
id
,
this
.
id
,
moveBeforeI
id
,
moveAfterIi
d
)
gl
.
boardService
.
moveIssue
(
issue
.
id
,
listFrom
.
id
,
this
.
id
,
moveBeforeI
d
,
moveAfterI
d
)
.
catch
(()
=>
{
.
catch
(()
=>
{
// TODO: handle request error
// TODO: handle request error
});
});
}
}
findIssue
(
id
)
{
findIssue
(
id
)
{
return
this
.
issues
.
fi
lter
(
issue
=>
issue
.
id
===
id
)[
0
]
;
return
this
.
issues
.
fi
nd
(
issue
=>
issue
.
id
===
id
)
;
}
}
removeIssue
(
removeIssue
)
{
removeIssue
(
removeIssue
)
{
...
...
app/assets/javascripts/boards/services/board_service.js
View file @
b4778a58
...
@@ -3,21 +3,21 @@
...
@@ -3,21 +3,21 @@
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
class
BoardService
{
class
BoardService
{
constructor
(
root
,
bulkUpdatePath
,
boardId
)
{
constructor
(
{
boardsEndpoint
,
listsEndpoint
,
bulkUpdatePath
,
boardId
}
)
{
this
.
boards
=
Vue
.
resource
(
`
${
roo
t
}
{/id}.json`
,
{},
{
this
.
boards
=
Vue
.
resource
(
`
${
boardsEndpoin
t
}
{/id}.json`
,
{},
{
issues
:
{
issues
:
{
method
:
'
GET
'
,
method
:
'
GET
'
,
url
:
`
${
root
}
/
${
boardId
}
/issues.json`
url
:
`
${
gon
.
relative_url_root
}
/boards/
${
boardId
}
/issues.json`
,
}
}
});
});
this
.
lists
=
Vue
.
resource
(
`
${
root
}
/
${
boardId
}
/lists
{/id}`
,
{},
{
this
.
lists
=
Vue
.
resource
(
`
${
listsEndpoint
}
{/id}`
,
{},
{
generate
:
{
generate
:
{
method
:
'
POST
'
,
method
:
'
POST
'
,
url
:
`
${
root
}
/
${
boardId
}
/lists
/generate.json`
url
:
`
${
listsEndpoint
}
/generate.json`
}
}
});
});
this
.
issue
=
Vue
.
resource
(
`
${
root
}
/
${
boardId
}
/issues{/id}`
,
{});
this
.
issue
=
Vue
.
resource
(
`
${
gon
.
relative_url_root
}
/boards
/
${
boardId
}
/issues{/id}`
,
{});
this
.
issues
=
Vue
.
resource
(
`
${
root
}
/
${
boardId
}
/lists
{/id}/issues`
,
{},
{
this
.
issues
=
Vue
.
resource
(
`
${
listsEndpoint
}
{/id}/issues`
,
{},
{
bulkUpdate
:
{
bulkUpdate
:
{
method
:
'
POST
'
,
method
:
'
POST
'
,
url
:
bulkUpdatePath
,
url
:
bulkUpdatePath
,
...
@@ -60,12 +60,12 @@ class BoardService {
...
@@ -60,12 +60,12 @@ class BoardService {
return
this
.
issues
.
get
(
data
);
return
this
.
issues
.
get
(
data
);
}
}
moveIssue
(
id
,
from_list_id
=
null
,
to_list_id
=
null
,
move_before_i
id
=
null
,
move_after_i
id
=
null
)
{
moveIssue
(
id
,
from_list_id
=
null
,
to_list_id
=
null
,
move_before_i
d
=
null
,
move_after_
id
=
null
)
{
return
this
.
issue
.
update
({
id
},
{
return
this
.
issue
.
update
({
id
},
{
from_list_id
,
from_list_id
,
to_list_id
,
to_list_id
,
move_before_i
i
d
,
move_before_id
,
move_after_i
i
d
,
move_after_id
,
});
});
}
}
...
...
app/assets/stylesheets/framework/dropdowns.scss
View file @
b4778a58
...
@@ -183,7 +183,7 @@
...
@@ -183,7 +183,7 @@
width
:
auto
;
width
:
auto
;
top
:
100%
;
top
:
100%
;
left
:
0
;
left
:
0
;
z-index
:
2
00
;
z-index
:
3
00
;
min-width
:
240px
;
min-width
:
240px
;
max-width
:
500px
;
max-width
:
500px
;
margin-top
:
2px
;
margin-top
:
2px
;
...
...
app/assets/stylesheets/pages/boards.scss
View file @
b4778a58
...
@@ -117,13 +117,12 @@
...
@@ -117,13 +117,12 @@
}
}
.board-title
{
.board-title
{
position
:
initial
;
padding
:
0
;
padding
:
0
;
border-bottom
:
0
;
border-bottom
:
0
;
>
span
{
>
span
{
display
:
block
;
display
:
block
;
transform
:
rotate
(
90deg
)
translate
(
25px
,
0
);
transform
:
rotate
(
90deg
)
translate
(
35px
,
10px
);
}
}
}
}
...
@@ -151,11 +150,18 @@
...
@@ -151,11 +150,18 @@
}
}
.board-header
{
.board-header
{
border-top-left-radius
:
$border-radius-default
;
position
:
relative
;
border-top-right-radius
:
$border-radius-default
;
&
.has-border
{
&.
has-border
:
:
before
{
border-top
:
3px
solid
;
border-top
:
3px
solid
;
border-color
:
inherit
;
border-top-left-radius
:
$border-radius-default
;
border-top-right-radius
:
$border-radius-default
;
content
:
''
;
position
:
absolute
;
width
:
calc
(
100%
+
2px
);
top
:
0
;
left
:
0
;
margin-top
:
-1px
;
margin-top
:
-1px
;
margin-right
:
-1px
;
margin-right
:
-1px
;
margin-left
:
-1px
;
margin-left
:
-1px
;
...
@@ -176,12 +182,16 @@
...
@@ -176,12 +182,16 @@
}
}
.board-title
{
.board-title
{
position
:
relative
;
margin
:
0
;
margin
:
0
;
padding
:
$gl-padding
;
padding
:
12px
$gl-padding
;
padding-bottom
:
(
$gl-padding
+
3px
);
font-size
:
1em
;
font-size
:
1em
;
border-bottom
:
1px
solid
$border-color
;
border-bottom
:
1px
solid
$border-color
;
display
:
flex
;
align-items
:
center
;
}
.board-title-text
{
margin-right
:
auto
;
}
}
.board-delete
{
.board-delete
{
...
@@ -221,43 +231,10 @@
...
@@ -221,43 +231,10 @@
}
}
}
}
.slide-down-enter
{
transform
:
translateY
(
-100%
);
}
.slide-down-enter-active
{
transition
:
transform
$fade-in-duration
;
+
.board-list
{
transform
:
translateY
(
-136px
);
transition
:
none
;
}
}
.slide-down-enter-to
{
+
.board-list
{
transform
:
translateY
(
0
);
transition
:
transform
$fade-in-duration
ease
;
}
}
.slide-down-leave
{
transform
:
translateY
(
0
);
}
.slide-down-leave-active
{
transition
:
all
$fade-in-duration
;
transform
:
translateY
(
-136px
);
+
.board-list
{
transition
:
transform
$fade-in-duration
ease
;
transform
:
translateY
(
-136px
);
}
}
.board-list-component
{
.board-list-component
{
height
:
calc
(
100%
-
49px
);
height
:
calc
(
100%
-
49px
);
overflow
:
hidden
;
overflow
:
hidden
;
position
:
relative
;
}
}
.board-list
{
.board-list
{
...
@@ -429,7 +406,7 @@
...
@@ -429,7 +406,7 @@
}
}
.board-new-issue-form
{
.board-new-issue-form
{
z-index
:
1
;
z-index
:
4
;
margin
:
5px
;
margin
:
5px
;
}
}
...
...
app/controllers/boards/application_controller.rb
0 → 100644
View file @
b4778a58
module
Boards
class
ApplicationController
<
::
ApplicationController
respond_to
:json
rescue_from
ActiveRecord
::
RecordNotFound
,
with: :record_not_found
private
def
board
@board
||=
Board
.
find
(
params
[
:board_id
])
end
def
board_parent
@board_parent
||=
board
.
parent
end
def
record_not_found
(
exception
)
render
json:
{
error:
exception
.
message
},
status: :not_found
end
end
end
app/controllers/boards/issues_controller.rb
0 → 100644
View file @
b4778a58
module
Boards
class
IssuesController
<
Boards
::
ApplicationController
include
BoardsResponses
before_action
:authorize_read_issue
,
only:
[
:index
]
before_action
:authorize_create_issue
,
only:
[
:create
]
before_action
:authorize_update_issue
,
only:
[
:update
]
skip_before_action
:authenticate_user!
,
only:
[
:index
]
def
index
issues
=
Boards
::
Issues
::
ListService
.
new
(
board_parent
,
current_user
,
filter_params
).
execute
issues
=
issues
.
page
(
params
[
:page
]).
per
(
params
[
:per
]
||
20
)
make_sure_position_is_set
(
issues
)
render
json:
{
issues:
serialize_as_json
(
issues
.
preload
(
:project
)),
size:
issues
.
total_count
}
end
def
create
service
=
Boards
::
Issues
::
CreateService
.
new
(
board_parent
,
project
,
current_user
,
issue_params
)
issue
=
service
.
execute
if
issue
.
valid?
render
json:
serialize_as_json
(
issue
)
else
render
json:
issue
.
errors
,
status: :unprocessable_entity
end
end
def
update
service
=
Boards
::
Issues
::
MoveService
.
new
(
board_parent
,
current_user
,
move_params
)
if
service
.
execute
(
issue
)
head
:ok
else
head
:unprocessable_entity
end
end
private
def
make_sure_position_is_set
(
issues
)
issues
.
each
do
|
issue
|
issue
.
move_to_end
&&
issue
.
save
unless
issue
.
relative_position
end
end
def
issue
@issue
||=
issues_finder
.
execute
.
find
(
params
[
:id
])
end
def
filter_params
params
.
merge
(
board_id:
params
[
:board_id
],
id:
params
[
:list_id
])
.
reject
{
|
_
,
value
|
value
.
nil?
}
end
def
issues_finder
IssuesFinder
.
new
(
current_user
,
project_id:
board_parent
.
id
)
end
def
project
board_parent
end
def
move_params
params
.
permit
(
:board_id
,
:id
,
:from_list_id
,
:to_list_id
,
:move_before_id
,
:move_after_id
)
end
def
issue_params
params
.
require
(
:issue
)
.
permit
(
:title
,
:milestone_id
,
:project_id
)
.
merge
(
board_id:
params
[
:board_id
],
list_id:
params
[
:list_id
],
request:
request
)
end
def
serialize_as_json
(
resource
)
resource
.
as_json
(
labels:
true
,
only:
[
:id
,
:iid
,
:project_id
,
:title
,
:confidential
,
:due_date
,
:relative_position
],
include:
{
project:
{
only:
[
:id
,
:path
]
},
assignees:
{
only:
[
:id
,
:name
,
:username
],
methods:
[
:avatar_url
]
},
milestone:
{
only:
[
:id
,
:title
]
}
},
user:
current_user
)
end
end
end
app/controllers/boards/lists_controller.rb
0 → 100644
View file @
b4778a58
module
Boards
class
ListsController
<
Boards
::
ApplicationController
include
BoardsResponses
before_action
:authorize_admin_list
,
only:
[
:create
,
:update
,
:destroy
,
:generate
]
before_action
:authorize_read_list
,
only:
[
:index
]
skip_before_action
:authenticate_user!
,
only:
[
:index
]
def
index
lists
=
Boards
::
Lists
::
ListService
.
new
(
board
.
parent
,
current_user
).
execute
(
board
)
render
json:
serialize_as_json
(
lists
)
end
def
create
list
=
Boards
::
Lists
::
CreateService
.
new
(
board
.
parent
,
current_user
,
list_params
).
execute
(
board
)
if
list
.
valid?
render
json:
serialize_as_json
(
list
)
else
render
json:
list
.
errors
,
status: :unprocessable_entity
end
end
def
update
list
=
board
.
lists
.
movable
.
find
(
params
[
:id
])
service
=
Boards
::
Lists
::
MoveService
.
new
(
board_parent
,
current_user
,
move_params
)
if
service
.
execute
(
list
)
head
:ok
else
head
:unprocessable_entity
end
end
def
destroy
list
=
board
.
lists
.
destroyable
.
find
(
params
[
:id
])
service
=
Boards
::
Lists
::
DestroyService
.
new
(
board_parent
,
current_user
)
if
service
.
execute
(
list
)
head
:ok
else
head
:unprocessable_entity
end
end
def
generate
service
=
Boards
::
Lists
::
GenerateService
.
new
(
board_parent
,
current_user
)
if
service
.
execute
(
board
)
render
json:
serialize_as_json
(
board
.
lists
.
movable
)
else
head
:unprocessable_entity
end
end
private
def
list_params
params
.
require
(
:list
).
permit
(
:label_id
)
end
def
move_params
params
.
require
(
:list
).
permit
(
:position
)
end
def
serialize_as_json
(
resource
)
resource
.
as_json
(
only:
[
:id
,
:list_type
,
:position
],
methods:
[
:title
],
label:
true
)
end
end
end
app/controllers/concerns/boards_responses.rb
0 → 100644
View file @
b4778a58
module
BoardsResponses
def
authorize_read_list
authorize_action_for!
(
board
.
parent
,
:read_list
)
end
def
authorize_read_issue
authorize_action_for!
(
board
.
parent
,
:read_issue
)
end
def
authorize_update_issue
authorize_action_for!
(
issue
,
:admin_issue
)
end
def
authorize_create_issue
authorize_action_for!
(
project
,
:admin_issue
)
end
def
authorize_admin_list
authorize_action_for!
(
board
.
parent
,
:admin_list
)
end
def
authorize_action_for!
(
resource
,
ability
)
return
render_403
unless
can?
(
current_user
,
ability
,
resource
)
end
def
respond_with_boards
respond_with
(
@boards
)
end
def
respond_with_board
respond_with
(
@board
)
end
def
respond_with
(
resource
)
respond_to
do
|
format
|
format
.
html
format
.
json
do
render
json:
serialize_as_json
(
resource
)
end
end
end
end
app/controllers/projects/boards/application_controller.rb
deleted
100644 → 0
View file @
cb555da1
module
Projects
module
Boards
class
ApplicationController
<
Projects
::
ApplicationController
respond_to
:json
rescue_from
ActiveRecord
::
RecordNotFound
,
with: :record_not_found
private
def
record_not_found
(
exception
)
render
json:
{
error:
exception
.
message
},
status: :not_found
end
end
end
end
app/controllers/projects/boards/issues_controller.rb
deleted
100644 → 0
View file @
cb555da1
module
Projects
module
Boards
class
IssuesController
<
Boards
::
ApplicationController
before_action
:authorize_read_issue!
,
only:
[
:index
]
before_action
:authorize_create_issue!
,
only:
[
:create
]
before_action
:authorize_update_issue!
,
only:
[
:update
]
def
index
issues
=
::
Boards
::
Issues
::
ListService
.
new
(
project
,
current_user
,
filter_params
).
execute
issues
=
issues
.
page
(
params
[
:page
]).
per
(
params
[
:per
]
||
20
)
make_sure_position_is_set
(
issues
)
render
json:
{
issues:
serialize_as_json
(
issues
),
size:
issues
.
total_count
}
end
def
create
service
=
::
Boards
::
Issues
::
CreateService
.
new
(
project
,
current_user
,
issue_params
)
issue
=
service
.
execute
if
issue
.
valid?
render
json:
serialize_as_json
(
issue
)
else
render
json:
issue
.
errors
,
status: :unprocessable_entity
end
end
def
update
service
=
::
Boards
::
Issues
::
MoveService
.
new
(
project
,
current_user
,
move_params
)
if
service
.
execute
(
issue
)
head
:ok
else
head
:unprocessable_entity
end
end
private
def
make_sure_position_is_set
(
issues
)
issues
.
each
do
|
issue
|
issue
.
move_to_end
&&
issue
.
save
unless
issue
.
relative_position
end
end
def
issue
@issue
||=
IssuesFinder
.
new
(
current_user
,
project_id:
project
.
id
)
.
execute
.
where
(
iid:
params
[
:id
])
.
first!
end
def
authorize_read_issue!
return
render_403
unless
can?
(
current_user
,
:read_issue
,
project
)
end
def
authorize_create_issue!
return
render_403
unless
can?
(
current_user
,
:admin_issue
,
project
)
end
def
authorize_update_issue!
return
render_403
unless
can?
(
current_user
,
:update_issue
,
issue
)
end
def
filter_params
params
.
merge
(
board_id:
params
[
:board_id
],
id:
params
[
:list_id
])
.
reject
{
|
_
,
value
|
value
.
nil?
}
end
def
move_params
params
.
permit
(
:board_id
,
:id
,
:from_list_id
,
:to_list_id
,
:move_before_iid
,
:move_after_iid
)
end
def
issue_params
params
.
require
(
:issue
).
permit
(
:title
).
merge
(
board_id:
params
[
:board_id
],
list_id:
params
[
:list_id
],
request:
request
)
end
def
serialize_as_json
(
resource
)
resource
.
as_json
(
labels:
true
,
only:
[
:id
,
:iid
,
:title
,
:confidential
,
:due_date
,
:relative_position
],
include:
{
assignees:
{
only:
[
:id
,
:name
,
:username
],
methods:
[
:avatar_url
]
},
milestone:
{
only:
[
:id
,
:title
]
}
},
user:
current_user
)
end
end
end
end
app/controllers/projects/boards/lists_controller.rb
deleted
100644 → 0
View file @
cb555da1
module
Projects
module
Boards
class
ListsController
<
Boards
::
ApplicationController
before_action
:authorize_admin_list!
,
only:
[
:create
,
:update
,
:destroy
,
:generate
]
before_action
:authorize_read_list!
,
only:
[
:index
]
def
index
lists
=
::
Boards
::
Lists
::
ListService
.
new
(
project
,
current_user
).
execute
(
board
)
render
json:
serialize_as_json
(
lists
)
end
def
create
list
=
::
Boards
::
Lists
::
CreateService
.
new
(
project
,
current_user
,
list_params
).
execute
(
board
)
if
list
.
valid?
render
json:
serialize_as_json
(
list
)
else
render
json:
list
.
errors
,
status: :unprocessable_entity
end
end
def
update
list
=
board
.
lists
.
movable
.
find
(
params
[
:id
])
service
=
::
Boards
::
Lists
::
MoveService
.
new
(
project
,
current_user
,
move_params
)
if
service
.
execute
(
list
)
head
:ok
else
head
:unprocessable_entity
end
end
def
destroy
list
=
board
.
lists
.
destroyable
.
find
(
params
[
:id
])
service
=
::
Boards
::
Lists
::
DestroyService
.
new
(
project
,
current_user
)
if
service
.
execute
(
list
)
head
:ok
else
head
:unprocessable_entity
end
end
def
generate
service
=
::
Boards
::
Lists
::
GenerateService
.
new
(
project
,
current_user
)
if
service
.
execute
(
board
)
render
json:
serialize_as_json
(
board
.
lists
.
movable
)
else
head
:unprocessable_entity
end
end
private
def
authorize_admin_list!
return
render_403
unless
can?
(
current_user
,
:admin_list
,
project
)
end
def
authorize_read_list!
return
render_403
unless
can?
(
current_user
,
:read_list
,
project
)
end
def
board
@board
||=
project
.
boards
.
find
(
params
[
:board_id
])
end
def
list_params
params
.
require
(
:list
).
permit
(
:label_id
)
end
def
move_params
params
.
require
(
:list
).
permit
(
:position
)
end
def
serialize_as_json
(
resource
)
resource
.
as_json
(
only:
[
:id
,
:list_type
,
:position
],
methods:
[
:title
],
label:
true
)
end
end
end
end
app/controllers/projects/boards_controller.rb
View file @
b4778a58
class
Projects::BoardsController
<
Projects
::
ApplicationController
class
Projects::BoardsController
<
Projects
::
ApplicationController
include
BoardsResponses
include
IssuableCollections
include
IssuableCollections
before_action
:authorize_read_board!
,
only:
[
:index
,
:show
]
before_action
:authorize_read_board!
,
only:
[
:index
,
:show
]
before_action
:assign_endpoint_vars
def
index
def
index
@boards
=
::
Boards
::
ListService
.
new
(
project
,
current_user
).
execute
@boards
=
Boards
::
ListService
.
new
(
project
,
current_user
).
execute
respond_to
do
|
format
|
respond_with_boards
format
.
html
format
.
json
do
render
json:
serialize_as_json
(
@boards
)
end
end
end
end
def
show
def
show
@board
=
project
.
boards
.
find
(
params
[
:id
])
@board
=
project
.
boards
.
find
(
params
[
:id
])
respond_to
do
|
format
|
respond_with_board
format
.
html
format
.
json
do
render
json:
serialize_as_json
(
@board
)
end
end
end
end
private
private
def
assign_endpoint_vars
@boards_endpoint
=
project_boards_url
(
project
)
@bulk_issues_path
=
bulk_update_project_issues_path
(
project
)
@namespace_path
=
project
.
namespace
.
full_path
@labels_endpoint
=
project_labels_path
(
project
)
end
def
authorize_read_board!
def
authorize_read_board!
return
access_denied!
unless
can?
(
current_user
,
:read_board
,
project
)
return
access_denied!
unless
can?
(
current_user
,
:read_board
,
project
)
end
end
...
...
app/helpers/boards_helper.rb
View file @
b4778a58
module
BoardsHelper
module
BoardsHelper
def
board_data
def
board
board
=
@board
||
@boards
.
first
@board
||=
@board
||
@boards
.
first
end
def
board_data
{
{
endpoint:
project_boards_path
(
@project
),
boards_endpoint:
@boards_endpoint
,
lists_endpoint:
board_lists_url
(
board
),
board_id:
board
.
id
,
board_id:
board
.
id
,
disabled:
"
#{
!
can?
(
current_user
,
:admin_list
,
@projec
t
)
}
"
,
disabled:
"
#{
!
can?
(
current_user
,
:admin_list
,
current_board_paren
t
)
}
"
,
issue_link_base:
project_issues_path
(
@project
)
,
issue_link_base:
build_issue_link_base
,
root_path:
root_path
,
root_path:
root_path
,
bulk_update_path:
bulk_update_project_issues_path
(
@project
)
,
bulk_update_path:
@bulk_issues_path
,
default_avatar:
image_path
(
default_avatar
)
default_avatar:
image_path
(
default_avatar
)
}
}
end
end
def
build_issue_link_base
project_issues_path
(
@project
)
end
def
current_board_json
board
=
@board
||
@boards
.
first
board
.
to_json
(
only:
[
:id
,
:name
,
:milestone_id
],
include:
{
milestone:
{
only:
[
:title
]
}
}
)
end
def
board_base_url
project_boards_path
(
@project
)
end
def
multiple_boards_available?
current_board_parent
.
multiple_issue_boards_available?
(
current_user
)
end
def
current_board_path
(
board
)
@current_board_path
||=
project_board_path
(
current_board_parent
,
board
)
end
def
current_board_parent
@current_board_parent
||=
@project
end
def
can_admin_issue?
can?
(
current_user
,
:admin_issue
,
current_board_parent
)
end
def
board_list_data
{
toggle:
"dropdown"
,
list_labels_path:
labels_filter_path
(
true
),
labels:
labels_filter_path
(
true
),
labels_endpoint:
@labels_endpoint
,
namespace_path:
@namespace_path
,
project_path:
@project
&
.
try
(
:path
)
}
end
def
board_sidebar_user_data
dropdown_options
=
issue_assignees_dropdown_options
{
toggle:
'dropdown'
,
field_name:
'issue[assignee_ids][]'
,
first_user:
current_user
&
.
username
,
current_user:
'true'
,
project_id:
@project
&
.
try
(
:id
),
null_user:
'true'
,
multi_select:
'true'
,
'dropdown-header'
:
dropdown_options
[
:data
][
:'dropdown-header'
],
'max-select'
:
dropdown_options
[
:data
][
:'max-select'
]
}
end
end
end
app/helpers/issuables_helper.rb
View file @
b4778a58
...
@@ -347,6 +347,14 @@ module IssuablesHelper
...
@@ -347,6 +347,14 @@ module IssuablesHelper
end
end
end
end
def
labels_path
if
@project
project_labels_path
(
@project
)
elsif
@group
group_labels_path
(
@group
)
end
end
def
issuable_sidebar_options
(
issuable
,
can_edit_issuable
)
def
issuable_sidebar_options
(
issuable
,
can_edit_issuable
)
{
{
endpoint:
"
#{
issuable_json_path
(
issuable
)
}
?basic=true"
,
endpoint:
"
#{
issuable_json_path
(
issuable
)
}
?basic=true"
,
...
...
app/helpers/labels_helper.rb
View file @
b4778a58
...
@@ -121,13 +121,14 @@ module LabelsHelper
...
@@ -121,13 +121,14 @@ module LabelsHelper
end
end
end
end
def
labels_filter_path
def
labels_filter_path
(
only_group_labels
=
false
)
return
group_labels_path
(
@group
,
:json
)
if
@group
project
=
@target_project
||
@project
project
=
@target_project
||
@project
if
project
if
project
project_labels_path
(
project
,
:json
)
project_labels_path
(
project
,
:json
)
elsif
@group
options
=
{
only_group_labels:
only_group_labels
}
if
only_group_labels
group_labels_path
(
@group
,
:json
,
options
)
else
else
dashboard_labels_path
(
:json
)
dashboard_labels_path
(
:json
)
end
end
...
...
app/helpers/search_helper.rb
View file @
b4778a58
...
@@ -134,19 +134,21 @@ module SearchHelper
...
@@ -134,19 +134,21 @@ module SearchHelper
end
end
def
search_filter_input_options
(
type
)
def
search_filter_input_options
(
type
)
opts
=
{
opts
=
id:
"filtered-search-
#{
type
}
"
,
{
placeholder:
'Search or filter results...'
,
id:
"filtered-search-
#{
type
}
"
,
data:
{
placeholder:
'Search or filter results...'
,
'username-params'
=>
@users
.
to_json
(
only:
[
:id
,
:username
])
data:
{
'username-params'
=>
@users
.
to_json
(
only:
[
:id
,
:username
])
}
}
}
}
if
@project
.
present?
if
@project
.
present?
opts
[
:data
][
'project-id'
]
=
@project
.
id
opts
[
:data
][
'project-id'
]
=
@project
.
id
opts
[
:data
][
'base-endpoint'
]
=
project_path
(
@project
)
opts
[
:data
][
'base-endpoint'
]
=
project_path
(
@project
)
else
else
# Group context
# Group context
opts
[
:data
][
'group-id'
]
=
@group
.
id
opts
[
:data
][
'base-endpoint'
]
=
group_canonical_path
(
@group
)
opts
[
:data
][
'base-endpoint'
]
=
group_canonical_path
(
@group
)
end
end
...
...
app/models/board.rb
View file @
b4778a58
...
@@ -3,7 +3,19 @@ class Board < ActiveRecord::Base
...
@@ -3,7 +3,19 @@ class Board < ActiveRecord::Base
has_many
:lists
,
->
{
order
(
:list_type
,
:position
)
},
dependent: :delete_all
# rubocop:disable Cop/ActiveRecordDependent
has_many
:lists
,
->
{
order
(
:list_type
,
:position
)
},
dependent: :delete_all
# rubocop:disable Cop/ActiveRecordDependent
validates
:project
,
presence:
true
validates
:project
,
presence:
true
,
if: :project_needed?
def
project_needed?
true
end
def
parent
project
end
def
group_board?
false
end
def
backlog_list
def
backlog_list
lists
.
merge
(
List
.
backlog
).
take
lists
.
merge
(
List
.
backlog
).
take
...
...
app/models/concerns/relative_positioning.rb
View file @
b4778a58
...
@@ -10,8 +10,12 @@ module RelativePositioning
...
@@ -10,8 +10,12 @@ module RelativePositioning
after_save
:save_positionable_neighbours
after_save
:save_positionable_neighbours
end
end
def
project_ids
[
project
.
id
]
end
def
max_relative_position
def
max_relative_position
self
.
class
.
in_projects
(
project
.
id
).
maximum
(
:relative_position
)
self
.
class
.
in_projects
(
project
_ids
).
maximum
(
:relative_position
)
end
end
def
prev_relative_position
def
prev_relative_position
...
@@ -19,7 +23,7 @@ module RelativePositioning
...
@@ -19,7 +23,7 @@ module RelativePositioning
if
self
.
relative_position
if
self
.
relative_position
prev_pos
=
self
.
class
prev_pos
=
self
.
class
.
in_projects
(
project
.
id
)
.
in_projects
(
project
_ids
)
.
where
(
'relative_position < ?'
,
self
.
relative_position
)
.
where
(
'relative_position < ?'
,
self
.
relative_position
)
.
maximum
(
:relative_position
)
.
maximum
(
:relative_position
)
end
end
...
@@ -32,7 +36,7 @@ module RelativePositioning
...
@@ -32,7 +36,7 @@ module RelativePositioning
if
self
.
relative_position
if
self
.
relative_position
next_pos
=
self
.
class
next_pos
=
self
.
class
.
in_projects
(
project
.
id
)
.
in_projects
(
project
_ids
)
.
where
(
'relative_position > ?'
,
self
.
relative_position
)
.
where
(
'relative_position > ?'
,
self
.
relative_position
)
.
minimum
(
:relative_position
)
.
minimum
(
:relative_position
)
end
end
...
@@ -59,7 +63,7 @@ module RelativePositioning
...
@@ -59,7 +63,7 @@ module RelativePositioning
pos_after
=
before
.
next_relative_position
pos_after
=
before
.
next_relative_position
if
before
.
shift_after?
if
before
.
shift_after?
issue_to_move
=
self
.
class
.
in_projects
(
project
.
id
).
find_by!
(
relative_position:
pos_after
)
issue_to_move
=
self
.
class
.
in_projects
(
project
_ids
).
find_by!
(
relative_position:
pos_after
)
issue_to_move
.
move_after
issue_to_move
.
move_after
@positionable_neighbours
=
[
issue_to_move
]
@positionable_neighbours
=
[
issue_to_move
]
...
@@ -74,7 +78,7 @@ module RelativePositioning
...
@@ -74,7 +78,7 @@ module RelativePositioning
pos_before
=
after
.
prev_relative_position
pos_before
=
after
.
prev_relative_position
if
after
.
shift_before?
if
after
.
shift_before?
issue_to_move
=
self
.
class
.
in_projects
(
project
.
id
).
find_by!
(
relative_position:
pos_before
)
issue_to_move
=
self
.
class
.
in_projects
(
project
_ids
).
find_by!
(
relative_position:
pos_before
)
issue_to_move
.
move_before
issue_to_move
.
move_before
@positionable_neighbours
=
[
issue_to_move
]
@positionable_neighbours
=
[
issue_to_move
]
...
...
app/models/label.rb
View file @
b4778a58
...
@@ -34,7 +34,8 @@ class Label < ActiveRecord::Base
...
@@ -34,7 +34,8 @@ class Label < ActiveRecord::Base
scope
:templates
,
->
{
where
(
template:
true
)
}
scope
:templates
,
->
{
where
(
template:
true
)
}
scope
:with_title
,
->
(
title
)
{
where
(
title:
title
)
}
scope
:with_title
,
->
(
title
)
{
where
(
title:
title
)
}
scope
:on_project_boards
,
->
(
project_id
)
{
joins
(
lists: :board
).
merge
(
List
.
movable
).
where
(
boards:
{
project_id:
project_id
})
}
scope
:with_lists_and_board
,
->
{
joins
(
lists: :board
).
merge
(
List
.
movable
)
}
scope
:on_project_boards
,
->
(
project_id
)
{
with_lists_and_board
.
where
(
boards:
{
project_id:
project_id
})
}
def
self
.
prioritized
(
project
)
def
self
.
prioritized
(
project
)
joins
(
:priorities
)
joins
(
:priorities
)
...
@@ -172,6 +173,7 @@ class Label < ActiveRecord::Base
...
@@ -172,6 +173,7 @@ class Label < ActiveRecord::Base
def
as_json
(
options
=
{})
def
as_json
(
options
=
{})
super
(
options
).
tap
do
|
json
|
super
(
options
).
tap
do
|
json
|
json
[
:type
]
=
self
.
try
(
:type
)
json
[
:priority
]
=
priority
(
options
[
:project
])
if
options
.
key?
(
:project
)
json
[
:priority
]
=
priority
(
options
[
:project
])
if
options
.
key?
(
:project
)
end
end
end
end
...
...
app/models/project.rb
View file @
b4778a58
...
@@ -1486,6 +1486,14 @@ class Project < ActiveRecord::Base
...
@@ -1486,6 +1486,14 @@ class Project < ActiveRecord::Base
end
end
end
end
def
multiple_issue_boards_available?
(
user
)
feature_available?
(
:multiple_issue_boards
,
user
)
end
def
issue_board_milestone_available?
(
user
=
nil
)
feature_available?
(
:issue_board_milestone
,
user
)
end
def
full_path_was
def
full_path_was
File
.
join
(
namespace
.
full_path
,
previous_changes
[
'path'
].
first
)
File
.
join
(
namespace
.
full_path
,
previous_changes
[
'path'
].
first
)
end
end
...
...
app/services/boards/base_service.rb
0 → 100644
View file @
b4778a58
module
Boards
class
BaseService
<
::
BaseService
# Parent can either a group or a project
attr_accessor
:parent
,
:current_user
,
:params
def
initialize
(
parent
,
user
,
params
=
{})
@parent
,
@current_user
,
@params
=
parent
,
user
,
params
.
dup
end
end
end
app/services/boards/create_service.rb
View file @
b4778a58
module
Boards
module
Boards
class
CreateService
<
BaseService
class
CreateService
<
B
oards
::
B
aseService
def
execute
def
execute
create_board!
if
can_create_board?
create_board!
if
can_create_board?
end
end
...
@@ -7,11 +7,11 @@ module Boards
...
@@ -7,11 +7,11 @@ module Boards
private
private
def
can_create_board?
def
can_create_board?
p
rojec
t
.
boards
.
size
==
0
p
aren
t
.
boards
.
size
==
0
end
end
def
create_board!
def
create_board!
board
=
p
rojec
t
.
boards
.
create
(
params
)
board
=
p
aren
t
.
boards
.
create
(
params
)
if
board
.
persisted?
if
board
.
persisted?
board
.
lists
.
create
(
list_type: :backlog
)
board
.
lists
.
create
(
list_type: :backlog
)
...
...
app/services/boards/issues/create_service.rb
View file @
b4778a58
module
Boards
module
Boards
module
Issues
module
Issues
class
CreateService
<
BaseService
class
CreateService
<
Boards
::
BaseService
attr_accessor
:project
def
initialize
(
parent
,
project
,
user
,
params
=
{})
@project
=
project
super
(
parent
,
user
,
params
)
end
def
execute
def
execute
create_issue
(
params
.
merge
(
label_ids:
[
list
.
label_id
]))
create_issue
(
params
.
merge
(
label_ids:
[
list
.
label_id
]))
end
end
...
@@ -8,7 +16,7 @@ module Boards
...
@@ -8,7 +16,7 @@ module Boards
private
private
def
board
def
board
@board
||=
p
rojec
t
.
boards
.
find
(
params
.
delete
(
:board_id
))
@board
||=
p
aren
t
.
boards
.
find
(
params
.
delete
(
:board_id
))
end
end
def
list
def
list
...
...
app/services/boards/issues/list_service.rb
View file @
b4778a58
module
Boards
module
Boards
module
Issues
module
Issues
class
ListService
<
BaseService
class
ListService
<
B
oards
::
B
aseService
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
=
without_board_labels
(
issues
)
unless
movable_list?
||
closed_list?
...
@@ -11,7 +11,7 @@ module Boards
...
@@ -11,7 +11,7 @@ module Boards
private
private
def
board
def
board
@board
||=
p
rojec
t
.
boards
.
find
(
params
[
:board_id
])
@board
||=
p
aren
t
.
boards
.
find
(
params
[
:board_id
])
end
end
def
list
def
list
...
@@ -33,14 +33,14 @@ module Boards
...
@@ -33,14 +33,14 @@ module Boards
end
end
def
filter_params
def
filter_params
set_p
rojec
t
set_p
aren
t
set_state
set_state
params
params
end
end
def
set_p
rojec
t
def
set_p
aren
t
params
[
:project_id
]
=
p
rojec
t
.
id
params
[
:project_id
]
=
p
aren
t
.
id
end
end
def
set_state
def
set_state
...
...
app/services/boards/issues/move_service.rb
View file @
b4778a58
module
Boards
module
Boards
module
Issues
module
Issues
class
MoveService
<
BaseService
class
MoveService
<
B
oards
::
B
aseService
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
.
empty?
update
_service
.
execute
(
issue
)
update
(
issue
)
end
end
private
private
def
board
def
board
@board
||=
p
rojec
t
.
boards
.
find
(
params
[
:board_id
])
@board
||=
p
aren
t
.
boards
.
find
(
params
[
:board_id
])
end
end
def
move_between_lists?
def
move_between_lists?
...
@@ -27,8 +27,8 @@ module Boards
...
@@ -27,8 +27,8 @@ module Boards
@moving_to_list
||=
board
.
lists
.
find_by
(
id:
params
[
:to_list_id
])
@moving_to_list
||=
board
.
lists
.
find_by
(
id:
params
[
:to_list_id
])
end
end
def
update
_service
def
update
(
issue
)
::
Issues
::
UpdateService
.
new
(
project
,
current_user
,
issue_params
)
::
Issues
::
UpdateService
.
new
(
issue
.
project
,
current_user
,
issue_params
).
execute
(
issue
)
end
end
def
issue_params
def
issue_params
...
@@ -42,7 +42,7 @@ module Boards
...
@@ -42,7 +42,7 @@ module Boards
)
)
end
end
attrs
[
:move_between_i
ids
]
=
move_between_iids
if
move_between_i
ids
attrs
[
:move_between_i
ds
]
=
move_between_ids
if
move_between_
ids
attrs
attrs
end
end
...
@@ -61,16 +61,16 @@ module Boards
...
@@ -61,16 +61,16 @@ module Boards
if
moving_to_list
.
movable?
if
moving_to_list
.
movable?
moving_from_list
.
label_id
moving_from_list
.
label_id
else
else
Label
.
on_project_boards
(
p
rojec
t
.
id
).
pluck
(
:label_id
)
Label
.
on_project_boards
(
p
aren
t
.
id
).
pluck
(
:label_id
)
end
end
Array
(
label_ids
).
compact
Array
(
label_ids
).
compact
end
end
def
move_between_i
i
ds
def
move_between_ids
return
unless
params
[
:move_after_i
id
]
||
params
[
:move_before_i
id
]
return
unless
params
[
:move_after_i
d
]
||
params
[
:move_before_
id
]
[
params
[
:move_after_i
id
],
params
[
:move_before_i
id
]]
[
params
[
:move_after_i
d
],
params
[
:move_before_
id
]]
end
end
end
end
end
end
...
...
app/services/boards/list_service.rb
View file @
b4778a58
module
Boards
module
Boards
class
ListService
<
BaseService
class
ListService
<
B
oards
::
B
aseService
def
execute
def
execute
create_board!
if
p
rojec
t
.
boards
.
empty?
create_board!
if
p
aren
t
.
boards
.
empty?
p
rojec
t
.
boards
p
aren
t
.
boards
end
end
private
private
def
create_board!
def
create_board!
Boards
::
CreateService
.
new
(
p
rojec
t
,
current_user
).
execute
Boards
::
CreateService
.
new
(
p
aren
t
,
current_user
).
execute
end
end
end
end
end
end
app/services/boards/lists/create_service.rb
View file @
b4778a58
module
Boards
module
Boards
module
Lists
module
Lists
class
CreateService
<
BaseService
class
CreateService
<
B
oards
::
B
aseService
def
execute
(
board
)
def
execute
(
board
)
List
.
transaction
do
List
.
transaction
do
label
=
available_labels
.
find
(
params
[
:label_id
])
label
=
available_labels
_for
(
board
)
.
find
(
params
[
:label_id
])
position
=
next_position
(
board
)
position
=
next_position
(
board
)
create_list
(
board
,
label
,
position
)
create_list
(
board
,
label
,
position
)
end
end
end
end
private
private
def
available_labels
def
available_labels
_for
(
board
)
LabelsFinder
.
new
(
current_user
,
project_id:
p
rojec
t
.
id
).
execute
LabelsFinder
.
new
(
current_user
,
project_id:
p
aren
t
.
id
).
execute
end
end
def
next_position
(
board
)
def
next_position
(
board
)
...
...
app/services/boards/lists/destroy_service.rb
View file @
b4778a58
module
Boards
module
Boards
module
Lists
module
Lists
class
DestroyService
<
BaseService
class
DestroyService
<
B
oards
::
B
aseService
def
execute
(
list
)
def
execute
(
list
)
return
false
unless
list
.
destroyable?
return
false
unless
list
.
destroyable?
...
...
app/services/boards/lists/generate_service.rb
View file @
b4778a58
module
Boards
module
Boards
module
Lists
module
Lists
class
GenerateService
<
BaseService
class
GenerateService
<
B
oards
::
B
aseService
def
execute
(
board
)
def
execute
(
board
)
return
false
unless
board
.
lists
.
movable
.
empty?
return
false
unless
board
.
lists
.
movable
.
empty?
...
@@ -15,11 +15,11 @@ module Boards
...
@@ -15,11 +15,11 @@ module Boards
def
create_list
(
board
,
params
)
def
create_list
(
board
,
params
)
label
=
find_or_create_label
(
params
)
label
=
find_or_create_label
(
params
)
Lists
::
CreateService
.
new
(
p
rojec
t
,
current_user
,
label_id:
label
.
id
).
execute
(
board
)
Lists
::
CreateService
.
new
(
p
aren
t
,
current_user
,
label_id:
label
.
id
).
execute
(
board
)
end
end
def
find_or_create_label
(
params
)
def
find_or_create_label
(
params
)
::
Labels
::
FindOrCreateService
.
new
(
current_user
,
p
rojec
t
,
params
).
execute
::
Labels
::
FindOrCreateService
.
new
(
current_user
,
p
aren
t
,
params
).
execute
end
end
def
label_params
def
label_params
...
...
app/services/boards/lists/list_service.rb
View file @
b4778a58
module
Boards
module
Boards
module
Lists
module
Lists
class
ListService
<
BaseService
class
ListService
<
B
oards
::
B
aseService
def
execute
(
board
)
def
execute
(
board
)
board
.
lists
.
create
(
list_type: :backlog
)
unless
board
.
lists
.
backlog
.
exists?
board
.
lists
.
create
(
list_type: :backlog
)
unless
board
.
lists
.
backlog
.
exists?
...
...
app/services/boards/lists/move_service.rb
View file @
b4778a58
module
Boards
module
Boards
module
Lists
module
Lists
class
MoveService
<
BaseService
class
MoveService
<
B
oards
::
B
aseService
def
execute
(
list
)
def
execute
(
list
)
@board
=
list
.
board
@board
=
list
.
board
@old_position
=
list
.
position
@old_position
=
list
.
position
...
...
app/services/issues/update_service.rb
View file @
b4778a58
...
@@ -3,7 +3,7 @@ module Issues
...
@@ -3,7 +3,7 @@ module Issues
include
SpamCheckService
include
SpamCheckService
def
execute
(
issue
)
def
execute
(
issue
)
handle_move_between_i
i
ds
(
issue
)
handle_move_between_ids
(
issue
)
filter_spam_check_params
filter_spam_check_params
change_issue_duplicate
(
issue
)
change_issue_duplicate
(
issue
)
move_issue_to_new_project
(
issue
)
||
update
(
issue
)
move_issue_to_new_project
(
issue
)
||
update
(
issue
)
...
@@ -54,13 +54,13 @@ module Issues
...
@@ -54,13 +54,13 @@ module Issues
end
end
end
end
def
handle_move_between_i
i
ds
(
issue
)
def
handle_move_between_ids
(
issue
)
return
unless
params
[
:move_between_i
i
ds
]
return
unless
params
[
:move_between_ids
]
after_i
id
,
before_iid
=
params
.
delete
(
:move_between_i
ids
)
after_i
d
,
before_id
=
params
.
delete
(
:move_between_
ids
)
issue_before
=
get_issue_if_allowed
(
issue
.
project
,
before_i
id
)
if
before_i
id
issue_before
=
get_issue_if_allowed
(
issue
.
project
,
before_i
d
)
if
before_
id
issue_after
=
get_issue_if_allowed
(
issue
.
project
,
after_i
id
)
if
after_i
id
issue_after
=
get_issue_if_allowed
(
issue
.
project
,
after_i
d
)
if
after_
id
issue
.
move_between
(
issue_before
,
issue_after
)
issue
.
move_between
(
issue_before
,
issue_after
)
end
end
...
@@ -87,8 +87,8 @@ module Issues
...
@@ -87,8 +87,8 @@ module Issues
private
private
def
get_issue_if_allowed
(
project
,
i
i
d
)
def
get_issue_if_allowed
(
project
,
id
)
issue
=
project
.
issues
.
find
_by
(
iid:
i
id
)
issue
=
project
.
issues
.
find
(
id
)
issue
if
can?
(
current_user
,
:update_issue
,
issue
)
issue
if
can?
(
current_user
,
:update_issue
,
issue
)
end
end
...
...
app/views/projects/boards/index.html.haml
View file @
b4778a58
=
render
"sh
ow"
=
render
"sh
ared/boards/show"
,
board:
@boards
.
first
app/views/projects/boards/show.html.haml
View file @
b4778a58
=
render
"sh
ow"
=
render
"sh
ared/boards/show"
,
board:
@board
app/views/
projects
/boards/_show.html.haml
→
app/views/
shared
/boards/_show.html.haml
View file @
b4778a58
...
@@ -9,7 +9,7 @@
...
@@ -9,7 +9,7 @@
=
webpack_bundle_tag
'filtered_search'
=
webpack_bundle_tag
'filtered_search'
=
webpack_bundle_tag
'boards'
=
webpack_bundle_tag
'boards'
%script
#js-board-template
{
type:
"text/x-template"
}=
render
"
projects
/boards/components/board"
%script
#js-board-template
{
type:
"text/x-template"
}=
render
"
shared
/boards/components/board"
%script
#js-board-modal-filter
{
type:
"text/x-template"
}=
render
"shared/issuable/search_bar"
,
type: :boards_modal
%script
#js-board-modal-filter
{
type:
"text/x-template"
}=
render
"shared/issuable/search_bar"
,
type: :boards_modal
=
render
"projects/issues/head"
=
render
"projects/issues/head"
...
@@ -30,7 +30,7 @@
...
@@ -30,7 +30,7 @@
":root-path"
=>
"rootPath"
,
":root-path"
=>
"rootPath"
,
":board-id"
=>
"boardId"
,
":board-id"
=>
"boardId"
,
":key"
=>
"_uid"
}
":key"
=>
"_uid"
}
=
render
"
projects
/boards/components/sidebar"
=
render
"
shared
/boards/components/sidebar"
%board-add-issues-modal
{
"blank-state-image"
=>
render
(
'shared/empty_states/icons/issues.svg'
),
%board-add-issues-modal
{
"blank-state-image"
=>
render
(
'shared/empty_states/icons/issues.svg'
),
"new-issue-path"
=>
new_project_issue_path
(
@project
),
"new-issue-path"
=>
new_project_issue_path
(
@project
),
"milestone-path"
=>
milestones_filter_dropdown_path
,
"milestone-path"
=>
milestones_filter_dropdown_path
,
...
...
app/views/
projects
/boards/components/_board.html.haml
→
app/views/
shared
/boards/components/_board.html.haml
View file @
b4778a58
...
@@ -7,20 +7,26 @@
...
@@ -7,20 +7,26 @@
":class"
:
"{
\"
fa-caret-down
\"
: list.isExpanded,
\"
fa-caret-right
\"
: !list.isExpanded && list.position === -1,
\"
fa-caret-left
\"
: !list.isExpanded && list.position !== -1 }"
,
":class"
:
"{
\"
fa-caret-down
\"
: list.isExpanded,
\"
fa-caret-right
\"
: !list.isExpanded && list.position === -1,
\"
fa-caret-left
\"
: !list.isExpanded && list.position !== -1 }"
,
"aria-hidden"
:
"true"
}
"aria-hidden"
:
"true"
}
%span
.has-tooltip
{
"v-if"
:
"list.type !==
\"
label
\"
"
,
%span
.
board-title-text.
has-tooltip
{
"v-if"
:
"list.type !==
\"
label
\"
"
,
":title"
=>
'(list.label ? list.label.description : "")'
}
":title"
=>
'(list.label ? list.label.description : "")'
}
{{ list.title }}
{{ list.title }}
%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"
},
class:
"label color-label title"
,
class:
"label color-label title
board-title-text
"
,
":style"
=>
"{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.color ? list.label.text_color :
\"
#2e2e2e
\"
) }"
}
":style"
=>
"{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.color ? list.label.text_color :
\"
#2e2e2e
\"
) }"
}
{{ list.title }}
{{ list.title }}
.issue-count-badge.pull-right.clearfix
{
"v-if"
=>
'list.type !== "blank"'
}
-
if
can?
(
current_user
,
:admin_list
,
current_board_parent
)
%board-delete
{
"inline-template"
=>
true
,
":list"
=>
"list"
,
"v-if"
=>
"!list.preset && list.id"
}
%button
.board-delete.has-tooltip.pull-right
{
type:
"button"
,
title:
"Delete list"
,
"aria-label"
=>
"Delete list"
,
data:
{
placement:
"bottom"
},
"@click.stop"
=>
"deleteBoard"
}
=
icon
(
"trash"
)
.issue-count-badge.clearfix
{
"v-if"
=>
'list.type !== "blank"'
}
%span
.issue-count-badge-count.pull-left
{
":class"
=>
'
{
"has-btn"
:
list
.
type
!==
"closed"
&&
!
disabled
}
'
}
%span
.issue-count-badge-count.pull-left
{
":class"
=>
'
{
"has-btn"
:
list
.
type
!==
"closed"
&&
!
disabled
}
'
}
{{ list.issuesSize }}
{{ list.issuesSize }}
-
if
can?
(
current_user
,
:admin_
issue
,
@projec
t
)
-
if
can?
(
current_user
,
:admin_
list
,
current_board_paren
t
)
%button
.issue-count-badge-add-button.btn.btn-small.btn-default.has-tooltip.js-no-trigger-collapse
{
type:
"button"
,
%button
.issue-count-badge-add-button.btn.btn-small.btn-default.has-tooltip.js-no-trigger-collapse
{
type:
"button"
,
"@click"
=>
"showNewIssueForm"
,
"@click"
=>
"showNewIssueForm"
,
"v-if"
=>
'list.type !== "closed"'
,
"v-if"
=>
'list.type !== "closed"'
,
...
@@ -28,12 +34,7 @@
...
@@ -28,12 +34,7 @@
"title"
=>
"New issue"
,
"title"
=>
"New issue"
,
data:
{
placement:
"top"
,
container:
"body"
}
}
data:
{
placement:
"top"
,
container:
"body"
}
}
=
icon
(
"plus"
,
class:
"js-no-trigger-collapse"
)
=
icon
(
"plus"
,
class:
"js-no-trigger-collapse"
)
-
if
can?
(
current_user
,
:admin_list
,
@project
)
%board-delete
{
"inline-template"
=>
true
,
":list"
=>
"list"
,
"v-if"
=>
"!list.preset && list.id"
}
%button
.board-delete.has-tooltip.pull-right
{
type:
"button"
,
title:
"Delete list"
,
"aria-label"
=>
"Delete list"
,
data:
{
placement:
"bottom"
},
"@click.stop"
=>
"deleteBoard"
}
=
icon
(
"trash"
)
%board-list
{
"v-if"
=>
'list.type !== "blank"'
,
%board-list
{
"v-if"
=>
'list.type !== "blank"'
,
":list"
=>
"list"
,
":list"
=>
"list"
,
":issues"
=>
"list.issues"
,
":issues"
=>
"list.issues"
,
...
@@ -42,5 +43,5 @@
...
@@ -42,5 +43,5 @@
":issue-link-base"
=>
"issueLinkBase"
,
":issue-link-base"
=>
"issueLinkBase"
,
":root-path"
=>
"rootPath"
,
":root-path"
=>
"rootPath"
,
"ref"
=>
"board-list"
}
"ref"
=>
"board-list"
}
-
if
can?
(
current_user
,
:admin_list
,
@projec
t
)
-
if
can?
(
current_user
,
:admin_list
,
current_board_paren
t
)
%board-blank-state
{
"v-if"
=>
'list.id == "blank"'
}
%board-blank-state
{
"v-if"
=>
'list.id == "blank"'
}
app/views/
projects
/boards/components/_sidebar.html.haml
→
app/views/
shared
/boards/components/_sidebar.html.haml
View file @
b4778a58
...
@@ -10,18 +10,19 @@
...
@@ -10,18 +10,19 @@
%br
/
%br
/
%span
%span
=
precede
"#"
do
=
precede
"#"
do
{{ issue.id }}
{{ issue.i
i
d }}
%a
.gutter-toggle.pull-right
{
role:
"button"
,
%a
.gutter-toggle.pull-right
{
role:
"button"
,
href:
"#"
,
href:
"#"
,
"@click.prevent"
=>
"closeSidebar"
,
"@click.prevent"
=>
"closeSidebar"
,
"aria-label"
=>
"Toggle sidebar"
}
"aria-label"
=>
"Toggle sidebar"
}
=
custom_icon
(
"icon_close"
,
size:
15
)
=
custom_icon
(
"icon_close"
,
size:
15
)
.js-issuable-update
.js-issuable-update
=
render
"
projects
/boards/components/sidebar/assignee"
=
render
"
shared
/boards/components/sidebar/assignee"
=
render
"
projects
/boards/components/sidebar/milestone"
=
render
"
shared
/boards/components/sidebar/milestone"
=
render
"
projects
/boards/components/sidebar/due_date"
=
render
"
shared
/boards/components/sidebar/due_date"
=
render
"
projects
/boards/components/sidebar/labels"
=
render
"
shared
/boards/components/sidebar/labels"
=
render
"
projects
/boards/components/sidebar/notifications"
=
render
"
shared
/boards/components/sidebar/notifications"
%remove-btn
{
":issue"
=>
"issue"
,
%remove-btn
{
":issue"
=>
"issue"
,
":issue-update"
=>
"'#{build_issue_link_base}/' + issue.iid + '.json'"
,
":list"
=>
"list"
,
":list"
=>
"list"
,
"v-if"
=>
"canRemove"
}
"v-if"
=>
"canRemove"
}
app/views/
projects
/boards/components/sidebar/_assignee.html.haml
→
app/views/
shared
/boards/components/sidebar/_assignee.html.haml
View file @
b4778a58
...
@@ -2,13 +2,13 @@
...
@@ -2,13 +2,13 @@
%template
{
"v-if"
=>
"issue.assignees"
}
%template
{
"v-if"
=>
"issue.assignees"
}
%assignee-title
{
":number-of-assignees"
=>
"issue.assignees.length"
,
%assignee-title
{
":number-of-assignees"
=>
"issue.assignees.length"
,
":loading"
=>
"loadingAssignees"
,
":loading"
=>
"loadingAssignees"
,
":editable"
=>
can
?
(
current_user
,
:admin_issue
,
@project
)
}
":editable"
=>
can
_admin_issue?
}
%assignees
.value
{
"root-path"
=>
"#{root_url}"
,
%assignees
.value
{
"root-path"
=>
"#{root_url}"
,
":users"
=>
"issue.assignees"
,
":users"
=>
"issue.assignees"
,
":editable"
=>
can
?
(
current_user
,
:admin_issue
,
@project
)
,
":editable"
=>
can
_admin_issue?
,
"@assign-self"
=>
"assignSelf"
}
"@assign-self"
=>
"assignSelf"
}
-
if
can
?
(
current_user
,
:admin_issue
,
@project
)
-
if
can
_admin_issue?
.selectbox.hide-collapsed
.selectbox.hide-collapsed
%input
.js-vue
{
type:
"hidden"
,
%input
.js-vue
{
type:
"hidden"
,
name:
"issue[assignee_ids][]"
,
name:
"issue[assignee_ids][]"
,
...
@@ -20,9 +20,9 @@
...
@@ -20,9 +20,9 @@
":data-username"
=>
"assignee.username"
}
":data-username"
=>
"assignee.username"
}
.dropdown
.dropdown
-
dropdown_options
=
issue_assignees_dropdown_options
-
dropdown_options
=
issue_assignees_dropdown_options
%button
.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar
{
type:
'button'
,
ref:
'assigneeDropdown'
,
data:
{
toggle:
'dropdown'
,
field_name:
'issue[assignee_ids][]'
,
first_user:
current_user
&
.
username
,
current_user:
'true'
,
project_id:
@project
.
id
,
null_user:
'true'
,
multi_select:
'true'
,
'dropdown-header'
:
dropdown_options
[
:data
][
:'dropdown-header'
],
'max-select'
:
dropdown_options
[
:data
][
:'max-select'
]
}
,
%button
.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar
{
type:
'button'
,
ref:
'assigneeDropdown'
,
data:
board_sidebar_user_data
,
":data-issuable-id"
=>
"issue.id"
,
":data-issuable-id"
=>
"issue.i
i
d"
,
":data-issue-update"
=>
"'#{
project_issues_path(@project)}/' + issue.
id + '.json'"
}
":data-issue-update"
=>
"'#{
build_issue_link_base}/' + issue.i
id + '.json'"
}
=
dropdown_options
[
:title
]
=
dropdown_options
[
:title
]
=
icon
(
"chevron-down"
)
=
icon
(
"chevron-down"
)
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
...
...
app/views/
projects
/boards/components/sidebar/_due_date.html.haml
→
app/views/
shared
/boards/components/sidebar/_due_date.html.haml
View file @
b4778a58
.block.due_date
.block.due_date
.title
.title
Due date
Due date
-
if
can
?
(
current_user
,
:admin_issue
,
@project
)
-
if
can
_admin_issue?
=
icon
(
"spinner spin"
,
class:
"block-loading"
)
=
icon
(
"spinner spin"
,
class:
"block-loading"
)
=
link_to
"Edit"
,
"#"
,
class:
"js-sidebar-dropdown-toggle edit-link pull-right"
=
link_to
"Edit"
,
"#"
,
class:
"js-sidebar-dropdown-toggle edit-link pull-right"
.value
.value
...
@@ -10,12 +10,12 @@
...
@@ -10,12 +10,12 @@
No due date
No due date
%span
.bold
{
"v-if"
=>
"issue.dueDate"
}
%span
.bold
{
"v-if"
=>
"issue.dueDate"
}
{{ issue.dueDate | due-date }}
{{ issue.dueDate | due-date }}
-
if
can
?
(
current_user
,
:admin_issue
,
@project
)
-
if
can
_admin_issue?
%span
.no-value.js-remove-due-date-holder
{
"v-if"
=>
"issue.dueDate"
}
%span
.no-value.js-remove-due-date-holder
{
"v-if"
=>
"issue.dueDate"
}
\-
\-
%a
.js-remove-due-date
{
href:
"#"
,
role:
"button"
}
%a
.js-remove-due-date
{
href:
"#"
,
role:
"button"
}
remove due date
remove due date
-
if
can
?
(
current_user
,
:admin_issue
,
@project
)
-
if
can
_admin_issue?
.selectbox
.selectbox
%input
{
type:
"hidden"
,
%input
{
type:
"hidden"
,
name:
"issue[due_date]"
,
name:
"issue[due_date]"
,
...
@@ -23,7 +23,7 @@
...
@@ -23,7 +23,7 @@
.dropdown
.dropdown
%button
.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date
{
type:
'button'
,
%button
.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date
{
type:
'button'
,
data:
{
toggle:
'dropdown'
,
field_name:
"issue[due_date]"
,
ability_name:
"issue"
},
data:
{
toggle:
'dropdown'
,
field_name:
"issue[due_date]"
,
ability_name:
"issue"
},
":data-issue-update"
=>
"'#{
project_issues_path(@project)}/' + issue.
id + '.json'"
}
":data-issue-update"
=>
"'#{
build_issue_link_base}/' + issue.i
id + '.json'"
}
%span
.dropdown-toggle-text
Due date
%span
.dropdown-toggle-text
Due date
=
icon
(
'chevron-down'
)
=
icon
(
'chevron-down'
)
.dropdown-menu.dropdown-menu-due-date
.dropdown-menu.dropdown-menu-due-date
...
...
app/views/
projects
/boards/components/sidebar/_labels.html.haml
→
app/views/
shared
/boards/components/sidebar/_labels.html.haml
View file @
b4778a58
.block.labels
.block.labels
.title
.title
Labels
Labels
-
if
can
?
(
current_user
,
:admin_issue
,
@project
)
-
if
can
_admin_issue?
=
icon
(
"spinner spin"
,
class:
"block-loading"
)
=
icon
(
"spinner spin"
,
class:
"block-loading"
)
=
link_to
"Edit"
,
"#"
,
class:
"js-sidebar-dropdown-toggle edit-link pull-right"
=
link_to
"Edit"
,
"#"
,
class:
"js-sidebar-dropdown-toggle edit-link pull-right"
.value.issuable-show-labels
.value.issuable-show-labels
...
@@ -11,7 +11,7 @@
...
@@ -11,7 +11,7 @@
"v-for"
=>
"label in issue.labels"
}
"v-for"
=>
"label in issue.labels"
}
%span
.label.color-label.has-tooltip
{
":style"
=>
"{ backgroundColor: label.color, color: label.textColor }"
}
%span
.label.color-label.has-tooltip
{
":style"
=>
"{ backgroundColor: label.color, color: label.textColor }"
}
{{ label.title }}
{{ label.title }}
-
if
can
?
(
current_user
,
:admin_issue
,
@project
)
-
if
can
_admin_issue?
.selectbox
.selectbox
%input
{
type:
"hidden"
,
%input
{
type:
"hidden"
,
name:
"issue[label_names][]"
,
name:
"issue[label_names][]"
,
...
@@ -19,12 +19,19 @@
...
@@ -19,12 +19,19 @@
":value"
=>
"label.id"
}
":value"
=>
"label.id"
}
.dropdown
.dropdown
%button
.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar
{
type:
"button"
,
%button
.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar
{
type:
"button"
,
data:
{
toggle:
"dropdown"
,
field_name:
"issue[label_names][]"
,
show_no:
"true"
,
show_any:
"true"
,
project_id:
@project
.
id
,
labels:
project_labels_path
(
@project
,
:json
),
namespace_path:
@project
.
try
(
:namespace
).
try
(
:full_path
),
project_path:
@project
.
try
(
:path
)
},
data:
{
toggle:
"dropdown"
,
":data-issue-update"
=>
"'#{project_issues_path(@project)}/' + issue.id + '.json'"
}
field_name:
"issue[label_names][]"
,
show_no:
"true"
,
show_any:
"true"
,
project_id:
@project
&
.
try
(
:id
),
labels:
labels_filter_path
(
false
),
namespace_path:
@project
.
try
(
:namespace
).
try
(
:full_path
),
project_path:
@project
.
try
(
:path
)
},
":data-issue-update"
=>
"'#{build_issue_link_base}/' + issue.iid + '.json'"
}
%span
.dropdown-toggle-text
%span
.dropdown-toggle-text
Label
Label
=
icon
(
'chevron-down'
)
=
icon
(
'chevron-down'
)
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
=
render
partial:
"shared/issuable/label_page_default"
=
render
partial:
"shared/issuable/label_page_default"
-
if
can?
current_user
,
:admin_label
,
@project
and
@project
-
if
can?
(
current_user
,
:admin_label
,
current_board_parent
)
=
render
partial:
"shared/issuable/label_page_create"
=
render
partial:
"shared/issuable/label_page_create"
app/views/
projects
/boards/components/sidebar/_milestone.html.haml
→
app/views/
shared
/boards/components/sidebar/_milestone.html.haml
View file @
b4778a58
.block.milestone
.block.milestone
.title
.title
Milestone
Milestone
-
if
can
?
(
current_user
,
:admin_issue
,
@project
)
-
if
can
_admin_issue?
=
icon
(
"spinner spin"
,
class:
"block-loading"
)
=
icon
(
"spinner spin"
,
class:
"block-loading"
)
=
link_to
"Edit"
,
"#"
,
class:
"js-sidebar-dropdown-toggle edit-link pull-right"
=
link_to
"Edit"
,
"#"
,
class:
"js-sidebar-dropdown-toggle edit-link pull-right"
.value
.value
...
@@ -9,17 +9,17 @@
...
@@ -9,17 +9,17 @@
None
None
%span
.bold.has-tooltip
{
"v-if"
=>
"issue.milestone"
}
%span
.bold.has-tooltip
{
"v-if"
=>
"issue.milestone"
}
{{ issue.milestone.title }}
{{ issue.milestone.title }}
-
if
can
?
(
current_user
,
:admin_issue
,
@project
)
-
if
can
_admin_issue?
.selectbox
.selectbox
%input
{
type:
"hidden"
,
%input
{
type:
"hidden"
,
":value"
=>
"issue.milestone.id"
,
":value"
=>
"issue.milestone.id"
,
name:
"issue[milestone_id]"
,
name:
"issue[milestone_id]"
,
"v-if"
=>
"issue.milestone"
}
"v-if"
=>
"issue.milestone"
}
.dropdown
.dropdown
%button
.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar
{
type:
"button"
,
data:
{
toggle:
"dropdown"
,
show_no:
"true"
,
field_name:
"issue[milestone_id]"
,
project_id:
@project
.
id
,
milestones:
project_milestones_path
(
@project
,
:json
),
ability_name:
"issue"
,
use_id:
"true"
,
default_no:
"true"
},
%button
.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar
{
type:
"button"
,
data:
{
toggle:
"dropdown"
,
show_no:
"true"
,
field_name:
"issue[milestone_id]"
,
milestones:
milestones_filter_path
(
format:
:json
),
ability_name:
"issue"
,
use_id:
"true"
,
default_no:
"true"
},
":data-selected"
=>
"milestoneTitle"
,
":data-selected"
=>
"milestoneTitle"
,
":data-issuable-id"
=>
"issue.id"
,
":data-issuable-id"
=>
"issue.i
i
d"
,
":data-issue-update"
=>
"'#{
project_issues_path(@project)}/' + issue.
id + '.json'"
}
":data-issue-update"
=>
"'#{
build_issue_link_base}/' + issue.i
id + '.json'"
}
Milestone
Milestone
=
icon
(
"chevron-down"
)
=
icon
(
"chevron-down"
)
.dropdown-menu.dropdown-select.dropdown-menu-selectable
.dropdown-menu.dropdown-select.dropdown-menu-selectable
...
...
app/views/
projects
/boards/components/sidebar/_notifications.html.haml
→
app/views/
shared
/boards/components/sidebar/_notifications.html.haml
View file @
b4778a58
-
if
current_user
-
if
current_user
.block.light.subscription
{
":data-url"
=>
"'#{
project_issues_path(@project)}/' + issue.
id + '/toggle_subscription'"
}
.block.light.subscription
{
":data-url"
=>
"'#{
build_issue_link_base}/' + issue.i
id + '/toggle_subscription'"
}
%span
.issuable-header-text.hide-collapsed.pull-left
%span
.issuable-header-text.hide-collapsed.pull-left
Notifications
Notifications
%button
.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed
{
type:
"button"
}
%button
.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed
{
type:
"button"
}
...
...
app/views/shared/boards/index.html.haml
0 → 100644
View file @
b4778a58
=
render
"show"
app/views/shared/boards/show.html.haml
0 → 100644
View file @
b4778a58
=
render
"show"
app/views/shared/issuable/_label_page_default.html.haml
View file @
b4778a58
...
@@ -8,20 +8,19 @@
...
@@ -8,20 +8,19 @@
-
if
show_boards_content
-
if
show_boards_content
.issue-board-dropdown-content
.issue-board-dropdown-content
%p
%p
Create lists from the labels you use in your project. Issues with that
Create lists from labels. Issues with that label appear in that list.
label will automatically be added to the list.
=
dropdown_filter
(
filter_placeholder
)
=
dropdown_filter
(
filter_placeholder
)
=
dropdown_content
=
dropdown_content
-
if
@projec
t
&&
show_footer
-
if
current_board_paren
t
&&
show_footer
=
dropdown_footer
do
=
dropdown_footer
do
%ul
.dropdown-footer-list
%ul
.dropdown-footer-list
-
if
can?
(
current_user
,
:admin_label
,
@projec
t
)
-
if
can?
(
current_user
,
:admin_label
,
current_board_paren
t
)
%li
%li
%a
.dropdown-toggle-page
{
href:
"#"
}
%a
.dropdown-toggle-page
{
href:
"#"
}
Create new label
Create new label
%li
%li
=
link_to
project_labels_path
(
@project
)
,
:"data-is-link"
=>
true
do
=
link_to
labels_path
,
:"data-is-link"
=>
true
do
-
if
show_create
&&
@project
&&
can?
(
current_user
,
:admin_label
,
@projec
t
)
-
if
show_create
&&
can?
(
current_user
,
:admin_label
,
current_board_paren
t
)
Manage labels
Manage labels
-
else
-
else
View labels
View labels
...
...
app/views/shared/issuable/_search_bar.html.haml
View file @
b4778a58
...
@@ -104,13 +104,13 @@
...
@@ -104,13 +104,13 @@
=
icon
(
'times'
)
=
icon
(
'times'
)
.filter-dropdown-container
.filter-dropdown-container
-
if
type
==
:boards
-
if
type
==
:boards
-
if
can?
(
current_user
,
:admin_list
,
@projec
t
)
-
if
can?
(
current_user
,
:admin_list
,
board
.
paren
t
)
.dropdown.prepend-left-10
#js-add-list
.dropdown.prepend-left-10
#js-add-list
%button
.btn.btn-create.btn-inverted.js-new-board-list
{
type:
"button"
,
data:
{
toggle:
"dropdown"
,
labels:
labels_filter_path
,
namespace_path:
@project
.
try
(
:namespace
).
try
(
:full_path
),
project_path:
@project
.
try
(
:path
)
}
}
%button
.btn.btn-create.btn-inverted.js-new-board-list
{
type:
"button"
,
data:
board_list_data
}
Add list
Add list
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-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"
}
=
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
,
@projec
t
)
-
if
can?
(
current_user
,
:admin_label
,
board
.
paren
t
)
=
render
partial:
"shared/issuable/label_page_create"
=
render
partial:
"shared/issuable/label_page_create"
=
dropdown_loading
=
dropdown_loading
#js-add-issues-btn
.prepend-left-10
#js-add-issues-btn
.prepend-left-10
...
...
config/routes.rb
View file @
b4778a58
...
@@ -74,6 +74,19 @@ Rails.application.routes.draw do
...
@@ -74,6 +74,19 @@ Rails.application.routes.draw do
# Notification settings
# Notification settings
resources
:notification_settings
,
only:
[
:create
,
:update
]
resources
:notification_settings
,
only:
[
:create
,
:update
]
# Boards resources shared between group and projects
resources
:boards
do
resources
:lists
,
module: :boards
,
only:
[
:index
,
:create
,
:update
,
:destroy
]
do
collection
do
post
:generate
end
resources
:issues
,
only:
[
:index
,
:create
,
:update
]
end
resources
:issues
,
module: :boards
,
only:
[
:index
,
:update
]
end
draw
:import
draw
:import
draw
:uploads
draw
:uploads
draw
:explore
draw
:explore
...
...
config/routes/project.rb
View file @
b4778a58
...
@@ -343,19 +343,7 @@ constraints(ProjectUrlConstrainer.new) do
...
@@ -343,19 +343,7 @@ constraints(ProjectUrlConstrainer.new) do
get
'noteable/:target_type/:target_id/notes'
=>
'notes#index'
,
as:
'noteable_notes'
get
'noteable/:target_type/:target_id/notes'
=>
'notes#index'
,
as:
'noteable_notes'
resources
:boards
,
only:
[
:index
,
:show
]
do
resources
:boards
,
only:
[
:index
,
:show
,
:create
,
:update
,
:destroy
]
scope
module: :boards
do
resources
:issues
,
only:
[
:index
,
:update
]
resources
:lists
,
only:
[
:index
,
:create
,
:update
,
:destroy
]
do
collection
do
post
:generate
end
resources
:issues
,
only:
[
:index
,
:create
]
end
end
end
resources
:todos
,
only:
[
:create
]
resources
:todos
,
only:
[
:create
]
...
...
doc/README.md
View file @
b4778a58
...
@@ -84,7 +84,7 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i
...
@@ -84,7 +84,7 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i
-
[
Discussions
](
user/discussions/index.md
)
Threads, comments, and resolvable discussions in issues, commits, and merge requests.
-
[
Discussions
](
user/discussions/index.md
)
Threads, comments, and resolvable discussions in issues, commits, and merge requests.
-
[
Issues
](
user/project/issues/index.md
)
-
[
Issues
](
user/project/issues/index.md
)
-
[
I
ssue Board
](
user/project/issue_board.md
)
-
[
Project i
ssue Board
](
user/project/issue_board.md
)
-
[
Issues and merge requests templates
](
user/project/description_templates.md
)
: Create templates for submitting new issues and merge requests.
-
[
Issues and merge requests templates
](
user/project/description_templates.md
)
: Create templates for submitting new issues and merge requests.
-
[
Labels
](
user/project/labels.md
)
: Categorize your issues or merge requests based on descriptive titles.
-
[
Labels
](
user/project/labels.md
)
: Categorize your issues or merge requests based on descriptive titles.
-
[
Merge Requests
](
user/project/merge_requests/index.md
)
-
[
Merge Requests
](
user/project/merge_requests/index.md
)
...
...
lib/gitlab/path_regex.rb
View file @
b4778a58
...
@@ -26,6 +26,7 @@ module Gitlab
...
@@ -26,6 +26,7 @@ module Gitlab
apple-touch-icon.png
apple-touch-icon.png
assets
assets
autocomplete
autocomplete
boards
ci
ci
dashboard
dashboard
deploy.html
deploy.html
...
...
spec/controllers/
projects/
boards/issues_controller_spec.rb
→
spec/controllers/boards/issues_controller_spec.rb
View file @
b4778a58
require
'spec_helper'
require
'spec_helper'
describe
Projects
::
Boards
::
IssuesController
do
describe
Boards
::
IssuesController
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:board
)
{
create
(
:board
,
project:
project
)
}
let
(
:board
)
{
create
(
:board
,
project:
project
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
...
@@ -133,6 +133,22 @@ describe Projects::Boards::IssuesController do
...
@@ -133,6 +133,22 @@ describe Projects::Boards::IssuesController do
expect
(
response
).
to
have_http_status
(
404
)
expect
(
response
).
to
have_http_status
(
404
)
end
end
end
end
context
'with invalid board id'
do
it
'returns a not found 404 response'
do
create_issue
user:
user
,
board:
999
,
list:
list1
,
title:
'New issue'
expect
(
response
).
to
have_http_status
(
404
)
end
end
context
'with invalid list id'
do
it
'returns a not found 404 response'
do
create_issue
user:
user
,
board:
board
,
list:
999
,
title:
'New issue'
expect
(
response
).
to
have_http_status
(
404
)
end
end
end
end
context
'with unauthorized user'
do
context
'with unauthorized user'
do
...
@@ -146,17 +162,15 @@ describe Projects::Boards::IssuesController do
...
@@ -146,17 +162,15 @@ describe Projects::Boards::IssuesController do
def
create_issue
(
user
:,
board
:,
list
:,
title
:)
def
create_issue
(
user
:,
board
:,
list
:,
title
:)
sign_in
(
user
)
sign_in
(
user
)
post
:create
,
namespace_id:
project
.
namespace
.
to_param
,
post
:create
,
board_id:
board
.
to_param
,
project_id:
project
,
board_id:
board
.
to_param
,
list_id:
list
.
to_param
,
list_id:
list
.
to_param
,
issue:
{
title:
title
},
issue:
{
title:
title
,
project_id:
project
.
id
},
format: :json
format: :json
end
end
end
end
describe
'PATCH update'
do
describe
'PATCH update'
do
let
(
:issue
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
planning
])
}
let
!
(
:issue
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
planning
])
}
context
'with valid params'
do
context
'with valid params'
do
it
'returns a successful 200 response'
do
it
'returns a successful 200 response'
do
...
@@ -186,7 +200,7 @@ describe Projects::Boards::IssuesController do
...
@@ -186,7 +200,7 @@ describe Projects::Boards::IssuesController do
end
end
it
'returns a not found 404 response for invalid issue id'
do
it
'returns a not found 404 response for invalid issue id'
do
move
user:
user
,
board:
board
,
issue:
999
,
from_list_id:
list1
.
id
,
to_list_id:
list2
.
id
move
user:
user
,
board:
board
,
issue:
double
(
id:
999
)
,
from_list_id:
list1
.
id
,
to_list_id:
list2
.
id
expect
(
response
).
to
have_http_status
(
404
)
expect
(
response
).
to
have_http_status
(
404
)
end
end
...
@@ -210,9 +224,9 @@ describe Projects::Boards::IssuesController do
...
@@ -210,9 +224,9 @@ describe Projects::Boards::IssuesController do
sign_in
(
user
)
sign_in
(
user
)
patch
:update
,
namespace_id:
project
.
namespace
.
to_param
,
patch
:update
,
namespace_id:
project
.
namespace
.
to_param
,
project_id:
project
,
project_id:
project
.
id
,
board_id:
board
.
to_param
,
board_id:
board
.
to_param
,
id:
issue
.
to_param
,
id:
issue
.
id
,
from_list_id:
from_list_id
,
from_list_id:
from_list_id
,
to_list_id:
to_list_id
,
to_list_id:
to_list_id
,
format: :json
format: :json
...
...
spec/controllers/
projects/
boards/lists_controller_spec.rb
→
spec/controllers/boards/lists_controller_spec.rb
View file @
b4778a58
require
'spec_helper'
require
'spec_helper'
describe
Projects
::
Boards
::
ListsController
do
describe
Boards
::
ListsController
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:board
)
{
create
(
:board
,
project:
project
)
}
let
(
:board
)
{
create
(
:board
,
project:
project
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
...
...
spec/factories/milestones.rb
View file @
b4778a58
...
@@ -7,6 +7,7 @@ FactoryGirl.define do
...
@@ -7,6 +7,7 @@ FactoryGirl.define do
group
nil
group
nil
project_id
nil
project_id
nil
group_id
nil
group_id
nil
parent
nil
end
end
trait
:active
do
trait
:active
do
...
@@ -26,6 +27,9 @@ FactoryGirl.define do
...
@@ -26,6 +27,9 @@ FactoryGirl.define do
milestone
.
project
=
evaluator
.
project
milestone
.
project
=
evaluator
.
project
elsif
evaluator
.
project_id
elsif
evaluator
.
project_id
milestone
.
project_id
=
evaluator
.
project_id
milestone
.
project_id
=
evaluator
.
project_id
elsif
evaluator
.
parent
id
=
evaluator
.
parent
.
id
evaluator
.
parent
.
is_a?
(
Group
)
?
board
.
group_id
=
id
:
evaluator
.
project_id
=
id
else
else
milestone
.
project
=
create
(
:project
)
milestone
.
project
=
create
(
:project
)
end
end
...
...
spec/fixtures/api/schemas/issue.json
View file @
b4778a58
...
@@ -8,10 +8,15 @@
...
@@ -8,10 +8,15 @@
"properties"
:
{
"properties"
:
{
"id"
:
{
"type"
:
"integer"
},
"id"
:
{
"type"
:
"integer"
},
"iid"
:
{
"type"
:
"integer"
},
"iid"
:
{
"type"
:
"integer"
},
"project_id"
:
{
"type"
:
[
"integer"
,
"null"
]
},
"title"
:
{
"type"
:
"string"
},
"title"
:
{
"type"
:
"string"
},
"confidential"
:
{
"type"
:
"boolean"
},
"confidential"
:
{
"type"
:
"boolean"
},
"due_date"
:
{
"type"
:
[
"date"
,
"null"
]
},
"due_date"
:
{
"type"
:
[
"date"
,
"null"
]
},
"relative_position"
:
{
"type"
:
"integer"
},
"relative_position"
:
{
"type"
:
"integer"
},
"project"
:
{
"id"
:
{
"type"
:
"integer"
},
"path"
:
{
"type"
:
"string"
}
},
"labels"
:
{
"labels"
:
{
"type"
:
"array"
,
"type"
:
"array"
,
"items"
:
{
"items"
:
{
...
@@ -34,6 +39,7 @@
...
@@ -34,6 +39,7 @@
"type"
:
"string"
,
"type"
:
"string"
,
"pattern"
:
"^#[0-9A-Fa-f]{3}{1,2}+$"
"pattern"
:
"^#[0-9A-Fa-f]{3}{1,2}+$"
},
},
"type"
:
{
"type"
:
"string"
},
"title"
:
{
"type"
:
"string"
},
"title"
:
{
"type"
:
"string"
},
"priority"
:
{
"type"
:
[
"integer"
,
"null"
]
}
"priority"
:
{
"type"
:
[
"integer"
,
"null"
]
}
},
},
...
...
spec/javascripts/boards/board_blank_state_spec.js
View file @
b4778a58
/* global BoardService */
/* global BoardService */
/* global mockBoardService */
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
'
~/boards/stores/boards_store
'
;
import
'
~/boards/stores/boards_store
'
;
import
boardBlankState
from
'
~/boards/components/board_blank_state
'
;
import
boardBlankState
from
'
~/boards/components/board_blank_state
'
;
...
@@ -12,7 +13,7 @@ describe('Boards blank state', () => {
...
@@ -12,7 +13,7 @@ describe('Boards blank state', () => {
const
Comp
=
Vue
.
extend
(
boardBlankState
);
const
Comp
=
Vue
.
extend
(
boardBlankState
);
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
boardService
=
new
BoardService
(
'
/test/issue-boards/board
'
,
''
,
'
1
'
);
gl
.
boardService
=
mockBoardService
(
);
spyOn
(
gl
.
boardService
,
'
generateDefaultLists
'
).
and
.
callFake
(()
=>
new
Promise
((
resolve
,
reject
)
=>
{
spyOn
(
gl
.
boardService
,
'
generateDefaultLists
'
).
and
.
callFake
(()
=>
new
Promise
((
resolve
,
reject
)
=>
{
if
(
fail
)
{
if
(
fail
)
{
...
...
spec/javascripts/boards/board_card_spec.js
View file @
b4778a58
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
/* global listObj */
/* global listObj */
/* global boardsMockInterceptor */
/* global boardsMockInterceptor */
/* global BoardService */
/* global BoardService */
/* global mockBoardService */
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/models/assignee
'
;
...
@@ -14,13 +15,13 @@ import '~/boards/stores/boards_store';
...
@@ -14,13 +15,13 @@ import '~/boards/stores/boards_store';
import
boardCard
from
'
~/boards/components/board_card
'
;
import
boardCard
from
'
~/boards/components/board_card
'
;
import
'
./mock_data
'
;
import
'
./mock_data
'
;
describe
(
'
Issue
card
'
,
()
=>
{
describe
(
'
Board
card
'
,
()
=>
{
let
vm
;
let
vm
;
beforeEach
((
done
)
=>
{
beforeEach
((
done
)
=>
{
Vue
.
http
.
interceptors
.
push
(
boardsMockInterceptor
);
Vue
.
http
.
interceptors
.
push
(
boardsMockInterceptor
);
gl
.
boardService
=
new
BoardService
(
'
/test/issue-boards/board
'
,
''
,
'
1
'
);
gl
.
boardService
=
mockBoardService
(
);
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
issueBoards
.
BoardsStore
.
detail
.
issue
=
{};
gl
.
issueBoards
.
BoardsStore
.
detail
.
issue
=
{};
...
...
spec/javascripts/boards/board_list_spec.js
View file @
b4778a58
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
/* global List */
/* global List */
/* global listObj */
/* global listObj */
/* global ListIssue */
/* global ListIssue */
/* global mockBoardService */
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
Sortable
from
'
vendor/Sortable
'
;
import
Sortable
from
'
vendor/Sortable
'
;
...
@@ -24,7 +25,7 @@ describe('Board list component', () => {
...
@@ -24,7 +25,7 @@ describe('Board list component', () => {
document
.
body
.
appendChild
(
el
);
document
.
body
.
appendChild
(
el
);
Vue
.
http
.
interceptors
.
push
(
boardsMockInterceptor
);
Vue
.
http
.
interceptors
.
push
(
boardsMockInterceptor
);
gl
.
boardService
=
new
BoardService
(
'
/test/issue-boards/board
'
,
''
,
'
1
'
);
gl
.
boardService
=
mockBoardService
(
);
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
IssueBoardsApp
=
new
Vue
();
gl
.
IssueBoardsApp
=
new
Vue
();
...
@@ -32,6 +33,7 @@ describe('Board list component', () => {
...
@@ -32,6 +33,7 @@ describe('Board list component', () => {
const
list
=
new
List
(
listObj
);
const
list
=
new
List
(
listObj
);
const
issue
=
new
ListIssue
({
const
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
iid
:
1
,
confidential
:
false
,
confidential
:
false
,
labels
:
[],
labels
:
[],
...
...
spec/javascripts/boards/board_new_issue_spec.js
View file @
b4778a58
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
/* global BoardService */
/* global BoardService */
/* global List */
/* global List */
/* global listObj */
/* global listObj */
/* global mockBoardService */
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
boardNewIssue
from
'
~/boards/components/board_new_issue
'
;
import
boardNewIssue
from
'
~/boards/components/board_new_issue
'
;
...
@@ -35,7 +36,7 @@ describe('Issue boards new issue form', () => {
...
@@ -35,7 +36,7 @@ describe('Issue boards new issue form', () => {
const
BoardNewIssueComp
=
Vue
.
extend
(
boardNewIssue
);
const
BoardNewIssueComp
=
Vue
.
extend
(
boardNewIssue
);
Vue
.
http
.
interceptors
.
push
(
boardsMockInterceptor
);
Vue
.
http
.
interceptors
.
push
(
boardsMockInterceptor
);
gl
.
boardService
=
new
BoardService
(
'
/test/issue-boards/board
'
,
''
,
'
1
'
);
gl
.
boardService
=
mockBoardService
(
);
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
IssueBoardsApp
=
new
Vue
();
gl
.
IssueBoardsApp
=
new
Vue
();
...
...
spec/javascripts/boards/boards_store_spec.js
View file @
b4778a58
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
/* global listObj */
/* global listObj */
/* global listObjDuplicate */
/* global listObjDuplicate */
/* global ListIssue */
/* global ListIssue */
/* global mockBoardService */
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
Cookies
from
'
js-cookie
'
;
import
Cookies
from
'
js-cookie
'
;
...
@@ -20,7 +21,7 @@ import './mock_data';
...
@@ -20,7 +21,7 @@ import './mock_data';
describe
(
'
Store
'
,
()
=>
{
describe
(
'
Store
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
boardsMockInterceptor
);
Vue
.
http
.
interceptors
.
push
(
boardsMockInterceptor
);
gl
.
boardService
=
new
BoardService
(
'
/test/issue-boards/board
'
,
''
,
'
1
'
);
gl
.
boardService
=
mockBoardService
(
);
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
issueBoards
.
BoardsStore
.
create
();
spyOn
(
gl
.
boardService
,
'
moveIssue
'
).
and
.
callFake
(()
=>
new
Promise
((
resolve
)
=>
{
spyOn
(
gl
.
boardService
,
'
moveIssue
'
).
and
.
callFake
(()
=>
new
Promise
((
resolve
)
=>
{
...
@@ -78,7 +79,7 @@ describe('Store', () => {
...
@@ -78,7 +79,7 @@ describe('Store', () => {
it
(
'
persists new list
'
,
(
done
)
=>
{
it
(
'
persists new list
'
,
(
done
)
=>
{
gl
.
issueBoards
.
BoardsStore
.
new
({
gl
.
issueBoards
.
BoardsStore
.
new
({
title
:
'
Test
'
,
title
:
'
Test
'
,
type
:
'
label
'
,
list_
type
:
'
label
'
,
label
:
{
label
:
{
id
:
1
,
id
:
1
,
title
:
'
Testing
'
,
title
:
'
Testing
'
,
...
@@ -210,6 +211,7 @@ describe('Store', () => {
...
@@ -210,6 +211,7 @@ describe('Store', () => {
it
(
'
moves issue in list
'
,
(
done
)
=>
{
it
(
'
moves issue in list
'
,
(
done
)
=>
{
const
issue
=
new
ListIssue
({
const
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
title
:
'
Testing
'
,
id
:
2
,
iid
:
2
,
iid
:
2
,
confidential
:
false
,
confidential
:
false
,
labels
:
[],
labels
:
[],
...
...
spec/javascripts/boards/components/board_spec.js
View file @
b4778a58
/* global mockBoardService */
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/services/board_service
'
;
import
'
~/boards/components/board
'
;
import
'
~/boards/components/board
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/list
'
;
import
'
../mock_data
'
;
describe
(
'
Board component
'
,
()
=>
{
describe
(
'
Board component
'
,
()
=>
{
let
vm
;
let
vm
;
...
@@ -13,8 +15,12 @@ describe('Board component', () => {
...
@@ -13,8 +15,12 @@ describe('Board component', () => {
el
=
document
.
createElement
(
'
div
'
);
el
=
document
.
createElement
(
'
div
'
);
document
.
body
.
appendChild
(
el
);
document
.
body
.
appendChild
(
el
);
// eslint-disable-next-line no-undef
gl
.
boardService
=
mockBoardService
({
gl
.
boardService
=
new
BoardService
(
'
/
'
,
'
/
'
,
1
);
boardsEndpoint
:
'
/
'
,
listsEndpoint
:
'
/
'
,
bulkUpdatePath
:
'
/
'
,
boardId
:
1
,
});
vm
=
new
gl
.
issueBoards
.
Board
({
vm
=
new
gl
.
issueBoards
.
Board
({
propsData
:
{
propsData
:
{
...
...
spec/javascripts/boards/issue_card_spec.js
View file @
b4778a58
...
@@ -37,6 +37,7 @@ describe('Issue card component', () => {
...
@@ -37,6 +37,7 @@ describe('Issue card component', () => {
list
=
listObj
;
list
=
listObj
;
issue
=
new
ListIssue
({
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
iid
:
1
,
confidential
:
false
,
confidential
:
false
,
labels
:
[
list
.
label
],
labels
:
[
list
.
label
],
...
@@ -238,65 +239,63 @@ describe('Issue card component', () => {
...
@@ -238,65 +239,63 @@ describe('Issue card component', () => {
});
});
describe
(
'
labels
'
,
()
=>
{
describe
(
'
labels
'
,
()
=>
{
describe
(
'
exists
'
,
()
=>
{
beforeEach
((
done
)
=>
{
beforeEach
((
done
)
=>
{
component
.
issue
.
addLabel
(
label1
);
component
.
issue
.
addLabel
(
label1
);
Vue
.
nextTick
(()
=>
done
());
Vue
.
nextTick
(()
=>
done
());
});
});
it
(
'
renders list label
'
,
()
=>
{
it
(
'
renders list label
'
,
()
=>
{
expect
(
expect
(
component
.
$el
.
querySelectorAll
(
'
.label
'
).
length
,
component
.
$el
.
querySelectorAll
(
'
.label
'
).
length
,
).
toBe
(
2
);
).
toBe
(
2
);
});
it
(
'
renders label
'
,
()
=>
{
const
nodes
=
[];
component
.
$el
.
querySelectorAll
(
'
.label
'
).
forEach
((
label
)
=>
{
nodes
.
push
(
label
.
title
);
});
});
it
(
'
renders label
'
,
()
=>
{
expect
(
const
nodes
=
[];
nodes
.
includes
(
label1
.
description
),
component
.
$el
.
querySelectorAll
(
'
.label
'
).
forEach
((
label
)
=>
{
).
toBe
(
true
);
nodes
.
push
(
label
.
title
);
});
});
expect
(
it
(
'
sets label description as title
'
,
()
=>
{
nodes
.
includes
(
label1
.
description
),
expect
(
).
toBe
(
true
);
component
.
$el
.
querySelector
(
'
.label
'
).
getAttribute
(
'
title
'
),
});
).
toContain
(
label1
.
description
);
});
it
(
'
sets label description as title
'
,
()
=>
{
it
(
'
sets background color of button
'
,
()
=>
{
expect
(
const
nodes
=
[];
component
.
$el
.
querySelector
(
'
.label
'
).
getAttribute
(
'
title
'
),
component
.
$el
.
querySelectorAll
(
'
.label
'
).
forEach
((
label
)
=>
{
).
toContain
(
label1
.
description
);
nodes
.
push
(
label
.
style
.
backgroundColor
);
});
});
it
(
'
sets background color of button
'
,
()
=>
{
expect
(
const
nodes
=
[];
nodes
.
includes
(
label1
.
color
),
component
.
$el
.
querySelectorAll
(
'
.label
'
).
forEach
((
label
)
=>
{
).
toBe
(
true
);
nodes
.
push
(
label
.
style
.
backgroundColor
);
});
});
expect
(
it
(
'
does not render label if label does not have an ID
'
,
(
done
)
=>
{
nodes
.
includes
(
label1
.
color
),
component
.
issue
.
addLabel
(
new
ListLabel
({
).
toBe
(
true
);
title
:
'
closed
'
,
});
})
)
;
it
(
'
does not render label if label does not have an ID
'
,
(
done
)
=>
{
Vue
.
nextTick
()
component
.
issue
.
addLabel
(
new
ListLabel
({
.
then
(()
=>
{
title
:
'
closed
'
,
expect
(
}));
component
.
$el
.
querySelectorAll
(
'
.label
'
).
length
,
).
toBe
(
2
);
expect
(
component
.
$el
.
textContent
,
).
not
.
toContain
(
'
closed
'
);
Vue
.
nextTick
()
done
();
.
then
(()
=>
{
})
expect
(
.
catch
(
done
.
fail
);
component
.
$el
.
querySelectorAll
(
'
.label
'
).
length
,
).
toBe
(
2
);
expect
(
component
.
$el
.
textContent
,
).
not
.
toContain
(
'
closed
'
);
done
();
})
.
catch
(
done
.
fail
);
});
});
});
});
});
});
});
spec/javascripts/boards/issue_spec.js
View file @
b4778a58
/* eslint-disable comma-dangle */
/* eslint-disable comma-dangle */
/* global BoardService */
/* global BoardService */
/* global ListIssue */
/* global ListIssue */
/* global mockBoardService */
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
'
~/lib/utils/url_utility
'
;
import
'
~/lib/utils/url_utility
'
;
...
@@ -16,11 +17,12 @@ describe('Issue model', () => {
...
@@ -16,11 +17,12 @@ describe('Issue model', () => {
let
issue
;
let
issue
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
gl
.
boardService
=
new
BoardService
(
'
/test/issue-boards/board
'
,
''
,
'
1
'
);
gl
.
boardService
=
mockBoardService
(
);
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
issueBoards
.
BoardsStore
.
create
();
issue
=
new
ListIssue
({
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
iid
:
1
,
confidential
:
false
,
confidential
:
false
,
labels
:
[{
labels
:
[{
...
...
spec/javascripts/boards/list_spec.js
View file @
b4778a58
/* eslint-disable comma-dangle */
/* eslint-disable comma-dangle */
/* global boardsMockInterceptor */
/* global boardsMockInterceptor */
/* global BoardService */
/* global BoardService */
/* global mockBoardService */
/* global List */
/* global List */
/* global ListIssue */
/* global ListIssue */
/* global listObj */
/* global listObj */
...
@@ -22,7 +23,9 @@ describe('List model', () => {
...
@@ -22,7 +23,9 @@ describe('List model', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
boardsMockInterceptor
);
Vue
.
http
.
interceptors
.
push
(
boardsMockInterceptor
);
gl
.
boardService
=
new
BoardService
(
'
/test/issue-boards/board
'
,
''
,
'
1
'
);
gl
.
boardService
=
mockBoardService
({
bulkUpdatePath
:
'
/test/issue-boards/board/1/lists
'
,
});
gl
.
issueBoards
.
BoardsStore
.
create
();
gl
.
issueBoards
.
BoardsStore
.
create
();
list
=
new
List
(
listObj
);
list
=
new
List
(
listObj
);
...
@@ -92,6 +95,7 @@ describe('List model', () => {
...
@@ -92,6 +95,7 @@ describe('List model', () => {
const
listDup
=
new
List
(
listObjDuplicate
);
const
listDup
=
new
List
(
listObjDuplicate
);
const
issue
=
new
ListIssue
({
const
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
title
:
'
Testing
'
,
id
:
_
.
random
(
10000
),
iid
:
_
.
random
(
10000
),
iid
:
_
.
random
(
10000
),
confidential
:
false
,
confidential
:
false
,
labels
:
[
list
.
label
,
listDup
.
label
],
labels
:
[
list
.
label
,
listDup
.
label
],
...
@@ -118,6 +122,7 @@ describe('List model', () => {
...
@@ -118,6 +122,7 @@ describe('List model', () => {
for
(
let
i
=
0
;
i
<
30
;
i
+=
1
)
{
for
(
let
i
=
0
;
i
<
30
;
i
+=
1
)
{
list
.
issues
.
push
(
new
ListIssue
({
list
.
issues
.
push
(
new
ListIssue
({
title
:
'
Testing
'
,
title
:
'
Testing
'
,
id
:
_
.
random
(
10000
)
+
i
,
iid
:
_
.
random
(
10000
)
+
i
,
iid
:
_
.
random
(
10000
)
+
i
,
confidential
:
false
,
confidential
:
false
,
labels
:
[
list
.
label
],
labels
:
[
list
.
label
],
...
@@ -137,7 +142,7 @@ describe('List model', () => {
...
@@ -137,7 +142,7 @@ describe('List model', () => {
it
(
'
does not increase page number if issue count is less than the page size
'
,
()
=>
{
it
(
'
does not increase page number if issue count is less than the page size
'
,
()
=>
{
list
.
issues
.
push
(
new
ListIssue
({
list
.
issues
.
push
(
new
ListIssue
({
title
:
'
Testing
'
,
title
:
'
Testing
'
,
i
i
d
:
_
.
random
(
10000
),
id
:
_
.
random
(
10000
),
confidential
:
false
,
confidential
:
false
,
labels
:
[
list
.
label
],
labels
:
[
list
.
label
],
assignees
:
[],
assignees
:
[],
...
@@ -156,7 +161,7 @@ describe('List model', () => {
...
@@ -156,7 +161,7 @@ describe('List model', () => {
spyOn
(
gl
.
boardService
,
'
newIssue
'
).
and
.
returnValue
(
Promise
.
resolve
({
spyOn
(
gl
.
boardService
,
'
newIssue
'
).
and
.
returnValue
(
Promise
.
resolve
({
json
()
{
json
()
{
return
{
return
{
i
i
d
:
42
,
id
:
42
,
};
};
},
},
}));
}));
...
@@ -165,14 +170,14 @@ describe('List model', () => {
...
@@ -165,14 +170,14 @@ describe('List model', () => {
it
(
'
adds new issue to top of list
'
,
(
done
)
=>
{
it
(
'
adds new issue to top of list
'
,
(
done
)
=>
{
list
.
issues
.
push
(
new
ListIssue
({
list
.
issues
.
push
(
new
ListIssue
({
title
:
'
Testing
'
,
title
:
'
Testing
'
,
i
i
d
:
_
.
random
(
10000
),
id
:
_
.
random
(
10000
),
confidential
:
false
,
confidential
:
false
,
labels
:
[
list
.
label
],
labels
:
[
list
.
label
],
assignees
:
[],
assignees
:
[],
}));
}));
const
dummyIssue
=
new
ListIssue
({
const
dummyIssue
=
new
ListIssue
({
title
:
'
new issue
'
,
title
:
'
new issue
'
,
i
i
d
:
_
.
random
(
10000
),
id
:
_
.
random
(
10000
),
confidential
:
false
,
confidential
:
false
,
labels
:
[
list
.
label
],
labels
:
[
list
.
label
],
assignees
:
[],
assignees
:
[],
...
...
spec/javascripts/boards/mock_data.js
View file @
b4778a58
/* global BoardService */
/* eslint-disable comma-dangle, no-unused-vars, quote-props */
/* eslint-disable comma-dangle, no-unused-vars, quote-props */
const
listObj
=
{
const
listObj
=
{
...
@@ -28,19 +29,19 @@ const listObjDuplicate = {
...
@@ -28,19 +29,19 @@ const listObjDuplicate = {
const
BoardsMockData
=
{
const
BoardsMockData
=
{
'
GET
'
:
{
'
GET
'
:
{
'
/test/
issue-boards/board/1/lists
{/id}/issues
'
:
{
'
/test/
boards/1
{/id}/issues
'
:
{
issues
:
[{
issues
:
[{
title
:
'
Testing
'
,
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
iid
:
1
,
confidential
:
false
,
confidential
:
false
,
labels
:
[],
labels
:
[],
assignees
:
[],
assignees
:
[],
}],
}],
size
:
1
}
}
},
},
'
POST
'
:
{
'
POST
'
:
{
'
/test/
issue-boards/board/1/lists
{/id}
'
:
listObj
'
/test/
boards/1
{/id}
'
:
listObj
},
},
'
PUT
'
:
{
'
PUT
'
:
{
'
/test/issue-boards/board/1/lists{/id}
'
:
{}
'
/test/issue-boards/board/1/lists{/id}
'
:
{}
...
@@ -58,7 +59,22 @@ const boardsMockInterceptor = (request, next) => {
...
@@ -58,7 +59,22 @@ const boardsMockInterceptor = (request, next) => {
}));
}));
};
};
const
mockBoardService
=
(
opts
=
{})
=>
{
const
boardsEndpoint
=
opts
.
boardsEndpoint
||
'
/test/issue-boards/board
'
;
const
listsEndpoint
=
opts
.
listsEndpoint
||
'
/test/boards/1
'
;
const
bulkUpdatePath
=
opts
.
bulkUpdatePath
||
''
;
const
boardId
=
opts
.
boardId
||
'
1
'
;
return
new
BoardService
({
boardsEndpoint
,
listsEndpoint
,
bulkUpdatePath
,
boardId
,
});
};
window
.
listObj
=
listObj
;
window
.
listObj
=
listObj
;
window
.
listObjDuplicate
=
listObjDuplicate
;
window
.
listObjDuplicate
=
listObjDuplicate
;
window
.
BoardsMockData
=
BoardsMockData
;
window
.
BoardsMockData
=
BoardsMockData
;
window
.
boardsMockInterceptor
=
boardsMockInterceptor
;
window
.
boardsMockInterceptor
=
boardsMockInterceptor
;
window
.
mockBoardService
=
mockBoardService
;
spec/javascripts/boards/modal_store_spec.js
View file @
b4778a58
...
@@ -18,6 +18,7 @@ describe('Modal store', () => {
...
@@ -18,6 +18,7 @@ describe('Modal store', () => {
issue
=
new
ListIssue
({
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
iid
:
1
,
confidential
:
false
,
confidential
:
false
,
labels
:
[],
labels
:
[],
...
@@ -25,6 +26,7 @@ describe('Modal store', () => {
...
@@ -25,6 +26,7 @@ describe('Modal store', () => {
});
});
issue2
=
new
ListIssue
({
issue2
=
new
ListIssue
({
title
:
'
Testing
'
,
title
:
'
Testing
'
,
id
:
1
,
iid
:
2
,
iid
:
2
,
confidential
:
false
,
confidential
:
false
,
labels
:
[],
labels
:
[],
...
...
spec/services/boards/issues/create_service_spec.rb
View file @
b4778a58
...
@@ -8,7 +8,7 @@ describe Boards::Issues::CreateService do
...
@@ -8,7 +8,7 @@ describe Boards::Issues::CreateService do
let
(
:label
)
{
create
(
:label
,
project:
project
,
name:
'in-progress'
)
}
let
(
:label
)
{
create
(
:label
,
project:
project
,
name:
'in-progress'
)
}
let!
(
:list
)
{
create
(
:list
,
board:
board
,
label:
label
,
position:
0
)
}
let!
(
:list
)
{
create
(
:list
,
board:
board
,
label:
label
,
position:
0
)
}
subject
(
:service
)
{
described_class
.
new
(
project
,
user
,
board_id:
board
.
id
,
list_id:
list
.
id
,
title:
'New issue'
)
}
subject
(
:service
)
{
described_class
.
new
(
board
.
parent
,
project
,
user
,
board_id:
board
.
id
,
list_id:
list
.
id
,
title:
'New issue'
)
}
before
do
before
do
project
.
team
<<
[
user
,
:developer
]
project
.
team
<<
[
user
,
:developer
]
...
...
spec/services/boards/issues/move_service_spec.rb
View file @
b4778a58
...
@@ -98,7 +98,7 @@ describe Boards::Issues::MoveService do
...
@@ -98,7 +98,7 @@ describe Boards::Issues::MoveService do
issue
.
move_to_end
&&
issue
.
save!
issue
.
move_to_end
&&
issue
.
save!
end
end
params
.
merge!
(
move_after_i
id:
issue1
.
iid
,
move_before_iid:
issue2
.
i
id
)
params
.
merge!
(
move_after_i
d:
issue1
.
id
,
move_before_id:
issue2
.
id
)
described_class
.
new
(
project
,
user
,
params
).
execute
(
issue
)
described_class
.
new
(
project
,
user
,
params
).
execute
(
issue
)
...
...
spec/services/issues/update_service_spec.rb
View file @
b4778a58
...
@@ -80,7 +80,7 @@ describe Issues::UpdateService, :mailer do
...
@@ -80,7 +80,7 @@ describe Issues::UpdateService, :mailer do
issue
.
save
issue
.
save
end
end
opts
[
:move_between_i
ids
]
=
[
issue1
.
iid
,
issue2
.
i
id
]
opts
[
:move_between_i
ds
]
=
[
issue1
.
id
,
issue2
.
id
]
update_issue
(
opts
)
update_issue
(
opts
)
...
...
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