Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
c05ed1d3
Commit
c05ed1d3
authored
Sep 16, 2020
by
Florie Guibert
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Paginate issues within board list
Update fetching of issues in GraphQL to paginate issues in lists
parent
2e1b16f4
Changes
33
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
739 additions
and
161 deletions
+739
-161
app/assets/javascripts/boards/boards_util.js
app/assets/javascripts/boards/boards_util.js
+18
-2
app/assets/javascripts/boards/components/board_column.vue
app/assets/javascripts/boards/components/board_column.vue
+4
-4
app/assets/javascripts/boards/components/board_list.vue
app/assets/javascripts/boards/components/board_list.vue
+7
-7
app/assets/javascripts/boards/components/board_list_header.vue
...ssets/javascripts/boards/components/board_list_header.vue
+0
-1
app/assets/javascripts/boards/components/board_list_new.vue
app/assets/javascripts/boards/components/board_list_new.vue
+166
-0
app/assets/javascripts/boards/index.js
app/assets/javascripts/boards/index.js
+5
-1
app/assets/javascripts/boards/queries/board_lists.query.graphql
...sets/javascripts/boards/queries/board_lists.query.graphql
+28
-0
app/assets/javascripts/boards/queries/group_board.query.graphql
...sets/javascripts/boards/queries/group_board.query.graphql
+0
-13
app/assets/javascripts/boards/queries/lists_issues.query.graphql
...ets/javascripts/boards/queries/lists_issues.query.graphql
+22
-6
app/assets/javascripts/boards/queries/project_board.query.graphql
...ts/javascripts/boards/queries/project_board.query.graphql
+0
-13
app/assets/javascripts/boards/stores/actions.js
app/assets/javascripts/boards/stores/actions.js
+21
-19
app/assets/javascripts/boards/stores/mutation_types.js
app/assets/javascripts/boards/stores/mutation_types.js
+1
-0
app/assets/javascripts/boards/stores/mutations.js
app/assets/javascripts/boards/stores/mutations.js
+17
-7
app/assets/javascripts/boards/stores/state.js
app/assets/javascripts/boards/stores/state.js
+2
-0
ee/app/assets/javascripts/boards/components/board_list_header.vue
...ssets/javascripts/boards/components/board_list_header.vue
+1
-11
ee/app/assets/javascripts/boards/components/epics_swimlanes.vue
.../assets/javascripts/boards/components/epics_swimlanes.vue
+37
-14
ee/app/assets/javascripts/boards/components/issues_lane_list.vue
...assets/javascripts/boards/components/issues_lane_list.vue
+6
-2
ee/app/assets/javascripts/boards/components/sidebar/board_sidebar_epic_select.vue
...s/boards/components/sidebar/board_sidebar_epic_select.vue
+2
-2
ee/app/assets/javascripts/boards/queries/epics_swimlanes.query.graphql
.../javascripts/boards/queries/epics_swimlanes.query.graphql
+3
-3
ee/app/assets/javascripts/boards/stores/actions.js
ee/app/assets/javascripts/boards/stores/actions.js
+41
-32
ee/app/assets/javascripts/boards/stores/mutation_types.js
ee/app/assets/javascripts/boards/stores/mutation_types.js
+2
-0
ee/app/assets/javascripts/boards/stores/mutations.js
ee/app/assets/javascripts/boards/stores/mutations.js
+27
-2
ee/spec/features/boards/swimlanes/epics_swimlanes_drag_issue_spec.rb
...tures/boards/swimlanes/epics_swimlanes_drag_issue_spec.rb
+1
-0
ee/spec/features/boards/swimlanes/epics_swimlanes_filtering_spec.rb
...atures/boards/swimlanes/epics_swimlanes_filtering_spec.rb
+18
-5
ee/spec/frontend/boards/components/epics_swimlanes_spec.js
ee/spec/frontend/boards/components/epics_swimlanes_spec.js
+12
-0
ee/spec/frontend/boards/stores/actions_spec.js
ee/spec/frontend/boards/stores/actions_spec.js
+3
-3
ee/spec/frontend/boards/stores/mutations_spec.js
ee/spec/frontend/boards/stores/mutations_spec.js
+16
-1
locale/gitlab.pot
locale/gitlab.pot
+3
-3
spec/features/labels_hierarchy_spec.rb
spec/features/labels_hierarchy_spec.rb
+1
-0
spec/frontend/boards/board_list_new_spec.js
spec/frontend/boards/board_list_new_spec.js
+234
-0
spec/frontend/boards/board_list_spec.js
spec/frontend/boards/board_list_spec.js
+1
-2
spec/frontend/boards/stores/actions_spec.js
spec/frontend/boards/stores/actions_spec.js
+29
-7
spec/frontend/boards/stores/mutations_spec.js
spec/frontend/boards/stores/mutations_spec.js
+11
-1
No files found.
app/assets/javascripts/boards/boards_util.js
View file @
c05ed1d3
...
@@ -17,9 +17,15 @@ export function formatIssue(issue) {
...
@@ -17,9 +17,15 @@ export function formatIssue(issue) {
export
function
formatListIssues
(
listIssues
)
{
export
function
formatListIssues
(
listIssues
)
{
const
issues
=
{};
const
issues
=
{};
let
listIssuesCount
;
const
listData
=
listIssues
.
nodes
.
reduce
((
map
,
list
)
=>
{
const
listData
=
listIssues
.
nodes
.
reduce
((
map
,
list
)
=>
{
const
sortedIssues
=
sortBy
(
list
.
issues
.
nodes
,
'
relativePosition
'
);
listIssuesCount
=
list
.
issues
.
count
;
let
sortedIssues
=
list
.
issues
.
edges
.
map
(
issueNode
=>
({
...
issueNode
.
node
,
}));
sortedIssues
=
sortBy
(
sortedIssues
,
'
relativePosition
'
);
return
{
return
{
...
map
,
...
map
,
[
list
.
id
]:
sortedIssues
.
map
(
i
=>
{
[
list
.
id
]:
sortedIssues
.
map
(
i
=>
{
...
@@ -39,7 +45,17 @@ export function formatListIssues(listIssues) {
...
@@ -39,7 +45,17 @@ export function formatListIssues(listIssues) {
};
};
},
{});
},
{});
return
{
listData
,
issues
};
return
{
listData
,
issues
,
listIssuesCount
};
}
export
function
formatListsPageInfo
(
lists
)
{
const
listData
=
lists
.
nodes
.
reduce
((
map
,
list
)
=>
{
return
{
...
map
,
[
list
.
id
]:
list
.
issues
.
pageInfo
,
};
},
{});
return
listData
;
}
}
export
function
fullBoardId
(
boardId
)
{
export
function
fullBoardId
(
boardId
)
{
...
...
app/assets/javascripts/boards/components/board_column.vue
View file @
c05ed1d3
...
@@ -7,6 +7,7 @@ import Tooltip from '~/vue_shared/directives/tooltip';
...
@@ -7,6 +7,7 @@ import Tooltip from '~/vue_shared/directives/tooltip';
import
EmptyComponent
from
'
~/vue_shared/components/empty_component
'
;
import
EmptyComponent
from
'
~/vue_shared/components/empty_component
'
;
import
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
BoardList
from
'
./board_list.vue
'
;
import
BoardList
from
'
./board_list.vue
'
;
import
BoardListNew
from
'
./board_list_new.vue
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
eventHub
from
'
../eventhub
'
;
import
eventHub
from
'
../eventhub
'
;
import
{
getBoardSortableDefaultOptions
,
sortableEnd
}
from
'
../mixins/sortable_default_options
'
;
import
{
getBoardSortableDefaultOptions
,
sortableEnd
}
from
'
../mixins/sortable_default_options
'
;
...
@@ -16,7 +17,7 @@ export default {
...
@@ -16,7 +17,7 @@ export default {
components
:
{
components
:
{
BoardPromotionState
:
EmptyComponent
,
BoardPromotionState
:
EmptyComponent
,
BoardListHeader
,
BoardListHeader
,
BoardList
,
BoardList
:
gon
.
features
?.
graphqlBoardLists
?
BoardListNew
:
BoardList
,
},
},
directives
:
{
directives
:
{
Tooltip
,
Tooltip
,
...
@@ -72,7 +73,7 @@ export default {
...
@@ -72,7 +73,7 @@ export default {
filter
:
{
filter
:
{
handler
()
{
handler
()
{
if
(
this
.
shouldFetchIssues
)
{
if
(
this
.
shouldFetchIssues
)
{
this
.
fetchIssuesForList
(
this
.
list
.
id
);
this
.
fetchIssuesForList
(
{
listId
:
this
.
list
.
id
}
);
}
else
{
}
else
{
this
.
list
.
page
=
1
;
this
.
list
.
page
=
1
;
this
.
list
.
getIssues
(
true
).
catch
(()
=>
{
this
.
list
.
getIssues
(
true
).
catch
(()
=>
{
...
@@ -85,7 +86,7 @@ export default {
...
@@ -85,7 +86,7 @@ export default {
},
},
mounted
()
{
mounted
()
{
if
(
this
.
shouldFetchIssues
)
{
if
(
this
.
shouldFetchIssues
)
{
this
.
fetchIssuesForList
(
this
.
list
.
id
);
this
.
fetchIssuesForList
(
{
listId
:
this
.
list
.
id
}
);
}
}
const
instance
=
this
;
const
instance
=
this
;
...
@@ -144,7 +145,6 @@ export default {
...
@@ -144,7 +145,6 @@ export default {
:disabled=
"disabled"
:disabled=
"disabled"
:issues=
"listIssues"
:issues=
"listIssues"
:list=
"list"
:list=
"list"
:loading=
"list.loading"
/>
/>
<!-- Will be only available in EE -->
<!-- Will be only available in EE -->
...
...
app/assets/javascripts/boards/components/board_list.vue
View file @
c05ed1d3
...
@@ -14,6 +14,8 @@ import {
...
@@ -14,6 +14,8 @@ import {
sortableEnd
,
sortableEnd
,
}
from
'
../mixins/sortable_default_options
'
;
}
from
'
../mixins/sortable_default_options
'
;
// This component is being replaced in favor of './board_list_new.vue' for GraphQL boards
if
(
gon
.
features
&&
gon
.
features
.
multiSelectBoard
)
{
if
(
gon
.
features
&&
gon
.
features
.
multiSelectBoard
)
{
Sortable
.
mount
(
new
MultiDrag
());
Sortable
.
mount
(
new
MultiDrag
());
}
}
...
@@ -39,10 +41,6 @@ export default {
...
@@ -39,10 +41,6 @@ export default {
type
:
Array
,
type
:
Array
,
required
:
true
,
required
:
true
,
},
},
loading
:
{
type
:
Boolean
,
required
:
true
,
},
},
},
data
()
{
data
()
{
return
{
return
{
...
@@ -62,6 +60,9 @@ export default {
...
@@ -62,6 +60,9 @@ export default {
issuesSizeExceedsMax
()
{
issuesSizeExceedsMax
()
{
return
this
.
list
.
maxIssueCount
>
0
&&
this
.
list
.
issuesSize
>
this
.
list
.
maxIssueCount
;
return
this
.
list
.
maxIssueCount
>
0
&&
this
.
list
.
issuesSize
>
this
.
list
.
maxIssueCount
;
},
},
loading
()
{
return
this
.
list
.
loading
;
},
},
},
watch
:
{
watch
:
{
filters
:
{
filters
:
{
...
@@ -72,7 +73,6 @@ export default {
...
@@ -72,7 +73,6 @@ export default {
deep
:
true
,
deep
:
true
,
},
},
issues
()
{
issues
()
{
if
(
this
.
glFeatures
.
graphqlBoardLists
)
return
;
this
.
$nextTick
(()
=>
{
this
.
$nextTick
(()
=>
{
if
(
if
(
this
.
scrollHeight
()
<=
this
.
listHeight
()
&&
this
.
scrollHeight
()
<=
this
.
listHeight
()
&&
...
@@ -98,6 +98,8 @@ export default {
...
@@ -98,6 +98,8 @@ export default {
eventHub
.
$on
(
`scroll-board-list-
${
this
.
list
.
id
}
`
,
this
.
scrollToTop
);
eventHub
.
$on
(
`scroll-board-list-
${
this
.
list
.
id
}
`
,
this
.
scrollToTop
);
},
},
mounted
()
{
mounted
()
{
// TODO: Use Draggable in ./board_list_new.vue to drag & drop issue
// https://gitlab.com/gitlab-org/gitlab/-/issues/218164
const
multiSelectOpts
=
{};
const
multiSelectOpts
=
{};
if
(
gon
.
features
&&
gon
.
features
.
multiSelectBoard
)
{
if
(
gon
.
features
&&
gon
.
features
.
multiSelectBoard
)
{
multiSelectOpts
.
multiDrag
=
true
;
multiSelectOpts
.
multiDrag
=
true
;
...
@@ -403,8 +405,6 @@ export default {
...
@@ -403,8 +405,6 @@ export default {
this
.
showIssueForm
=
!
this
.
showIssueForm
;
this
.
showIssueForm
=
!
this
.
showIssueForm
;
},
},
onScroll
()
{
onScroll
()
{
if
(
this
.
glFeatures
.
graphqlBoardLists
)
return
;
if
(
!
this
.
list
.
loadingMore
&&
this
.
scrollTop
()
>
this
.
scrollHeight
()
-
this
.
scrollOffset
)
{
if
(
!
this
.
list
.
loadingMore
&&
this
.
scrollTop
()
>
this
.
scrollHeight
()
-
this
.
scrollOffset
)
{
this
.
loadNextPage
();
this
.
loadNextPage
();
}
}
...
...
app/assets/javascripts/boards/components/board_list_header.vue
View file @
c05ed1d3
...
@@ -176,7 +176,6 @@ export default {
...
@@ -176,7 +176,6 @@ export default {
<header
<header
:class=
"
{
:class=
"
{
'has-border': list.label
&&
list.label.color,
'has-border': list.label
&&
list.label.color,
'gl-relative': list.isExpanded,
'gl-h-full': !list.isExpanded,
'gl-h-full': !list.isExpanded,
'board-inner gl-rounded-top-left-base gl-rounded-top-right-base': isSwimlanesHeader,
'board-inner gl-rounded-top-left-base gl-rounded-top-right-base': isSwimlanesHeader,
}"
}"
...
...
app/assets/javascripts/boards/components/board_list_new.vue
0 → 100644
View file @
c05ed1d3
<
script
>
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
BoardNewIssue
from
'
./board_new_issue.vue
'
;
import
BoardCard
from
'
./board_card.vue
'
;
import
eventHub
from
'
../eventhub
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
{
sprintf
,
__
}
from
'
~/locale
'
;
import
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
export
default
{
name
:
'
BoardList
'
,
components
:
{
BoardCard
,
BoardNewIssue
,
GlLoadingIcon
,
},
mixins
:
[
glFeatureFlagMixin
()],
props
:
{
disabled
:
{
type
:
Boolean
,
required
:
true
,
},
list
:
{
type
:
Object
,
required
:
true
,
},
issues
:
{
type
:
Array
,
required
:
true
,
},
},
data
()
{
return
{
scrollOffset
:
250
,
filters
:
boardsStore
.
state
.
filters
,
showCount
:
false
,
showIssueForm
:
false
,
};
},
computed
:
{
...
mapState
([
'
pageInfoByListId
'
,
'
listsFlags
'
]),
paginatedIssueText
()
{
return
sprintf
(
__
(
'
Showing %{pageSize} of %{total} issues
'
),
{
pageSize
:
this
.
issues
.
length
,
total
:
this
.
list
.
issuesSize
,
});
},
issuesSizeExceedsMax
()
{
return
this
.
list
.
maxIssueCount
>
0
&&
this
.
list
.
issuesSize
>
this
.
list
.
maxIssueCount
;
},
hasNextPage
()
{
return
this
.
pageInfoByListId
[
this
.
list
.
id
].
hasNextPage
;
},
loading
()
{
return
this
.
listsFlags
[
this
.
list
.
id
]?.
isLoading
;
},
},
watch
:
{
filters
:
{
handler
()
{
this
.
list
.
loadingMore
=
false
;
this
.
$refs
.
list
.
scrollTop
=
0
;
},
deep
:
true
,
},
issues
()
{
this
.
$nextTick
(()
=>
{
this
.
showCount
=
this
.
scrollHeight
()
>
Math
.
ceil
(
this
.
listHeight
());
});
},
},
created
()
{
eventHub
.
$on
(
`toggle-issue-form-
${
this
.
list
.
id
}
`
,
this
.
toggleForm
);
eventHub
.
$on
(
`scroll-board-list-
${
this
.
list
.
id
}
`
,
this
.
scrollToTop
);
},
mounted
()
{
// Scroll event on list to load more
this
.
$refs
.
list
.
addEventListener
(
'
scroll
'
,
this
.
onScroll
);
},
beforeDestroy
()
{
eventHub
.
$off
(
`toggle-issue-form-
${
this
.
list
.
id
}
`
,
this
.
toggleForm
);
eventHub
.
$off
(
`scroll-board-list-
${
this
.
list
.
id
}
`
,
this
.
scrollToTop
);
this
.
$refs
.
list
.
removeEventListener
(
'
scroll
'
,
this
.
onScroll
);
},
methods
:
{
...
mapActions
([
'
fetchIssuesForList
'
]),
listHeight
()
{
return
this
.
$refs
.
list
.
getBoundingClientRect
().
height
;
},
scrollHeight
()
{
return
this
.
$refs
.
list
.
scrollHeight
;
},
scrollTop
()
{
return
this
.
$refs
.
list
.
scrollTop
+
this
.
listHeight
();
},
scrollToTop
()
{
this
.
$refs
.
list
.
scrollTop
=
0
;
},
loadNextPage
()
{
const
loadingDone
=
()
=>
{
this
.
list
.
loadingMore
=
false
;
};
this
.
list
.
loadingMore
=
true
;
this
.
fetchIssuesForList
({
listId
:
this
.
list
.
id
,
fetchNext
:
true
})
.
then
(
loadingDone
)
.
catch
(
loadingDone
);
},
toggleForm
()
{
this
.
showIssueForm
=
!
this
.
showIssueForm
;
},
onScroll
()
{
window
.
requestAnimationFrame
(()
=>
{
if
(
!
this
.
list
.
loadingMore
&&
this
.
scrollTop
()
>
this
.
scrollHeight
()
-
this
.
scrollOffset
&&
this
.
hasNextPage
)
{
this
.
loadNextPage
();
}
});
},
},
};
</
script
>
<
template
>
<div
v-show=
"list.isExpanded"
class=
"board-list-component gl-relative gl-h-full gl-display-flex gl-flex-direction-column"
data-qa-selector=
"board_list_cards_area"
>
<div
v-if=
"loading"
class=
"gl-mt-4 gl-text-center"
:aria-label=
"__('Loading issues')"
data-testid=
"board_list_loading"
>
<gl-loading-icon
/>
</div>
<board-new-issue
v-if=
"list.type !== 'closed' && showIssueForm"
:list=
"list"
/>
<ul
v-show=
"!loading"
ref=
"list"
:data-board=
"list.id"
:data-board-type=
"list.type"
:class=
"
{ 'bg-danger-100': issuesSizeExceedsMax }"
class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2 js-board-list"
>
<board-card
v-for=
"(issue, index) in issues"
ref=
"issue"
:key=
"issue.id"
:index=
"index"
:list=
"list"
:issue=
"issue"
:disabled=
"disabled"
/>
<li
v-if=
"showCount"
class=
"board-list-count gl-text-center"
data-issue-id=
"-1"
>
<gl-loading-icon
v-show=
"list.loadingMore"
label=
"Loading more issues"
/>
<span
v-if=
"issues.length === list.issuesSize"
>
{{
__
(
'
Showing all issues
'
)
}}
</span>
<span
v-else
>
{{
paginatedIssueText
}}
</span>
</li>
</ul>
</div>
</
template
>
app/assets/javascripts/boards/index.js
View file @
c05ed1d3
...
@@ -161,6 +161,7 @@ export default () => {
...
@@ -161,6 +161,7 @@ export default () => {
'
fetchEpicsSwimlanes
'
,
'
fetchEpicsSwimlanes
'
,
'
resetIssues
'
,
'
resetIssues
'
,
'
resetEpics
'
,
'
resetEpics
'
,
'
fetchLists
'
,
]),
]),
initialBoardLoad
()
{
initialBoardLoad
()
{
boardsStore
boardsStore
...
@@ -183,7 +184,10 @@ export default () => {
...
@@ -183,7 +184,10 @@ export default () => {
this
.
setFilters
(
convertObjectPropsToCamelCase
(
urlParamsToObject
(
window
.
location
.
search
)));
this
.
setFilters
(
convertObjectPropsToCamelCase
(
urlParamsToObject
(
window
.
location
.
search
)));
if
(
gon
.
features
.
boardsWithSwimlanes
&&
this
.
isShowingEpicsSwimlanes
)
{
if
(
gon
.
features
.
boardsWithSwimlanes
&&
this
.
isShowingEpicsSwimlanes
)
{
this
.
resetEpics
();
this
.
resetEpics
();
this
.
fetchEpicsSwimlanes
({
withLists
:
false
});
this
.
resetIssues
();
this
.
fetchEpicsSwimlanes
({});
}
else
if
(
gon
.
features
.
graphqlBoardLists
&&
!
this
.
isShowingEpicsSwimlanes
)
{
this
.
fetchLists
();
this
.
resetIssues
();
this
.
resetIssues
();
}
}
},
},
...
...
app/assets/javascripts/boards/queries/board_lists.query.graphql
0 → 100644
View file @
c05ed1d3
#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
query
ListIssues
(
$fullPath
:
ID
!
$boardId
:
ID
!
$filters
:
BoardIssueInput
$isGroup
:
Boolean
=
false
$isProject
:
Boolean
=
false
)
{
group
(
fullPath
:
$fullPath
)
@include
(
if
:
$isGroup
)
{
board
(
id
:
$boardId
)
{
lists
(
issueFilters
:
$filters
)
{
nodes
{
...
BoardListFragment
}
}
}
}
project
(
fullPath
:
$fullPath
)
@include
(
if
:
$isProject
)
{
board
(
id
:
$boardId
)
{
lists
(
issueFilters
:
$filters
)
{
nodes
{
...
BoardListFragment
}
}
}
}
}
app/assets/javascripts/boards/queries/group_board.query.graphql
deleted
100644 → 0
View file @
2e1b16f4
#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
query
GroupBoard
(
$fullPath
:
ID
!,
$boardId
:
ID
!)
{
group
(
fullPath
:
$fullPath
)
{
board
(
id
:
$boardId
)
{
lists
{
nodes
{
...
BoardListFragment
}
}
}
}
}
app/assets/javascripts/boards/queries/lists_issues.query.graphql
View file @
c05ed1d3
...
@@ -7,15 +7,24 @@ query ListIssues(
...
@@ -7,15 +7,24 @@ query ListIssues(
$filters
:
BoardIssueInput
$filters
:
BoardIssueInput
$isGroup
:
Boolean
=
false
$isGroup
:
Boolean
=
false
$isProject
:
Boolean
=
false
$isProject
:
Boolean
=
false
$after
:
String
$first
:
Int
)
{
)
{
group
(
fullPath
:
$fullPath
)
@include
(
if
:
$isGroup
)
{
group
(
fullPath
:
$fullPath
)
@include
(
if
:
$isGroup
)
{
board
(
id
:
$boardId
)
{
board
(
id
:
$boardId
)
{
lists
(
id
:
$id
)
{
lists
(
id
:
$id
)
{
nodes
{
nodes
{
id
id
issues
(
filters
:
$filters
)
{
issues
(
first
:
$first
,
filters
:
$filters
,
after
:
$after
)
{
nodes
{
count
...
IssueNode
edges
{
node
{
...
IssueNode
}
}
pageInfo
{
endCursor
hasNextPage
}
}
}
}
}
}
...
@@ -27,9 +36,16 @@ query ListIssues(
...
@@ -27,9 +36,16 @@ query ListIssues(
lists
(
id
:
$id
)
{
lists
(
id
:
$id
)
{
nodes
{
nodes
{
id
id
issues
(
filters
:
$filters
)
{
issues
(
first
:
$first
,
filters
:
$filters
,
after
:
$after
)
{
nodes
{
count
...
IssueNode
edges
{
node
{
...
IssueNode
}
}
pageInfo
{
endCursor
hasNextPage
}
}
}
}
}
}
...
...
app/assets/javascripts/boards/queries/project_board.query.graphql
deleted
100644 → 0
View file @
2e1b16f4
#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
query
ProjectBoard
(
$fullPath
:
ID
!,
$boardId
:
ID
!)
{
project
(
fullPath
:
$fullPath
)
{
board
(
id
:
$boardId
)
{
lists
{
nodes
{
...
BoardListFragment
}
}
}
}
}
app/assets/javascripts/boards/stores/actions.js
View file @
c05ed1d3
...
@@ -3,16 +3,15 @@ import { sortBy, pick } from 'lodash';
...
@@ -3,16 +3,15 @@ import { sortBy, pick } from 'lodash';
import
createFlash
from
'
~/flash
'
;
import
createFlash
from
'
~/flash
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
create
DefaultClient
from
'
~/lib/graphql
'
;
import
create
GqClient
,
{
fetchPolicies
}
from
'
~/lib/graphql
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
BoardType
,
ListType
,
inactiveId
}
from
'
~/boards/constants
'
;
import
{
BoardType
,
ListType
,
inactiveId
}
from
'
~/boards/constants
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
{
formatListIssues
,
fullBoardId
}
from
'
../boards_util
'
;
import
{
formatListIssues
,
fullBoardId
,
formatListsPageInfo
}
from
'
../boards_util
'
;
import
boardStore
from
'
~/boards/stores/boards_store
'
;
import
boardStore
from
'
~/boards/stores/boards_store
'
;
import
listsIssuesQuery
from
'
../queries/lists_issues.query.graphql
'
;
import
listsIssuesQuery
from
'
../queries/lists_issues.query.graphql
'
;
import
projectBoardQuery
from
'
../queries/project_board.query.graphql
'
;
import
boardListsQuery
from
'
../queries/board_lists.query.graphql
'
;
import
groupBoardQuery
from
'
../queries/group_board.query.graphql
'
;
import
createBoardListMutation
from
'
../queries/board_list_create.mutation.graphql
'
;
import
createBoardListMutation
from
'
../queries/board_list_create.mutation.graphql
'
;
import
updateBoardListMutation
from
'
../queries/board_list_update.mutation.graphql
'
;
import
updateBoardListMutation
from
'
../queries/board_list_update.mutation.graphql
'
;
import
issueMoveListMutation
from
'
../queries/issue_move_list.mutation.graphql
'
;
import
issueMoveListMutation
from
'
../queries/issue_move_list.mutation.graphql
'
;
...
@@ -22,7 +21,12 @@ const notImplemented = () => {
...
@@ -22,7 +21,12 @@ const notImplemented = () => {
throw
new
Error
(
'
Not implemented!
'
);
throw
new
Error
(
'
Not implemented!
'
);
};
};
export
const
gqlClient
=
createDefaultClient
();
export
const
gqlClient
=
createGqClient
(
{},
{
fetchPolicy
:
fetchPolicies
.
NO_CACHE
,
},
);
export
default
{
export
default
{
setInitialBoardData
:
({
commit
},
data
)
=>
{
setInitialBoardData
:
({
commit
},
data
)
=>
{
...
@@ -50,27 +54,20 @@ export default {
...
@@ -50,27 +54,20 @@ export default {
},
},
fetchLists
:
({
commit
,
state
,
dispatch
})
=>
{
fetchLists
:
({
commit
,
state
,
dispatch
})
=>
{
const
{
endpoints
,
boardType
}
=
state
;
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
const
{
fullPath
,
boardId
}
=
endpoints
;
const
{
fullPath
,
boardId
}
=
endpoints
;
let
query
;
if
(
boardType
===
BoardType
.
group
)
{
query
=
groupBoardQuery
;
}
else
if
(
boardType
===
BoardType
.
project
)
{
query
=
projectBoardQuery
;
}
else
{
createFlash
(
__
(
'
Invalid board
'
));
return
Promise
.
reject
();
}
const
variables
=
{
const
variables
=
{
fullPath
,
fullPath
,
boardId
:
fullBoardId
(
boardId
),
boardId
:
fullBoardId
(
boardId
),
filters
:
filterParams
,
isGroup
:
boardType
===
BoardType
.
group
,
isProject
:
boardType
===
BoardType
.
project
,
};
};
return
gqlClient
return
gqlClient
.
query
({
.
query
({
query
,
query
:
boardListsQuery
,
variables
,
variables
,
})
})
.
then
(({
data
})
=>
{
.
then
(({
data
})
=>
{
...
@@ -197,7 +194,9 @@ export default {
...
@@ -197,7 +194,9 @@ export default {
notImplemented
();
notImplemented
();
},
},
fetchIssuesForList
:
({
state
,
commit
},
listId
)
=>
{
fetchIssuesForList
:
({
state
,
commit
},
{
listId
,
fetchNext
=
false
})
=>
{
commit
(
types
.
REQUEST_ISSUES_FOR_LIST
,
{
listId
,
fetchNext
});
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
const
{
fullPath
,
boardId
}
=
endpoints
;
const
{
fullPath
,
boardId
}
=
endpoints
;
...
@@ -208,6 +207,8 @@ export default {
...
@@ -208,6 +207,8 @@ export default {
filters
:
filterParams
,
filters
:
filterParams
,
isGroup
:
boardType
===
BoardType
.
group
,
isGroup
:
boardType
===
BoardType
.
group
,
isProject
:
boardType
===
BoardType
.
project
,
isProject
:
boardType
===
BoardType
.
project
,
first
:
20
,
after
:
fetchNext
?
state
.
pageInfoByListId
[
listId
].
endCursor
:
undefined
,
};
};
return
gqlClient
return
gqlClient
...
@@ -221,7 +222,8 @@ export default {
...
@@ -221,7 +222,8 @@ export default {
.
then
(({
data
})
=>
{
.
then
(({
data
})
=>
{
const
{
lists
}
=
data
[
boardType
]?.
board
;
const
{
lists
}
=
data
[
boardType
]?.
board
;
const
listIssues
=
formatListIssues
(
lists
);
const
listIssues
=
formatListIssues
(
lists
);
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
,
{
listIssues
,
listId
});
const
listPageInfo
=
formatListsPageInfo
(
lists
);
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
,
{
listIssues
,
listPageInfo
,
listId
});
})
})
.
catch
(()
=>
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_FAILURE
,
listId
));
.
catch
(()
=>
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_FAILURE
,
listId
));
},
},
...
...
app/assets/javascripts/boards/stores/mutation_types.js
View file @
c05ed1d3
...
@@ -12,6 +12,7 @@ export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
...
@@ -12,6 +12,7 @@ export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
export
const
REQUEST_REMOVE_LIST
=
'
REQUEST_REMOVE_LIST
'
;
export
const
REQUEST_REMOVE_LIST
=
'
REQUEST_REMOVE_LIST
'
;
export
const
RECEIVE_REMOVE_LIST_SUCCESS
=
'
RECEIVE_REMOVE_LIST_SUCCESS
'
;
export
const
RECEIVE_REMOVE_LIST_SUCCESS
=
'
RECEIVE_REMOVE_LIST_SUCCESS
'
;
export
const
RECEIVE_REMOVE_LIST_ERROR
=
'
RECEIVE_REMOVE_LIST_ERROR
'
;
export
const
RECEIVE_REMOVE_LIST_ERROR
=
'
RECEIVE_REMOVE_LIST_ERROR
'
;
export
const
REQUEST_ISSUES_FOR_LIST
=
'
REQUEST_ISSUES_FOR_LIST
'
;
export
const
RECEIVE_ISSUES_FOR_LIST_FAILURE
=
'
RECEIVE_ISSUES_FOR_LIST_FAILURE
'
;
export
const
RECEIVE_ISSUES_FOR_LIST_FAILURE
=
'
RECEIVE_ISSUES_FOR_LIST_FAILURE
'
;
export
const
RECEIVE_ISSUES_FOR_LIST_SUCCESS
=
'
RECEIVE_ISSUES_FOR_LIST_SUCCESS
'
;
export
const
RECEIVE_ISSUES_FOR_LIST_SUCCESS
=
'
RECEIVE_ISSUES_FOR_LIST_SUCCESS
'
;
export
const
REQUEST_ADD_ISSUE
=
'
REQUEST_ADD_ISSUE
'
;
export
const
REQUEST_ADD_ISSUE
=
'
REQUEST_ADD_ISSUE
'
;
...
...
app/assets/javascripts/boards/stores/mutations.js
View file @
c05ed1d3
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
{
sortBy
,
pull
}
from
'
lodash
'
;
import
{
sortBy
,
pull
,
union
}
from
'
lodash
'
;
import
{
formatIssue
,
moveIssueListHelper
}
from
'
../boards_util
'
;
import
{
formatIssue
,
moveIssueListHelper
}
from
'
../boards_util
'
;
import
*
as
mutationTypes
from
'
./mutation_types
'
;
import
*
as
mutationTypes
from
'
./mutation_types
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
s__
}
from
'
~/locale
'
;
...
@@ -99,20 +99,30 @@ export default {
...
@@ -99,20 +99,30 @@ export default {
notImplemented
();
notImplemented
();
},
},
[
mutationTypes
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
]:
(
state
,
{
listIssues
,
listId
})
=>
{
[
mutationTypes
.
REQUEST_ISSUES_FOR_LIST
]:
(
state
,
{
listId
,
fetchNext
})
=>
{
Vue
.
set
(
state
.
listsFlags
,
listId
,
{
[
fetchNext
?
'
isLoadingMore
'
:
'
isLoading
'
]:
true
});
},
[
mutationTypes
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
]:
(
state
,
{
listIssues
,
listPageInfo
,
listId
},
)
=>
{
const
{
listData
,
issues
}
=
listIssues
;
const
{
listData
,
issues
}
=
listIssues
;
Vue
.
set
(
state
,
'
issues
'
,
{
...
state
.
issues
,
...
issues
});
Vue
.
set
(
state
,
'
issues
'
,
{
...
state
.
issues
,
...
issues
});
Vue
.
set
(
state
.
issuesByListId
,
listId
,
listData
[
listId
]);
Vue
.
set
(
const
listIndex
=
state
.
boardLists
.
findIndex
(
l
=>
l
.
id
===
listId
);
state
.
issuesByListId
,
Vue
.
set
(
state
.
boardLists
[
listIndex
],
'
loading
'
,
false
);
listId
,
union
(
state
.
issuesByListId
[
listId
]
||
[],
listData
[
listId
]),
);
Vue
.
set
(
state
.
pageInfoByListId
,
listId
,
listPageInfo
[
listId
]);
Vue
.
set
(
state
.
listsFlags
,
listId
,
{
isLoading
:
false
,
isLoadingMore
:
false
});
},
},
[
mutationTypes
.
RECEIVE_ISSUES_FOR_LIST_FAILURE
]:
(
state
,
listId
)
=>
{
[
mutationTypes
.
RECEIVE_ISSUES_FOR_LIST_FAILURE
]:
(
state
,
listId
)
=>
{
state
.
error
=
s__
(
state
.
error
=
s__
(
'
Boards|An error occurred while fetching the board issues. Please reload the page.
'
,
'
Boards|An error occurred while fetching the board issues. Please reload the page.
'
,
);
);
const
listIndex
=
state
.
boardLists
.
findIndex
(
l
=>
l
.
id
===
listId
);
Vue
.
set
(
state
.
listsFlags
,
listId
,
{
isLoading
:
false
,
isLoadingMore
:
false
});
Vue
.
set
(
state
.
boardLists
[
listIndex
],
'
loading
'
,
false
);
},
},
[
mutationTypes
.
RESET_ISSUES
]:
state
=>
{
[
mutationTypes
.
RESET_ISSUES
]:
state
=>
{
...
...
app/assets/javascripts/boards/stores/state.js
View file @
c05ed1d3
...
@@ -9,7 +9,9 @@ export default () => ({
...
@@ -9,7 +9,9 @@ export default () => ({
activeId
:
inactiveId
,
activeId
:
inactiveId
,
sidebarType
:
''
,
sidebarType
:
''
,
boardLists
:
[],
boardLists
:
[],
listsFlags
:
{},
issuesByListId
:
{},
issuesByListId
:
{},
pageInfoByListId
:
{},
issues
:
{},
issues
:
{},
filterParams
:
{},
filterParams
:
{},
error
:
undefined
,
error
:
undefined
,
...
...
ee/app/assets/javascripts/boards/components/board_list_header.vue
View file @
c05ed1d3
<
script
>
<
script
>
import
{
mapState
,
mapActions
,
mapGetters
}
from
'
vuex
'
;
import
{
mapState
,
mapActions
,
mapGetters
}
from
'
vuex
'
;
import
BoardListHeaderFoss
from
'
~/boards/components/board_list_header.vue
'
;
import
BoardListHeaderFoss
from
'
~/boards/components/board_list_header.vue
'
;
import
{
__
,
sprintf
,
s__
,
n__
}
from
'
~/locale
'
;
import
{
__
,
sprintf
,
s__
}
from
'
~/locale
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
{
inactiveId
,
LIST
}
from
'
~/boards/constants
'
;
import
{
inactiveId
,
LIST
}
from
'
~/boards/constants
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
...
@@ -16,13 +16,6 @@ export default {
...
@@ -16,13 +16,6 @@ export default {
computed
:
{
computed
:
{
...
mapState
([
'
activeId
'
,
'
issuesByListId
'
]),
...
mapState
([
'
activeId
'
,
'
issuesByListId
'
]),
...
mapGetters
([
'
isSwimlanesOn
'
]),
...
mapGetters
([
'
isSwimlanesOn
'
]),
issuesCount
()
{
if
(
this
.
isSwimlanesOn
)
{
return
this
.
issuesByListId
[
this
.
list
.
id
]
?
this
.
issuesByListId
[
this
.
list
.
id
].
length
:
0
;
}
return
this
.
list
.
issuesSize
;
},
issuesTooltip
()
{
issuesTooltip
()
{
const
{
maxIssueCount
}
=
this
.
list
;
const
{
maxIssueCount
}
=
this
.
list
;
...
@@ -36,9 +29,6 @@ export default {
...
@@ -36,9 +29,6 @@ export default {
// TODO: Remove this pattern.
// TODO: Remove this pattern.
return
BoardListHeaderFoss
.
computed
.
issuesTooltip
.
call
(
this
);
return
BoardListHeaderFoss
.
computed
.
issuesTooltip
.
call
(
this
);
},
},
issuesTooltipLabel
()
{
return
n__
(
`%d issue`
,
`%d issues`
,
this
.
issuesCount
);
},
weightCountToolTip
()
{
weightCountToolTip
()
{
const
{
totalWeight
}
=
this
.
list
;
const
{
totalWeight
}
=
this
.
list
;
...
...
ee/app/assets/javascripts/boards/components/epics_swimlanes.vue
View file @
c05ed1d3
<
script
>
<
script
>
import
{
mapActions
,
mapGetters
,
mapState
}
from
'
vuex
'
;
import
{
mapActions
,
mapGetters
,
mapState
}
from
'
vuex
'
;
import
{
GlIcon
,
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
{
Gl
Button
,
Gl
Icon
,
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
Draggable
from
'
vuedraggable
'
;
import
Draggable
from
'
vuedraggable
'
;
import
BoardListHeader
from
'
ee_else_ce/boards/components/board_list_header.vue
'
;
import
BoardListHeader
from
'
ee_else_ce/boards/components/board_list_header.vue
'
;
import
{
DRAGGABLE_TAG
}
from
'
../constants
'
;
import
{
DRAGGABLE_TAG
}
from
'
../constants
'
;
...
@@ -14,6 +14,7 @@ export default {
...
@@ -14,6 +14,7 @@ export default {
BoardListHeader
,
BoardListHeader
,
EpicLane
,
EpicLane
,
IssuesLaneList
,
IssuesLaneList
,
GlButton
,
GlIcon
,
GlIcon
,
},
},
directives
:
{
directives
:
{
...
@@ -35,14 +36,14 @@ export default {
...
@@ -35,14 +36,14 @@ export default {
},
},
},
},
computed
:
{
computed
:
{
...
mapState
([
'
epics
'
]),
...
mapState
([
'
epics
'
,
'
pageInfoByListId
'
,
'
listsFlags
'
]),
...
mapGetters
([
'
getUnassignedIssues
'
]),
...
mapGetters
([
'
getUnassignedIssues
'
]),
unassignedIssues
()
{
unassignedIssues
()
{
return
listId
=>
this
.
getUnassignedIssues
(
listId
);
return
listId
=>
this
.
getUnassignedIssues
(
listId
);
},
},
unassignedIssuesCount
()
{
unassignedIssuesCount
()
{
return
this
.
lists
.
reduce
(
return
this
.
lists
.
reduce
(
(
total
,
list
)
=>
total
+
this
.
getUnassignedIssues
(
list
.
id
).
length
,
(
total
,
list
)
=>
total
+
this
.
listsFlags
[
list
.
id
]?.
unassignedIssuesCount
||
0
,
0
,
0
,
);
);
},
},
...
@@ -65,9 +66,12 @@ export default {
...
@@ -65,9 +66,12 @@ export default {
return
this
.
canAdminList
?
options
:
{};
return
this
.
canAdminList
?
options
:
{};
},
},
hasMoreUnassignedIssues
()
{
return
this
.
lists
.
some
(
list
=>
this
.
pageInfoByListId
[
list
.
id
]?.
hasNextPage
);
},
},
},
methods
:
{
methods
:
{
...
mapActions
([
'
moveList
'
]),
...
mapActions
([
'
moveList
'
,
'
fetchIssuesForList
'
]),
handleDragOnEnd
(
params
)
{
handleDragOnEnd
(
params
)
{
const
{
newIndex
,
oldIndex
,
item
}
=
params
;
const
{
newIndex
,
oldIndex
,
item
}
=
params
;
const
{
listId
}
=
item
.
dataset
;
const
{
listId
}
=
item
.
dataset
;
...
@@ -78,6 +82,13 @@ export default {
...
@@ -78,6 +82,13 @@ export default {
adjustmentValue
:
newIndex
<
oldIndex
?
1
:
-
1
,
adjustmentValue
:
newIndex
<
oldIndex
?
1
:
-
1
,
});
});
},
},
fetchMoreUnassignedIssues
()
{
this
.
lists
.
forEach
(
list
=>
{
if
(
this
.
pageInfoByListId
[
list
.
id
]?.
hasNextPage
)
{
this
.
fetchIssuesForList
({
listId
:
list
.
id
,
fetchNext
:
true
,
noEpicIssues
:
true
});
}
});
},
},
},
};
};
</
script
>
</
script
>
...
@@ -142,17 +153,29 @@ export default {
...
@@ -142,17 +153,29 @@ export default {
</span>
</span>
</div>
</div>
</div>
</div>
<div
class=
"gl-display-flex"
data-testid=
"board-lane-unassigned-issues"
>
<div
data-testid=
"board-lane-unassigned-issues"
>
<issues-lane-list
<div
class=
"gl-display-flex"
>
v-for=
"list in lists"
<issues-lane-list
:key=
"`$
{list.id}-issues`"
v-for=
"list in lists"
:list="list"
:key=
"`$
{list.id}-issues`"
:issues="unassignedIssues(list.id)"
:list="list"
:is-unassigned-issues-lane="true"
:issues="unassignedIssues(list.id)"
:disabled="disabled"
:is-unassigned-issues-lane="true"
:can-admin-list="canAdminList"
:disabled="disabled"
/>
:can-admin-list="canAdminList"
/>
</div>
</div>
</div>
</div>
</div>
<div
v-if=
"hasMoreUnassignedIssues"
class=
"gl-p-3 gl-pr-0 gl-sticky gl-left-0 gl-max-w-full"
>
<gl-button
category=
"tertiary"
variant=
"info"
class=
"gl-w-full"
@
click=
"fetchMoreUnassignedIssues()"
>
{{
s__
(
'
Board|Load more issues
'
)
}}
</gl-button>
</div>
</div>
</div>
</
template
>
</
template
>
ee/app/assets/javascripts/boards/components/issues_lane_list.vue
View file @
c05ed1d3
...
@@ -50,7 +50,7 @@ export default {
...
@@ -50,7 +50,7 @@ export default {
};
};
},
},
computed
:
{
computed
:
{
...
mapState
([
'
activeId
'
,
'
filterParams
'
,
'
canAdminEpic
'
]),
...
mapState
([
'
activeId
'
,
'
filterParams
'
,
'
canAdminEpic
'
,
'
listsFlags
'
]),
treeRootWrapper
()
{
treeRootWrapper
()
{
return
this
.
canAdminList
&&
this
.
canAdminEpic
?
Draggable
:
'
ul
'
;
return
this
.
canAdminList
&&
this
.
canAdminEpic
?
Draggable
:
'
ul
'
;
},
},
...
@@ -68,12 +68,15 @@ export default {
...
@@ -68,12 +68,15 @@ export default {
return
this
.
canAdminList
?
options
:
{};
return
this
.
canAdminList
?
options
:
{};
},
},
isLoadingMore
()
{
return
this
.
listsFlags
[
this
.
list
.
id
]?.
isLoadingMore
;
},
},
},
watch
:
{
watch
:
{
filterParams
:
{
filterParams
:
{
handler
()
{
handler
()
{
if
(
this
.
isUnassignedIssuesLane
)
{
if
(
this
.
isUnassignedIssuesLane
)
{
this
.
fetchIssuesForList
(
this
.
list
.
id
);
this
.
fetchIssuesForList
(
{
listId
:
this
.
list
.
id
,
noEpicIssues
:
true
}
);
}
}
},
},
deep
:
true
,
deep
:
true
,
...
@@ -173,6 +176,7 @@ export default {
...
@@ -173,6 +176,7 @@ export default {
:is-active=
"isActiveIssue(issue)"
:is-active=
"isActiveIssue(issue)"
@
show=
"showIssue(issue)"
@
show=
"showIssue(issue)"
/>
/>
<gl-loading-icon
v-if=
"isLoadingMore && isUnassignedIssuesLane"
size=
"sm"
class=
"gl-py-3"
/>
</component>
</component>
</div>
</div>
</div>
</div>
...
...
ee/app/assets/javascripts/boards/components/sidebar/board_sidebar_epic_select.vue
View file @
c05ed1d3
...
@@ -5,7 +5,7 @@ import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
...
@@ -5,7 +5,7 @@ import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
BoardEditableItem
from
'
~/boards/components/sidebar/board_editable_item.vue
'
;
import
BoardEditableItem
from
'
~/boards/components/sidebar/board_editable_item.vue
'
;
import
{
UPDATE_ISSUE_BY_ID
}
from
'
~/boards/stores/mutation_types
'
;
import
{
UPDATE_ISSUE_BY_ID
}
from
'
~/boards/stores/mutation_types
'
;
import
{
RECEIVE_EPICS_SUCCESS
}
from
'
../../stores/mutation_types
'
;
import
{
RECEIVE_
FIRST_
EPICS_SUCCESS
}
from
'
../../stores/mutation_types
'
;
export
default
{
export
default
{
components
:
{
components
:
{
...
@@ -38,7 +38,7 @@ export default {
...
@@ -38,7 +38,7 @@ export default {
methods
:
{
methods
:
{
...
mapMutations
({
...
mapMutations
({
updateIssueById
:
UPDATE_ISSUE_BY_ID
,
updateIssueById
:
UPDATE_ISSUE_BY_ID
,
receiveEpicsSuccess
:
RECEIVE_EPICS_SUCCESS
,
receiveEpicsSuccess
:
RECEIVE_
FIRST_
EPICS_SUCCESS
,
}),
}),
...
mapActions
([
'
setActiveIssueEpic
'
]),
...
mapActions
([
'
setActiveIssueEpic
'
]),
openEpicsDropdown
()
{
openEpicsDropdown
()
{
...
...
ee/app/assets/javascripts/boards/queries/epics_swimlanes.query.graphql
View file @
c05ed1d3
...
@@ -12,12 +12,12 @@ query BoardEE(
...
@@ -12,12 +12,12 @@ query BoardEE(
)
{
)
{
group
(
fullPath
:
$fullPath
)
@include
(
if
:
$isGroup
)
{
group
(
fullPath
:
$fullPath
)
@include
(
if
:
$isGroup
)
{
board
(
id
:
$boardId
)
{
board
(
id
:
$boardId
)
{
lists
@include
(
if
:
$withLists
)
{
lists
(
issueFilters
:
$issueFilters
)
@include
(
if
:
$withLists
)
{
nodes
{
nodes
{
...
BoardListFragment
...
BoardListFragment
}
}
}
}
epics
(
first
:
2
,
issueFilters
:
$issueFilters
,
after
:
$after
)
{
epics
(
first
:
2
0
,
issueFilters
:
$issueFilters
,
after
:
$after
)
{
edges
{
edges
{
node
{
node
{
...
BoardEpicNode
...
BoardEpicNode
...
@@ -32,7 +32,7 @@ query BoardEE(
...
@@ -32,7 +32,7 @@ query BoardEE(
}
}
project
(
fullPath
:
$fullPath
)
@include
(
if
:
$isProject
)
{
project
(
fullPath
:
$fullPath
)
@include
(
if
:
$isProject
)
{
board
(
id
:
$boardId
)
{
board
(
id
:
$boardId
)
{
lists
@include
(
if
:
$withLists
)
{
lists
(
issueFilters
:
$issueFilters
)
@include
(
if
:
$withLists
)
{
nodes
{
nodes
{
...
BoardListFragment
...
BoardListFragment
}
}
...
...
ee/app/assets/javascripts/boards/stores/actions.js
View file @
c05ed1d3
...
@@ -10,11 +10,11 @@ import { EpicFilterType } from '../constants';
...
@@ -10,11 +10,11 @@ import { EpicFilterType } from '../constants';
import
boardsStoreEE
from
'
./boards_store_ee
'
;
import
boardsStoreEE
from
'
./boards_store_ee
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
{
fullEpicId
}
from
'
../boards_util
'
;
import
{
fullEpicId
}
from
'
../boards_util
'
;
import
{
formatListIssues
,
fullBoardId
}
from
'
~/boards/boards_util
'
;
import
{
formatListIssues
,
f
ormatListsPageInfo
,
f
ullBoardId
}
from
'
~/boards/boards_util
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
create
DefaultClient
from
'
~/lib/graphql
'
;
import
create
GqClient
,
{
fetchPolicies
}
from
'
~/lib/graphql
'
;
import
epicsSwimlanesQuery
from
'
../queries/epics_swimlanes.query.graphql
'
;
import
epicsSwimlanesQuery
from
'
../queries/epics_swimlanes.query.graphql
'
;
import
issueSetEpic
from
'
../queries/issue_set_epic.mutation.graphql
'
;
import
issueSetEpic
from
'
../queries/issue_set_epic.mutation.graphql
'
;
import
listsIssuesQuery
from
'
~/boards/queries/lists_issues.query.graphql
'
;
import
listsIssuesQuery
from
'
~/boards/queries/lists_issues.query.graphql
'
;
...
@@ -25,19 +25,24 @@ const notImplemented = () => {
...
@@ -25,19 +25,24 @@ const notImplemented = () => {
throw
new
Error
(
'
Not implemented!
'
);
throw
new
Error
(
'
Not implemented!
'
);
};
};
export
const
gqlClient
=
createDefaultClient
();
export
const
gqlClient
=
createGqClient
(
{},
{
fetchPolicy
:
fetchPolicies
.
NO_CACHE
,
},
);
const
fetchAndFormatListIssues
=
(
state
,
extraVariables
)
=>
{
const
fetchAndFormatListIssues
=
(
state
,
extraVariables
)
=>
{
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
const
{
fullPath
,
boardId
}
=
endpoints
;
const
{
fullPath
,
boardId
}
=
endpoints
;
const
variables
=
{
const
variables
=
{
...
extraVariables
,
fullPath
,
fullPath
,
boardId
:
fullBoardId
(
boardId
),
boardId
:
fullBoardId
(
boardId
),
filters
:
{
...
filterParams
},
filters
:
{
...
filterParams
},
isGroup
:
boardType
===
BoardType
.
group
,
isGroup
:
boardType
===
BoardType
.
group
,
isProject
:
boardType
===
BoardType
.
project
,
isProject
:
boardType
===
BoardType
.
project
,
...
extraVariables
,
};
};
return
gqlClient
return
gqlClient
...
@@ -50,7 +55,7 @@ const fetchAndFormatListIssues = (state, extraVariables) => {
...
@@ -50,7 +55,7 @@ const fetchAndFormatListIssues = (state, extraVariables) => {
})
})
.
then
(({
data
})
=>
{
.
then
(({
data
})
=>
{
const
{
lists
}
=
data
[
boardType
]?.
board
;
const
{
lists
}
=
data
[
boardType
]?.
board
;
return
formatListIssues
(
lists
)
;
return
{
listIssues
:
formatListIssues
(
lists
),
listPageInfo
:
formatListsPageInfo
(
lists
)
}
;
});
});
};
};
...
@@ -104,7 +109,22 @@ export default {
...
@@ -104,7 +109,22 @@ export default {
}));
}));
if
(
!
withLists
)
{
if
(
!
withLists
)
{
commit
(
types
.
RECEIVE_EPICS_SUCCESS
,
{
epics
:
epicsFormatted
});
commit
(
types
.
RECEIVE_EPICS_SUCCESS
,
epicsFormatted
);
}
else
{
if
(
lists
)
{
let
boardLists
=
lists
.
nodes
.
map
(
list
=>
boardsStore
.
updateListPosition
({
...
list
,
doNotFetchIssues
:
true
}),
);
boardLists
=
sortBy
([...
boardLists
],
'
position
'
);
commit
(
types
.
RECEIVE_BOARD_LISTS_SUCCESS
,
boardLists
);
}
if
(
epicsFormatted
)
{
commit
(
types
.
RECEIVE_FIRST_EPICS_SUCCESS
,
{
epics
:
epicsFormatted
,
canAdminEpic
:
epics
.
edges
[
0
]?.
node
?.
userPermissions
?.
adminEpic
,
});
}
}
}
if
(
epics
.
pageInfo
?.
hasNextPage
)
{
if
(
epics
.
pageInfo
?.
hasNextPage
)
{
...
@@ -113,12 +133,6 @@ export default {
...
@@ -113,12 +133,6 @@ export default {
endCursor
:
epics
.
pageInfo
.
endCursor
,
endCursor
:
epics
.
pageInfo
.
endCursor
,
});
});
}
}
return
{
epics
:
epicsFormatted
,
lists
:
lists
?.
nodes
,
canAdminEpic
:
epics
.
edges
[
0
]?.
node
?.
userPermissions
?.
adminEpic
,
};
})
})
.
catch
(()
=>
commit
(
types
.
RECEIVE_SWIMLANES_FAILURE
));
.
catch
(()
=>
commit
(
types
.
RECEIVE_SWIMLANES_FAILURE
));
},
},
...
@@ -177,19 +191,28 @@ export default {
...
@@ -177,19 +191,28 @@ export default {
notImplemented
();
notImplemented
();
},
},
fetchIssuesForList
:
({
state
,
commit
},
listId
,
noEpicIssues
=
false
)
=>
{
fetchIssuesForList
:
({
state
,
commit
},
{
listId
,
fetchNext
=
false
,
noEpicIssues
=
false
})
=>
{
commit
(
types
.
REQUEST_ISSUES_FOR_LIST
,
{
listId
,
fetchNext
});
const
{
filterParams
}
=
state
;
const
{
filterParams
}
=
state
;
const
variables
=
{
const
variables
=
{
id
:
listId
,
id
:
listId
,
filters
:
noEpicIssues
filters
:
noEpicIssues
?
{
...
filterParams
,
epicWildcardId
:
EpicFilterType
.
none
}
?
{
...
filterParams
,
epicWildcardId
:
EpicFilterType
.
none
.
toUpperCase
()
}
:
filterParams
,
:
filterParams
,
after
:
fetchNext
?
state
.
pageInfoByListId
[
listId
].
endCursor
:
undefined
,
first
:
20
,
};
};
return
fetchAndFormatListIssues
(
state
,
variables
)
return
fetchAndFormatListIssues
(
state
,
variables
)
.
then
(
listIssues
=>
{
.
then
(({
listIssues
,
listPageInfo
})
=>
{
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
,
{
listIssues
,
listId
});
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
,
{
listIssues
,
listPageInfo
,
listId
,
noEpicIssues
,
});
})
})
.
catch
(()
=>
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_FAILURE
,
listId
));
.
catch
(()
=>
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_FAILURE
,
listId
));
},
},
...
@@ -204,7 +227,7 @@ export default {
...
@@ -204,7 +227,7 @@ export default {
};
};
return
fetchAndFormatListIssues
(
state
,
variables
)
return
fetchAndFormatListIssues
(
state
,
variables
)
.
then
(
listIssues
=>
{
.
then
(
({
listIssues
})
=>
{
commit
(
types
.
RECEIVE_ISSUES_FOR_EPIC_SUCCESS
,
{
...
listIssues
,
epicId
});
commit
(
types
.
RECEIVE_ISSUES_FOR_EPIC_SUCCESS
,
{
...
listIssues
,
epicId
});
})
})
.
catch
(()
=>
commit
(
types
.
RECEIVE_ISSUES_FOR_EPIC_FAILURE
,
epicId
));
.
catch
(()
=>
commit
(
types
.
RECEIVE_ISSUES_FOR_EPIC_FAILURE
,
epicId
));
...
@@ -214,21 +237,7 @@ export default {
...
@@ -214,21 +237,7 @@ export default {
commit
(
types
.
TOGGLE_EPICS_SWIMLANES
);
commit
(
types
.
TOGGLE_EPICS_SWIMLANES
);
if
(
state
.
isShowingEpicsSwimlanes
)
{
if
(
state
.
isShowingEpicsSwimlanes
)
{
dispatch
(
'
fetchEpicsSwimlanes
'
,
{})
dispatch
(
'
fetchEpicsSwimlanes
'
,
{}).
catch
(()
=>
commit
(
types
.
RECEIVE_SWIMLANES_FAILURE
));
.
then
(({
lists
,
epics
,
canAdminEpic
})
=>
{
if
(
lists
)
{
let
boardLists
=
lists
.
map
(
list
=>
boardsStore
.
updateListPosition
({
...
list
,
doNotFetchIssues
:
true
}),
);
boardLists
=
sortBy
([...
boardLists
],
'
position
'
);
commit
(
types
.
RECEIVE_BOARD_LISTS_SUCCESS
,
boardLists
);
}
if
(
epics
)
{
commit
(
types
.
RECEIVE_EPICS_SUCCESS
,
{
epics
,
canAdminEpic
});
}
})
.
catch
(()
=>
commit
(
types
.
RECEIVE_SWIMLANES_FAILURE
));
}
else
if
(
!
gon
.
features
.
graphqlBoardLists
)
{
}
else
if
(
!
gon
.
features
.
graphqlBoardLists
)
{
boardsStore
.
create
();
boardsStore
.
create
();
eventHub
.
$emit
(
'
initialBoardLoad
'
);
eventHub
.
$emit
(
'
initialBoardLoad
'
);
...
...
ee/app/assets/javascripts/boards/stores/mutation_types.js
View file @
c05ed1d3
...
@@ -11,6 +11,7 @@ export const REQUEST_REMOVE_BOARD = 'REQUEST_REMOVE_BOARD';
...
@@ -11,6 +11,7 @@ export const REQUEST_REMOVE_BOARD = 'REQUEST_REMOVE_BOARD';
export
const
RECEIVE_REMOVE_BOARD_SUCCESS
=
'
RECEIVE_REMOVE_BOARD_SUCCESS
'
;
export
const
RECEIVE_REMOVE_BOARD_SUCCESS
=
'
RECEIVE_REMOVE_BOARD_SUCCESS
'
;
export
const
RECEIVE_REMOVE_BOARD_ERROR
=
'
RECEIVE_REMOVE_BOARD_ERROR
'
;
export
const
RECEIVE_REMOVE_BOARD_ERROR
=
'
RECEIVE_REMOVE_BOARD_ERROR
'
;
export
const
TOGGLE_PROMOTION_STATE
=
'
TOGGLE_PROMOTION_STATE
'
;
export
const
TOGGLE_PROMOTION_STATE
=
'
TOGGLE_PROMOTION_STATE
'
;
export
const
REQUEST_ISSUES_FOR_LIST
=
'
REQUEST_ISSUES_FOR_LIST
'
;
export
const
RECEIVE_ISSUES_FOR_LIST_FAILURE
=
'
RECEIVE_ISSUES_FOR_LIST_FAILURE
'
;
export
const
RECEIVE_ISSUES_FOR_LIST_FAILURE
=
'
RECEIVE_ISSUES_FOR_LIST_FAILURE
'
;
export
const
RECEIVE_ISSUES_FOR_LIST_SUCCESS
=
'
RECEIVE_ISSUES_FOR_LIST_SUCCESS
'
;
export
const
RECEIVE_ISSUES_FOR_LIST_SUCCESS
=
'
RECEIVE_ISSUES_FOR_LIST_SUCCESS
'
;
export
const
REQUEST_ISSUES_FOR_EPIC
=
'
REQUEST_ISSUES_FOR_EPIC
'
;
export
const
REQUEST_ISSUES_FOR_EPIC
=
'
REQUEST_ISSUES_FOR_EPIC
'
;
...
@@ -19,6 +20,7 @@ export const RECEIVE_ISSUES_FOR_EPIC_FAILURE = 'RECEIVE_ISSUES_FOR_EPIC_FAILURE'
...
@@ -19,6 +20,7 @@ export const RECEIVE_ISSUES_FOR_EPIC_FAILURE = 'RECEIVE_ISSUES_FOR_EPIC_FAILURE'
export
const
TOGGLE_EPICS_SWIMLANES
=
'
TOGGLE_EPICS_SWIMLANES
'
;
export
const
TOGGLE_EPICS_SWIMLANES
=
'
TOGGLE_EPICS_SWIMLANES
'
;
export
const
RECEIVE_BOARD_LISTS_SUCCESS
=
'
RECEIVE_BOARD_LISTS_SUCCESS
'
;
export
const
RECEIVE_BOARD_LISTS_SUCCESS
=
'
RECEIVE_BOARD_LISTS_SUCCESS
'
;
export
const
RECEIVE_SWIMLANES_FAILURE
=
'
RECEIVE_SWIMLANES_FAILURE
'
;
export
const
RECEIVE_SWIMLANES_FAILURE
=
'
RECEIVE_SWIMLANES_FAILURE
'
;
export
const
RECEIVE_FIRST_EPICS_SUCCESS
=
'
RECEIVE_FIRST_EPICS_SUCCESS
'
;
export
const
RECEIVE_EPICS_SUCCESS
=
'
RECEIVE_EPICS_SUCCESS
'
;
export
const
RECEIVE_EPICS_SUCCESS
=
'
RECEIVE_EPICS_SUCCESS
'
;
export
const
RESET_EPICS
=
'
RESET_EPICS
'
;
export
const
RESET_EPICS
=
'
RESET_EPICS
'
;
export
const
SET_SHOW_LABELS
=
'
SET_SHOW_LABELS
'
;
export
const
SET_SHOW_LABELS
=
'
SET_SHOW_LABELS
'
;
...
...
ee/app/assets/javascripts/boards/stores/mutations.js
View file @
c05ed1d3
...
@@ -68,6 +68,25 @@ export default {
...
@@ -68,6 +68,25 @@ export default {
notImplemented
();
notImplemented
();
},
},
[
mutationTypes
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
]:
(
state
,
{
listIssues
,
listPageInfo
,
listId
,
noEpicIssues
},
)
=>
{
const
{
listData
,
issues
,
listIssuesCount
}
=
listIssues
;
Vue
.
set
(
state
,
'
issues
'
,
{
...
state
.
issues
,
...
issues
});
Vue
.
set
(
state
.
issuesByListId
,
listId
,
union
(
state
.
issuesByListId
[
listId
]
||
[],
listData
[
listId
]),
);
Vue
.
set
(
state
.
pageInfoByListId
,
listId
,
listPageInfo
[
listId
]);
Vue
.
set
(
state
.
listsFlags
,
listId
,
{
isLoading
:
false
,
isLoadingMore
:
false
,
unassignedIssuesCount
:
noEpicIssues
?
listIssuesCount
:
undefined
,
});
},
[
mutationTypes
.
REQUEST_ISSUES_FOR_EPIC
]:
(
state
,
epicId
)
=>
{
[
mutationTypes
.
REQUEST_ISSUES_FOR_EPIC
]:
(
state
,
epicId
)
=>
{
Vue
.
set
(
state
.
epicsFlags
,
epicId
,
{
isLoading
:
true
});
Vue
.
set
(
state
.
epicsFlags
,
epicId
,
{
isLoading
:
true
});
},
},
...
@@ -103,9 +122,15 @@ export default {
...
@@ -103,9 +122,15 @@ export default {
state
.
epicsSwimlanesFetchInProgress
=
false
;
state
.
epicsSwimlanesFetchInProgress
=
false
;
},
},
[
mutationTypes
.
RECEIVE_EPICS_SUCCESS
]:
(
state
,
{
epics
,
canAdminEpic
})
=>
{
[
mutationTypes
.
RECEIVE_FIRST_EPICS_SUCCESS
]:
(
state
,
{
epics
,
canAdminEpic
})
=>
{
Vue
.
set
(
state
,
'
epics
'
,
epics
);
if
(
canAdminEpic
!==
undefined
)
{
state
.
canAdminEpic
=
canAdminEpic
;
}
},
[
mutationTypes
.
RECEIVE_EPICS_SUCCESS
]:
(
state
,
epics
)
=>
{
Vue
.
set
(
state
,
'
epics
'
,
union
(
state
.
epics
||
[],
epics
));
Vue
.
set
(
state
,
'
epics
'
,
union
(
state
.
epics
||
[],
epics
));
state
.
canAdminEpic
=
canAdminEpic
;
},
},
[
mutationTypes
.
RESET_EPICS
]:
state
=>
{
[
mutationTypes
.
RESET_EPICS
]:
state
=>
{
...
...
ee/spec/features/boards/swimlanes/epics_swimlanes_drag_issue_spec.rb
View file @
c05ed1d3
...
@@ -81,6 +81,7 @@ RSpec.describe 'epics swimlanes', :js do
...
@@ -81,6 +81,7 @@ RSpec.describe 'epics swimlanes', :js do
it
'between lists within unassigned lane'
do
it
'between lists within unassigned lane'
do
wait_for_board_cards
(
1
,
2
)
wait_for_board_cards
(
1
,
2
)
wait_for_board_cards_in_second_epic
(
1
,
1
)
wait_for_board_cards_in_unassigned_lane
(
0
,
1
)
wait_for_board_cards_in_unassigned_lane
(
0
,
1
)
drag
(
list_from_index:
6
,
list_to_index:
7
)
drag
(
list_from_index:
6
,
list_to_index:
7
)
...
...
ee/spec/features/boards/swimlanes/epics_swimlanes_filtering_spec.rb
View file @
c05ed1d3
...
@@ -43,47 +43,60 @@ RSpec.describe 'epics swimlanes filtering', :js do
...
@@ -43,47 +43,60 @@ RSpec.describe 'epics swimlanes filtering', :js do
page
.
find
(
'.dropdown-item'
,
text:
'Epic'
).
click
page
.
find
(
'.dropdown-item'
,
text:
'Epic'
).
click
end
end
wait_for_all_requests
stub_const
(
"Gitlab::QueryLimiting::Transaction::THRESHOLD"
,
200
)
stub_const
(
"Gitlab::QueryLimiting::Transaction::THRESHOLD"
,
200
)
end
end
it
'filters by author'
do
it
'filters by author'
do
wait_for_all_requests
set_filter
(
"author"
,
user2
.
username
)
set_filter
(
"author"
,
user2
.
username
)
click_filter_link
(
user2
.
username
)
click_filter_link
(
user2
.
username
)
submit_filter
submit_filter
wait_for_requests
wait_for_
all_
requests
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
2
,
1
)
wait_for_empty_boards
((
3
..
4
))
wait_for_empty_boards
((
3
..
4
))
end
end
it
'filters by assignee'
do
it
'filters by assignee'
do
wait_for_all_requests
set_filter
(
"assignee"
,
user
.
username
)
set_filter
(
"assignee"
,
user
.
username
)
click_filter_link
(
user
.
username
)
click_filter_link
(
user
.
username
)
submit_filter
submit_filter
wait_for_requests
wait_for_
all_
requests
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
2
,
1
)
wait_for_empty_boards
((
3
..
4
))
wait_for_empty_boards
((
3
..
4
))
end
end
it
'filters by milestone'
do
it
'filters by milestone'
do
wait_for_all_requests
set_filter
(
"milestone"
,
"
\"
#{
milestone
.
title
}
"
)
set_filter
(
"milestone"
,
"
\"
#{
milestone
.
title
}
"
)
click_filter_link
(
milestone
.
title
)
click_filter_link
(
milestone
.
title
)
submit_filter
submit_filter
wait_for_requests
wait_for_all_requests
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
3
,
0
)
wait_for_board_cards
(
3
,
0
)
wait_for_board_cards
(
4
,
0
)
wait_for_board_cards
(
4
,
0
)
end
end
it
'filters by label'
do
it
'filters by label'
do
wait_for_all_requests
set_filter
(
"label"
,
testing
.
title
)
set_filter
(
"label"
,
testing
.
title
)
click_filter_link
(
testing
.
title
)
click_filter_link
(
testing
.
title
)
submit_filter
submit_filter
wait_for_requests
wait_for_all_requests
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
2
,
1
)
wait_for_empty_boards
((
3
..
4
))
wait_for_empty_boards
((
3
..
4
))
end
end
...
@@ -91,7 +104,7 @@ RSpec.describe 'epics swimlanes filtering', :js do
...
@@ -91,7 +104,7 @@ RSpec.describe 'epics swimlanes filtering', :js do
def
visit_board_page
def
visit_board_page
visit
project_boards_path
(
project
)
visit
project_boards_path
(
project
)
wait_for_requests
wait_for_
all_
requests
end
end
def
wait_for_board_cards
(
board_number
,
expected_cards
)
def
wait_for_board_cards
(
board_number
,
expected_cards
)
...
...
ee/spec/frontend/boards/components/epics_swimlanes_spec.js
View file @
c05ed1d3
...
@@ -21,6 +21,18 @@ describe('EpicsSwimlanes', () => {
...
@@ -21,6 +21,18 @@ describe('EpicsSwimlanes', () => {
epics
:
mockEpics
,
epics
:
mockEpics
,
issuesByListId
:
mockIssuesByListId
,
issuesByListId
:
mockIssuesByListId
,
issues
,
issues
,
pageInfoByListId
:
{
'
gid://gitlab/List/1
'
:
{},
'
gid://gitlab/List/2
'
:
{},
},
listsFlags
:
{
'
gid://gitlab/List/1
'
:
{
unassignedIssuesCount
:
1
,
},
'
gid://gitlab/List/2
'
:
{
unassignedIssuesCount
:
1
,
},
},
},
},
getters
,
getters
,
});
});
...
...
ee/spec/frontend/boards/stores/actions_spec.js
View file @
c05ed1d3
...
@@ -104,7 +104,7 @@ describe('fetchEpicsSwimlanes', () => {
...
@@ -104,7 +104,7 @@ describe('fetchEpicsSwimlanes', () => {
[
[
{
{
type
:
types
.
RECEIVE_EPICS_SUCCESS
,
type
:
types
.
RECEIVE_EPICS_SUCCESS
,
payload
:
{
epics
:
[
mockEpic
]
}
,
payload
:
[
mockEpic
]
,
},
},
],
],
[],
[],
...
@@ -150,7 +150,7 @@ describe('fetchEpicsSwimlanes', () => {
...
@@ -150,7 +150,7 @@ describe('fetchEpicsSwimlanes', () => {
[
[
{
{
type
:
types
.
RECEIVE_EPICS_SUCCESS
,
type
:
types
.
RECEIVE_EPICS_SUCCESS
,
payload
:
{
epics
:
[
mockEpic
]
}
,
payload
:
[
mockEpic
]
,
},
},
],
],
[
[
...
@@ -288,7 +288,7 @@ describe('fetchIssuesForEpic', () => {
...
@@ -288,7 +288,7 @@ describe('fetchIssuesForEpic', () => {
{
{
id
:
listId
,
id
:
listId
,
issues
:
{
issues
:
{
nodes
:
[
mockIssue
],
edges
:
[{
node
:
[
mockIssue
]
}
],
},
},
},
},
],
],
...
...
ee/spec/frontend/boards/stores/mutations_spec.js
View file @
c05ed1d3
...
@@ -206,6 +206,21 @@ describe('RECEIVE_SWIMLANES_FAILURE', () => {
...
@@ -206,6 +206,21 @@ describe('RECEIVE_SWIMLANES_FAILURE', () => {
});
});
});
});
describe
(
'
RECEIVE_FIRST_EPICS_SUCCESS
'
,
()
=>
{
it
(
'
populates epics and canAdminEpic with payload
'
,
()
=>
{
state
=
{
...
state
,
epics
:
{},
canAdminEpic
:
false
,
};
mutations
.
RECEIVE_FIRST_EPICS_SUCCESS
(
state
,
{
epics
:
mockEpics
,
canAdminEpic
:
true
});
expect
(
state
.
epics
).
toEqual
(
mockEpics
);
expect
(
state
.
canAdminEpic
).
toEqual
(
true
);
});
});
describe
(
'
RECEIVE_EPICS_SUCCESS
'
,
()
=>
{
describe
(
'
RECEIVE_EPICS_SUCCESS
'
,
()
=>
{
it
(
'
populates epics with payload
'
,
()
=>
{
it
(
'
populates epics with payload
'
,
()
=>
{
state
=
{
state
=
{
...
@@ -213,7 +228,7 @@ describe('RECEIVE_EPICS_SUCCESS', () => {
...
@@ -213,7 +228,7 @@ describe('RECEIVE_EPICS_SUCCESS', () => {
epics
:
{},
epics
:
{},
};
};
mutations
.
RECEIVE_EPICS_SUCCESS
(
state
,
{
epics
:
mockEpics
}
);
mutations
.
RECEIVE_EPICS_SUCCESS
(
state
,
mockEpics
);
expect
(
state
.
epics
).
toEqual
(
mockEpics
);
expect
(
state
.
epics
).
toEqual
(
mockEpics
);
});
});
...
...
locale/gitlab.pot
View file @
c05ed1d3
...
@@ -4179,6 +4179,9 @@ msgstr ""
...
@@ -4179,6 +4179,9 @@ msgstr ""
msgid "Boards|View scope"
msgid "Boards|View scope"
msgstr ""
msgstr ""
msgid "Board|Load more issues"
msgstr ""
msgid "Both project and dashboard_path are required"
msgid "Both project and dashboard_path are required"
msgstr ""
msgstr ""
...
@@ -13994,9 +13997,6 @@ msgstr ""
...
@@ -13994,9 +13997,6 @@ msgstr ""
msgid "Invalid URL"
msgid "Invalid URL"
msgstr ""
msgstr ""
msgid "Invalid board"
msgstr ""
msgid "Invalid container_name"
msgid "Invalid container_name"
msgstr ""
msgstr ""
...
...
spec/features/labels_hierarchy_spec.rb
View file @
c05ed1d3
...
@@ -17,6 +17,7 @@ RSpec.describe 'Labels Hierarchy', :js do
...
@@ -17,6 +17,7 @@ RSpec.describe 'Labels Hierarchy', :js do
let!
(
:project_label_1
)
{
create
(
:label
,
project:
project_1
,
title:
'Label_4'
)
}
let!
(
:project_label_1
)
{
create
(
:label
,
project:
project_1
,
title:
'Label_4'
)
}
before
do
before
do
stub_feature_flags
(
graphql_board_lists:
false
)
grandparent
.
add_owner
(
user
)
grandparent
.
add_owner
(
user
)
sign_in
(
user
)
sign_in
(
user
)
...
...
spec/frontend/boards/board_list_new_spec.js
0 → 100644
View file @
c05ed1d3
/* global List */
/* global ListIssue */
import
Vuex
from
'
vuex
'
;
import
{
useFakeRequestAnimationFrame
}
from
'
helpers/fake_request_animation_frame
'
;
import
{
createLocalVue
,
mount
}
from
'
@vue/test-utils
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
BoardList
from
'
~/boards/components/board_list_new.vue
'
;
import
BoardCard
from
'
~/boards/components/board_card.vue
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
{
listObj
,
mockIssuesByListId
,
issues
}
from
'
./mock_data
'
;
import
defaultState
from
'
~/boards/stores/state
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
const
actions
=
{
fetchIssuesForList
:
jest
.
fn
(),
};
const
createStore
=
(
state
=
defaultState
)
=>
{
return
new
Vuex
.
Store
({
state
,
actions
,
});
};
const
createComponent
=
({
listIssueProps
=
{},
componentProps
=
{},
listProps
=
{},
state
=
{},
}
=
{})
=>
{
const
store
=
createStore
({
issuesByListId
:
mockIssuesByListId
,
issues
,
pageInfoByListId
:
{
'
gid://gitlab/List/1
'
:
{
hasNextPage
:
true
},
'
gid://gitlab/List/2
'
:
{},
},
listsFlags
:
{
'
gid://gitlab/List/1
'
:
{},
'
gid://gitlab/List/2
'
:
{},
},
...
state
,
});
const
list
=
new
List
({
...
listObj
,
id
:
'
gid://gitlab/List/1
'
,
...
listProps
,
doNotFetchIssues
:
true
,
});
const
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
confidential
:
false
,
labels
:
[],
assignees
:
[],
...
listIssueProps
,
});
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
listProps
,
'
issuesSize
'
))
{
list
.
issuesSize
=
1
;
}
const
component
=
mount
(
BoardList
,
{
localVue
,
propsData
:
{
disabled
:
false
,
list
,
issues
:
[
issue
],
...
componentProps
,
},
store
,
provide
:
{
groupId
:
null
,
rootPath
:
'
/
'
,
},
});
return
component
;
};
describe
(
'
Board list component
'
,
()
=>
{
let
wrapper
;
useFakeRequestAnimationFrame
();
describe
(
'
When Expanded
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
renders component
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-list-component
'
).
exists
()).
toBe
(
true
);
});
it
(
'
renders loading icon
'
,
()
=>
{
wrapper
=
createComponent
({
state
:
{
listsFlags
:
{
'
gid://gitlab/List/1
'
:
{
isLoading
:
true
}
}
},
});
expect
(
wrapper
.
find
(
'
[data-testid="board_list_loading"
'
).
exists
()).
toBe
(
true
);
});
it
(
'
renders issues
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
BoardCard
).
length
).
toBe
(
1
);
});
it
(
'
sets data attribute with issue id
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card
'
).
attributes
(
'
data-issue-id
'
)).
toBe
(
'
1
'
);
});
it
(
'
shows new issue form
'
,
async
()
=>
{
wrapper
.
vm
.
toggleForm
();
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
'
.board-new-issue-form
'
).
exists
()).
toBe
(
true
);
});
it
(
'
shows new issue form after eventhub event
'
,
async
()
=>
{
eventHub
.
$emit
(
`toggle-issue-form-
${
wrapper
.
vm
.
list
.
id
}
`
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
'
.board-new-issue-form
'
).
exists
()).
toBe
(
true
);
});
it
(
'
does not show new issue form for closed list
'
,
()
=>
{
wrapper
.
setProps
({
list
:
{
type
:
'
closed
'
}
});
wrapper
.
vm
.
toggleForm
();
expect
(
wrapper
.
find
(
'
.board-new-issue-form
'
).
exists
()).
toBe
(
false
);
});
it
(
'
shows count list item
'
,
async
()
=>
{
wrapper
.
vm
.
showCount
=
true
;
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
'
.board-list-count
'
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
'
.board-list-count
'
).
text
()).
toBe
(
'
Showing all issues
'
);
});
it
(
'
sets data attribute with invalid id
'
,
async
()
=>
{
wrapper
.
vm
.
showCount
=
true
;
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
'
.board-list-count
'
).
attributes
(
'
data-issue-id
'
)).
toBe
(
'
-1
'
);
});
it
(
'
shows how many more issues to load
'
,
async
()
=>
{
wrapper
.
vm
.
showCount
=
true
;
wrapper
.
setProps
({
list
:
{
issuesSize
:
20
}
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
'
.board-list-count
'
).
text
()).
toBe
(
'
Showing 1 of 20 issues
'
);
});
});
describe
(
'
load more issues
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
listProps
:
{
issuesSize
:
25
},
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
loads more issues after scrolling
'
,
()
=>
{
wrapper
.
vm
.
$refs
.
list
.
dispatchEvent
(
new
Event
(
'
scroll
'
));
expect
(
actions
.
fetchIssuesForList
).
toHaveBeenCalled
();
});
it
(
'
does not load issues if already loading
'
,
()
=>
{
wrapper
.
vm
.
$refs
.
list
.
dispatchEvent
(
new
Event
(
'
scroll
'
));
wrapper
.
vm
.
$refs
.
list
.
dispatchEvent
(
new
Event
(
'
scroll
'
));
expect
(
actions
.
fetchIssuesForList
).
toHaveBeenCalledTimes
(
1
);
});
it
(
'
shows loading more spinner
'
,
async
()
=>
{
wrapper
.
vm
.
showCount
=
true
;
wrapper
.
vm
.
list
.
loadingMore
=
true
;
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
'
.board-list-count .gl-spinner
'
).
exists
()).
toBe
(
true
);
});
});
describe
(
'
max issue count warning
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
listProps
:
{
issuesSize
:
50
},
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
when issue count exceeds max issue count
'
,
()
=>
{
it
(
'
sets background to bg-danger-100
'
,
async
()
=>
{
wrapper
.
setProps
({
list
:
{
issuesSize
:
4
,
maxIssueCount
:
3
}
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
'
.bg-danger-100
'
).
exists
()).
toBe
(
true
);
});
});
describe
(
'
when list issue count does NOT exceed list max issue count
'
,
()
=>
{
it
(
'
does not sets background to bg-danger-100
'
,
()
=>
{
wrapper
.
setProps
({
list
:
{
issuesSize
:
2
,
maxIssueCount
:
3
}
});
expect
(
wrapper
.
find
(
'
.bg-danger-100
'
).
exists
()).
toBe
(
false
);
});
});
describe
(
'
when list max issue count is 0
'
,
()
=>
{
it
(
'
does not sets background to bg-danger-100
'
,
()
=>
{
wrapper
.
setProps
({
list
:
{
maxIssueCount
:
0
}
});
expect
(
wrapper
.
find
(
'
.bg-danger-100
'
).
exists
()).
toBe
(
false
);
});
});
});
});
spec/frontend/boards/board_list_spec.js
View file @
c05ed1d3
...
@@ -44,7 +44,6 @@ const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listP
...
@@ -44,7 +44,6 @@ const createComponent = ({ done, listIssueProps = {}, componentProps = {}, listP
disabled
:
false
,
disabled
:
false
,
list
,
list
,
issues
:
list
.
issues
,
issues
:
list
.
issues
,
loading
:
false
,
...
componentProps
,
...
componentProps
,
},
},
provide
:
{
provide
:
{
...
@@ -94,7 +93,7 @@ describe('Board list component', () => {
...
@@ -94,7 +93,7 @@ describe('Board list component', () => {
});
});
it
(
'
renders loading icon
'
,
()
=>
{
it
(
'
renders loading icon
'
,
()
=>
{
component
.
loading
=
true
;
component
.
l
ist
.
l
oading
=
true
;
return
Vue
.
nextTick
().
then
(()
=>
{
return
Vue
.
nextTick
().
then
(()
=>
{
expect
(
component
.
$el
.
querySelector
(
'
.board-list-loading
'
)).
not
.
toBeNull
();
expect
(
component
.
$el
.
querySelector
(
'
.board-list-loading
'
)).
not
.
toBeNull
();
...
...
spec/frontend/boards/stores/actions_spec.js
View file @
c05ed1d3
...
@@ -250,6 +250,13 @@ describe('fetchIssuesForList', () => {
...
@@ -250,6 +250,13 @@ describe('fetchIssuesForList', () => {
boardType
:
'
group
'
,
boardType
:
'
group
'
,
};
};
const
mockIssuesNodes
=
mockIssues
.
map
(
issue
=>
({
node
:
issue
}));
const
pageInfo
=
{
endCursor
:
''
,
hasNextPage
:
false
,
};
const
queryResponse
=
{
const
queryResponse
=
{
data
:
{
data
:
{
group
:
{
group
:
{
...
@@ -259,7 +266,8 @@ describe('fetchIssuesForList', () => {
...
@@ -259,7 +266,8 @@ describe('fetchIssuesForList', () => {
{
{
id
:
listId
,
id
:
listId
,
issues
:
{
issues
:
{
nodes
:
mockIssues
,
edges
:
mockIssuesNodes
,
pageInfo
,
},
},
},
},
],
],
...
@@ -271,17 +279,25 @@ describe('fetchIssuesForList', () => {
...
@@ -271,17 +279,25 @@ describe('fetchIssuesForList', () => {
const
formattedIssues
=
formatListIssues
(
queryResponse
.
data
.
group
.
board
.
lists
);
const
formattedIssues
=
formatListIssues
(
queryResponse
.
data
.
group
.
board
.
lists
);
it
(
'
should commit mutation RECEIVE_ISSUES_FOR_LIST_SUCCESS on success
'
,
done
=>
{
const
listPageInfo
=
{
[
listId
]:
pageInfo
,
};
it
(
'
should commit mutations REQUEST_ISSUES_FOR_LIST and RECEIVE_ISSUES_FOR_LIST_SUCCESS on success
'
,
done
=>
{
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
queryResponse
);
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
queryResponse
);
testAction
(
testAction
(
actions
.
fetchIssuesForList
,
actions
.
fetchIssuesForList
,
listId
,
{
listId
}
,
state
,
state
,
[
[
{
type
:
types
.
REQUEST_ISSUES_FOR_LIST
,
payload
:
{
listId
,
fetchNext
:
false
},
},
{
{
type
:
types
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
,
type
:
types
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
,
payload
:
{
listIssues
:
formattedIssues
,
listId
},
payload
:
{
listIssues
:
formattedIssues
,
list
PageInfo
,
list
Id
},
},
},
],
],
[],
[],
...
@@ -289,14 +305,20 @@ describe('fetchIssuesForList', () => {
...
@@ -289,14 +305,20 @@ describe('fetchIssuesForList', () => {
);
);
});
});
it
(
'
should commit mutation RECEIVE_ISSUES_FOR_LIST_FAILURE on failure
'
,
done
=>
{
it
(
'
should commit mutation
s REQUEST_ISSUES_FOR_LIST and
RECEIVE_ISSUES_FOR_LIST_FAILURE on failure
'
,
done
=>
{
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
Promise
.
reject
());
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
Promise
.
reject
());
testAction
(
testAction
(
actions
.
fetchIssuesForList
,
actions
.
fetchIssuesForList
,
listId
,
{
listId
}
,
state
,
state
,
[{
type
:
types
.
RECEIVE_ISSUES_FOR_LIST_FAILURE
,
payload
:
listId
}],
[
{
type
:
types
.
REQUEST_ISSUES_FOR_LIST
,
payload
:
{
listId
,
fetchNext
:
false
},
},
{
type
:
types
.
RECEIVE_ISSUES_FOR_LIST_FAILURE
,
payload
:
listId
},
],
[],
[],
done
,
done
,
);
);
...
...
spec/frontend/boards/stores/mutations_spec.js
View file @
c05ed1d3
...
@@ -173,13 +173,23 @@ describe('Board Store Mutations', () => {
...
@@ -173,13 +173,23 @@ describe('Board Store Mutations', () => {
state
=
{
state
=
{
...
state
,
...
state
,
issuesByListId
:
{},
issuesByListId
:
{
'
gid://gitlab/List/1
'
:
[],
},
issues
:
{},
issues
:
{},
boardLists
:
mockListsWithModel
,
boardLists
:
mockListsWithModel
,
};
};
const
listPageInfo
=
{
'
gid://gitlab/List/1
'
:
{
endCursor
:
''
,
hasNextPage
:
false
,
},
};
mutations
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
(
state
,
{
mutations
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
(
state
,
{
listIssues
:
{
listData
:
listIssues
,
issues
},
listIssues
:
{
listData
:
listIssues
,
issues
},
listPageInfo
,
listId
:
'
gid://gitlab/List/1
'
,
listId
:
'
gid://gitlab/List/1
'
,
});
});
...
...
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