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
Show 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) {
export
function
formatListIssues
(
listIssues
)
{
const
issues
=
{};
let
listIssuesCount
;
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
{
...
map
,
[
list
.
id
]:
sortedIssues
.
map
(
i
=>
{
...
...
@@ -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
)
{
...
...
app/assets/javascripts/boards/components/board_column.vue
View file @
c05ed1d3
...
...
@@ -7,6 +7,7 @@ import Tooltip from '~/vue_shared/directives/tooltip';
import
EmptyComponent
from
'
~/vue_shared/components/empty_component
'
;
import
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
BoardList
from
'
./board_list.vue
'
;
import
BoardListNew
from
'
./board_list_new.vue
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
eventHub
from
'
../eventhub
'
;
import
{
getBoardSortableDefaultOptions
,
sortableEnd
}
from
'
../mixins/sortable_default_options
'
;
...
...
@@ -16,7 +17,7 @@ export default {
components
:
{
BoardPromotionState
:
EmptyComponent
,
BoardListHeader
,
BoardList
,
BoardList
:
gon
.
features
?.
graphqlBoardLists
?
BoardListNew
:
BoardList
,
},
directives
:
{
Tooltip
,
...
...
@@ -72,7 +73,7 @@ export default {
filter
:
{
handler
()
{
if
(
this
.
shouldFetchIssues
)
{
this
.
fetchIssuesForList
(
this
.
list
.
id
);
this
.
fetchIssuesForList
(
{
listId
:
this
.
list
.
id
}
);
}
else
{
this
.
list
.
page
=
1
;
this
.
list
.
getIssues
(
true
).
catch
(()
=>
{
...
...
@@ -85,7 +86,7 @@ export default {
},
mounted
()
{
if
(
this
.
shouldFetchIssues
)
{
this
.
fetchIssuesForList
(
this
.
list
.
id
);
this
.
fetchIssuesForList
(
{
listId
:
this
.
list
.
id
}
);
}
const
instance
=
this
;
...
...
@@ -144,7 +145,6 @@ export default {
:disabled=
"disabled"
:issues=
"listIssues"
:list=
"list"
:loading=
"list.loading"
/>
<!-- Will be only available in EE -->
...
...
app/assets/javascripts/boards/components/board_list.vue
View file @
c05ed1d3
...
...
@@ -14,6 +14,8 @@ import {
sortableEnd
,
}
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
)
{
Sortable
.
mount
(
new
MultiDrag
());
}
...
...
@@ -39,10 +41,6 @@ export default {
type
:
Array
,
required
:
true
,
},
loading
:
{
type
:
Boolean
,
required
:
true
,
},
},
data
()
{
return
{
...
...
@@ -62,6 +60,9 @@ export default {
issuesSizeExceedsMax
()
{
return
this
.
list
.
maxIssueCount
>
0
&&
this
.
list
.
issuesSize
>
this
.
list
.
maxIssueCount
;
},
loading
()
{
return
this
.
list
.
loading
;
},
},
watch
:
{
filters
:
{
...
...
@@ -72,7 +73,6 @@ export default {
deep
:
true
,
},
issues
()
{
if
(
this
.
glFeatures
.
graphqlBoardLists
)
return
;
this
.
$nextTick
(()
=>
{
if
(
this
.
scrollHeight
()
<=
this
.
listHeight
()
&&
...
...
@@ -98,6 +98,8 @@ export default {
eventHub
.
$on
(
`scroll-board-list-
${
this
.
list
.
id
}
`
,
this
.
scrollToTop
);
},
mounted
()
{
// TODO: Use Draggable in ./board_list_new.vue to drag & drop issue
// https://gitlab.com/gitlab-org/gitlab/-/issues/218164
const
multiSelectOpts
=
{};
if
(
gon
.
features
&&
gon
.
features
.
multiSelectBoard
)
{
multiSelectOpts
.
multiDrag
=
true
;
...
...
@@ -403,8 +405,6 @@ export default {
this
.
showIssueForm
=
!
this
.
showIssueForm
;
},
onScroll
()
{
if
(
this
.
glFeatures
.
graphqlBoardLists
)
return
;
if
(
!
this
.
list
.
loadingMore
&&
this
.
scrollTop
()
>
this
.
scrollHeight
()
-
this
.
scrollOffset
)
{
this
.
loadNextPage
();
}
...
...
app/assets/javascripts/boards/components/board_list_header.vue
View file @
c05ed1d3
...
...
@@ -176,7 +176,6 @@ export default {
<header
:class=
"
{
'has-border': list.label
&&
list.label.color,
'gl-relative': list.isExpanded,
'gl-h-full': !list.isExpanded,
'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 () => {
'
fetchEpicsSwimlanes
'
,
'
resetIssues
'
,
'
resetEpics
'
,
'
fetchLists
'
,
]),
initialBoardLoad
()
{
boardsStore
...
...
@@ -183,7 +184,10 @@ export default () => {
this
.
setFilters
(
convertObjectPropsToCamelCase
(
urlParamsToObject
(
window
.
location
.
search
)));
if
(
gon
.
features
.
boardsWithSwimlanes
&&
this
.
isShowingEpicsSwimlanes
)
{
this
.
resetEpics
();
this
.
fetchEpicsSwimlanes
({
withLists
:
false
});
this
.
resetIssues
();
this
.
fetchEpicsSwimlanes
({});
}
else
if
(
gon
.
features
.
graphqlBoardLists
&&
!
this
.
isShowingEpicsSwimlanes
)
{
this
.
fetchLists
();
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,17 +7,26 @@ query ListIssues(
$filters
:
BoardIssueInput
$isGroup
:
Boolean
=
false
$isProject
:
Boolean
=
false
$after
:
String
$first
:
Int
)
{
group
(
fullPath
:
$fullPath
)
@include
(
if
:
$isGroup
)
{
board
(
id
:
$boardId
)
{
lists
(
id
:
$id
)
{
nodes
{
id
issues
(
filters
:
$filters
)
{
nodes
{
issues
(
first
:
$first
,
filters
:
$filters
,
after
:
$after
)
{
count
edges
{
node
{
...
IssueNode
}
}
pageInfo
{
endCursor
hasNextPage
}
}
}
}
}
...
...
@@ -27,11 +36,18 @@ query ListIssues(
lists
(
id
:
$id
)
{
nodes
{
id
issues
(
filters
:
$filters
)
{
nodes
{
issues
(
first
:
$first
,
filters
:
$filters
,
after
:
$after
)
{
count
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';
import
createFlash
from
'
~/flash
'
;
import
{
__
}
from
'
~/locale
'
;
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
{
BoardType
,
ListType
,
inactiveId
}
from
'
~/boards/constants
'
;
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
listsIssuesQuery
from
'
../queries/lists_issues.query.graphql
'
;
import
projectBoardQuery
from
'
../queries/project_board.query.graphql
'
;
import
groupBoardQuery
from
'
../queries/group_board.query.graphql
'
;
import
boardListsQuery
from
'
../queries/board_lists.query.graphql
'
;
import
createBoardListMutation
from
'
../queries/board_list_create.mutation.graphql
'
;
import
updateBoardListMutation
from
'
../queries/board_list_update.mutation.graphql
'
;
import
issueMoveListMutation
from
'
../queries/issue_move_list.mutation.graphql
'
;
...
...
@@ -22,7 +21,12 @@ const notImplemented = () => {
throw
new
Error
(
'
Not implemented!
'
);
};
export
const
gqlClient
=
createDefaultClient
();
export
const
gqlClient
=
createGqClient
(
{},
{
fetchPolicy
:
fetchPolicies
.
NO_CACHE
,
},
);
export
default
{
setInitialBoardData
:
({
commit
},
data
)
=>
{
...
...
@@ -50,27 +54,20 @@ export default {
},
fetchLists
:
({
commit
,
state
,
dispatch
})
=>
{
const
{
endpoints
,
boardType
}
=
state
;
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
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
=
{
fullPath
,
boardId
:
fullBoardId
(
boardId
),
filters
:
filterParams
,
isGroup
:
boardType
===
BoardType
.
group
,
isProject
:
boardType
===
BoardType
.
project
,
};
return
gqlClient
.
query
({
query
,
query
:
boardListsQuery
,
variables
,
})
.
then
(({
data
})
=>
{
...
...
@@ -197,7 +194,9 @@ export default {
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
{
fullPath
,
boardId
}
=
endpoints
;
...
...
@@ -208,6 +207,8 @@ export default {
filters
:
filterParams
,
isGroup
:
boardType
===
BoardType
.
group
,
isProject
:
boardType
===
BoardType
.
project
,
first
:
20
,
after
:
fetchNext
?
state
.
pageInfoByListId
[
listId
].
endCursor
:
undefined
,
};
return
gqlClient
...
...
@@ -221,7 +222,8 @@ export default {
.
then
(({
data
})
=>
{
const
{
lists
}
=
data
[
boardType
]?.
board
;
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
));
},
...
...
app/assets/javascripts/boards/stores/mutation_types.js
View file @
c05ed1d3
...
...
@@ -12,6 +12,7 @@ export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
export
const
REQUEST_REMOVE_LIST
=
'
REQUEST_REMOVE_LIST
'
;
export
const
RECEIVE_REMOVE_LIST_SUCCESS
=
'
RECEIVE_REMOVE_LIST_SUCCESS
'
;
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_SUCCESS
=
'
RECEIVE_ISSUES_FOR_LIST_SUCCESS
'
;
export
const
REQUEST_ADD_ISSUE
=
'
REQUEST_ADD_ISSUE
'
;
...
...
app/assets/javascripts/boards/stores/mutations.js
View file @
c05ed1d3
import
Vue
from
'
vue
'
;
import
{
sortBy
,
pull
}
from
'
lodash
'
;
import
{
sortBy
,
pull
,
union
}
from
'
lodash
'
;
import
{
formatIssue
,
moveIssueListHelper
}
from
'
../boards_util
'
;
import
*
as
mutationTypes
from
'
./mutation_types
'
;
import
{
s__
}
from
'
~/locale
'
;
...
...
@@ -99,20 +99,30 @@ export default {
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
;
Vue
.
set
(
state
,
'
issues
'
,
{
...
state
.
issues
,
...
issues
});
Vue
.
set
(
state
.
issuesByListId
,
listId
,
listData
[
listId
]);
const
listIndex
=
state
.
boardLists
.
findIndex
(
l
=>
l
.
id
===
listId
);
Vue
.
set
(
state
.
boardLists
[
listIndex
],
'
loading
'
,
false
);
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
});
},
[
mutationTypes
.
RECEIVE_ISSUES_FOR_LIST_FAILURE
]:
(
state
,
listId
)
=>
{
state
.
error
=
s__
(
'
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
.
boardLists
[
listIndex
],
'
loading
'
,
false
);
Vue
.
set
(
state
.
listsFlags
,
listId
,
{
isLoading
:
false
,
isLoadingMore
:
false
});
},
[
mutationTypes
.
RESET_ISSUES
]:
state
=>
{
...
...
app/assets/javascripts/boards/stores/state.js
View file @
c05ed1d3
...
...
@@ -9,7 +9,9 @@ export default () => ({
activeId
:
inactiveId
,
sidebarType
:
''
,
boardLists
:
[],
listsFlags
:
{},
issuesByListId
:
{},
pageInfoByListId
:
{},
issues
:
{},
filterParams
:
{},
error
:
undefined
,
...
...
ee/app/assets/javascripts/boards/components/board_list_header.vue
View file @
c05ed1d3
<
script
>
import
{
mapState
,
mapActions
,
mapGetters
}
from
'
vuex
'
;
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
{
inactiveId
,
LIST
}
from
'
~/boards/constants
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
...
...
@@ -16,13 +16,6 @@ export default {
computed
:
{
...
mapState
([
'
activeId
'
,
'
issuesByListId
'
]),
...
mapGetters
([
'
isSwimlanesOn
'
]),
issuesCount
()
{
if
(
this
.
isSwimlanesOn
)
{
return
this
.
issuesByListId
[
this
.
list
.
id
]
?
this
.
issuesByListId
[
this
.
list
.
id
].
length
:
0
;
}
return
this
.
list
.
issuesSize
;
},
issuesTooltip
()
{
const
{
maxIssueCount
}
=
this
.
list
;
...
...
@@ -36,9 +29,6 @@ export default {
// TODO: Remove this pattern.
return
BoardListHeaderFoss
.
computed
.
issuesTooltip
.
call
(
this
);
},
issuesTooltipLabel
()
{
return
n__
(
`%d issue`
,
`%d issues`
,
this
.
issuesCount
);
},
weightCountToolTip
()
{
const
{
totalWeight
}
=
this
.
list
;
...
...
ee/app/assets/javascripts/boards/components/epics_swimlanes.vue
View file @
c05ed1d3
<
script
>
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
BoardListHeader
from
'
ee_else_ce/boards/components/board_list_header.vue
'
;
import
{
DRAGGABLE_TAG
}
from
'
../constants
'
;
...
...
@@ -14,6 +14,7 @@ export default {
BoardListHeader
,
EpicLane
,
IssuesLaneList
,
GlButton
,
GlIcon
,
},
directives
:
{
...
...
@@ -35,14 +36,14 @@ export default {
},
},
computed
:
{
...
mapState
([
'
epics
'
]),
...
mapState
([
'
epics
'
,
'
pageInfoByListId
'
,
'
listsFlags
'
]),
...
mapGetters
([
'
getUnassignedIssues
'
]),
unassignedIssues
()
{
return
listId
=>
this
.
getUnassignedIssues
(
listId
);
},
unassignedIssuesCount
()
{
return
this
.
lists
.
reduce
(
(
total
,
list
)
=>
total
+
this
.
getUnassignedIssues
(
list
.
id
).
length
,
(
total
,
list
)
=>
total
+
this
.
listsFlags
[
list
.
id
]?.
unassignedIssuesCount
||
0
,
0
,
);
},
...
...
@@ -65,9 +66,12 @@ export default {
return
this
.
canAdminList
?
options
:
{};
},
hasMoreUnassignedIssues
()
{
return
this
.
lists
.
some
(
list
=>
this
.
pageInfoByListId
[
list
.
id
]?.
hasNextPage
);
},
},
methods
:
{
...
mapActions
([
'
moveList
'
]),
...
mapActions
([
'
moveList
'
,
'
fetchIssuesForList
'
]),
handleDragOnEnd
(
params
)
{
const
{
newIndex
,
oldIndex
,
item
}
=
params
;
const
{
listId
}
=
item
.
dataset
;
...
...
@@ -78,6 +82,13 @@ export default {
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
>
...
...
@@ -142,7 +153,8 @@ export default {
</span>
</div>
</div>
<div
class=
"gl-display-flex"
data-testid=
"board-lane-unassigned-issues"
>
<div
data-testid=
"board-lane-unassigned-issues"
>
<div
class=
"gl-display-flex"
>
<issues-lane-list
v-for=
"list in lists"
:key=
"`$
{list.id}-issues`"
...
...
@@ -155,4 +167,15 @@ export default {
</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>
</
template
>
ee/app/assets/javascripts/boards/components/issues_lane_list.vue
View file @
c05ed1d3
...
...
@@ -50,7 +50,7 @@ export default {
};
},
computed
:
{
...
mapState
([
'
activeId
'
,
'
filterParams
'
,
'
canAdminEpic
'
]),
...
mapState
([
'
activeId
'
,
'
filterParams
'
,
'
canAdminEpic
'
,
'
listsFlags
'
]),
treeRootWrapper
()
{
return
this
.
canAdminList
&&
this
.
canAdminEpic
?
Draggable
:
'
ul
'
;
},
...
...
@@ -68,12 +68,15 @@ export default {
return
this
.
canAdminList
?
options
:
{};
},
isLoadingMore
()
{
return
this
.
listsFlags
[
this
.
list
.
id
]?.
isLoadingMore
;
},
},
watch
:
{
filterParams
:
{
handler
()
{
if
(
this
.
isUnassignedIssuesLane
)
{
this
.
fetchIssuesForList
(
this
.
list
.
id
);
this
.
fetchIssuesForList
(
{
listId
:
this
.
list
.
id
,
noEpicIssues
:
true
}
);
}
},
deep
:
true
,
...
...
@@ -173,6 +176,7 @@ export default {
:is-active=
"isActiveIssue(issue)"
@
show=
"showIssue(issue)"
/>
<gl-loading-icon
v-if=
"isLoadingMore && isUnassignedIssuesLane"
size=
"sm"
class=
"gl-py-3"
/>
</component>
</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';
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
BoardEditableItem
from
'
~/boards/components/sidebar/board_editable_item.vue
'
;
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
{
components
:
{
...
...
@@ -38,7 +38,7 @@ export default {
methods
:
{
...
mapMutations
({
updateIssueById
:
UPDATE_ISSUE_BY_ID
,
receiveEpicsSuccess
:
RECEIVE_EPICS_SUCCESS
,
receiveEpicsSuccess
:
RECEIVE_
FIRST_
EPICS_SUCCESS
,
}),
...
mapActions
([
'
setActiveIssueEpic
'
]),
openEpicsDropdown
()
{
...
...
ee/app/assets/javascripts/boards/queries/epics_swimlanes.query.graphql
View file @
c05ed1d3
...
...
@@ -12,12 +12,12 @@ query BoardEE(
)
{
group
(
fullPath
:
$fullPath
)
@include
(
if
:
$isGroup
)
{
board
(
id
:
$boardId
)
{
lists
@include
(
if
:
$withLists
)
{
lists
(
issueFilters
:
$issueFilters
)
@include
(
if
:
$withLists
)
{
nodes
{
...
BoardListFragment
}
}
epics
(
first
:
2
,
issueFilters
:
$issueFilters
,
after
:
$after
)
{
epics
(
first
:
2
0
,
issueFilters
:
$issueFilters
,
after
:
$after
)
{
edges
{
node
{
...
BoardEpicNode
...
...
@@ -32,7 +32,7 @@ query BoardEE(
}
project
(
fullPath
:
$fullPath
)
@include
(
if
:
$isProject
)
{
board
(
id
:
$boardId
)
{
lists
@include
(
if
:
$withLists
)
{
lists
(
issueFilters
:
$issueFilters
)
@include
(
if
:
$withLists
)
{
nodes
{
...
BoardListFragment
}
...
...
ee/app/assets/javascripts/boards/stores/actions.js
View file @
c05ed1d3
...
...
@@ -10,11 +10,11 @@ import { EpicFilterType } from '../constants';
import
boardsStoreEE
from
'
./boards_store_ee
'
;
import
*
as
types
from
'
./mutation_types
'
;
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
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
issueSetEpic
from
'
../queries/issue_set_epic.mutation.graphql
'
;
import
listsIssuesQuery
from
'
~/boards/queries/lists_issues.query.graphql
'
;
...
...
@@ -25,19 +25,24 @@ const notImplemented = () => {
throw
new
Error
(
'
Not implemented!
'
);
};
export
const
gqlClient
=
createDefaultClient
();
export
const
gqlClient
=
createGqClient
(
{},
{
fetchPolicy
:
fetchPolicies
.
NO_CACHE
,
},
);
const
fetchAndFormatListIssues
=
(
state
,
extraVariables
)
=>
{
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
const
{
fullPath
,
boardId
}
=
endpoints
;
const
variables
=
{
...
extraVariables
,
fullPath
,
boardId
:
fullBoardId
(
boardId
),
filters
:
{
...
filterParams
},
isGroup
:
boardType
===
BoardType
.
group
,
isProject
:
boardType
===
BoardType
.
project
,
...
extraVariables
,
};
return
gqlClient
...
...
@@ -50,7 +55,7 @@ const fetchAndFormatListIssues = (state, extraVariables) => {
})
.
then
(({
data
})
=>
{
const
{
lists
}
=
data
[
boardType
]?.
board
;
return
formatListIssues
(
lists
)
;
return
{
listIssues
:
formatListIssues
(
lists
),
listPageInfo
:
formatListsPageInfo
(
lists
)
}
;
});
};
...
...
@@ -104,7 +109,22 @@ export default {
}));
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
)
{
...
...
@@ -113,12 +133,6 @@ export default {
endCursor
:
epics
.
pageInfo
.
endCursor
,
});
}
return
{
epics
:
epicsFormatted
,
lists
:
lists
?.
nodes
,
canAdminEpic
:
epics
.
edges
[
0
]?.
node
?.
userPermissions
?.
adminEpic
,
};
})
.
catch
(()
=>
commit
(
types
.
RECEIVE_SWIMLANES_FAILURE
));
},
...
...
@@ -177,19 +191,28 @@ export default {
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
variables
=
{
id
:
listId
,
filters
:
noEpicIssues
?
{
...
filterParams
,
epicWildcardId
:
EpicFilterType
.
none
}
?
{
...
filterParams
,
epicWildcardId
:
EpicFilterType
.
none
.
toUpperCase
()
}
:
filterParams
,
after
:
fetchNext
?
state
.
pageInfoByListId
[
listId
].
endCursor
:
undefined
,
first
:
20
,
};
return
fetchAndFormatListIssues
(
state
,
variables
)
.
then
(
listIssues
=>
{
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
,
{
listIssues
,
listId
});
.
then
(({
listIssues
,
listPageInfo
})
=>
{
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
,
{
listIssues
,
listPageInfo
,
listId
,
noEpicIssues
,
});
})
.
catch
(()
=>
commit
(
types
.
RECEIVE_ISSUES_FOR_LIST_FAILURE
,
listId
));
},
...
...
@@ -204,7 +227,7 @@ export default {
};
return
fetchAndFormatListIssues
(
state
,
variables
)
.
then
(
listIssues
=>
{
.
then
(
({
listIssues
})
=>
{
commit
(
types
.
RECEIVE_ISSUES_FOR_EPIC_SUCCESS
,
{
...
listIssues
,
epicId
});
})
.
catch
(()
=>
commit
(
types
.
RECEIVE_ISSUES_FOR_EPIC_FAILURE
,
epicId
));
...
...
@@ -214,21 +237,7 @@ export default {
commit
(
types
.
TOGGLE_EPICS_SWIMLANES
);
if
(
state
.
isShowingEpicsSwimlanes
)
{
dispatch
(
'
fetchEpicsSwimlanes
'
,
{})
.
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
));
dispatch
(
'
fetchEpicsSwimlanes
'
,
{}).
catch
(()
=>
commit
(
types
.
RECEIVE_SWIMLANES_FAILURE
));
}
else
if
(
!
gon
.
features
.
graphqlBoardLists
)
{
boardsStore
.
create
();
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';
export
const
RECEIVE_REMOVE_BOARD_SUCCESS
=
'
RECEIVE_REMOVE_BOARD_SUCCESS
'
;
export
const
RECEIVE_REMOVE_BOARD_ERROR
=
'
RECEIVE_REMOVE_BOARD_ERROR
'
;
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_SUCCESS
=
'
RECEIVE_ISSUES_FOR_LIST_SUCCESS
'
;
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'
export
const
TOGGLE_EPICS_SWIMLANES
=
'
TOGGLE_EPICS_SWIMLANES
'
;
export
const
RECEIVE_BOARD_LISTS_SUCCESS
=
'
RECEIVE_BOARD_LISTS_SUCCESS
'
;
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
RESET_EPICS
=
'
RESET_EPICS
'
;
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 {
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
)
=>
{
Vue
.
set
(
state
.
epicsFlags
,
epicId
,
{
isLoading
:
true
});
},
...
...
@@ -103,9 +122,15 @@ export default {
state
.
epicsSwimlanesFetchInProgress
=
false
;
},
[
mutationTypes
.
RECEIVE_EPICS_SUCCESS
]:
(
state
,
{
epics
,
canAdminEpic
})
=>
{
Vue
.
set
(
state
,
'
epics
'
,
union
(
state
.
epics
||
[],
epics
));
[
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
));
},
[
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
it
'between lists within unassigned lane'
do
wait_for_board_cards
(
1
,
2
)
wait_for_board_cards_in_second_epic
(
1
,
1
)
wait_for_board_cards_in_unassigned_lane
(
0
,
1
)
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
page
.
find
(
'.dropdown-item'
,
text:
'Epic'
).
click
end
wait_for_all_requests
stub_const
(
"Gitlab::QueryLimiting::Transaction::THRESHOLD"
,
200
)
end
it
'filters by author'
do
wait_for_all_requests
set_filter
(
"author"
,
user2
.
username
)
click_filter_link
(
user2
.
username
)
submit_filter
wait_for_requests
wait_for_
all_
requests
wait_for_board_cards
(
2
,
1
)
wait_for_empty_boards
((
3
..
4
))
end
it
'filters by assignee'
do
wait_for_all_requests
set_filter
(
"assignee"
,
user
.
username
)
click_filter_link
(
user
.
username
)
submit_filter
wait_for_requests
wait_for_
all_
requests
wait_for_board_cards
(
2
,
1
)
wait_for_empty_boards
((
3
..
4
))
end
it
'filters by milestone'
do
wait_for_all_requests
set_filter
(
"milestone"
,
"
\"
#{
milestone
.
title
}
"
)
click_filter_link
(
milestone
.
title
)
submit_filter
wait_for_requests
wait_for_all_requests
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
3
,
0
)
wait_for_board_cards
(
4
,
0
)
end
it
'filters by label'
do
wait_for_all_requests
set_filter
(
"label"
,
testing
.
title
)
click_filter_link
(
testing
.
title
)
submit_filter
wait_for_requests
wait_for_all_requests
wait_for_board_cards
(
2
,
1
)
wait_for_empty_boards
((
3
..
4
))
end
...
...
@@ -91,7 +104,7 @@ RSpec.describe 'epics swimlanes filtering', :js do
def
visit_board_page
visit
project_boards_path
(
project
)
wait_for_requests
wait_for_
all_
requests
end
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', () => {
epics
:
mockEpics
,
issuesByListId
:
mockIssuesByListId
,
issues
,
pageInfoByListId
:
{
'
gid://gitlab/List/1
'
:
{},
'
gid://gitlab/List/2
'
:
{},
},
listsFlags
:
{
'
gid://gitlab/List/1
'
:
{
unassignedIssuesCount
:
1
,
},
'
gid://gitlab/List/2
'
:
{
unassignedIssuesCount
:
1
,
},
},
},
getters
,
});
...
...
ee/spec/frontend/boards/stores/actions_spec.js
View file @
c05ed1d3
...
...
@@ -104,7 +104,7 @@ describe('fetchEpicsSwimlanes', () => {
[
{
type
:
types
.
RECEIVE_EPICS_SUCCESS
,
payload
:
{
epics
:
[
mockEpic
]
}
,
payload
:
[
mockEpic
]
,
},
],
[],
...
...
@@ -150,7 +150,7 @@ describe('fetchEpicsSwimlanes', () => {
[
{
type
:
types
.
RECEIVE_EPICS_SUCCESS
,
payload
:
{
epics
:
[
mockEpic
]
}
,
payload
:
[
mockEpic
]
,
},
],
[
...
...
@@ -288,7 +288,7 @@ describe('fetchIssuesForEpic', () => {
{
id
:
listId
,
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', () => {
});
});
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
'
,
()
=>
{
it
(
'
populates epics with payload
'
,
()
=>
{
state
=
{
...
...
@@ -213,7 +228,7 @@ describe('RECEIVE_EPICS_SUCCESS', () => {
epics
:
{},
};
mutations
.
RECEIVE_EPICS_SUCCESS
(
state
,
{
epics
:
mockEpics
}
);
mutations
.
RECEIVE_EPICS_SUCCESS
(
state
,
mockEpics
);
expect
(
state
.
epics
).
toEqual
(
mockEpics
);
});
...
...
locale/gitlab.pot
View file @
c05ed1d3
...
...
@@ -4179,6 +4179,9 @@ msgstr ""
msgid "Boards|View scope"
msgstr ""
msgid "Board|Load more issues"
msgstr ""
msgid "Both project and dashboard_path are required"
msgstr ""
...
...
@@ -13994,9 +13997,6 @@ msgstr ""
msgid "Invalid URL"
msgstr ""
msgid "Invalid board"
msgstr ""
msgid "Invalid container_name"
msgstr ""
...
...
spec/features/labels_hierarchy_spec.rb
View file @
c05ed1d3
...
...
@@ -17,6 +17,7 @@ RSpec.describe 'Labels Hierarchy', :js do
let!
(
:project_label_1
)
{
create
(
:label
,
project:
project_1
,
title:
'Label_4'
)
}
before
do
stub_feature_flags
(
graphql_board_lists:
false
)
grandparent
.
add_owner
(
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
disabled
:
false
,
list
,
issues
:
list
.
issues
,
loading
:
false
,
...
componentProps
,
},
provide
:
{
...
...
@@ -94,7 +93,7 @@ describe('Board list component', () => {
});
it
(
'
renders loading icon
'
,
()
=>
{
component
.
loading
=
true
;
component
.
l
ist
.
l
oading
=
true
;
return
Vue
.
nextTick
().
then
(()
=>
{
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', () => {
boardType
:
'
group
'
,
};
const
mockIssuesNodes
=
mockIssues
.
map
(
issue
=>
({
node
:
issue
}));
const
pageInfo
=
{
endCursor
:
''
,
hasNextPage
:
false
,
};
const
queryResponse
=
{
data
:
{
group
:
{
...
...
@@ -259,7 +266,8 @@ describe('fetchIssuesForList', () => {
{
id
:
listId
,
issues
:
{
nodes
:
mockIssues
,
edges
:
mockIssuesNodes
,
pageInfo
,
},
},
],
...
...
@@ -271,17 +279,25 @@ describe('fetchIssuesForList', () => {
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
);
testAction
(
actions
.
fetchIssuesForList
,
listId
,
{
listId
}
,
state
,
[
{
type
:
types
.
REQUEST_ISSUES_FOR_LIST
,
payload
:
{
listId
,
fetchNext
:
false
},
},
{
type
:
types
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
,
payload
:
{
listIssues
:
formattedIssues
,
listId
},
payload
:
{
listIssues
:
formattedIssues
,
list
PageInfo
,
list
Id
},
},
],
[],
...
...
@@ -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
());
testAction
(
actions
.
fetchIssuesForList
,
listId
,
{
listId
}
,
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
,
);
...
...
spec/frontend/boards/stores/mutations_spec.js
View file @
c05ed1d3
...
...
@@ -173,13 +173,23 @@ describe('Board Store Mutations', () => {
state
=
{
...
state
,
issuesByListId
:
{},
issuesByListId
:
{
'
gid://gitlab/List/1
'
:
[],
},
issues
:
{},
boardLists
:
mockListsWithModel
,
};
const
listPageInfo
=
{
'
gid://gitlab/List/1
'
:
{
endCursor
:
''
,
hasNextPage
:
false
,
},
};
mutations
.
RECEIVE_ISSUES_FOR_LIST_SUCCESS
(
state
,
{
listIssues
:
{
listData
:
listIssues
,
issues
},
listPageInfo
,
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