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
e803aca0
Commit
e803aca0
authored
Jul 08, 2020
by
Florie Guibert
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Swimlanes - Fetch epics in GraphQL
- Group and project queries
parent
b229106d
Changes
25
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
523 additions
and
162 deletions
+523
-162
app/assets/javascripts/boards/components/board_content.vue
app/assets/javascripts/boards/components/board_content.vue
+7
-3
app/assets/javascripts/boards/stores/mutations.js
app/assets/javascripts/boards/stores/mutations.js
+2
-1
app/assets/javascripts/boards/stores/state.js
app/assets/javascripts/boards/stores/state.js
+4
-1
app/assets/javascripts/graphql_shared/fragments/epic.fragment.graphql
...avascripts/graphql_shared/fragments/epic.fragment.graphql
+10
-0
app/controllers/projects/boards_controller.rb
app/controllers/projects/boards_controller.rb
+1
-0
ee/app/assets/javascripts/boards/components/epic_lane.vue
ee/app/assets/javascripts/boards/components/epic_lane.vue
+9
-14
ee/app/assets/javascripts/boards/components/epics_swimlanes.vue
.../assets/javascripts/boards/components/epics_swimlanes.vue
+5
-11
ee/app/assets/javascripts/boards/components/issues_lane_list.vue
...assets/javascripts/boards/components/issues_lane_list.vue
+1
-0
ee/app/assets/javascripts/boards/queries/group_epics.query.graphql
...sets/javascripts/boards/queries/group_epics.query.graphql
+0
-50
ee/app/assets/javascripts/boards/queries/group_epics_swimlanes.query.graphql
...cripts/boards/queries/group_epics_swimlanes.query.graphql
+6
-0
ee/app/assets/javascripts/boards/queries/project_epics_swimlanes.query.graphql
...ipts/boards/queries/project_epics_swimlanes.query.graphql
+19
-0
ee/app/assets/javascripts/boards/stores/actions.js
ee/app/assets/javascripts/boards/stores/actions.js
+15
-28
ee/app/assets/javascripts/boards/stores/getters.js
ee/app/assets/javascripts/boards/stores/getters.js
+11
-0
ee/app/assets/javascripts/boards/stores/mutations.js
ee/app/assets/javascripts/boards/stores/mutations.js
+4
-1
ee/app/assets/javascripts/boards/stores/state.js
ee/app/assets/javascripts/boards/stores/state.js
+1
-3
ee/spec/frontend/boards/components/board_content_spec.js
ee/spec/frontend/boards/components/board_content_spec.js
+98
-0
ee/spec/frontend/boards/components/epic_lane_spec.js
ee/spec/frontend/boards/components/epic_lane_spec.js
+21
-23
ee/spec/frontend/boards/components/epics_swimlanes_spec.js
ee/spec/frontend/boards/components/epics_swimlanes_spec.js
+73
-0
ee/spec/frontend/boards/mock_data.js
ee/spec/frontend/boards/mock_data.js
+71
-24
ee/spec/frontend/boards/stores/getters_spec.js
ee/spec/frontend/boards/stores/getters_spec.js
+36
-0
ee/spec/frontend/boards/stores/mutations_spec.js
ee/spec/frontend/boards/stores/mutations_spec.js
+5
-3
locale/gitlab.pot
locale/gitlab.pot
+6
-0
spec/frontend/boards/components/board_content_spec.js
spec/frontend/boards/components/board_content_spec.js
+66
-0
spec/frontend/boards/mock_data.js
spec/frontend/boards/mock_data.js
+35
-0
spec/frontend/boards/stores/mutations_spec.js
spec/frontend/boards/stores/mutations_spec.js
+17
-0
No files found.
app/assets/javascripts/boards/components/board_content.vue
View file @
e803aca0
<
script
>
import
{
mapState
}
from
'
vuex
'
;
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
BoardColumn
from
'
ee_else_ce/boards/components/board_column.vue
'
;
import
EpicsSwimlanes
from
'
ee_component/boards/components/epics_swimlanes.vue
'
;
import
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
export
default
{
components
:
{
BoardColumn
,
EpicsSwimlanes
,
EpicsSwimlanes
:
()
=>
import
(
'
ee_component/boards/components/epics_swimlanes.vue
'
),
GlAlert
,
},
mixins
:
[
glFeatureFlagMixin
()],
props
:
{
...
...
@@ -42,7 +43,7 @@ export default {
},
},
computed
:
{
...
mapState
([
'
isShowingEpicsSwimlanes
'
,
'
boardLists
'
]),
...
mapState
([
'
isShowingEpicsSwimlanes
'
,
'
boardLists
'
,
'
error
'
]),
isSwimlanesOn
()
{
return
this
.
glFeatures
.
boardsWithSwimlanes
&&
this
.
isShowingEpicsSwimlanes
;
},
...
...
@@ -52,6 +53,9 @@ export default {
<
template
>
<div>
<gl-alert
v-if=
"error"
variant=
"danger"
:dismissible=
"false"
>
{{
error
}}
</gl-alert>
<div
v-if=
"!isSwimlanesOn"
class=
"boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
...
...
app/assets/javascripts/boards/stores/mutations.js
View file @
e803aca0
import
*
as
mutationTypes
from
'
./mutation_types
'
;
import
{
__
}
from
'
~/locale
'
;
const
notImplemented
=
()
=>
{
/* eslint-disable-next-line @gitlab/require-i18n-strings */
...
...
@@ -62,7 +63,7 @@ export default {
},
[
mutationTypes
.
RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE
]:
state
=>
{
state
.
listIssueFetchFailure
=
true
;
state
.
error
=
__
(
'
An error occurred while fetching the board issues. Please reload the page.
'
)
;
state
.
isLoadingIssues
=
false
;
},
...
...
app/assets/javascripts/boards/stores/state.js
View file @
e803aca0
...
...
@@ -5,7 +5,10 @@ export default () => ({
boardType
:
null
,
isShowingLabels
:
true
,
activeId
:
inactiveId
,
boardLists
:
[],
issuesByListId
:
{},
isLoadingIssues
:
false
,
listIssueFetchFailure
:
false
,
error
:
undefined
,
// TODO: remove after ce/ee split of board_content.vue
isShowingEpicsSwimlanes
:
false
,
});
app/assets/javascripts/graphql_shared/fragments/epic.fragment.graphql
0 → 100644
View file @
e803aca0
fragment
EpicNode
on
Epic
{
id
iid
title
state
reference
webUrl
createdAt
closedAt
}
app/controllers/projects/boards_controller.rb
View file @
e803aca0
...
...
@@ -9,6 +9,7 @@ class Projects::BoardsController < Projects::ApplicationController
before_action
:assign_endpoint_vars
before_action
do
push_frontend_feature_flag
(
:multi_select_board
,
default_enabled:
true
)
push_frontend_feature_flag
(
:boards_with_swimlanes
,
project
,
default_enabled:
false
)
end
private
...
...
ee/app/assets/javascripts/boards/components/epic_lane.vue
View file @
e803aca0
<
script
>
import
{
GlButton
,
GlIcon
,
GlLink
,
GlPopover
,
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
{
mapGetters
}
from
'
vuex
'
;
import
{
__
,
n__
,
sprintf
}
from
'
~/locale
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
import
{
formatDate
}
from
'
~/lib/utils/datetime_utility
'
;
...
...
@@ -27,10 +28,6 @@ export default {
type
:
Array
,
required
:
true
,
},
issues
:
{
type
:
Object
,
required
:
true
,
},
isLoadingIssues
:
{
type
:
Boolean
,
required
:
false
,
...
...
@@ -51,6 +48,7 @@ export default {
};
},
computed
:
{
...
mapGetters
([
'
getIssuesByEpic
'
]),
isOpen
()
{
return
this
.
epic
.
state
===
statusType
.
open
;
},
...
...
@@ -70,8 +68,10 @@ export default {
return
this
.
isOpen
?
'
gl-text-green-500
'
:
'
gl-text-blue-500
'
;
},
issuesCount
()
{
const
{
openedIssues
,
closedIssues
}
=
this
.
epic
.
descendantCounts
;
return
openedIssues
+
closedIssues
;
return
this
.
lists
.
reduce
(
(
total
,
list
)
=>
total
+
this
.
getIssuesByEpic
(
list
.
id
,
this
.
epic
.
id
).
length
,
0
,
);
},
issuesCountTooltipText
()
{
return
n__
(
`%d issue in this group`
,
`%d issues in this group`
,
this
.
issuesCount
);
...
...
@@ -90,13 +90,6 @@ export default {
},
},
methods
:
{
epicIssuesForList
(
listId
)
{
if
(
this
.
issues
[
listId
])
{
return
this
.
issues
[
listId
].
filter
(
issue
=>
issue
.
epic
&&
issue
.
epic
.
id
===
this
.
epic
.
id
);
}
return
[];
},
toggleExpanded
()
{
this
.
isExpanded
=
!
this
.
isExpanded
;
},
...
...
@@ -156,10 +149,12 @@ export default {
v-for=
"list in lists"
:key=
"`${list.id}-issues`"
:list=
"list"
:issues=
"
epicIssuesForList(list
.id)"
:issues=
"
getIssuesByEpic(list.id, epic
.id)"
:is-loading=
"isLoadingIssues"
:disabled=
"disabled"
:root-path=
"rootPath"
:epic-id=
"epic.id"
:epic-is-confidential=
"epic.confidential"
/>
</div>
</div>
...
...
ee/app/assets/javascripts/boards/components/epics_swimlanes.vue
View file @
e803aca0
<
script
>
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
{
mapActions
,
map
Getters
,
map
State
}
from
'
vuex
'
;
import
{
GlIcon
,
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
BoardListHeader
from
'
ee_else_ce/boards/components/board_list_header.vue
'
;
import
{
n__
}
from
'
~/locale
'
;
...
...
@@ -45,9 +45,10 @@ export default {
},
},
computed
:
{
...
mapState
([
'
epics
'
,
'
issuesByListId
'
,
'
isLoadingIssues
'
]),
...
mapState
([
'
epics
'
,
'
isLoadingIssues
'
]),
...
mapGetters
([
'
unassignedIssues
'
]),
unassignedIssuesCount
()
{
return
this
.
lists
.
reduce
((
total
,
list
)
=>
total
+
this
.
unassignedIssues
(
list
).
length
,
0
);
return
this
.
lists
.
reduce
((
total
,
list
)
=>
total
+
this
.
unassignedIssues
(
list
.
id
).
length
,
0
);
},
unassignedIssuesCountTooltipText
()
{
return
n__
(
`%d unassigned issue`
,
`%d unassigned issues`
,
this
.
unassignedIssuesCount
);
...
...
@@ -58,12 +59,6 @@ export default {
},
methods
:
{
...
mapActions
([
'
fetchIssuesForAllLists
'
]),
unassignedIssues
(
list
)
{
if
(
this
.
issuesByListId
[
list
.
id
])
{
return
this
.
issuesByListId
[
list
.
id
].
filter
(
i
=>
i
.
epic
===
null
);
}
return
[];
},
},
};
</
script
>
...
...
@@ -99,7 +94,6 @@ export default {
:key=
"epic.id"
:epic=
"epic"
:lists=
"lists"
:issues=
"issuesByListId"
:is-loading-issues=
"isLoadingIssues"
:disabled=
"disabled"
:root-path=
"rootPath"
...
...
@@ -129,7 +123,7 @@ export default {
v-for=
"list in lists"
:key=
"`$
{list.id}-issues`"
:list="list"
:issues="unassignedIssues(list)"
:issues="unassignedIssues(list
.id
)"
:group-id="groupId"
:is-unassigned-issues-lane="true"
:is-loading="isLoadingIssues"
...
...
ee/app/assets/javascripts/boards/components/issues_lane_list.vue
View file @
e803aca0
...
...
@@ -22,6 +22,7 @@ export default {
issues
:
{
type
:
Array
,
required
:
true
,
default
:
()
=>
[],
},
groupId
:
{
type
:
Number
,
...
...
ee/app/assets/javascripts/boards/queries/group_epics.query.graphql
deleted
100644 → 0
View file @
b229106d
query
groupEpicsEE
(
$fullPath
:
ID
!)
{
group
(
fullPath
:
$fullPath
)
{
epics
(
first
:
10
)
{
nodes
{
id
iid
title
state
reference
webUrl
createdAt
closedAt
descendantCounts
{
openedIssues
closedIssues
}
issues
{
nodes
{
id
iid
title
referencePath
:
reference
dueDate
timeEstimate
weight
confidential
path
:
webUrl
assignees
{
nodes
{
id
username
name
avatar
:
avatarUrl
webUrl
}
}
labels
{
nodes
{
id
title
color
description
}
}
}
}
}
}
}
}
ee/app/assets/javascripts/boards/queries/epics_swimlanes.query.graphql
→
ee/app/assets/javascripts/boards/queries/
group_
epics_swimlanes.query.graphql
View file @
e803aca0
#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
#import "~/graphql_shared/fragments/epic.fragment.graphql"
query
GroupBoardEE
(
$fullPath
:
ID
!,
$boardId
:
ID
!)
{
group
(
fullPath
:
$fullPath
)
{
...
...
@@ -8,6 +9,11 @@ query GroupBoardEE($fullPath: ID!, $boardId: ID!) {
...
BoardListFragment
}
}
epics
{
nodes
{
...
EpicNode
}
}
}
}
}
ee/app/assets/javascripts/boards/queries/project_epics_swimlanes.query.graphql
0 → 100644
View file @
e803aca0
#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
#import "~/graphql_shared/fragments/epic.fragment.graphql"
query
ProjectBoardEE
(
$fullPath
:
ID
!,
$boardId
:
ID
!)
{
project
(
fullPath
:
$fullPath
)
{
board
(
id
:
$boardId
)
{
lists
{
nodes
{
...
BoardListFragment
}
}
epics
{
nodes
{
...
EpicNode
}
}
}
}
}
ee/app/assets/javascripts/boards/stores/actions.js
View file @
e803aca0
import
axios
from
'
axios
'
;
import
{
sortBy
}
from
'
lodash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
actionsCE
from
'
~/boards/stores/actions
'
;
import
boardsStoreEE
from
'
./boards_store_ee
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
import
epicsSwimlanes
from
'
../queries/epics_swimlanes.query.graphql
'
;
import
groupEpics
from
'
../queries/group_epics.query.graphql
'
;
import
{
BoardType
}
from
'
~/boards/constants
'
;
import
groupEpicsSwimlanesQuery
from
'
../queries/group_epics_swimlanes.query.graphql
'
;
import
projectEpicsSwimlanesQuery
from
'
../queries/project_epics_swimlanes.query.graphql
'
;
const
notImplemented
=
()
=>
{
/* eslint-disable-next-line @gitlab/require-i18n-strings */
...
...
@@ -19,29 +20,12 @@ const gqlClient = createDefaultClient();
const
fetchEpicsSwimlanes
=
({
endpoints
,
boardType
})
=>
{
const
{
fullPath
,
boardId
}
=
endpoints
;
const
query
=
epicsSwimlanes
;
const
variables
=
{
fullPath
,
boardId
:
`gid://gitlab/Board/
${
boardId
}
`
,
};
const
query
=
boardType
===
BoardType
.
group
?
groupEpicsSwimlanesQuery
:
projectEpicsSwimlanesQuery
;
return
gqlClient
.
query
({
query
,
variables
,
})
.
then
(({
data
})
=>
{
const
{
lists
}
=
data
[
boardType
]?.
board
;
return
lists
?.
nodes
;
});
};
const
fetchEpics
=
({
endpoints
})
=>
{
const
{
fullPath
}
=
endpoints
;
const
query
=
groupEpics
;
const
variables
=
{
fullPath
,
boardId
:
`gid://gitlab/Board/
${
boardId
}
`
,
};
return
gqlClient
...
...
@@ -50,9 +34,8 @@ const fetchEpics = ({ endpoints }) => {
variables
,
})
.
then
(({
data
})
=>
{
const
{
group
}
=
data
;
const
epics
=
group
?.
epics
.
nodes
||
[];
return
epics
.
map
(
e
=>
({
const
{
epics
,
lists
}
=
data
[
boardType
]?.
board
;
const
epicsFormatted
=
epics
.
nodes
.
map
(
e
=>
({
...
e
,
issues
:
(
e
?.
issues
?.
nodes
||
[]).
map
(
i
=>
({
...
i
,
...
...
@@ -60,6 +43,10 @@ const fetchEpics = ({ endpoints }) => {
assignees
:
i
.
assignees
?.
nodes
||
[],
})),
}));
return
{
epics
:
epicsFormatted
,
lists
:
lists
.
nodes
,
};
});
};
...
...
@@ -108,8 +95,8 @@ export default {
commit
(
types
.
TOGGLE_EPICS_SWIMLANES
);
if
(
state
.
isShowingEpicsSwimlanes
)
{
Promise
.
all
([
fetchEpicsSwimlanes
(
state
),
fetchEpics
(
state
)]
)
.
then
((
[
lists
,
epics
]
)
=>
{
fetchEpicsSwimlanes
(
state
)
.
then
((
{
lists
,
epics
}
)
=>
{
if
(
lists
)
{
let
boardLists
=
lists
.
map
(
list
=>
boardsStore
.
updateListPosition
({
...
list
,
doNotFetchIssues
:
true
}),
...
...
ee/app/assets/javascripts/boards/stores/getters.js
View file @
e803aca0
...
...
@@ -2,4 +2,15 @@ import gettersCE from '~/boards/stores/getters';
export
default
{
...
gettersCE
,
getIssues
:
state
=>
listId
=>
{
return
state
.
issuesByListId
[
listId
]
||
[];
},
getIssuesByEpic
:
(
state
,
getters
)
=>
(
listId
,
epicId
)
=>
{
return
getters
.
getIssues
(
listId
).
filter
(
issue
=>
issue
.
epic
&&
issue
.
epic
.
id
===
epicId
);
},
unassignedIssues
:
(
state
,
getters
)
=>
listId
=>
{
return
getters
.
getIssues
(
listId
).
filter
(
i
=>
i
.
epic
===
null
);
},
};
ee/app/assets/javascripts/boards/stores/mutations.js
View file @
e803aca0
import
mutationsCE
from
'
~/boards/stores/mutations
'
;
import
{
__
}
from
'
~/locale
'
;
import
*
as
mutationTypes
from
'
./mutation_types
'
;
const
notImplemented
=
()
=>
{
...
...
@@ -75,7 +76,9 @@ export default {
},
[
mutationTypes
.
RECEIVE_SWIMLANES_FAILURE
]:
state
=>
{
state
.
epicsSwimlanesFetchFailure
=
true
;
state
.
error
=
__
(
'
An error occurred while fetching the board swimlanes. Please reload the page.
'
,
);
state
.
epicsSwimlanesFetchInProgress
=
false
;
},
...
...
ee/app/assets/javascripts/boards/stores/state.js
View file @
e803aca0
...
...
@@ -5,8 +5,6 @@ export default () => ({
isShowingEpicsSwimlanes
:
false
,
epicsSwimlanesFetchInProgress
:
false
,
epicsSwimlanesFetchFailure
:
false
,
epicsSwimlanes
:
{},
epics
:
{},
boardLists
:
[]
,
issuesByEpicId
:
{}
,
});
ee/spec/frontend/boards/components/board_content_spec.js
0 → 100644
View file @
e803aca0
import
Vuex
from
'
vuex
'
;
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
EpicsSwimlanes
from
'
ee_component/boards/components/epics_swimlanes.vue
'
;
import
BoardColumn
from
'
ee_else_ce/boards/components/board_column.vue
'
;
import
getters
from
'
ee/boards/stores/getters
'
;
import
{
mockListsWithModel
,
mockIssuesByListId
}
from
'
../mock_data
'
;
import
BoardContent
from
'
~/boards/components/board_content.vue
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
BoardContent
'
,
()
=>
{
let
wrapper
;
const
defaultState
=
{
isShowingEpicsSwimlanes
:
false
,
boardLists
:
mockListsWithModel
,
error
:
undefined
,
issuesByListId
:
mockIssuesByListId
,
};
const
createStore
=
(
state
=
defaultState
)
=>
{
return
new
Vuex
.
Store
({
state
,
actions
:
{
fetchIssuesForAllLists
:
()
=>
{},
},
getters
,
});
};
const
createComponent
=
state
=>
{
const
store
=
createStore
({
...
defaultState
,
...
state
,
});
wrapper
=
shallowMount
(
BoardContent
,
{
localVue
,
propsData
:
{
lists
:
mockListsWithModel
,
canAdminList
:
true
,
groupId
:
1
,
disabled
:
false
,
issueLinkBase
:
'
/
'
,
rootPath
:
'
/
'
,
boardId
:
'
1
'
,
},
store
,
provide
:
{
glFeatures
:
{
boardsWithSwimlanes
:
true
},
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
Swimlanes off
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
renders a BoardColumn component per list
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
BoardColumn
)).
toHaveLength
(
mockListsWithModel
.
length
);
});
it
(
'
does not display EpicsSwimlanes component
'
,
()
=>
{
expect
(
wrapper
.
contains
(
EpicsSwimlanes
)).
toBe
(
false
);
expect
(
wrapper
.
contains
(
GlAlert
)).
toBe
(
false
);
});
});
describe
(
'
Swimlanes on
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
isShowingEpicsSwimlanes
:
true
});
});
it
(
'
does not display BoardColumn component
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
BoardColumn
)).
toHaveLength
(
0
);
});
it
(
'
displays EpicsSwimlanes component
'
,
()
=>
{
expect
(
wrapper
.
contains
(
'
.board-swimlanes
'
)).
toBe
(
true
);
expect
(
wrapper
.
contains
(
GlAlert
)).
toBe
(
false
);
});
it
(
'
displays alert if an error occurs when fetching swimlanes
'
,
()
=>
{
createComponent
({
isShowingEpicsSwimlanes
:
true
,
error
:
'
An error occurred while fetching the board swimlanes. Please reload the page.
'
,
});
expect
(
wrapper
.
contains
(
GlAlert
)).
toBe
(
true
);
});
});
});
ee/spec/frontend/boards/components/epic_lane_spec.js
View file @
e803aca0
import
Vue
from
'
vue
'
;
import
AxiosMockAdapter
from
'
axios-mock-adapter
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
EpicLane
from
'
ee/boards/components/epic_lane.vue
'
;
import
IssuesLaneList
from
'
ee/boards/components/issues_lane_list.vue
'
;
import
{
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
{
mockEpic
,
mockLists
,
mockIssues
}
from
'
../mock_data
'
;
import
List
from
'
~/boards/models/list
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
getters
from
'
ee/boards/stores/getters
'
;
import
{
mockEpic
,
mockListsWithModel
,
mockIssuesByListId
}
from
'
../mock_data
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
EpicLane
'
,
()
=>
{
let
wrapper
;
let
axiosMock
;
beforeEach
(()
=>
{
axiosMock
=
new
AxiosMockAdapter
(
axios
);
axiosMock
.
onGet
(
`
${
TEST_HOST
}
/lists/1/issues`
).
reply
(
200
,
{
issues
:
mockIssues
});
});
const
createStore
=
()
=>
{
return
new
Vuex
.
Store
({
state
:
{
issuesByListId
:
mockIssuesByListId
,
},
getters
,
});
};
const
createComponent
=
(
props
=
{})
=>
{
const
issues
=
mockLists
.
reduce
((
map
,
list
)
=>
{
return
{
...
map
,
[
list
.
id
]:
mockIssues
,
};
},
{});
const
store
=
createStore
();
const
defaultProps
=
{
epic
:
mockEpic
,
lists
:
mockLists
.
map
(
listMock
=>
Vue
.
observable
(
new
List
(
listMock
))),
issues
,
lists
:
mockListsWithModel
,
disabled
:
false
,
rootPath
:
'
/
'
,
};
wrapper
=
shallowMount
(
EpicLane
,
{
localVue
,
propsData
:
{
...
defaultProps
,
...
props
,
},
store
,
});
};
afterEach
(()
=>
{
axiosMock
.
restore
();
wrapper
.
destroy
();
});
...
...
@@ -61,8 +59,8 @@ describe('EpicLane', () => {
expect
(
wrapper
.
find
(
GlIcon
).
attributes
(
'
aria-label
'
)).
toEqual
(
'
Closed
'
);
});
it
(
'
displays
total count of issues in epic
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
[data-testid="epic-lane-issue-count"]
'
).
text
()).
toContain
(
5
);
it
(
'
displays
count of issues in epic which belong to board
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
[data-testid="epic-lane-issue-count"]
'
).
text
()).
toContain
(
2
);
});
it
(
'
displays 2 icons
'
,
()
=>
{
...
...
ee/spec/frontend/boards/components/epics_swimlanes_spec.js
0 → 100644
View file @
e803aca0
import
Vuex
from
'
vuex
'
;
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
EpicsSwimlanes
from
'
ee/boards/components/epics_swimlanes.vue
'
;
import
BoardListHeader
from
'
ee_else_ce/boards/components/board_list_header.vue
'
;
import
EpicLane
from
'
ee/boards/components/epic_lane.vue
'
;
import
IssueLaneList
from
'
ee/boards/components/issues_lane_list.vue
'
;
import
getters
from
'
ee/boards/stores/getters
'
;
import
{
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
mockListsWithModel
,
mockEpics
,
mockIssuesByListId
}
from
'
../mock_data
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
EpicsSwimlanes
'
,
()
=>
{
let
wrapper
;
const
createStore
=
()
=>
{
return
new
Vuex
.
Store
({
actions
:
{
fetchIssuesForAllLists
:
jest
.
fn
(),
},
state
:
{
epics
:
mockEpics
,
isLoadingIssues
:
false
,
issuesByListId
:
mockIssuesByListId
,
},
getters
,
});
};
const
createComponent
=
()
=>
{
const
store
=
createStore
();
const
defaultProps
=
{
lists
:
mockListsWithModel
,
boardId
:
'
1
'
,
disabled
:
false
,
rootPath
:
'
/
'
,
};
wrapper
=
shallowMount
(
EpicsSwimlanes
,
{
localVue
,
propsData
:
defaultProps
,
store
,
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
template
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
displays BoardListHeader components for lists
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
BoardListHeader
)).
toHaveLength
(
2
);
});
it
(
'
displays EpicLane components for epic
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
EpicLane
)).
toHaveLength
(
5
);
});
it
(
'
displays IssueLaneList component
'
,
()
=>
{
expect
(
wrapper
.
contains
(
IssueLaneList
)).
toBe
(
true
);
});
it
(
'
displays issues icon and count for unassigned issue
'
,
()
=>
{
expect
(
wrapper
.
find
(
GlIcon
).
props
(
'
name
'
)).
toEqual
(
'
issues
'
);
expect
(
wrapper
.
find
(
'
[data-testid="issues-lane-issue-count"
'
).
text
()).
toEqual
(
'
2
'
);
});
});
});
ee/spec/frontend/boards/mock_data.js
View file @
e803aca0
import
Vue
from
'
vue
'
;
import
List
from
'
~/boards/models/list
'
;
export
const
mockLists
=
[
{
id
:
1
,
id
:
'
gid://gitlab/List/1
'
,
title
:
'
Backlog
'
,
position
:
null
,
listType
:
'
backlog
'
,
...
...
@@ -11,7 +14,7 @@ export const mockLists = [
milestone
:
null
,
},
{
id
:
10
,
id
:
'
gid://gitlab/List/2
'
,
title
:
'
To Do
'
,
position
:
0
,
listType
:
'
label
'
,
...
...
@@ -29,6 +32,10 @@ export const mockLists = [
},
];
export
const
mockListsWithModel
=
mockLists
.
map
(
listMock
=>
Vue
.
observable
(
new
List
({
...
listMock
,
doNotFetchIssues
:
true
})),
);
const
defaultDescendantCounts
=
{
openedIssues
:
0
,
closedIssues
:
0
,
...
...
@@ -53,7 +60,7 @@ const labels = [
},
];
const
mockIssue
=
{
export
const
mockIssue
=
{
id
:
'
gid://gitlab/Issue/436
'
,
iid
:
27
,
title
:
'
Issue 1
'
,
...
...
@@ -65,27 +72,62 @@ const mockIssue = {
path
:
'
/gitlab-org/gitlab-test/-/issues/27
'
,
assignees
,
labels
,
epic
:
{
id
:
'
gid://gitlab/Epic/41
'
,
},
};
export
const
mockIssue
s
=
[
mockIssue
,
{
id
:
'
gid://gitlab/Issue/437
'
,
iid
:
28
,
title
:
'
Issue 2
'
,
referencePath
:
'
#28
'
,
dueDate
:
null
,
timeEstimate
:
0
,
weight
:
null
,
confidential
:
false
,
path
:
'
/gitlab-org/gitlab-test/-/issues/28
'
,
assignees
,
labels
,
export
const
mockIssue
2
=
{
id
:
'
gid://gitlab/Issue/437
'
,
iid
:
28
,
title
:
'
Issue 2
'
,
referencePath
:
'
#28
'
,
dueDate
:
null
,
timeEstimate
:
0
,
weight
:
null
,
confidential
:
false
,
path
:
'
/gitlab-org/gitlab-test/-/issues/28
'
,
assignees
,
labels
,
epic
:
{
id
:
'
gid://gitlab/Epic/40
'
,
},
];
};
export
const
mockIssue3
=
{
id
:
'
gid://gitlab/Issue/438
'
,
iid
:
29
,
title
:
'
Issue 3
'
,
referencePath
:
'
#29
'
,
dueDate
:
null
,
timeEstimate
:
0
,
weight
:
null
,
confidential
:
false
,
path
:
'
/gitlab-org/gitlab-test/-/issues/28
'
,
assignees
,
labels
,
epic
:
null
,
};
export
const
mockIssue4
=
{
id
:
'
gid://gitlab/Issue/439
'
,
iid
:
30
,
title
:
'
Issue 4
'
,
referencePath
:
'
#30
'
,
dueDate
:
null
,
timeEstimate
:
0
,
weight
:
null
,
confidential
:
false
,
path
:
'
/gitlab-org/gitlab-test/-/issues/28
'
,
assignees
,
labels
,
epic
:
null
,
};
export
const
mockIssues
=
[
mockIssue
,
mockIssue2
];
export
const
mockEpic
=
{
id
:
1
,
id
:
'
gid://gitlab/Epic/41
'
,
iid
:
1
,
title
:
'
Epic title
'
,
state
:
'
opened
'
,
...
...
@@ -99,7 +141,7 @@ export const mockEpic = {
export
const
mockEpics
=
[
{
id
:
41
,
id
:
'
gid://gitlab/Epic/41
'
,
iid
:
2
,
description
:
null
,
title
:
'
Another marketing
'
,
...
...
@@ -116,7 +158,7 @@ export const mockEpics = [
},
},
{
id
:
40
,
id
:
'
gid://gitlab/Epic/40
'
,
iid
:
1
,
description
:
null
,
title
:
'
Marketing epic
'
,
...
...
@@ -130,7 +172,7 @@ export const mockEpics = [
hasParent
:
false
,
},
{
id
:
39
,
id
:
'
gid://gitlab/Epic/39
'
,
iid
:
12
,
description
:
null
,
title
:
'
Epic with end in first timeframe month
'
,
...
...
@@ -144,7 +186,7 @@ export const mockEpics = [
hasParent
:
false
,
},
{
id
:
38
,
id
:
'
gid://gitlab/Epic/38
'
,
iid
:
11
,
description
:
null
,
title
:
'
Epic with end date out of range
'
,
...
...
@@ -158,7 +200,7 @@ export const mockEpics = [
hasParent
:
false
,
},
{
id
:
37
,
id
:
'
gid://gitlab/Epic/37
'
,
iid
:
10
,
description
:
null
,
title
:
'
Epic with timeline in same month
'
,
...
...
@@ -172,3 +214,8 @@ export const mockEpics = [
hasParent
:
false
,
},
];
export
const
mockIssuesByListId
=
{
'
gid://gitlab/List/1
'
:
[
mockIssue
,
mockIssue3
,
mockIssue4
],
'
gid://gitlab/List/2
'
:
mockIssues
,
};
ee/spec/frontend/boards/stores/getters_spec.js
0 → 100644
View file @
e803aca0
import
getters
from
'
ee/boards/stores/getters
'
;
import
{
mockIssue
,
mockIssue3
,
mockIssue4
,
mockIssues
,
mockIssuesByListId
}
from
'
../mock_data
'
;
describe
(
'
EE Boards Store Getters
'
,
()
=>
{
const
boardsState
=
{
issuesByListId
:
mockIssuesByListId
,
};
describe
(
'
getIssues
'
,
()
=>
{
it
(
'
returns issues for a given listId
'
,
()
=>
{
expect
(
getters
.
getIssues
(
boardsState
)(
'
gid://gitlab/List/2
'
)).
toEqual
(
mockIssues
);
});
});
describe
(
'
getIssuesByEpic
'
,
()
=>
{
it
(
'
returns issues for a given listId and epicId
'
,
()
=>
{
const
getIssues
=
()
=>
mockIssues
;
expect
(
getters
.
getIssuesByEpic
(
boardsState
,
{
getIssues
})(
'
gid://gitlab/List/2
'
,
'
gid://gitlab/Epic/41
'
,
),
).
toEqual
([
mockIssue
]);
});
});
describe
(
'
unassignedIssues
'
,
()
=>
{
it
(
'
returns issues for a given listId and epicId
'
,
()
=>
{
const
getIssues
=
()
=>
[
mockIssue
,
mockIssue3
,
mockIssue4
];
expect
(
getters
.
unassignedIssues
(
boardsState
,
{
getIssues
})(
'
gid://gitlab/List/1
'
)).
toEqual
([
mockIssue3
,
mockIssue4
,
]);
});
});
});
ee/spec/frontend/boards/stores/mutations_spec.js
View file @
e803aca0
...
...
@@ -118,16 +118,18 @@ describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
});
describe
(
'
RECEIVE_SWIMLANES_FAILURE
'
,
()
=>
{
it
(
'
sets epicsSwimlanesFetchInProgress to false and
epicsSwimlanesFetchFailure to tru
e
'
,
()
=>
{
it
(
'
sets epicsSwimlanesFetchInProgress to false and
sets error messag
e
'
,
()
=>
{
const
state
=
{
epicsSwimlanesFetchInProgress
:
true
,
e
picsSwimlanesFetchFailure
:
false
,
e
rror
:
undefined
,
};
mutations
.
RECEIVE_SWIMLANES_FAILURE
(
state
);
expect
(
state
.
epicsSwimlanesFetchInProgress
).
toBe
(
false
);
expect
(
state
.
epicsSwimlanesFetchFailure
).
toBe
(
true
);
expect
(
state
.
error
).
toEqual
(
'
An error occurred while fetching the board swimlanes. Please reload the page.
'
,
);
});
});
...
...
locale/gitlab.pot
View file @
e803aca0
...
...
@@ -2657,9 +2657,15 @@ msgstr ""
msgid "An error occurred while fetching the Service Desk address."
msgstr ""
msgid "An error occurred while fetching the board issues. Please reload the page."
msgstr ""
msgid "An error occurred while fetching the board lists. Please try again."
msgstr ""
msgid "An error occurred while fetching the board swimlanes. Please reload the page."
msgstr ""
msgid "An error occurred while fetching the builds."
msgstr ""
...
...
spec/frontend/boards/components/board_content_spec.js
0 → 100644
View file @
e803aca0
import
Vuex
from
'
vuex
'
;
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
EpicsSwimlanes
from
'
ee_component/boards/components/epics_swimlanes.vue
'
;
import
BoardColumn
from
'
ee_else_ce/boards/components/board_column.vue
'
;
import
{
mockListsWithModel
}
from
'
../mock_data
'
;
import
BoardContent
from
'
~/boards/components/board_content.vue
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
BoardContent
'
,
()
=>
{
let
wrapper
;
const
defaultState
=
{
isShowingEpicsSwimlanes
:
false
,
boardLists
:
mockListsWithModel
,
error
:
undefined
,
};
const
createStore
=
(
state
=
defaultState
)
=>
{
return
new
Vuex
.
Store
({
state
,
actions
:
{
fetchIssuesForAllLists
:
()
=>
{},
},
});
};
const
createComponent
=
state
=>
{
const
store
=
createStore
({
...
defaultState
,
...
state
,
});
wrapper
=
shallowMount
(
BoardContent
,
{
localVue
,
propsData
:
{
lists
:
mockListsWithModel
,
canAdminList
:
true
,
groupId
:
1
,
disabled
:
false
,
issueLinkBase
:
'
/
'
,
rootPath
:
'
/
'
,
boardId
:
'
1
'
,
},
store
,
});
};
beforeEach
(()
=>
{
createComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
renders a BoardColumn component per list
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
BoardColumn
)).
toHaveLength
(
mockListsWithModel
.
length
);
});
it
(
'
does not display EpicsSwimlanes component
'
,
()
=>
{
expect
(
wrapper
.
contains
(
EpicsSwimlanes
)).
toBe
(
false
);
expect
(
wrapper
.
contains
(
GlAlert
)).
toBe
(
false
);
});
});
spec/frontend/boards/mock_data.js
View file @
e803aca0
import
Vue
from
'
vue
'
;
import
List
from
'
~/boards/models/list
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
export
const
boardObj
=
{
...
...
@@ -165,3 +167,36 @@ export const setMockEndpoints = (opts = {}) => {
boardId
,
});
};
export
const
mockLists
=
[
{
id
:
'
gid://gitlab/List/1
'
,
title
:
'
Backlog
'
,
position
:
null
,
listType
:
'
backlog
'
,
collapsed
:
false
,
label
:
null
,
assignee
:
null
,
milestone
:
null
,
},
{
id
:
'
gid://gitlab/List/2
'
,
title
:
'
To Do
'
,
position
:
0
,
listType
:
'
label
'
,
collapsed
:
false
,
label
:
{
id
:
'
gid://gitlab/GroupLabel/121
'
,
title
:
'
To Do
'
,
color
:
'
#F0AD4E
'
,
textColor
:
'
#FFFFFF
'
,
description
:
null
,
},
assignee
:
null
,
milestone
:
null
,
},
];
export
const
mockListsWithModel
=
mockLists
.
map
(
listMock
=>
Vue
.
observable
(
new
List
({
...
listMock
,
doNotFetchIssues
:
true
})),
);
spec/frontend/boards/stores/mutations_spec.js
View file @
e803aca0
...
...
@@ -113,6 +113,23 @@ describe('Board Store Mutations', () => {
expectNotImplemented
(
mutations
.
REQUEST_ADD_ISSUE
);
});
describe
(
'
RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE
'
,
()
=>
{
it
(
'
sets isLoadingIssues to false and sets error message
'
,
()
=>
{
state
=
{
...
state
,
isLoadingIssues
:
true
,
error
:
undefined
,
};
mutations
.
RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE
(
state
);
expect
(
state
.
isLoadingIssues
).
toBe
(
false
);
expect
(
state
.
error
).
toEqual
(
'
An error occurred while fetching the board issues. Please reload the page.
'
,
);
});
});
describe
(
'
RECEIVE_ADD_ISSUE_SUCCESS
'
,
()
=>
{
expectNotImplemented
(
mutations
.
RECEIVE_ADD_ISSUE_SUCCESS
);
});
...
...
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