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
02d8146f
Commit
02d8146f
authored
Oct 07, 2020
by
Florie Guibert
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Swimlanes - Add list to board
Store lists by id on VueX state
parent
18b27142
Changes
27
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
314 additions
and
125 deletions
+314
-125
app/assets/javascripts/boards/boards_util.js
app/assets/javascripts/boards/boards_util.js
+21
-0
app/assets/javascripts/boards/components/board_content.vue
app/assets/javascripts/boards/components/board_content.vue
+5
-2
app/assets/javascripts/boards/components/board_settings_sidebar.vue
.../javascripts/boards/components/board_settings_sidebar.vue
+1
-1
app/assets/javascripts/boards/components/new_list_dropdown.js
...assets/javascripts/boards/components/new_list_dropdown.js
+22
-10
app/assets/javascripts/boards/queries/board_list_create.mutation.graphql
...scripts/boards/queries/board_list_create.mutation.graphql
+17
-3
app/assets/javascripts/boards/stores/actions.js
app/assets/javascripts/boards/stores/actions.js
+32
-33
app/assets/javascripts/boards/stores/getters.js
app/assets/javascripts/boards/stores/getters.js
+13
-0
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
-19
app/assets/javascripts/boards/stores/state.js
app/assets/javascripts/boards/stores/state.js
+1
-1
app/serializers/label_entity.rb
app/serializers/label_entity.rb
+1
-1
app/serializers/label_serializer.rb
app/serializers/label_serializer.rb
+1
-1
ee/app/assets/javascripts/boards/boards_util.js
ee/app/assets/javascripts/boards/boards_util.js
+10
-0
ee/app/assets/javascripts/boards/components/assignees_list_selector.js
.../javascripts/boards/components/assignees_list_selector.js
+0
-0
ee/app/assets/javascripts/boards/components/boards_list_selector/index.js
...vascripts/boards/components/boards_list_selector/index.js
+26
-9
ee/app/assets/javascripts/boards/components/epics_swimlanes.vue
.../assets/javascripts/boards/components/epics_swimlanes.vue
+3
-1
ee/app/assets/javascripts/boards/components/new_list_dropdown.js
...assets/javascripts/boards/components/new_list_dropdown.js
+4
-4
ee/app/assets/javascripts/boards/stores/actions.js
ee/app/assets/javascripts/boards/stores/actions.js
+8
-7
ee/app/assets/javascripts/boards/stores/getters.js
ee/app/assets/javascripts/boards/stores/getters.js
+7
-0
ee/app/assets/javascripts/boards/stores/mutations.js
ee/app/assets/javascripts/boards/stores/mutations.js
+2
-2
ee/spec/frontend/boards/components/board_list_selector/board_list_selector_spec.js
...omponents/board_list_selector/board_list_selector_spec.js
+24
-2
ee/spec/frontend/boards/stores/mutations_spec.js
ee/spec/frontend/boards/stores/mutations_spec.js
+8
-5
locale/gitlab.pot
locale/gitlab.pot
+3
-3
spec/frontend/boards/stores/actions_spec.js
spec/frontend/boards/stores/actions_spec.js
+17
-3
spec/frontend/boards/stores/getters_spec.js
spec/frontend/boards/stores/getters_spec.js
+29
-1
spec/frontend/boards/stores/mutations_spec.js
spec/frontend/boards/stores/mutations_spec.js
+39
-16
spec/serializers/label_serializer_spec.rb
spec/serializers/label_serializer_spec.rb
+2
-1
No files found.
app/assets/javascripts/boards/boards_util.js
View file @
02d8146f
...
...
@@ -2,11 +2,24 @@ import { sortBy } from 'lodash';
import
ListIssue
from
'
ee_else_ce/boards/models/issue
'
;
import
{
ListType
}
from
'
./constants
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
export
function
getMilestone
()
{
return
null
;
}
export
function
formatBoardLists
(
lists
)
{
const
formattedLists
=
lists
.
nodes
.
map
(
list
=>
boardsStore
.
updateListPosition
({
...
list
,
doNotFetchIssues
:
true
}),
);
return
formattedLists
.
reduce
((
map
,
list
)
=>
{
return
{
...
map
,
[
list
.
id
]:
list
,
};
},
{});
}
export
function
formatIssue
(
issue
)
{
return
new
ListIssue
({
...
issue
,
...
...
@@ -62,6 +75,13 @@ export function fullBoardId(boardId) {
return
`gid://gitlab/Board/
${
boardId
}
`
;
}
export
function
fullLabelId
(
label
)
{
if
(
label
.
project_id
!==
null
)
{
return
`gid://gitlab/ProjectLabel/
${
label
.
id
}
`
;
}
return
`gid://gitlab/GroupLabel/
${
label
.
id
}
`
;
}
export
function
moveIssueListHelper
(
issue
,
fromList
,
toList
)
{
if
(
toList
.
type
===
ListType
.
label
)
{
issue
.
addLabel
(
toList
.
label
);
...
...
@@ -85,4 +105,5 @@ export default {
formatIssue
,
formatListIssues
,
fullBoardId
,
fullLabelId
,
};
app/assets/javascripts/boards/components/board_content.vue
View file @
02d8146f
<
script
>
import
{
mapState
,
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
{
sortBy
}
from
'
lodash
'
;
import
BoardColumn
from
'
ee_else_ce/boards/components/board_column.vue
'
;
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
...
...
@@ -30,7 +31,9 @@ export default {
...
mapState
([
'
boardLists
'
,
'
error
'
]),
...
mapGetters
([
'
isSwimlanesOn
'
]),
boardListsToUse
()
{
return
this
.
glFeatures
.
graphqlBoardLists
?
this
.
boardLists
:
this
.
lists
;
const
lists
=
this
.
glFeatures
.
graphqlBoardLists
||
this
.
isSwimlanesOn
?
this
.
boardLists
:
this
.
lists
;
return
sortBy
([...
Object
.
values
(
lists
)],
'
position
'
);
},
},
mounted
()
{
...
...
@@ -68,7 +71,7 @@ export default {
<template
v-else
>
<epics-swimlanes
ref=
"swimlanes"
:lists=
"boardLists"
:lists=
"boardLists
ToUse
"
:can-admin-list=
"canAdminList"
:disabled=
"disabled"
/>
...
...
app/assets/javascripts/boards/components/board_settings_sidebar.vue
View file @
02d8146f
...
...
@@ -34,7 +34,7 @@ export default {
referencing a List Model class. Reactivity only applies to plain JS objects
*/
if
(
this
.
glFeatures
.
graphqlBoardLists
)
{
return
this
.
boardLists
.
find
(({
id
})
=>
id
===
this
.
activeId
)
;
return
this
.
boardLists
[
this
.
activeId
]
;
}
return
boardsStore
.
state
.
lists
.
find
(({
id
})
=>
id
===
this
.
activeId
);
},
...
...
app/assets/javascripts/boards/components/new_list_dropdown.js
View file @
02d8146f
...
...
@@ -6,8 +6,14 @@ import axios from '~/lib/utils/axios_utils';
import
{
deprecatedCreateFlash
as
flash
}
from
'
~/flash
'
;
import
CreateLabelDropdown
from
'
../../create_label
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
{
fullLabelId
}
from
'
../boards_util
'
;
import
store
from
'
~/boards/stores
'
;
import
initDeprecatedJQueryDropdown
from
'
~/deprecated_jquery_dropdown
'
;
function
shouldCreateListGraphQL
(
label
)
{
return
store
.
getters
.
shouldUseGraphQL
&&
!
store
.
getters
.
getListByLabelId
(
fullLabelId
(
label
));
}
$
(
document
)
.
off
(
'
created.label
'
)
.
on
(
'
created.label
'
,
(
e
,
label
,
addNewList
)
=>
{
...
...
@@ -15,16 +21,20 @@ $(document)
return
;
}
boardsStore
.
new
({
title
:
label
.
title
,
position
:
boardsStore
.
state
.
lists
.
length
-
2
,
list_type
:
'
label
'
,
label
:
{
id
:
label
.
id
,
if
(
shouldCreateListGraphQL
(
label
))
{
store
.
dispatch
(
'
createList
'
,
{
labelId
:
fullLabelId
(
label
)
});
}
else
{
boardsStore
.
new
({
title
:
label
.
title
,
color
:
label
.
color
,
},
});
position
:
boardsStore
.
state
.
lists
.
length
-
2
,
list_type
:
'
label
'
,
label
:
{
id
:
label
.
id
,
title
:
label
.
title
,
color
:
label
.
color
,
},
});
}
});
export
default
function
initNewListDropdown
()
{
...
...
@@ -74,7 +84,9 @@ export default function initNewListDropdown() {
const
label
=
options
.
selectedObj
;
e
.
preventDefault
();
if
(
!
boardsStore
.
findListByLabelId
(
label
.
id
))
{
if
(
shouldCreateListGraphQL
(
label
))
{
store
.
dispatch
(
'
createList
'
,
{
labelId
:
fullLabelId
(
label
)
});
}
else
if
(
!
boardsStore
.
findListByLabelId
(
label
.
id
))
{
boardsStore
.
new
({
title
:
label
.
title
,
position
:
boardsStore
.
state
.
lists
.
length
-
2
,
...
...
app/assets/javascripts/boards/queries/board_list_create.mutation.graphql
View file @
02d8146f
#import "
.
/board_list.fragment.graphql"
#import "
ee_else_ce/boards/queries
/board_list.fragment.graphql"
mutation
CreateBoardList
(
$boardId
:
BoardID
!,
$backlog
:
Boolean
)
{
boardListCreate
(
input
:
{
boardId
:
$boardId
,
backlog
:
$backlog
})
{
mutation
CreateBoardList
(
$boardId
:
BoardID
!
$backlog
:
Boolean
$labelId
:
LabelID
$milestoneId
:
MilestoneID
$assigneeId
:
UserID
)
{
boardListCreate
(
input
:
{
boardId
:
$boardId
backlog
:
$backlog
labelId
:
$labelId
milestoneId
:
$milestoneId
assigneeId
:
$assigneeId
}
)
{
list
{
...
BoardListFragment
}
...
...
app/assets/javascripts/boards/stores/actions.js
View file @
02d8146f
import
Cookies
from
'
js-cookie
'
;
import
{
sortBy
,
pick
}
from
'
lodash
'
;
import
createFlash
from
'
~/flash
'
;
import
{
pick
}
from
'
lodash
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
createGqClient
,
{
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
,
formatListsPageInfo
}
from
'
../boards_util
'
;
import
{
formatBoardLists
,
formatListIssues
,
fullBoardId
,
formatListsPageInfo
,
}
from
'
../boards_util
'
;
import
boardStore
from
'
~/boards/stores/boards_store
'
;
import
listsIssuesQuery
from
'
../queries/lists_issues.query.graphql
'
;
...
...
@@ -71,38 +75,29 @@ export default {
variables
,
})
.
then
(({
data
})
=>
{
let
{
lists
}
=
data
[
boardType
]?.
board
;
// Temporarily using positioning logic from boardStore
lists
=
lists
.
nodes
.
map
(
list
=>
boardStore
.
updateListPosition
({
...
list
,
doNotFetchIssues
:
true
,
}),
);
commit
(
types
.
RECEIVE_BOARD_LISTS_SUCCESS
,
sortBy
(
lists
,
'
position
'
));
const
{
lists
}
=
data
[
boardType
]?.
board
;
commit
(
types
.
RECEIVE_BOARD_LISTS_SUCCESS
,
formatBoardLists
(
lists
));
// Backlog list needs to be created if it doesn't exist
if
(
!
lists
.
find
(
l
=>
l
.
t
ype
===
ListType
.
backlog
))
{
if
(
!
lists
.
nodes
.
find
(
l
=>
l
.
listT
ype
===
ListType
.
backlog
))
{
dispatch
(
'
createList
'
,
{
backlog
:
true
});
}
dispatch
(
'
showWelcomeList
'
);
})
.
catch
(()
=>
{
createFlash
(
__
(
'
An error occurred while fetching the board lists. Please reload the page.
'
),
);
});
.
catch
(()
=>
commit
(
types
.
RECEIVE_BOARD_LISTS_FAILURE
));
},
// This action only supports backlog list creation at this stage
// Future iterations will add the ability to create other list types
createList
:
({
state
,
commit
,
dispatch
},
{
backlog
=
false
})
=>
{
createList
:
({
state
,
commit
,
dispatch
},
{
backlog
,
labelId
,
milestoneId
,
assigneeId
})
=>
{
const
{
boardId
}
=
state
.
endpoints
;
gqlClient
.
mutate
({
mutation
:
createBoardListMutation
,
variables
:
{
boardId
:
fullBoardId
(
boardId
),
backlog
,
labelId
,
milestoneId
,
assigneeId
,
},
})
.
then
(({
data
})
=>
{
...
...
@@ -113,16 +108,15 @@ export default {
dispatch
(
'
addList
'
,
list
);
}
})
.
catch
(()
=>
{
commit
(
types
.
CREATE_LIST_FAILURE
);
});
.
catch
(()
=>
commit
(
types
.
CREATE_LIST_FAILURE
));
},
addList
:
({
state
,
commit
},
list
)
=>
{
const
lists
=
state
.
boardLists
;
addList
:
({
commit
},
list
)
=>
{
// Temporarily using positioning logic from boardStore
lists
.
push
(
boardStore
.
updateListPosition
({
...
list
,
doNotFetchIssues
:
true
}));
commit
(
types
.
RECEIVE_BOARD_LISTS_SUCCESS
,
sortBy
(
lists
,
'
position
'
));
commit
(
types
.
RECEIVE_ADD_LIST_SUCCESS
,
boardStore
.
updateListPosition
({
...
list
,
doNotFetchIssues
:
true
}),
);
},
showWelcomeList
:
({
state
,
dispatch
})
=>
{
...
...
@@ -130,7 +124,9 @@ export default {
return
;
}
if
(
state
.
boardLists
.
find
(
list
=>
list
.
type
!==
ListType
.
backlog
&&
list
.
type
!==
ListType
.
closed
)
Object
.
entries
(
state
.
boardLists
).
find
(
([,
list
])
=>
list
.
type
!==
ListType
.
backlog
&&
list
.
type
!==
ListType
.
closed
,
)
)
{
return
;
}
...
...
@@ -152,13 +148,16 @@ export default {
notImplemented
();
},
moveList
:
({
state
,
commit
,
dispatch
},
{
listId
,
newIndex
,
adjustmentValue
})
=>
{
moveList
:
(
{
state
,
commit
,
dispatch
},
{
listId
,
replacedListId
,
newIndex
,
adjustmentValue
},
)
=>
{
const
{
boardLists
}
=
state
;
const
backupList
=
[...
boardLists
]
;
const
movedList
=
boardLists
.
find
(({
id
})
=>
id
===
listId
)
;
const
backupList
=
{
...
boardLists
}
;
const
movedList
=
boardLists
[
listId
]
;
const
newPosition
=
newIndex
-
1
;
const
listAtNewIndex
=
boardLists
[
newIndex
];
const
listAtNewIndex
=
boardLists
[
replacedListId
];
movedList
.
position
=
newPosition
;
listAtNewIndex
.
position
+=
adjustmentValue
;
...
...
app/assets/javascripts/boards/stores/getters.js
View file @
02d8146f
import
{
find
}
from
'
lodash
'
;
import
{
inactiveId
}
from
'
../constants
'
;
export
default
{
...
...
@@ -22,4 +23,16 @@ export default {
getActiveIssue
:
state
=>
{
return
state
.
issues
[
state
.
activeId
]
||
{};
},
getListByLabelId
:
state
=>
labelId
=>
{
return
find
(
state
.
boardLists
,
l
=>
l
.
label
?.
id
===
labelId
);
},
getListByTitle
:
state
=>
title
=>
{
return
find
(
state
.
boardLists
,
l
=>
l
.
title
===
title
);
},
shouldUseGraphQL
:
()
=>
{
return
gon
?.
features
?.
graphqlBoardLists
;
},
};
app/assets/javascripts/boards/stores/mutation_types.js
View file @
02d8146f
...
...
@@ -3,6 +3,7 @@ export const SET_FILTERS = 'SET_FILTERS';
export
const
CREATE_LIST_SUCCESS
=
'
CREATE_LIST_SUCCESS
'
;
export
const
CREATE_LIST_FAILURE
=
'
CREATE_LIST_FAILURE
'
;
export
const
RECEIVE_BOARD_LISTS_SUCCESS
=
'
RECEIVE_BOARD_LISTS_SUCCESS
'
;
export
const
RECEIVE_BOARD_LISTS_FAILURE
=
'
RECEIVE_BOARD_LISTS_FAILURE
'
;
export
const
SHOW_PROMOTION_LIST
=
'
SHOW_PROMOTION_LIST
'
;
export
const
REQUEST_ADD_LIST
=
'
REQUEST_ADD_LIST
'
;
export
const
RECEIVE_ADD_LIST_SUCCESS
=
'
RECEIVE_ADD_LIST_SUCCESS
'
;
...
...
app/assets/javascripts/boards/stores/mutations.js
View file @
02d8146f
import
Vue
from
'
vue
'
;
import
{
sortBy
,
pull
,
union
}
from
'
lodash
'
;
import
{
pull
,
union
}
from
'
lodash
'
;
import
{
formatIssue
,
moveIssueListHelper
}
from
'
../boards_util
'
;
import
*
as
mutationTypes
from
'
./mutation_types
'
;
import
{
s__
}
from
'
~/locale
'
;
...
...
@@ -10,16 +10,10 @@ const notImplemented = () => {
throw
new
Error
(
'
Not implemented!
'
);
};
const
getListById
=
({
state
,
listId
})
=>
{
const
listIndex
=
state
.
boardLists
.
findIndex
(
l
=>
l
.
id
===
listId
);
const
list
=
state
.
boardLists
[
listIndex
];
return
{
listIndex
,
list
};
};
export
const
removeIssueFromList
=
({
state
,
listId
,
issueId
})
=>
{
Vue
.
set
(
state
.
issuesByListId
,
listId
,
pull
(
state
.
issuesByListId
[
listId
],
issueId
));
const
{
listIndex
,
list
}
=
getListById
({
state
,
listId
})
;
Vue
.
set
(
state
.
boardLists
,
listI
ndex
,
{
...
list
,
issuesSize
:
list
.
issuesSize
-
1
});
const
list
=
state
.
boardLists
[
listId
]
;
Vue
.
set
(
state
.
boardLists
,
listI
d
,
{
...
list
,
issuesSize
:
list
.
issuesSize
-
1
});
};
export
const
addIssueToList
=
({
state
,
listId
,
issueId
,
moveBeforeId
,
moveAfterId
,
atIndex
})
=>
{
...
...
@@ -32,8 +26,8 @@ export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfter
}
listIssues
.
splice
(
newIndex
,
0
,
issueId
);
Vue
.
set
(
state
.
issuesByListId
,
listId
,
listIssues
);
const
{
listIndex
,
list
}
=
getListById
({
state
,
listId
})
;
Vue
.
set
(
state
.
boardLists
,
listI
ndex
,
{
...
list
,
issuesSize
:
list
.
issuesSize
+
1
});
const
list
=
state
.
boardLists
[
listId
]
;
Vue
.
set
(
state
.
boardLists
,
listI
d
,
{
...
list
,
issuesSize
:
list
.
issuesSize
+
1
});
};
export
default
{
...
...
@@ -49,6 +43,12 @@ export default {
state
.
boardLists
=
lists
;
},
[
mutationTypes
.
RECEIVE_BOARD_LISTS_FAILURE
]:
state
=>
{
state
.
error
=
s__
(
'
Boards|An error occurred while fetching the board lists. Please reload the page.
'
,
);
},
[
mutationTypes
.
SET_ACTIVE_ID
](
state
,
{
id
,
sidebarType
})
{
state
.
activeId
=
id
;
state
.
sidebarType
=
sidebarType
;
...
...
@@ -66,8 +66,8 @@ export default {
notImplemented
();
},
[
mutationTypes
.
RECEIVE_ADD_LIST_SUCCESS
]:
()
=>
{
notImplemented
(
);
[
mutationTypes
.
RECEIVE_ADD_LIST_SUCCESS
]:
(
state
,
list
)
=>
{
Vue
.
set
(
state
.
boardLists
,
list
.
id
,
list
);
},
[
mutationTypes
.
RECEIVE_ADD_LIST_ERROR
]:
()
=>
{
...
...
@@ -76,10 +76,8 @@ export default {
[
mutationTypes
.
MOVE_LIST
]:
(
state
,
{
movedList
,
listAtNewIndex
})
=>
{
const
{
boardLists
}
=
state
;
const
movedListIndex
=
state
.
boardLists
.
findIndex
(
l
=>
l
.
id
===
movedList
.
id
);
Vue
.
set
(
boardLists
,
movedListIndex
,
movedList
);
Vue
.
set
(
boardLists
,
movedListIndex
.
position
+
1
,
listAtNewIndex
);
Vue
.
set
(
state
,
'
boardLists
'
,
sortBy
(
boardLists
,
'
position
'
));
Vue
.
set
(
boardLists
,
movedList
.
id
,
movedList
);
Vue
.
set
(
boardLists
,
listAtNewIndex
.
id
,
listAtNewIndex
);
},
[
mutationTypes
.
UPDATE_LIST_FAILURE
]:
(
state
,
backupList
)
=>
{
...
...
@@ -156,8 +154,8 @@ export default {
state
,
{
originalIssue
,
fromListId
,
toListId
,
moveBeforeId
,
moveAfterId
},
)
=>
{
const
fromList
=
state
.
boardLists
.
find
(
l
=>
l
.
id
===
fromListId
)
;
const
toList
=
state
.
boardLists
.
find
(
l
=>
l
.
id
===
toListId
)
;
const
fromList
=
state
.
boardLists
[
fromListId
]
;
const
toList
=
state
.
boardLists
[
toListId
]
;
const
issue
=
moveIssueListHelper
(
originalIssue
,
fromList
,
toList
);
Vue
.
set
(
state
.
issues
,
issue
.
id
,
issue
);
...
...
app/assets/javascripts/boards/stores/state.js
View file @
02d8146f
...
...
@@ -8,7 +8,7 @@ export default () => ({
isShowingLabels
:
true
,
activeId
:
inactiveId
,
sidebarType
:
''
,
boardLists
:
[]
,
boardLists
:
{}
,
listsFlags
:
{},
issuesByListId
:
{},
pageInfoByListId
:
{},
...
...
app/serializers/label_entity.rb
View file @
02d8146f
...
...
@@ -7,7 +7,7 @@ class LabelEntity < Grape::Entity
expose
:color
expose
:description
expose
:group_id
expose
:project_id
expose
:project_id
,
if:
->
(
label
,
_
)
{
!
label
.
is_a?
(
GlobalLabel
)
}
expose
:template
expose
:text_color
expose
:created_at
...
...
app/serializers/label_serializer.rb
View file @
02d8146f
...
...
@@ -4,6 +4,6 @@ class LabelSerializer < BaseSerializer
entity
LabelEntity
def
represent_appearance
(
resource
)
represent
(
resource
,
{
only:
[
:id
,
:title
,
:color
,
:text_color
]
})
represent
(
resource
,
{
only:
[
:id
,
:title
,
:color
,
:text_color
,
:project_id
]
})
end
end
ee/app/assets/javascripts/boards/boards_util.js
View file @
02d8146f
...
...
@@ -6,7 +6,17 @@ export function fullEpicId(epicId) {
return
`gid://gitlab/Epic/
${
epicId
}
`
;
}
export
function
fullMilestoneId
(
milestoneId
)
{
return
`gid://gitlab/Milestone/
${
milestoneId
}
`
;
}
export
function
fullUserId
(
userId
)
{
return
`gid://gitlab/User/
${
userId
}
`
;
}
export
default
{
getMilestone
,
fullEpicId
,
fullMilestoneId
,
fullUserId
,
};
ee/app/assets/javascripts/boards/components/assignees_list_slector.js
→
ee/app/assets/javascripts/boards/components/assignees_list_s
e
lector.js
View file @
02d8146f
File moved
ee/app/assets/javascripts/boards/components/boards_list_selector/index.js
View file @
02d8146f
import
Vue
from
'
vue
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
vuexStore
from
'
~/boards/stores
'
;
import
ListContainer
from
'
./list_container.vue
'
;
import
{
fullMilestoneId
,
fullUserId
}
from
'
../../boards_util
'
;
export
default
Vue
.
extend
({
components
:
{
...
...
@@ -20,6 +22,7 @@ export default Vue.extend({
return
{
loading
:
true
,
store
:
boardsStore
,
vuexStore
,
};
},
mounted
()
{
...
...
@@ -46,19 +49,33 @@ export default Vue.extend({
return
foundName
||
username
.
indexOf
(
query
)
>
-
1
;
});
},
handleItemClick
(
item
)
{
if
(
!
this
.
store
.
findList
(
'
title
'
,
item
.
name
))
{
const
list
=
{
title
:
item
.
name
,
position
:
this
.
store
.
state
.
lists
.
length
-
2
,
list_type
:
this
.
listType
,
};
prepareListObject
(
item
)
{
const
list
=
{
title
:
item
.
name
,
position
:
this
.
store
.
state
.
lists
.
length
-
2
,
list_type
:
this
.
listType
,
};
if
(
this
.
listType
===
'
milestones
'
)
{
list
.
milestone
=
item
;
}
else
if
(
this
.
listType
===
'
assignees
'
)
{
list
.
user
=
item
;
}
return
list
;
},
handleItemClick
(
item
)
{
if
(
this
.
vuexStore
.
getters
.
shouldUseGraphQL
&&
!
this
.
vuexStore
.
getters
.
getListByTitle
(
item
.
title
)
)
{
if
(
this
.
listType
===
'
milestones
'
)
{
list
.
milestone
=
item
;
this
.
vuexStore
.
dispatch
(
'
createList
'
,
{
milestoneId
:
fullMilestoneId
(
item
.
id
)
})
;
}
else
if
(
this
.
listType
===
'
assignees
'
)
{
list
.
user
=
item
;
this
.
vuexStore
.
dispatch
(
'
createList
'
,
{
assigneeId
:
fullUserId
(
item
.
id
)
})
;
}
}
else
if
(
!
this
.
store
.
findList
(
'
title
'
,
item
.
title
))
{
const
list
=
this
.
prepareListObject
(
item
);
this
.
store
.
new
(
list
);
}
...
...
ee/app/assets/javascripts/boards/components/epics_swimlanes.vue
View file @
02d8146f
...
...
@@ -73,11 +73,13 @@ export default {
methods
:
{
...
mapActions
([
'
moveList
'
,
'
fetchIssuesForList
'
]),
handleDragOnEnd
(
params
)
{
const
{
newIndex
,
oldIndex
,
item
}
=
params
;
const
{
newIndex
,
oldIndex
,
item
,
to
}
=
params
;
const
{
listId
}
=
item
.
dataset
;
const
replacedListId
=
to
.
children
[
newIndex
].
dataset
.
listId
;
this
.
moveList
({
listId
,
replacedListId
,
newIndex
,
adjustmentValue
:
newIndex
<
oldIndex
?
1
:
-
1
,
});
...
...
ee/app/assets/javascripts/boards/components/new_list_dropdown.js
View file @
02d8146f
import
$
from
'
jquery
'
;
import
initNewListDropdown
from
'
~/boards/components/new_list_dropdown
'
;
import
AssigneeList
from
'
./assignees_list_slector
'
;
import
AssigneeList
from
'
./assignees_list_s
e
lector
'
;
import
MilestoneList
from
'
./milestone_list_selector
'
;
const
handleDropdownHide
=
e
=>
{
...
...
@@ -12,7 +12,7 @@ const handleDropdownHide = e => {
};
let
assigneeList
;
let
milstoneList
;
let
mil
e
stoneList
;
const
handleDropdownTabClick
=
e
=>
{
const
$addListEl
=
$
(
'
#js-add-list
'
);
...
...
@@ -21,8 +21,8 @@ const handleDropdownTabClick = e => {
assigneeList
=
AssigneeList
();
}
if
(
e
.
target
.
dataset
.
action
===
'
tab-milestones
'
&&
!
milstoneList
)
{
milstoneList
=
MilestoneList
();
if
(
e
.
target
.
dataset
.
action
===
'
tab-milestones
'
&&
!
mil
e
stoneList
)
{
mil
e
stoneList
=
MilestoneList
();
}
};
...
...
ee/app/assets/javascripts/boards/stores/actions.js
View file @
02d8146f
import
{
sortBy
,
pick
}
from
'
lodash
'
;
import
{
pick
}
from
'
lodash
'
;
import
Cookies
from
'
js-cookie
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
...
...
@@ -10,7 +10,12 @@ import { EpicFilterType } from '../constants';
import
boardsStoreEE
from
'
./boards_store_ee
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
{
fullEpicId
}
from
'
../boards_util
'
;
import
{
formatListIssues
,
formatListsPageInfo
,
fullBoardId
}
from
'
~/boards/boards_util
'
;
import
{
formatBoardLists
,
formatListIssues
,
formatListsPageInfo
,
fullBoardId
,
}
from
'
~/boards/boards_util
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
...
...
@@ -112,11 +117,7 @@ export default {
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
);
commit
(
types
.
RECEIVE_BOARD_LISTS_SUCCESS
,
formatBoardLists
(
lists
));
}
if
(
epicsFormatted
)
{
...
...
ee/app/assets/javascripts/boards/stores/getters.js
View file @
02d8146f
...
...
@@ -14,4 +14,11 @@ export default {
getEpicById
:
state
=>
epicId
=>
{
return
state
.
epics
.
find
(
epic
=>
epic
.
id
===
epicId
);
},
shouldUseGraphQL
:
state
=>
{
return
(
(
gon
?.
features
?.
boardsWithSwimlanes
&&
state
.
isShowingEpicsSwimlanes
)
||
gon
?.
features
?.
graphqlBoardLists
);
},
};
ee/app/assets/javascripts/boards/stores/mutations.js
View file @
02d8146f
...
...
@@ -141,8 +141,8 @@ export default {
state
,
{
originalIssue
,
fromListId
,
toListId
,
moveBeforeId
,
moveAfterId
,
epicId
},
)
=>
{
const
fromList
=
state
.
boardLists
.
find
(
l
=>
l
.
id
===
fromListId
)
;
const
toList
=
state
.
boardLists
.
find
(
l
=>
l
.
id
===
toListId
)
;
const
fromList
=
state
.
boardLists
[
fromListId
]
;
const
toList
=
state
.
boardLists
[
toListId
]
;
const
issue
=
moveIssueListHelper
(
originalIssue
,
fromList
,
toList
);
...
...
ee/spec/frontend/boards/components/board_list_selector/board_list_selector_spec.js
View file @
02d8146f
...
...
@@ -7,8 +7,15 @@ import { mockAssigneesList } from 'jest/boards/mock_data';
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
{
createStore
}
from
'
~/boards/stores
'
;
describe
(
'
BoardListSelector
'
,
()
=>
{
global
.
gon
.
features
=
{
...(
global
.
gon
.
features
||
{}),
boardsWithSwimlanes
:
false
,
graphqlBoardLists
:
false
,
};
const
dummyEndpoint
=
`
${
TEST_HOST
}
/users.json`
;
const
createComponent
=
()
=>
...
...
@@ -28,6 +35,7 @@ describe('BoardListSelector', () => {
setFixtures
(
'
<div class="flash-container"></div>
'
);
vm
=
createComponent
();
vm
.
vuexStore
=
createStore
();
});
afterEach
(()
=>
{
...
...
@@ -86,8 +94,8 @@ describe('BoardListSelector', () => {
});
describe
(
'
handleItemClick
'
,
()
=>
{
it
(
'
creates new list in a store instance
'
,
()
=>
{
jest
.
spyOn
(
vm
.
store
,
'
new
'
).
mock
Implementation
(()
=>
{});
it
(
'
graphqlBoardLists FF off -
creates new list in a store instance
'
,
()
=>
{
jest
.
spyOn
(
vm
.
store
,
'
new
'
).
mock
ReturnValue
(
{});
const
assignee
=
mockAssigneesList
[
0
];
expect
(
vm
.
store
.
findList
(
'
title
'
,
assignee
.
name
)).
not
.
toBeDefined
();
...
...
@@ -95,6 +103,20 @@ describe('BoardListSelector', () => {
expect
(
vm
.
store
.
new
).
toHaveBeenCalledWith
(
expect
.
any
(
Object
));
});
it
(
'
graphqlBoardLists FF on - creates new list in a store instance
'
,
()
=>
{
global
.
gon
.
features
.
graphqlBoardLists
=
true
;
jest
.
spyOn
(
vm
.
vuexStore
,
'
dispatch
'
).
mockReturnValue
({});
const
assignee
=
mockAssigneesList
[
0
];
expect
(
vm
.
vuexStore
.
getters
.
getListByTitle
(
assignee
.
name
)).
not
.
toBeDefined
();
vm
.
handleItemClick
(
assignee
);
expect
(
vm
.
vuexStore
.
dispatch
).
toHaveBeenCalledWith
(
'
createList
'
,
{
assigneeId
:
'
gid://gitlab/User/2
'
,
});
});
});
});
});
ee/spec/frontend/boards/stores/mutations_spec.js
View file @
02d8146f
import
mutations
from
'
ee/boards/stores/mutations
'
;
import
{
mockLists
,
mockIssue
,
mockIssue2
,
mockEpics
,
...
...
@@ -18,10 +17,15 @@ const expectNotImplemented = action => {
const
epicId
=
mockEpic
.
id
;
const
initialBoardListsState
=
{
'
gid://gitlab/List/1
'
:
mockListsWithModel
[
0
],
'
gid://gitlab/List/2
'
:
mockListsWithModel
[
1
],
};
let
state
=
{
issuesByListId
:
{},
issues
:
{},
boardLists
:
mockListsWithModel
,
boardLists
:
initialBoardListsState
,
epicsFlags
:
{
[
epicId
]:
{
isLoading
:
true
},
},
...
...
@@ -182,10 +186,10 @@ describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
boardLists
:
{},
};
mutations
.
RECEIVE_BOARD_LISTS_SUCCESS
(
state
,
mockLists
);
mutations
.
RECEIVE_BOARD_LISTS_SUCCESS
(
state
,
initialBoardListsState
);
expect
(
state
.
epicsSwimlanesFetchInProgress
).
toBe
(
false
);
expect
(
state
.
boardLists
).
toEqual
(
mockLists
);
expect
(
state
.
boardLists
).
toEqual
(
initialBoardListsState
);
});
});
...
...
@@ -273,7 +277,6 @@ describe('MOVE_ISSUE', () => {
state
=
{
...
state
,
issuesByListId
:
listIssues
,
boardLists
:
mockListsWithModel
,
issues
,
};
});
...
...
locale/gitlab.pot
View file @
02d8146f
...
...
@@ -2851,9 +2851,6 @@ msgstr ""
msgid "An error occurred while fetching the Service Desk address."
msgstr ""
msgid "An error occurred while fetching the board lists. Please reload the page."
msgstr ""
msgid "An error occurred while fetching the board lists. Please try again."
msgstr ""
...
...
@@ -4185,6 +4182,9 @@ msgstr ""
msgid "Boards|An error occurred while fetching the board issues. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching the board lists. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching the board swimlanes. Please reload the page."
msgstr ""
...
...
spec/frontend/boards/stores/actions_spec.js
View file @
02d8146f
...
...
@@ -177,16 +177,26 @@ describe('createList', () => {
describe
(
'
moveList
'
,
()
=>
{
it
(
'
should commit MOVE_LIST mutation and dispatch updateList action
'
,
done
=>
{
const
initialBoardListsState
=
{
'
gid://gitlab/List/1
'
:
mockListsWithModel
[
0
],
'
gid://gitlab/List/2
'
:
mockListsWithModel
[
1
],
};
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
},
boardType
:
'
group
'
,
disabled
:
false
,
boardLists
:
mockListsWithModel
,
boardLists
:
initialBoardListsState
,
};
testAction
(
actions
.
moveList
,
{
listId
:
'
gid://gitlab/List/1
'
,
newIndex
:
1
,
adjustmentValue
:
1
},
{
listId
:
'
gid://gitlab/List/1
'
,
replacedListId
:
'
gid://gitlab/List/2
'
,
newIndex
:
1
,
adjustmentValue
:
1
,
},
state
,
[
{
...
...
@@ -197,7 +207,11 @@ describe('moveList', () => {
[
{
type
:
'
updateList
'
,
payload
:
{
listId
:
'
gid://gitlab/List/1
'
,
position
:
0
,
backupList
:
mockListsWithModel
},
payload
:
{
listId
:
'
gid://gitlab/List/1
'
,
position
:
0
,
backupList
:
initialBoardListsState
,
},
},
],
done
,
...
...
spec/frontend/boards/stores/getters_spec.js
View file @
02d8146f
import
getters
from
'
~/boards/stores/getters
'
;
import
{
inactiveId
}
from
'
~/boards/constants
'
;
import
{
mockIssue
,
mockIssue2
,
mockIssues
,
mockIssuesByListId
,
issues
}
from
'
../mock_data
'
;
import
{
mockIssue
,
mockIssue2
,
mockIssues
,
mockIssuesByListId
,
issues
,
mockListsWithModel
,
}
from
'
../mock_data
'
;
describe
(
'
Boards - Getters
'
,
()
=>
{
describe
(
'
getLabelToggleState
'
,
()
=>
{
...
...
@@ -130,4 +137,25 @@ describe('Boards - Getters', () => {
);
});
});
const boardsState = {
boardLists: {
'gid://gitlab/List/1': mockListsWithModel[0],
'gid://gitlab/List/2': mockListsWithModel[1],
},
};
describe('getListByLabelId', () => {
it('returns list for a given label id', () => {
expect(getters.getListByLabelId(boardsState)('gid://gitlab/GroupLabel/121')).toEqual(
mockListsWithModel[1],
);
});
});
describe('getListByTitle', () => {
it('returns list for a given list title', () => {
expect(getters.getListByTitle(boardsState)('To Do')).toEqual(mockListsWithModel[1]);
});
});
});
spec/frontend/boards/stores/mutations_spec.js
View file @
02d8146f
...
...
@@ -2,8 +2,6 @@ import mutations from '~/boards/stores/mutations';
import
*
as
types
from
'
~/boards/stores/mutation_types
'
;
import
defaultState
from
'
~/boards/stores/state
'
;
import
{
listObj
,
listObjDuplicate
,
mockListsWithModel
,
mockLists
,
rawIssue
,
...
...
@@ -22,6 +20,11 @@ const expectNotImplemented = action => {
describe
(
'
Board Store Mutations
'
,
()
=>
{
let
state
;
const
initialBoardListsState
=
{
'
gid://gitlab/List/1
'
:
mockListsWithModel
[
0
],
'
gid://gitlab/List/2
'
:
mockListsWithModel
[
1
],
};
beforeEach
(()
=>
{
state
=
defaultState
();
});
...
...
@@ -56,11 +59,19 @@ describe('Board Store Mutations', () => {
describe
(
'
RECEIVE_BOARD_LISTS_SUCCESS
'
,
()
=>
{
it
(
'
Should set boardLists to state
'
,
()
=>
{
const
lists
=
[
listObj
,
listObjDuplicate
];
mutations
[
types
.
RECEIVE_BOARD_LISTS_SUCCESS
](
state
,
initialBoardListsState
);
expect
(
state
.
boardLists
).
toEqual
(
initialBoardListsState
);
});
});
mutations
[
types
.
RECEIVE_BOARD_LISTS_SUCCESS
](
state
,
lists
);
describe
(
'
RECEIVE_BOARD_LISTS_FAILURE
'
,
()
=>
{
it
(
'
Should set error in state
'
,
()
=>
{
mutations
[
types
.
RECEIVE_BOARD_LISTS_FAILURE
](
state
);
expect
(
state
.
boardLists
).
toEqual
(
lists
);
expect
(
state
.
error
).
toEqual
(
'
An error occurred while fetching the board lists. Please reload the page.
'
,
);
});
});
...
...
@@ -95,7 +106,13 @@ describe('Board Store Mutations', () => {
});
describe
(
'
RECEIVE_ADD_LIST_SUCCESS
'
,
()
=>
{
expectNotImplemented
(
mutations
.
RECEIVE_ADD_LIST_SUCCESS
);
it
(
'
adds list to boardLists state
'
,
()
=>
{
mutations
.
RECEIVE_ADD_LIST_SUCCESS
(
state
,
mockListsWithModel
[
0
]);
expect
(
state
.
boardLists
).
toEqual
({
[
mockListsWithModel
[
0
].
id
]:
mockListsWithModel
[
0
],
});
});
});
describe
(
'
RECEIVE_ADD_LIST_ERROR
'
,
()
=>
{
...
...
@@ -106,7 +123,7 @@ describe('Board Store Mutations', () => {
it
(
'
updates boardLists state with reordered lists
'
,
()
=>
{
state
=
{
...
state
,
boardLists
:
mockListsWithModel
,
boardLists
:
initialBoardListsState
,
};
mutations
.
MOVE_LIST
(
state
,
{
...
...
@@ -114,7 +131,10 @@ describe('Board Store Mutations', () => {
listAtNewIndex
:
mockListsWithModel
[
1
],
});
expect
(
state
.
boardLists
).
toEqual
([
mockListsWithModel
[
1
],
mockListsWithModel
[
0
]]);
expect
(
state
.
boardLists
).
toEqual
({
'
gid://gitlab/List/2
'
:
mockListsWithModel
[
1
],
'
gid://gitlab/List/1
'
:
mockListsWithModel
[
0
],
});
});
});
...
...
@@ -122,13 +142,16 @@ describe('Board Store Mutations', () => {
it
(
'
updates boardLists state with previous order and sets error message
'
,
()
=>
{
state
=
{
...
state
,
boardLists
:
[
mockListsWithModel
[
1
],
mockListsWithModel
[
0
]],
boardLists
:
{
'
gid://gitlab/List/2
'
:
mockListsWithModel
[
1
],
'
gid://gitlab/List/1
'
:
mockListsWithModel
[
0
],
},
error
:
undefined
,
};
mutations
.
UPDATE_LIST_FAILURE
(
state
,
mockListsWithModel
);
mutations
.
UPDATE_LIST_FAILURE
(
state
,
initialBoardListsState
);
expect
(
state
.
boardLists
).
toEqual
(
mockListsWithModel
);
expect
(
state
.
boardLists
).
toEqual
(
initialBoardListsState
);
expect
(
state
.
error
).
toEqual
(
'
An error occurred while updating the list. Please try again.
'
);
});
});
...
...
@@ -177,7 +200,7 @@ describe('Board Store Mutations', () => {
'
gid://gitlab/List/1
'
:
[],
},
issues
:
{},
boardLists
:
mockListsWithModel
,
boardLists
:
initialBoardListsState
,
};
const
listPageInfo
=
{
...
...
@@ -202,7 +225,7 @@ describe('Board Store Mutations', () => {
it
(
'
sets error message
'
,
()
=>
{
state
=
{
...
state
,
boardLists
:
mockListsWithModel
,
boardLists
:
initialBoardListsState
,
error
:
undefined
,
};
...
...
@@ -284,7 +307,7 @@ describe('Board Store Mutations', () => {
state
=
{
...
state
,
issuesByListId
:
listIssues
,
boardLists
:
mockListsWithModel
,
boardLists
:
initialBoardListsState
,
issues
,
};
...
...
@@ -332,7 +355,7 @@ describe('Board Store Mutations', () => {
state
=
{
...
state
,
issuesByListId
:
listIssues
,
boardLists
:
mockListsWithModel
,
boardLists
:
initialBoardListsState
,
};
mutations
.
MOVE_ISSUE_FAILURE
(
state
,
{
...
...
@@ -400,7 +423,7 @@ describe('Board Store Mutations', () => {
...
state
,
issuesByListId
:
listIssues
,
issues
,
boardLists
:
mockListsWithModel
,
boardLists
:
initialBoardListsState
,
};
mutations
.
ADD_ISSUE_TO_LIST_FAILURE
(
state
,
{
list
:
mockLists
[
0
],
issue
:
mockIssue2
});
...
...
spec/serializers/label_serializer_spec.rb
View file @
02d8146f
...
...
@@ -37,11 +37,12 @@ RSpec.describe LabelSerializer do
subject
{
serializer
.
represent_appearance
(
resource
)
}
it
'serializes only attributes used for appearance'
do
expect
(
subject
.
keys
).
to
eq
([
:id
,
:title
,
:color
,
:text_color
])
expect
(
subject
.
keys
).
to
eq
([
:id
,
:title
,
:color
,
:
project_id
,
:
text_color
])
expect
(
subject
[
:id
]).
to
eq
(
resource
.
id
)
expect
(
subject
[
:title
]).
to
eq
(
resource
.
title
)
expect
(
subject
[
:color
]).
to
eq
(
resource
.
color
)
expect
(
subject
[
:text_color
]).
to
eq
(
resource
.
text_color
)
expect
(
subject
[
:project_id
]).
to
eq
(
resource
.
project_id
)
end
end
end
...
...
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