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
78ef57fa
Commit
78ef57fa
authored
Sep 02, 2021
by
Florie Guibert
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Delete legacy board store and models
Clean up legacy board code
parent
d98c7844
Changes
45
Show whitespace changes
Inline
Side-by-side
Showing
45 changed files
with
19 additions
and
4486 deletions
+19
-4486
app/assets/javascripts/boards/components/board_content.vue
app/assets/javascripts/boards/components/board_content.vue
+0
-5
app/assets/javascripts/boards/components/board_form.vue
app/assets/javascripts/boards/components/board_form.vue
+4
-9
app/assets/javascripts/boards/components/boards_selector_deprecated.vue
...ascripts/boards/components/boards_selector_deprecated.vue
+0
-360
app/assets/javascripts/boards/ee_functions.js
app/assets/javascripts/boards/ee_functions.js
+0
-4
app/assets/javascripts/boards/index.js
app/assets/javascripts/boards/index.js
+7
-132
app/assets/javascripts/boards/models/assignee.js
app/assets/javascripts/boards/models/assignee.js
+0
-13
app/assets/javascripts/boards/models/issue.js
app/assets/javascripts/boards/models/issue.js
+0
-99
app/assets/javascripts/boards/models/iteration.js
app/assets/javascripts/boards/models/iteration.js
+0
-9
app/assets/javascripts/boards/models/label.js
app/assets/javascripts/boards/models/label.js
+0
-11
app/assets/javascripts/boards/models/list.js
app/assets/javascripts/boards/models/list.js
+0
-182
app/assets/javascripts/boards/models/milestone.js
app/assets/javascripts/boards/models/milestone.js
+0
-15
app/assets/javascripts/boards/models/project.js
app/assets/javascripts/boards/models/project.js
+0
-7
app/assets/javascripts/boards/stores/actions.js
app/assets/javascripts/boards/stores/actions.js
+3
-0
app/assets/javascripts/boards/stores/boards_store.js
app/assets/javascripts/boards/stores/boards_store.js
+0
-872
app/assets/javascripts/boards/stores/boards_store_ee.js
app/assets/javascripts/boards/stores/boards_store_ee.js
+0
-5
app/views/shared/boards/_show.html.haml
app/views/shared/boards/_show.html.haml
+1
-1
ee/app/assets/javascripts/boards/components/boards_list_selector/assignees_list_item.vue
...s/components/boards_list_selector/assignees_list_item.vue
+0
-42
ee/app/assets/javascripts/boards/components/boards_list_selector/index.js
...vascripts/boards/components/boards_list_selector/index.js
+0
-89
ee/app/assets/javascripts/boards/components/boards_list_selector/list_container.vue
...boards/components/boards_list_selector/list_container.vue
+0
-70
ee/app/assets/javascripts/boards/components/boards_list_selector/list_content.vue
...s/boards/components/boards_list_selector/list_content.vue
+0
-41
ee/app/assets/javascripts/boards/components/boards_list_selector/list_filter.vue
...ts/boards/components/boards_list_selector/list_filter.vue
+0
-37
ee/app/assets/javascripts/boards/components/boards_list_selector/milestones_list_item.vue
.../components/boards_list_selector/milestones_list_item.vue
+0
-28
ee/app/assets/javascripts/boards/ee_functions.js
ee/app/assets/javascripts/boards/ee_functions.js
+0
-10
ee/app/assets/javascripts/boards/models/issue.js
ee/app/assets/javascripts/boards/models/issue.js
+0
-25
ee/app/assets/javascripts/boards/models/list.js
ee/app/assets/javascripts/boards/models/list.js
+0
-51
ee/app/assets/javascripts/boards/stores/boards_store_ee.js
ee/app/assets/javascripts/boards/stores/boards_store_ee.js
+0
-219
ee/app/assets/javascripts/epic/models/label.js
ee/app/assets/javascripts/epic/models/label.js
+0
-2
ee/app/assets/javascripts/epic_boards/index.js
ee/app/assets/javascripts/epic_boards/index.js
+0
-3
ee/app/assets/javascripts/vue_shared/components/sidebar/epics_select/store/actions.js
...e_shared/components/sidebar/epics_select/store/actions.js
+1
-38
ee/spec/frontend/boards/components/board_list_selector/assignees_list_item_spec.js
...omponents/board_list_selector/assignees_list_item_spec.js
+0
-52
ee/spec/frontend/boards/components/board_list_selector/board_list_selector_spec.js
...omponents/board_list_selector/board_list_selector_spec.js
+0
-104
ee/spec/frontend/boards/components/board_list_selector/list_container_spec.js
...rds/components/board_list_selector/list_container_spec.js
+0
-92
ee/spec/frontend/boards/components/board_list_selector/list_content_spec.js
...oards/components/board_list_selector/list_content_spec.js
+0
-36
ee/spec/frontend/boards/components/board_list_selector/list_filter_spec.js
...boards/components/board_list_selector/list_filter_spec.js
+0
-81
ee/spec/frontend/boards/mock_data.js
ee/spec/frontend/boards/mock_data.js
+0
-9
ee/spec/frontend/boards/models/list_spec.js
ee/spec/frontend/boards/models/list_spec.js
+0
-121
ee/spec/frontend/boards/stores/actions_spec.js
ee/spec/frontend/boards/stores/actions_spec.js
+0
-10
ee/spec/frontend/boards/stores/boards_store_ee_spec.js
ee/spec/frontend/boards/stores/boards_store_ee_spec.js
+0
-126
ee/spec/frontend/vue_shared/components/sidebar/epics_select/store/actions_spec.js
...red/components/sidebar/epics_select/store/actions_spec.js
+0
-30
locale/gitlab.pot
locale/gitlab.pot
+0
-15
spec/frontend/boards/boards_store_spec.js
spec/frontend/boards/boards_store_spec.js
+0
-1013
spec/frontend/boards/components/board_content_spec.js
spec/frontend/boards/components/board_content_spec.js
+3
-3
spec/frontend/boards/issue_spec.js
spec/frontend/boards/issue_spec.js
+0
-162
spec/frontend/boards/list_spec.js
spec/frontend/boards/list_spec.js
+0
-230
spec/frontend/boards/mock_data.js
spec/frontend/boards/mock_data.js
+0
-23
No files found.
app/assets/javascripts/boards/components/board_content.vue
View file @
78ef57fa
...
...
@@ -21,11 +21,6 @@ export default {
},
inject
:
[
'
canAdminList
'
],
props
:
{
lists
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
disabled
:
{
type
:
Boolean
,
required
:
true
,
...
...
app/assets/javascripts/boards/components/board_form.vue
View file @
78ef57fa
<
script
>
import
{
GlModal
,
GlAlert
}
from
'
@gitlab/ui
'
;
import
{
mapGetters
,
mapActions
,
mapState
}
from
'
vuex
'
;
import
ListLabel
from
'
~/boards/models/label
'
;
import
{
TYPE_ITERATION
,
TYPE_MILESTONE
}
from
'
~/graphql_shared/constants
'
;
import
{
convertToGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
getParameterByName
,
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
...
...
@@ -289,14 +288,10 @@ export default {
setBoardLabels
(
labels
)
{
labels
.
forEach
((
label
)
=>
{
if
(
label
.
set
&&
!
this
.
board
.
labels
.
find
((
l
)
=>
l
.
id
===
label
.
id
))
{
this
.
board
.
labels
.
push
(
new
ListLabel
({
id
:
label
.
id
,
title
:
label
.
title
,
color
:
label
.
color
,
this
.
board
.
labels
.
push
({
...
label
,
textColor
:
label
.
text_color
,
}),
);
});
}
else
if
(
!
label
.
set
)
{
this
.
board
.
labels
=
this
.
board
.
labels
.
filter
((
selected
)
=>
selected
.
id
!==
label
.
id
);
}
...
...
app/assets/javascripts/boards/components/boards_selector_deprecated.vue
deleted
100644 → 0
View file @
d98c7844
<
script
>
import
{
GlLoadingIcon
,
GlSearchBoxByType
,
GlDropdown
,
GlDropdownDivider
,
GlDropdownSectionHeader
,
GlDropdownItem
,
GlModalDirective
,
}
from
'
@gitlab/ui
'
;
import
{
throttle
}
from
'
lodash
'
;
import
{
mapGetters
,
mapState
}
from
'
vuex
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
httpStatusCodes
from
'
~/lib/utils/http_status
'
;
import
groupQuery
from
'
../graphql/group_boards.query.graphql
'
;
import
projectQuery
from
'
../graphql/project_boards.query.graphql
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
BoardForm
from
'
./board_form.vue
'
;
const
MIN_BOARDS_TO_VIEW_RECENT
=
10
;
export
default
{
name
:
'
BoardsSelector
'
,
components
:
{
BoardForm
,
GlLoadingIcon
,
GlSearchBoxByType
,
GlDropdown
,
GlDropdownDivider
,
GlDropdownSectionHeader
,
GlDropdownItem
,
},
directives
:
{
GlModalDirective
,
},
props
:
{
currentBoard
:
{
type
:
Object
,
required
:
true
,
},
throttleDuration
:
{
type
:
Number
,
default
:
200
,
required
:
false
,
},
boardBaseUrl
:
{
type
:
String
,
required
:
true
,
},
hasMissingBoards
:
{
type
:
Boolean
,
required
:
true
,
},
canAdminBoard
:
{
type
:
Boolean
,
required
:
true
,
},
multipleIssueBoardsAvailable
:
{
type
:
Boolean
,
required
:
true
,
},
labelsPath
:
{
type
:
String
,
required
:
true
,
},
labelsWebUrl
:
{
type
:
String
,
required
:
true
,
},
projectId
:
{
type
:
Number
,
required
:
true
,
},
groupId
:
{
type
:
Number
,
required
:
true
,
},
scopedIssueBoardFeatureEnabled
:
{
type
:
Boolean
,
required
:
true
,
},
weights
:
{
type
:
Array
,
required
:
true
,
},
enabledScopedLabels
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
data
()
{
return
{
hasScrollFade
:
false
,
loadingBoards
:
0
,
loadingRecentBoards
:
false
,
scrollFadeInitialized
:
false
,
boards
:
[],
recentBoards
:
[],
state
:
boardsStore
.
state
,
throttledSetScrollFade
:
throttle
(
this
.
setScrollFade
,
this
.
throttleDuration
),
contentClientHeight
:
0
,
maxPosition
:
0
,
store
:
boardsStore
,
filterTerm
:
''
,
};
},
computed
:
{
...
mapState
([
'
boardType
'
]),
...
mapGetters
([
'
isGroupBoard
'
]),
parentType
()
{
return
this
.
boardType
;
},
loading
()
{
return
this
.
loadingRecentBoards
||
Boolean
(
this
.
loadingBoards
);
},
currentPage
()
{
return
this
.
state
.
currentPage
;
},
filteredBoards
()
{
return
this
.
boards
.
filter
((
board
)
=>
board
.
name
.
toLowerCase
().
includes
(
this
.
filterTerm
.
toLowerCase
()),
);
},
board
()
{
return
this
.
state
.
currentBoard
;
},
showDelete
()
{
return
this
.
boards
.
length
>
1
;
},
scrollFadeClass
()
{
return
{
'
fade-out
'
:
!
this
.
hasScrollFade
,
};
},
showRecentSection
()
{
return
(
this
.
recentBoards
.
length
&&
this
.
boards
.
length
>
MIN_BOARDS_TO_VIEW_RECENT
&&
!
this
.
filterTerm
.
length
);
},
},
watch
:
{
filteredBoards
()
{
this
.
scrollFadeInitialized
=
false
;
this
.
$nextTick
(
this
.
setScrollFade
);
},
},
created
()
{
boardsStore
.
setCurrentBoard
(
this
.
currentBoard
);
},
methods
:
{
showPage
(
page
)
{
boardsStore
.
showPage
(
page
);
},
cancel
()
{
this
.
showPage
(
''
);
},
loadBoards
(
toggleDropdown
=
true
)
{
if
(
toggleDropdown
&&
this
.
boards
.
length
>
0
)
{
return
;
}
this
.
$apollo
.
addSmartQuery
(
'
boards
'
,
{
variables
()
{
return
{
fullPath
:
this
.
state
.
endpoints
.
fullPath
};
},
query
()
{
return
this
.
isGroupBoard
?
groupQuery
:
projectQuery
;
},
loadingKey
:
'
loadingBoards
'
,
update
(
data
)
{
if
(
!
data
?.[
this
.
parentType
])
{
return
[];
}
return
data
[
this
.
parentType
].
boards
.
edges
.
map
(({
node
})
=>
({
id
:
getIdFromGraphQLId
(
node
.
id
),
name
:
node
.
name
,
}));
},
});
this
.
loadingRecentBoards
=
true
;
boardsStore
.
recentBoards
()
.
then
((
res
)
=>
{
this
.
recentBoards
=
res
.
data
;
})
.
catch
((
err
)
=>
{
/**
* If user is unauthorized we'd still want to resolve the
* request to display all boards.
*/
if
(
err
?.
response
?.
status
===
httpStatusCodes
.
UNAUTHORIZED
)
{
this
.
recentBoards
=
[];
// recent boards are empty
return
;
}
throw
err
;
})
.
then
(()
=>
this
.
$nextTick
())
// Wait for boards list in DOM
.
then
(()
=>
{
this
.
setScrollFade
();
})
.
catch
(()
=>
{})
.
finally
(()
=>
{
this
.
loadingRecentBoards
=
false
;
});
},
isScrolledUp
()
{
const
{
content
}
=
this
.
$refs
;
if
(
!
content
)
{
return
false
;
}
const
currentPosition
=
this
.
contentClientHeight
+
content
.
scrollTop
;
return
currentPosition
<
this
.
maxPosition
;
},
initScrollFade
()
{
const
{
content
}
=
this
.
$refs
;
if
(
!
content
)
{
return
;
}
this
.
scrollFadeInitialized
=
true
;
this
.
contentClientHeight
=
content
.
clientHeight
;
this
.
maxPosition
=
content
.
scrollHeight
;
},
setScrollFade
()
{
if
(
!
this
.
scrollFadeInitialized
)
this
.
initScrollFade
();
this
.
hasScrollFade
=
this
.
isScrolledUp
();
},
},
};
</
script
>
<
template
>
<div
class=
"boards-switcher js-boards-selector gl-mr-3"
>
<span
class=
"boards-selector-wrapper js-boards-selector-wrapper"
>
<gl-dropdown
data-qa-selector=
"boards_dropdown"
toggle-class=
"dropdown-menu-toggle js-dropdown-toggle"
menu-class=
"flex-column dropdown-extended-height"
:text=
"board.name"
@
show=
"loadBoards"
>
<p
class=
"gl-new-dropdown-header-top"
@
mousedown
.
prevent
>
{{
s__
(
'
IssueBoards|Switch board
'
)
}}
</p>
<gl-search-box-by-type
ref=
"searchBox"
v-model=
"filterTerm"
class=
"m-2"
/>
<div
v-if=
"!loading"
ref=
"content"
data-qa-selector=
"boards_dropdown_content"
class=
"dropdown-content flex-fill"
@
scroll.passive=
"throttledSetScrollFade"
>
<gl-dropdown-item
v-show=
"filteredBoards.length === 0"
class=
"gl-pointer-events-none text-secondary"
>
{{
s__
(
'
IssueBoards|No matching boards found
'
)
}}
</gl-dropdown-item>
<gl-dropdown-section-header
v-if=
"showRecentSection"
>
{{
__
(
'
Recent
'
)
}}
</gl-dropdown-section-header>
<template
v-if=
"showRecentSection"
>
<gl-dropdown-item
v-for=
"recentBoard in recentBoards"
:key=
"`recent-$
{recentBoard.id}`"
class="js-dropdown-item"
:href="`${boardBaseUrl}/${recentBoard.id}`"
>
{{
recentBoard
.
name
}}
</gl-dropdown-item>
</
template
>
<gl-dropdown-divider
v-if=
"showRecentSection"
/>
<gl-dropdown-section-header
v-if=
"showRecentSection"
>
{{ __('All') }}
</gl-dropdown-section-header>
<gl-dropdown-item
v-for=
"otherBoard in filteredBoards"
:key=
"otherBoard.id"
class=
"js-dropdown-item"
:href=
"`${boardBaseUrl}/${otherBoard.id}`"
>
{{ otherBoard.name }}
</gl-dropdown-item>
<gl-dropdown-item
v-if=
"hasMissingBoards"
class=
"no-pointer-events"
>
{{
s__(
'IssueBoards|Some of your boards are hidden, activate a license to see them again.',
)
}}
</gl-dropdown-item>
</div>
<div
v-show=
"filteredBoards.length > 0"
class=
"dropdown-content-faded-mask"
:class=
"scrollFadeClass"
></div>
<gl-loading-icon
v-if=
"loading"
size=
"sm"
/>
<div
v-if=
"canAdminBoard"
>
<gl-dropdown-divider
/>
<gl-dropdown-item
v-if=
"multipleIssueBoardsAvailable"
v-gl-modal-directive=
"'board-config-modal'"
data-qa-selector=
"create_new_board_button"
@
click.prevent=
"showPage('new')"
>
{{ s__('IssueBoards|Create new board') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if=
"showDelete"
v-gl-modal-directive=
"'board-config-modal'"
class=
"text-danger js-delete-board"
@
click.prevent=
"showPage('delete')"
>
{{ s__('IssueBoards|Delete board') }}
</gl-dropdown-item>
</div>
</gl-dropdown>
<board-form
v-if=
"currentPage"
:labels-path=
"labelsPath"
:labels-web-url=
"labelsWebUrl"
:project-id=
"projectId"
:group-id=
"groupId"
:can-admin-board=
"canAdminBoard"
:scoped-issue-board-feature-enabled=
"scopedIssueBoardFeatureEnabled"
:weights=
"weights"
:enable-scoped-labels=
"enabledScopedLabels"
:current-board=
"currentBoard"
:current-page=
"state.currentPage"
@
cancel=
"cancel"
/>
</span>
</div>
</template>
app/assets/javascripts/boards/ee_functions.js
deleted
100644 → 0
View file @
d98c7844
export
const
setWeightFetchingState
=
()
=>
{};
export
const
setEpicFetchingState
=
()
=>
{};
export
const
getMilestoneTitle
=
()
=>
({});
app/assets/javascripts/boards/index.js
View file @
78ef57fa
...
...
@@ -4,33 +4,19 @@ import Vue from 'vue';
import
VueApollo
from
'
vue-apollo
'
;
import
{
mapActions
}
from
'
vuex
'
;
import
'
ee_else_ce/boards/models/issue
'
;
import
'
ee_else_ce/boards/models/list
'
;
import
{
setWeightFetchingState
,
setEpicFetchingState
}
from
'
ee_else_ce/boards/ee_functions
'
;
import
toggleEpicsSwimlanes
from
'
ee_else_ce/boards/toggle_epics_swimlanes
'
;
import
toggleLabels
from
'
ee_else_ce/boards/toggle_labels
'
;
import
BoardAddNewColumnTrigger
from
'
~/boards/components/board_add_new_column_trigger.vue
'
;
import
BoardContent
from
'
~/boards/components/board_content.vue
'
;
import
'
./models/label
'
;
import
'
./models/assignee
'
;
import
'
~/boards/models/milestone
'
;
import
'
~/boards/models/project
'
;
import
'
~/boards/filters/due_date_filters
'
;
import
{
issuableTypes
}
from
'
~/boards/constants
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
FilteredSearchBoards
from
'
~/boards/filtered_search_boards
'
;
import
initBoardsFilteredSearch
from
'
~/boards/mount_filtered_search_issue_boards
'
;
import
store
from
'
~/boards/stores
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
toggleFocusMode
from
'
~/boards/toggle_focus
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
import
{
NavigationType
,
convertObjectPropsToCamelCase
,
parseBoolean
,
}
from
'
~/lib/utils/common_utils
'
;
import
{
__
}
from
'
~/locale
'
;
import
sidebarEventHub
from
'
~/sidebar/event_hub
'
;
import
{
NavigationType
,
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
introspectionQueryResultData
from
'
~/sidebar/fragmentTypes.json
'
;
import
{
fullBoardId
}
from
'
./boards_util
'
;
import
boardConfigToggle
from
'
./config_toggle
'
;
...
...
@@ -77,8 +63,6 @@ export default () => {
initBoardsFilteredSearch
(
apolloProvider
);
}
boardsStore
.
create
();
// eslint-disable-next-line @gitlab/no-runtime-template-compiler
issueBoardsApp
=
new
Vue
({
el
:
$boardApp
,
...
...
@@ -116,22 +100,13 @@ export default () => {
apolloProvider
,
data
()
{
return
{
state
:
boardsStore
.
state
,
loading
:
0
,
boardsEndpoint
:
$boardApp
.
dataset
.
boardsEndpoint
,
recentBoardsEndpoint
:
$boardApp
.
dataset
.
recentBoardsEndpoint
,
listsEndpoint
:
$boardApp
.
dataset
.
listsEndpoint
,
disabled
:
parseBoolean
(
$boardApp
.
dataset
.
disabled
),
bulkUpdatePath
:
$boardApp
.
dataset
.
bulkUpdatePath
,
detailIssue
:
boardsStore
.
detail
,
parent
:
$boardApp
.
dataset
.
parent
,
detailIssueVisible
:
false
,
};
},
computed
:
{
detailIssueVisible
()
{
return
Object
.
keys
(
this
.
detailIssue
.
issue
).
length
;
},
},
created
()
{
this
.
setInitialBoardData
({
boardId
:
$boardApp
.
dataset
.
boardId
,
...
...
@@ -154,129 +129,29 @@ export default () => {
:
null
,
},
});
boardsStore
.
setEndpoints
({
boardsEndpoint
:
this
.
boardsEndpoint
,
recentBoardsEndpoint
:
this
.
recentBoardsEndpoint
,
listsEndpoint
:
this
.
listsEndpoint
,
bulkUpdatePath
:
this
.
bulkUpdatePath
,
boardId
:
$boardApp
.
dataset
.
boardId
,
fullPath
:
$boardApp
.
dataset
.
fullPath
,
});
boardsStore
.
rootPath
=
this
.
boardsEndpoint
;
eventHub
.
$on
(
'
updateTokens
'
,
this
.
updateTokens
);
eventHub
.
$on
(
'
newDetailIssue
'
,
this
.
updateDetailIssue
);
eventHub
.
$on
(
'
clearDetailIssue
'
,
this
.
clearDetailIssue
);
sidebarEventHub
.
$on
(
'
toggleSubscription
'
,
this
.
toggleSubscription
);
eventHub
.
$on
(
'
toggleDetailIssue
'
,
this
.
toggleDetailIssue
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'
updateTokens
'
,
this
.
updateTokens
);
eventHub
.
$off
(
'
newDetailIssue
'
,
this
.
updateDetailIssue
);
eventHub
.
$off
(
'
clearDetailIssue
'
,
this
.
clearDetailIssue
);
sidebarEventHub
.
$off
(
'
toggleSubscription
'
,
this
.
toggleSubscription
);
eventHub
.
$off
(
'
toggleDetailIssue
'
,
this
.
toggleDetailIssue
);
},
mounted
()
{
if
(
!
gon
?.
features
?.
issueBoardsFilteredSearch
)
{
this
.
filterManager
=
new
FilteredSearchBoards
(
boardsStore
.
filter
,
true
,
boardsStore
.
cantEdit
,
);
this
.
filterManager
=
new
FilteredSearchBoards
({
path
:
''
},
true
,
[]);
this
.
filterManager
.
setup
();
}
this
.
performSearch
();
boardsStore
.
disabled
=
this
.
disabled
;
},
methods
:
{
...
mapActions
([
'
setInitialBoardData
'
,
'
performSearch
'
,
'
setError
'
]),
updateTokens
()
{
this
.
filterManager
.
updateTokens
();
},
updateDetailIssue
(
newIssue
,
multiSelect
=
false
)
{
const
{
sidebarInfoEndpoint
}
=
newIssue
;
if
(
sidebarInfoEndpoint
&&
newIssue
.
subscribed
===
undefined
)
{
newIssue
.
setFetchingState
(
'
subscriptions
'
,
true
);
setWeightFetchingState
(
newIssue
,
true
);
setEpicFetchingState
(
newIssue
,
true
);
boardsStore
.
getIssueInfo
(
sidebarInfoEndpoint
)
.
then
((
res
)
=>
res
.
data
)
.
then
((
data
)
=>
{
const
{
subscribed
,
totalTimeSpent
,
timeEstimate
,
humanTimeEstimate
,
humanTotalTimeSpent
,
weight
,
epic
,
assignees
,
}
=
convertObjectPropsToCamelCase
(
data
);
newIssue
.
setFetchingState
(
'
subscriptions
'
,
false
);
setWeightFetchingState
(
newIssue
,
false
);
setEpicFetchingState
(
newIssue
,
false
);
newIssue
.
updateData
({
humanTimeSpent
:
humanTotalTimeSpent
,
timeSpent
:
totalTimeSpent
,
humanTimeEstimate
,
timeEstimate
,
subscribed
,
weight
,
epic
,
assignees
,
});
})
.
catch
(()
=>
{
newIssue
.
setFetchingState
(
'
subscriptions
'
,
false
);
setWeightFetchingState
(
newIssue
,
false
);
this
.
setError
({
message
:
__
(
'
An error occurred while fetching sidebar data
'
)
});
});
}
if
(
multiSelect
)
{
boardsStore
.
toggleMultiSelect
(
newIssue
);
if
(
boardsStore
.
detail
.
issue
)
{
boardsStore
.
clearDetailIssue
();
return
;
}
return
;
}
boardsStore
.
setIssueDetail
(
newIssue
);
},
clearDetailIssue
(
multiSelect
=
false
)
{
if
(
multiSelect
)
{
boardsStore
.
clearMultiSelect
();
}
boardsStore
.
clearDetailIssue
();
},
toggleSubscription
(
id
)
{
const
{
issue
}
=
boardsStore
.
detail
;
if
(
issue
.
id
===
id
&&
issue
.
toggleSubscriptionEndpoint
)
{
issue
.
setFetchingState
(
'
subscriptions
'
,
true
);
boardsStore
.
toggleIssueSubscription
(
issue
.
toggleSubscriptionEndpoint
)
.
then
(()
=>
{
issue
.
setFetchingState
(
'
subscriptions
'
,
false
);
issue
.
updateData
({
subscribed
:
!
issue
.
subscribed
,
});
})
.
catch
(()
=>
{
issue
.
setFetchingState
(
'
subscriptions
'
,
false
);
this
.
setError
({
message
:
__
(
'
An error occurred when toggling the notification subscription
'
),
});
});
}
},
getNodes
(
data
)
{
return
data
[
this
.
parent
]?.
board
?.
lists
.
nodes
;
toggleDetailIssue
(
hasSidebar
)
{
this
.
detailIssueVisible
=
hasSidebar
;
},
},
});
...
...
app/assets/javascripts/boards/models/assignee.js
deleted
100644 → 0
View file @
d98c7844
export
default
class
ListAssignee
{
constructor
(
obj
)
{
this
.
id
=
obj
.
id
;
this
.
name
=
obj
.
name
;
this
.
username
=
obj
.
username
;
this
.
avatar
=
obj
.
avatarUrl
||
obj
.
avatar_url
||
obj
.
avatar
||
gon
.
default_avatar_url
;
this
.
path
=
obj
.
path
;
this
.
state
=
obj
.
state
;
this
.
webUrl
=
obj
.
web_url
||
obj
.
webUrl
;
}
}
window
.
ListAssignee
=
ListAssignee
;
app/assets/javascripts/boards/models/issue.js
deleted
100644 → 0
View file @
d98c7844
/* eslint-disable no-unused-vars */
/* global ListLabel */
/* global ListMilestone */
/* global ListAssignee */
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
'
./label
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
IssueProject
from
'
./project
'
;
class
ListIssue
{
constructor
(
obj
)
{
this
.
subscribed
=
obj
.
subscribed
;
this
.
labels
=
[];
this
.
assignees
=
[];
this
.
selected
=
false
;
this
.
position
=
obj
.
position
||
obj
.
relative_position
||
obj
.
relativePosition
||
Infinity
;
this
.
isFetching
=
{
subscriptions
:
true
,
};
this
.
closed
=
obj
.
closed
;
this
.
isLoading
=
{};
this
.
refreshData
(
obj
);
}
refreshData
(
obj
)
{
boardsStore
.
refreshIssueData
(
this
,
obj
);
}
addLabel
(
label
)
{
boardsStore
.
addIssueLabel
(
this
,
label
);
}
findLabel
(
findLabel
)
{
return
boardsStore
.
findIssueLabel
(
this
,
findLabel
);
}
removeLabel
(
removeLabel
)
{
boardsStore
.
removeIssueLabel
(
this
,
removeLabel
);
}
removeLabels
(
labels
)
{
boardsStore
.
removeIssueLabels
(
this
,
labels
);
}
addAssignee
(
assignee
)
{
boardsStore
.
addIssueAssignee
(
this
,
assignee
);
}
findAssignee
(
findAssignee
)
{
return
boardsStore
.
findIssueAssignee
(
this
,
findAssignee
);
}
setAssignees
(
assignees
)
{
boardsStore
.
setIssueAssignees
(
this
,
assignees
);
}
removeAssignee
(
removeAssignee
)
{
boardsStore
.
removeIssueAssignee
(
this
,
removeAssignee
);
}
removeAllAssignees
()
{
boardsStore
.
removeAllIssueAssignees
(
this
);
}
addMilestone
(
milestone
)
{
boardsStore
.
addIssueMilestone
(
this
,
milestone
);
}
removeMilestone
(
removeMilestone
)
{
boardsStore
.
removeIssueMilestone
(
this
,
removeMilestone
);
}
getLists
()
{
return
boardsStore
.
state
.
lists
.
filter
((
list
)
=>
list
.
findIssue
(
this
.
id
));
}
updateData
(
newData
)
{
boardsStore
.
updateIssueData
(
this
,
newData
);
}
setFetchingState
(
key
,
value
)
{
boardsStore
.
setIssueFetchingState
(
this
,
key
,
value
);
}
setLoadingState
(
key
,
value
)
{
boardsStore
.
setIssueLoadingState
(
this
,
key
,
value
);
}
update
()
{
return
boardsStore
.
updateIssue
(
this
);
}
}
window
.
ListIssue
=
ListIssue
;
export
default
ListIssue
;
app/assets/javascripts/boards/models/iteration.js
deleted
100644 → 0
View file @
d98c7844
export
default
class
ListIteration
{
constructor
(
obj
)
{
this
.
id
=
obj
.
id
;
this
.
title
=
obj
.
title
;
this
.
state
=
obj
.
state
;
this
.
webUrl
=
obj
.
web_url
||
obj
.
webUrl
;
this
.
description
=
obj
.
description
;
}
}
app/assets/javascripts/boards/models/label.js
deleted
100644 → 0
View file @
d98c7844
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
export
default
class
ListLabel
{
constructor
(
obj
)
{
Object
.
assign
(
this
,
convertObjectPropsToCamelCase
(
obj
,
{
dropKeys
:
[
'
priority
'
]
}),
{
priority
:
obj
.
priority
!==
null
?
obj
.
priority
:
Infinity
,
});
}
}
window
.
ListLabel
=
ListLabel
;
app/assets/javascripts/boards/models/list.js
deleted
100644 → 0
View file @
d98c7844
/* eslint-disable class-methods-use-this */
import
createFlash
from
'
~/flash
'
;
import
{
__
}
from
'
~/locale
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
ListAssignee
from
'
./assignee
'
;
import
ListIteration
from
'
./iteration
'
;
import
ListLabel
from
'
./label
'
;
import
ListMilestone
from
'
./milestone
'
;
import
'
ee_else_ce/boards/models/issue
'
;
const
TYPES
=
{
backlog
:
{
isPreset
:
true
,
isExpandable
:
true
,
isBlank
:
false
,
},
closed
:
{
isPreset
:
true
,
isExpandable
:
true
,
isBlank
:
false
,
},
blank
:
{
isPreset
:
true
,
isExpandable
:
false
,
isBlank
:
true
,
},
default
:
{
// includes label, assignee, and milestone lists
isPreset
:
false
,
isExpandable
:
true
,
isBlank
:
false
,
},
};
class
List
{
constructor
(
obj
)
{
this
.
id
=
obj
.
id
;
this
.
position
=
obj
.
position
;
this
.
title
=
obj
.
title
;
this
.
type
=
obj
.
list_type
||
obj
.
listType
;
const
typeInfo
=
this
.
getTypeInfo
(
this
.
type
);
this
.
preset
=
Boolean
(
typeInfo
.
isPreset
);
this
.
isExpandable
=
Boolean
(
typeInfo
.
isExpandable
);
this
.
isExpanded
=
!
obj
.
collapsed
;
this
.
page
=
1
;
this
.
highlighted
=
obj
.
highlighted
;
this
.
loading
=
true
;
this
.
loadingMore
=
false
;
this
.
issues
=
obj
.
issues
||
[];
this
.
issuesSize
=
obj
.
issuesSize
||
obj
.
issuesCount
||
0
;
this
.
maxIssueCount
=
obj
.
maxIssueCount
||
obj
.
max_issue_count
||
0
;
if
(
obj
.
label
)
{
this
.
label
=
new
ListLabel
(
obj
.
label
);
}
else
if
(
obj
.
user
||
obj
.
assignee
)
{
this
.
assignee
=
new
ListAssignee
(
obj
.
user
||
obj
.
assignee
);
this
.
title
=
this
.
assignee
.
name
;
}
else
if
(
IS_EE
&&
obj
.
milestone
)
{
this
.
milestone
=
new
ListMilestone
(
obj
.
milestone
);
this
.
title
=
this
.
milestone
.
title
;
}
else
if
(
IS_EE
&&
obj
.
iteration
)
{
this
.
iteration
=
new
ListIteration
(
obj
.
iteration
);
this
.
title
=
this
.
iteration
.
title
;
}
// doNotFetchIssues is a temporary workaround until issues are fetched using GraphQL on issue boards
// Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/229416
if
(
!
typeInfo
.
isBlank
&&
this
.
id
&&
!
obj
.
doNotFetchIssues
)
{
this
.
getIssues
().
catch
(()
=>
{
// TODO: handle request error
});
}
}
guid
()
{
const
s4
=
()
=>
Math
.
floor
((
1
+
Math
.
random
())
*
0x10000
)
.
toString
(
16
)
.
substring
(
1
);
return
`
${
s4
()}${
s4
()}
-
${
s4
()}
-
${
s4
()}
-
${
s4
()}
-
${
s4
()}${
s4
()}${
s4
()}
`
;
}
save
()
{
return
boardsStore
.
saveList
(
this
);
}
destroy
()
{
boardsStore
.
destroy
(
this
);
}
update
()
{
return
boardsStore
.
updateListFunc
(
this
);
}
nextPage
()
{
return
boardsStore
.
goToNextPage
(
this
);
}
getIssues
(
emptyIssues
=
true
)
{
return
boardsStore
.
getListIssues
(
this
,
emptyIssues
);
}
newIssue
(
issue
)
{
return
boardsStore
.
newListIssue
(
this
,
issue
);
}
addMultipleIssues
(
issues
,
listFrom
,
newIndex
)
{
boardsStore
.
addMultipleListIssues
(
this
,
issues
,
listFrom
,
newIndex
);
}
addIssue
(
issue
,
listFrom
,
newIndex
)
{
boardsStore
.
addListIssue
(
this
,
issue
,
listFrom
,
newIndex
);
}
moveIssue
(
issue
,
oldIndex
,
newIndex
,
moveBeforeId
,
moveAfterId
)
{
boardsStore
.
moveListIssues
(
this
,
issue
,
oldIndex
,
newIndex
,
moveBeforeId
,
moveAfterId
);
}
moveMultipleIssues
({
issues
,
oldIndicies
,
newIndex
,
moveBeforeId
,
moveAfterId
})
{
boardsStore
.
moveListMultipleIssues
({
list
:
this
,
issues
,
oldIndicies
,
newIndex
,
moveBeforeId
,
moveAfterId
,
})
.
catch
(()
=>
createFlash
({
message
:
__
(
'
Something went wrong while moving issues.
'
),
}),
);
}
updateIssueLabel
(
issue
,
listFrom
,
moveBeforeId
,
moveAfterId
)
{
boardsStore
.
moveIssue
(
issue
.
id
,
listFrom
.
id
,
this
.
id
,
moveBeforeId
,
moveAfterId
).
catch
(()
=>
{
// TODO: handle request error
});
}
updateMultipleIssues
(
issues
,
listFrom
,
moveBeforeId
,
moveAfterId
)
{
boardsStore
.
moveMultipleIssues
({
ids
:
issues
.
map
((
issue
)
=>
issue
.
id
),
fromListId
:
listFrom
.
id
,
toListId
:
this
.
id
,
moveBeforeId
,
moveAfterId
,
})
.
catch
(()
=>
createFlash
({
message
:
__
(
'
Something went wrong while moving issues.
'
),
}),
);
}
findIssue
(
id
)
{
return
boardsStore
.
findListIssue
(
this
,
id
);
}
removeMultipleIssues
(
removeIssues
)
{
return
boardsStore
.
removeListMultipleIssues
(
this
,
removeIssues
);
}
removeIssue
(
removeIssue
)
{
return
boardsStore
.
removeListIssues
(
this
,
removeIssue
);
}
getTypeInfo
(
type
)
{
return
TYPES
[
type
]
||
TYPES
.
default
;
}
onNewIssueResponse
(
issue
,
data
)
{
boardsStore
.
onNewListIssueResponse
(
this
,
issue
,
data
);
}
}
window
.
List
=
List
;
export
default
List
;
app/assets/javascripts/boards/models/milestone.js
deleted
100644 → 0
View file @
d98c7844
export
default
class
ListMilestone
{
constructor
(
obj
)
{
this
.
id
=
obj
.
id
;
this
.
title
=
obj
.
title
;
if
(
IS_EE
)
{
this
.
path
=
obj
.
path
;
this
.
state
=
obj
.
state
;
this
.
webUrl
=
obj
.
web_url
||
obj
.
webUrl
;
this
.
description
=
obj
.
description
;
}
}
}
window
.
ListMilestone
=
ListMilestone
;
app/assets/javascripts/boards/models/project.js
deleted
100644 → 0
View file @
d98c7844
export
default
class
IssueProject
{
constructor
(
obj
)
{
this
.
id
=
obj
.
id
;
this
.
path
=
obj
.
path
;
this
.
fullPath
=
obj
.
path_with_namespace
;
}
}
app/assets/javascripts/boards/stores/actions.js
View file @
78ef57fa
...
...
@@ -18,6 +18,7 @@ import {
}
from
'
ee_else_ce/boards/constants
'
;
import
createBoardListMutation
from
'
ee_else_ce/boards/graphql/board_list_create.mutation.graphql
'
;
import
issueMoveListMutation
from
'
ee_else_ce/boards/graphql/issue_move_list.mutation.graphql
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
createGqClient
,
{
fetchPolicies
}
from
'
~/lib/graphql
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
...
...
@@ -61,10 +62,12 @@ export default {
setActiveId
({
commit
},
{
id
,
sidebarType
})
{
commit
(
types
.
SET_ACTIVE_ID
,
{
id
,
sidebarType
});
eventHub
.
$emit
(
'
toggleDetailIssue
'
,
true
);
},
unsetActiveId
({
dispatch
})
{
dispatch
(
'
setActiveId
'
,
{
id
:
inactiveId
,
sidebarType
:
''
});
eventHub
.
$emit
(
'
toggleDetailIssue
'
,
false
);
},
setFilters
:
({
commit
,
state
:
{
issuableType
}
},
filters
)
=>
{
...
...
app/assets/javascripts/boards/stores/boards_store.js
deleted
100644 → 0
View file @
d98c7844
/* eslint-disable no-shadow, no-param-reassign, consistent-return */
/* global List */
/* global ListIssue */
import
{
sortBy
}
from
'
lodash
'
;
import
BoardsStoreEE
from
'
ee_else_ce/boards/stores/boards_store_ee
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
parseBoolean
,
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
mergeUrlParams
,
queryToObject
,
getUrlParamsArray
}
from
'
~/lib/utils/url_utility
'
;
import
{
ListType
,
flashAnimationDuration
}
from
'
../constants
'
;
import
eventHub
from
'
../eventhub
'
;
import
ListAssignee
from
'
../models/assignee
'
;
import
ListLabel
from
'
../models/label
'
;
import
ListMilestone
from
'
../models/milestone
'
;
import
IssueProject
from
'
../models/project
'
;
const
PER_PAGE
=
20
;
export
const
gqlClient
=
createDefaultClient
();
const
boardsStore
=
{
disabled
:
false
,
timeTracking
:
{
limitToHours
:
false
,
},
scopedLabels
:
{
enabled
:
false
,
},
filter
:
{
path
:
''
,
},
state
:
{
currentBoard
:
{
labels
:
[],
},
currentPage
:
''
,
endpoints
:
{},
},
detail
:
{
issue
:
{},
list
:
{},
},
moving
:
{
issue
:
{},
list
:
{},
},
multiSelect
:
{
list
:
[]
},
setEndpoints
({
boardsEndpoint
,
listsEndpoint
,
bulkUpdatePath
,
boardId
,
recentBoardsEndpoint
,
fullPath
,
})
{
const
listsEndpointGenerate
=
`
${
listsEndpoint
}
/generate.json`
;
this
.
state
.
endpoints
=
{
boardsEndpoint
,
boardId
,
listsEndpoint
,
listsEndpointGenerate
,
bulkUpdatePath
,
fullPath
,
recentBoardsEndpoint
:
`
${
recentBoardsEndpoint
}
.json`
,
};
},
create
()
{
this
.
state
.
lists
=
[];
this
.
filter
.
path
=
getUrlParamsArray
().
join
(
'
&
'
);
this
.
detail
=
{
issue
:
{},
list
:
{},
};
},
showPage
(
page
)
{
this
.
state
.
currentPage
=
page
;
},
updateListPosition
(
listObj
)
{
const
listType
=
listObj
.
listType
||
listObj
.
list_type
;
let
{
position
}
=
listObj
;
if
(
listType
===
ListType
.
closed
)
{
position
=
Infinity
;
}
else
if
(
listType
===
ListType
.
backlog
)
{
position
=
-
1
;
}
const
list
=
new
List
({
...
listObj
,
position
});
return
list
;
},
addList
(
listObj
)
{
const
list
=
this
.
updateListPosition
(
listObj
);
this
.
state
.
lists
=
sortBy
([...
this
.
state
.
lists
,
list
],
'
position
'
);
return
list
;
},
new
(
listObj
)
{
const
list
=
this
.
addList
(
listObj
);
const
backlogList
=
this
.
findList
(
'
type
'
,
'
backlog
'
);
list
.
save
()
.
then
(()
=>
{
list
.
highlighted
=
true
;
setTimeout
(()
=>
{
list
.
highlighted
=
false
;
},
flashAnimationDuration
);
// Remove any new issues from the backlog
// as they will be visible in the new list
list
.
issues
.
forEach
(
backlogList
.
removeIssue
.
bind
(
backlogList
));
this
.
state
.
lists
=
sortBy
(
this
.
state
.
lists
,
'
position
'
);
})
.
catch
(()
=>
{
// https://gitlab.com/gitlab-org/gitlab-foss/issues/30821
});
},
updateNewListDropdown
(
listId
)
{
document
.
querySelector
(
`.js-board-list-
${
getIdFromGraphQLId
(
listId
)}
`
)
?.
classList
.
remove
(
'
is-active
'
);
},
findIssueLabel
(
issue
,
findLabel
)
{
return
issue
.
labels
.
find
((
label
)
=>
label
.
id
===
findLabel
.
id
);
},
goToNextPage
(
list
)
{
if
(
list
.
issuesSize
>
list
.
issues
.
length
)
{
if
(
list
.
issues
.
length
/
PER_PAGE
>=
1
)
{
list
.
page
+=
1
;
}
return
list
.
getIssues
(
false
);
}
},
addListIssue
(
list
,
issue
,
listFrom
,
newIndex
)
{
let
moveBeforeId
=
null
;
let
moveAfterId
=
null
;
if
(
!
list
.
findIssue
(
issue
.
id
))
{
if
(
newIndex
!==
undefined
)
{
list
.
issues
.
splice
(
newIndex
,
0
,
issue
);
if
(
list
.
issues
[
newIndex
-
1
])
{
moveBeforeId
=
list
.
issues
[
newIndex
-
1
].
id
;
}
if
(
list
.
issues
[
newIndex
+
1
])
{
moveAfterId
=
list
.
issues
[
newIndex
+
1
].
id
;
}
}
else
{
list
.
issues
.
push
(
issue
);
}
if
(
list
.
label
)
{
issue
.
addLabel
(
list
.
label
);
}
if
(
list
.
assignee
)
{
if
(
listFrom
&&
listFrom
.
type
===
'
assignee
'
)
{
issue
.
removeAssignee
(
listFrom
.
assignee
);
}
issue
.
addAssignee
(
list
.
assignee
);
}
if
(
IS_EE
&&
list
.
milestone
)
{
if
(
listFrom
&&
listFrom
.
type
===
'
milestone
'
)
{
issue
.
removeMilestone
(
listFrom
.
milestone
);
}
issue
.
addMilestone
(
list
.
milestone
);
}
if
(
listFrom
)
{
list
.
issuesSize
+=
1
;
list
.
updateIssueLabel
(
issue
,
listFrom
,
moveBeforeId
,
moveAfterId
);
}
}
},
findListIssue
(
list
,
id
)
{
return
list
.
issues
.
find
((
issue
)
=>
issue
.
id
===
id
);
},
removeList
(
id
)
{
const
list
=
this
.
findList
(
'
id
'
,
id
);
if
(
!
list
)
return
;
this
.
state
.
lists
=
this
.
state
.
lists
.
filter
((
list
)
=>
list
.
id
!==
id
);
},
moveList
(
listFrom
,
orderLists
)
{
orderLists
.
forEach
((
id
,
i
)
=>
{
const
list
=
this
.
findList
(
'
id
'
,
parseInt
(
id
,
10
));
list
.
position
=
i
;
});
listFrom
.
update
();
},
addMultipleListIssues
(
list
,
issues
,
listFrom
,
newIndex
)
{
let
moveBeforeId
=
null
;
let
moveAfterId
=
null
;
const
listHasIssues
=
issues
.
every
((
issue
)
=>
list
.
findIssue
(
issue
.
id
));
if
(
!
listHasIssues
)
{
if
(
newIndex
!==
undefined
)
{
if
(
list
.
issues
[
newIndex
-
1
])
{
moveBeforeId
=
list
.
issues
[
newIndex
-
1
].
id
;
}
if
(
list
.
issues
[
newIndex
])
{
moveAfterId
=
list
.
issues
[
newIndex
].
id
;
}
list
.
issues
.
splice
(
newIndex
,
0
,
...
issues
);
}
else
{
list
.
issues
.
push
(...
issues
);
}
if
(
list
.
label
)
{
issues
.
forEach
((
issue
)
=>
issue
.
addLabel
(
list
.
label
));
}
if
(
list
.
assignee
)
{
if
(
listFrom
&&
listFrom
.
type
===
'
assignee
'
)
{
issues
.
forEach
((
issue
)
=>
issue
.
removeAssignee
(
listFrom
.
assignee
));
}
issues
.
forEach
((
issue
)
=>
issue
.
addAssignee
(
list
.
assignee
));
}
if
(
IS_EE
&&
list
.
milestone
)
{
if
(
listFrom
&&
listFrom
.
type
===
'
milestone
'
)
{
issues
.
forEach
((
issue
)
=>
issue
.
removeMilestone
(
listFrom
.
milestone
));
}
issues
.
forEach
((
issue
)
=>
issue
.
addMilestone
(
list
.
milestone
));
}
if
(
listFrom
)
{
list
.
issuesSize
+=
issues
.
length
;
list
.
updateMultipleIssues
(
issues
,
listFrom
,
moveBeforeId
,
moveAfterId
);
}
}
},
removeListIssues
(
list
,
removeIssue
)
{
list
.
issues
=
list
.
issues
.
filter
((
issue
)
=>
{
const
matchesRemove
=
removeIssue
.
id
===
issue
.
id
;
if
(
matchesRemove
)
{
list
.
issuesSize
-=
1
;
issue
.
removeLabel
(
list
.
label
);
}
return
!
matchesRemove
;
});
},
removeListMultipleIssues
(
list
,
removeIssues
)
{
const
ids
=
removeIssues
.
map
((
issue
)
=>
issue
.
id
);
list
.
issues
=
list
.
issues
.
filter
((
issue
)
=>
{
const
matchesRemove
=
ids
.
includes
(
issue
.
id
);
if
(
matchesRemove
)
{
list
.
issuesSize
-=
1
;
issue
.
removeLabel
(
list
.
label
);
}
return
!
matchesRemove
;
});
},
startMoving
(
list
,
issue
)
{
Object
.
assign
(
this
.
moving
,
{
list
,
issue
});
},
onNewListIssueResponse
(
list
,
issue
,
data
)
{
issue
.
refreshData
(
data
);
if
(
list
.
issues
.
length
>
1
)
{
const
moveBeforeId
=
list
.
issues
[
1
].
id
;
this
.
moveIssue
(
issue
.
id
,
null
,
null
,
null
,
moveBeforeId
);
}
},
moveMultipleIssuesToList
({
listFrom
,
listTo
,
issues
,
newIndex
})
{
const
issueTo
=
issues
.
map
((
issue
)
=>
listTo
.
findIssue
(
issue
.
id
));
const
issueLists
=
issues
.
map
((
issue
)
=>
issue
.
getLists
()).
flat
();
const
listLabels
=
issueLists
.
map
((
list
)
=>
list
.
label
);
const
hasMoveableIssues
=
issueTo
.
filter
(
Boolean
).
length
>
0
;
if
(
!
hasMoveableIssues
)
{
// Check if target list assignee is already present in this issue
if
(
listTo
.
type
===
ListType
.
assignee
&&
listFrom
.
type
===
ListType
.
assignee
&&
issues
.
some
((
issue
)
=>
issue
.
findAssignee
(
listTo
.
assignee
))
)
{
const
targetIssues
=
issues
.
map
((
issue
)
=>
listTo
.
findIssue
(
issue
.
id
));
targetIssues
.
forEach
((
targetIssue
)
=>
targetIssue
.
removeAssignee
(
listFrom
.
assignee
));
}
else
if
(
listTo
.
type
===
'
milestone
'
)
{
const
currentMilestones
=
issues
.
map
((
issue
)
=>
issue
.
milestone
);
const
currentLists
=
this
.
state
.
lists
.
filter
((
list
)
=>
list
.
type
===
'
milestone
'
&&
list
.
id
!==
listTo
.
id
)
.
filter
((
list
)
=>
list
.
issues
.
some
((
listIssue
)
=>
issues
.
some
((
issue
)
=>
listIssue
.
id
===
issue
.
id
)),
);
issues
.
forEach
((
issue
)
=>
{
currentMilestones
.
forEach
((
milestone
)
=>
{
issue
.
removeMilestone
(
milestone
);
});
});
issues
.
forEach
((
issue
)
=>
{
issue
.
addMilestone
(
listTo
.
milestone
);
});
currentLists
.
forEach
((
currentList
)
=>
{
issues
.
forEach
((
issue
)
=>
{
currentList
.
removeIssue
(
issue
);
});
});
listTo
.
addMultipleIssues
(
issues
,
listFrom
,
newIndex
);
}
else
{
// Add to new lists issues if it doesn't already exist
listTo
.
addMultipleIssues
(
issues
,
listFrom
,
newIndex
);
}
}
else
{
listTo
.
updateMultipleIssues
(
issues
,
listFrom
);
issues
.
forEach
((
issue
)
=>
{
issue
.
removeLabel
(
listFrom
.
label
);
});
}
if
(
listTo
.
type
===
ListType
.
closed
&&
listFrom
.
type
!==
ListType
.
backlog
)
{
issueLists
.
forEach
((
list
)
=>
{
issues
.
forEach
((
issue
)
=>
{
list
.
removeIssue
(
issue
);
});
});
issues
.
forEach
((
issue
)
=>
{
issue
.
removeLabels
(
listLabels
);
});
}
else
if
(
listTo
.
type
===
ListType
.
backlog
&&
listFrom
.
type
===
ListType
.
assignee
)
{
issues
.
forEach
((
issue
)
=>
{
issue
.
removeAssignee
(
listFrom
.
assignee
);
});
issueLists
.
forEach
((
list
)
=>
{
issues
.
forEach
((
issue
)
=>
{
list
.
removeIssue
(
issue
);
});
});
}
else
if
(
listTo
.
type
===
ListType
.
backlog
&&
listFrom
.
type
===
ListType
.
milestone
)
{
issues
.
forEach
((
issue
)
=>
{
issue
.
removeMilestone
(
listFrom
.
milestone
);
});
issueLists
.
forEach
((
list
)
=>
{
issues
.
forEach
((
issue
)
=>
{
list
.
removeIssue
(
issue
);
});
});
}
else
if
(
this
.
shouldRemoveIssue
(
listFrom
,
listTo
)
&&
this
.
issuesAreContiguous
(
listFrom
,
issues
)
)
{
listFrom
.
removeMultipleIssues
(
issues
);
}
},
issuesAreContiguous
(
list
,
issues
)
{
// When there's only 1 issue selected, we can return early.
if
(
issues
.
length
===
1
)
return
true
;
// Create list of ids for issues involved.
const
listIssueIds
=
list
.
issues
.
map
((
issue
)
=>
issue
.
id
);
const
movedIssueIds
=
issues
.
map
((
issue
)
=>
issue
.
id
);
// Check if moved issue IDs is sub-array
// of source list issue IDs (i.e. contiguous selection).
return
listIssueIds
.
join
(
'
|
'
).
includes
(
movedIssueIds
.
join
(
'
|
'
));
},
moveIssueToList
(
listFrom
,
listTo
,
issue
,
newIndex
)
{
const
issueTo
=
listTo
.
findIssue
(
issue
.
id
);
const
issueLists
=
issue
.
getLists
();
const
listLabels
=
issueLists
.
map
((
listIssue
)
=>
listIssue
.
label
);
if
(
!
issueTo
)
{
// Check if target list assignee is already present in this issue
if
(
listTo
.
type
===
'
assignee
'
&&
listFrom
.
type
===
'
assignee
'
&&
issue
.
findAssignee
(
listTo
.
assignee
)
)
{
const
targetIssue
=
listTo
.
findIssue
(
issue
.
id
);
targetIssue
.
removeAssignee
(
listFrom
.
assignee
);
}
else
if
(
listTo
.
type
===
'
milestone
'
)
{
const
currentMilestone
=
issue
.
milestone
;
const
currentLists
=
this
.
state
.
lists
.
filter
((
list
)
=>
list
.
type
===
'
milestone
'
&&
list
.
id
!==
listTo
.
id
)
.
filter
((
list
)
=>
list
.
issues
.
some
((
listIssue
)
=>
issue
.
id
===
listIssue
.
id
));
issue
.
removeMilestone
(
currentMilestone
);
issue
.
addMilestone
(
listTo
.
milestone
);
currentLists
.
forEach
((
currentList
)
=>
currentList
.
removeIssue
(
issue
));
listTo
.
addIssue
(
issue
,
listFrom
,
newIndex
);
}
else
{
// Add to new lists issues if it doesn't already exist
listTo
.
addIssue
(
issue
,
listFrom
,
newIndex
);
}
}
else
{
listTo
.
updateIssueLabel
(
issue
,
listFrom
);
issueTo
.
removeLabel
(
listFrom
.
label
);
}
if
(
listTo
.
type
===
'
closed
'
&&
listFrom
.
type
!==
'
backlog
'
)
{
issueLists
.
forEach
((
list
)
=>
{
list
.
removeIssue
(
issue
);
});
issue
.
removeLabels
(
listLabels
);
}
else
if
(
listTo
.
type
===
'
backlog
'
&&
listFrom
.
type
===
'
assignee
'
)
{
issue
.
removeAssignee
(
listFrom
.
assignee
);
listFrom
.
removeIssue
(
issue
);
}
else
if
(
listTo
.
type
===
'
backlog
'
&&
listFrom
.
type
===
'
milestone
'
)
{
issue
.
removeMilestone
(
listFrom
.
milestone
);
listFrom
.
removeIssue
(
issue
);
}
else
if
(
this
.
shouldRemoveIssue
(
listFrom
,
listTo
))
{
listFrom
.
removeIssue
(
issue
);
}
},
shouldRemoveIssue
(
listFrom
,
listTo
)
{
return
(
(
listTo
.
type
!==
'
label
'
&&
listFrom
.
type
===
'
assignee
'
)
||
(
listTo
.
type
!==
'
assignee
'
&&
listFrom
.
type
===
'
label
'
)
||
listFrom
.
type
===
'
backlog
'
||
listFrom
.
type
===
'
closed
'
);
},
moveIssueInList
(
list
,
issue
,
oldIndex
,
newIndex
,
idArray
)
{
const
beforeId
=
parseInt
(
idArray
[
newIndex
-
1
],
10
)
||
null
;
const
afterId
=
parseInt
(
idArray
[
newIndex
+
1
],
10
)
||
null
;
list
.
moveIssue
(
issue
,
oldIndex
,
newIndex
,
beforeId
,
afterId
);
},
moveMultipleIssuesInList
({
list
,
issues
,
oldIndicies
,
newIndex
,
idArray
})
{
const
beforeId
=
parseInt
(
idArray
[
newIndex
-
1
],
10
)
||
null
;
const
afterId
=
parseInt
(
idArray
[
newIndex
+
issues
.
length
],
10
)
||
null
;
list
.
moveMultipleIssues
({
issues
,
oldIndicies
,
newIndex
,
moveBeforeId
:
beforeId
,
moveAfterId
:
afterId
,
});
},
findList
(
key
,
val
)
{
return
this
.
state
.
lists
.
find
((
list
)
=>
list
[
key
]
===
val
);
},
findListByLabelId
(
id
)
{
return
this
.
state
.
lists
.
find
((
list
)
=>
list
.
type
===
'
label
'
&&
list
.
label
.
id
===
id
);
},
toggleFilter
(
filter
)
{
const
filterPath
=
this
.
filter
.
path
.
split
(
'
&
'
);
const
filterIndex
=
filterPath
.
indexOf
(
filter
);
if
(
filterIndex
===
-
1
)
{
filterPath
.
push
(
filter
);
}
else
{
filterPath
.
splice
(
filterIndex
,
1
);
}
this
.
filter
.
path
=
filterPath
.
join
(
'
&
'
);
this
.
updateFiltersUrl
();
eventHub
.
$emit
(
'
updateTokens
'
);
},
setListDetail
(
newList
)
{
this
.
detail
.
list
=
newList
;
},
updateFiltersUrl
()
{
window
.
history
.
pushState
(
null
,
null
,
`?
${
this
.
filter
.
path
}
`
);
},
clearDetailIssue
()
{
this
.
setIssueDetail
({});
},
setIssueDetail
(
issueDetail
)
{
this
.
detail
.
issue
=
issueDetail
;
},
setTimeTrackingLimitToHours
(
limitToHours
)
{
this
.
timeTracking
.
limitToHours
=
parseBoolean
(
limitToHours
);
},
generateBoardGid
(
boardId
)
{
return
`gid://gitlab/Board/
${
boardId
}
`
;
},
generateBoardsPath
(
id
)
{
return
`
${
this
.
state
.
endpoints
.
boardsEndpoint
}${
id
?
`/
${
id
}
`
:
''
}
.json`
;
},
generateIssuesPath
(
id
)
{
return
`
${
this
.
state
.
endpoints
.
listsEndpoint
}${
id
?
`/
${
id
}
`
:
''
}
/issues`
;
},
generateIssuePath
(
boardId
,
id
)
{
return
`
${
gon
.
relative_url_root
}
/-/boards/
${
boardId
?
`
${
boardId
}
`
:
''
}
/issues
${
id
?
`/
${
id
}
`
:
''
}
`
;
},
generateMultiDragPath
(
boardId
)
{
return
`
${
gon
.
relative_url_root
}
/-/boards/
${
boardId
?
`
${
boardId
}
`
:
''
}
/issues/bulk_move`
;
},
all
()
{
return
axios
.
get
(
this
.
state
.
endpoints
.
listsEndpoint
);
},
createList
(
entityId
,
entityType
)
{
const
list
=
{
[
entityType
]:
entityId
,
};
return
axios
.
post
(
this
.
state
.
endpoints
.
listsEndpoint
,
{
list
,
});
},
updateList
(
id
,
position
,
collapsed
)
{
return
axios
.
put
(
`
${
this
.
state
.
endpoints
.
listsEndpoint
}
/
${
id
}
`
,
{
list
:
{
position
,
collapsed
,
},
});
},
updateListFunc
(
list
)
{
const
collapsed
=
!
list
.
isExpanded
;
return
this
.
updateList
(
list
.
id
,
list
.
position
,
collapsed
).
catch
(()
=>
{
// TODO: handle request error
});
},
destroyList
(
id
)
{
return
axios
.
delete
(
`
${
this
.
state
.
endpoints
.
listsEndpoint
}
/
${
id
}
`
);
},
destroy
(
list
)
{
const
index
=
this
.
state
.
lists
.
indexOf
(
list
);
this
.
state
.
lists
.
splice
(
index
,
1
);
this
.
updateNewListDropdown
(
list
.
id
);
this
.
destroyList
(
list
.
id
).
catch
(()
=>
{
// TODO: handle request error
});
},
saveList
(
list
)
{
const
entity
=
list
.
label
||
list
.
assignee
||
list
.
milestone
||
list
.
iteration
;
let
entityType
=
''
;
if
(
list
.
label
)
{
entityType
=
'
label_id
'
;
}
else
if
(
list
.
assignee
)
{
entityType
=
'
assignee_id
'
;
}
else
if
(
IS_EE
&&
list
.
milestone
)
{
entityType
=
'
milestone_id
'
;
}
else
if
(
IS_EE
&&
list
.
iteration
)
{
entityType
=
'
iteration_id
'
;
}
return
this
.
createList
(
entity
.
id
,
entityType
)
.
then
((
res
)
=>
res
.
data
)
.
then
((
data
)
=>
{
list
.
id
=
data
.
id
;
list
.
type
=
data
.
list_type
;
list
.
position
=
data
.
position
;
list
.
label
=
data
.
label
;
return
list
.
getIssues
();
});
},
getListIssues
(
list
,
emptyIssues
=
true
)
{
const
data
=
{
...
queryToObject
(
this
.
filter
.
path
,
{
gatherArrays
:
true
}),
page
:
list
.
page
,
};
if
(
list
.
label
&&
data
.
label_name
)
{
data
.
label_name
=
data
.
label_name
.
filter
((
label
)
=>
label
!==
list
.
label
.
title
);
}
if
(
emptyIssues
)
{
list
.
loading
=
true
;
}
return
this
.
getIssuesForList
(
list
.
id
,
data
)
.
then
((
res
)
=>
res
.
data
)
.
then
((
data
)
=>
{
list
.
loading
=
false
;
list
.
issuesSize
=
data
.
size
;
if
(
emptyIssues
)
{
list
.
issues
=
[];
}
data
.
issues
.
forEach
((
issueObj
)
=>
{
list
.
addIssue
(
new
ListIssue
(
issueObj
));
});
return
data
;
});
},
getIssuesForList
(
id
,
filter
=
{})
{
const
data
=
{
id
};
Object
.
keys
(
filter
).
forEach
((
key
)
=>
{
data
[
key
]
=
filter
[
key
];
});
return
axios
.
get
(
mergeUrlParams
(
data
,
this
.
generateIssuesPath
(
id
)));
},
moveIssue
(
id
,
fromListId
=
null
,
toListId
=
null
,
moveBeforeId
=
null
,
moveAfterId
=
null
)
{
return
axios
.
put
(
this
.
generateIssuePath
(
this
.
state
.
endpoints
.
boardId
,
id
),
{
from_list_id
:
fromListId
,
to_list_id
:
toListId
,
move_before_id
:
moveBeforeId
,
move_after_id
:
moveAfterId
,
});
},
moveListIssues
(
list
,
issue
,
oldIndex
,
newIndex
,
moveBeforeId
,
moveAfterId
)
{
list
.
issues
.
splice
(
oldIndex
,
1
);
list
.
issues
.
splice
(
newIndex
,
0
,
issue
);
this
.
moveIssue
(
issue
.
id
,
null
,
null
,
moveBeforeId
,
moveAfterId
).
catch
(()
=>
{
// TODO: handle request error
});
},
moveMultipleIssues
({
ids
,
fromListId
,
toListId
,
moveBeforeId
,
moveAfterId
})
{
return
axios
.
put
(
this
.
generateMultiDragPath
(
this
.
state
.
endpoints
.
boardId
),
{
from_list_id
:
fromListId
,
to_list_id
:
toListId
,
move_before_id
:
moveBeforeId
,
move_after_id
:
moveAfterId
,
ids
,
});
},
moveListMultipleIssues
({
list
,
issues
,
oldIndicies
,
newIndex
,
moveBeforeId
,
moveAfterId
})
{
oldIndicies
.
reverse
().
forEach
((
index
)
=>
{
list
.
issues
.
splice
(
index
,
1
);
});
list
.
issues
.
splice
(
newIndex
,
0
,
...
issues
);
return
this
.
moveMultipleIssues
({
ids
:
issues
.
map
((
issue
)
=>
issue
.
id
),
fromListId
:
null
,
toListId
:
null
,
moveBeforeId
,
moveAfterId
,
});
},
newIssue
(
id
,
issue
)
{
if
(
typeof
id
===
'
string
'
)
{
id
=
getIdFromGraphQLId
(
id
);
}
return
axios
.
post
(
this
.
generateIssuesPath
(
id
),
{
issue
,
});
},
newListIssue
(
list
,
issue
)
{
list
.
addIssue
(
issue
,
null
,
0
);
list
.
issuesSize
+=
1
;
let
listId
=
list
.
id
;
if
(
typeof
listId
===
'
string
'
)
{
listId
=
getIdFromGraphQLId
(
listId
);
}
return
this
.
newIssue
(
list
.
id
,
issue
)
.
then
((
res
)
=>
res
.
data
)
.
then
((
data
)
=>
list
.
onNewIssueResponse
(
issue
,
data
));
},
getBacklog
(
data
)
{
return
axios
.
get
(
mergeUrlParams
(
data
,
`
${
gon
.
relative_url_root
}
/-/boards/
${
this
.
state
.
endpoints
.
boardId
}
/issues.json`
,
),
);
},
removeIssueLabel
(
issue
,
removeLabel
)
{
if
(
removeLabel
)
{
issue
.
labels
=
issue
.
labels
.
filter
((
label
)
=>
removeLabel
.
id
!==
label
.
id
);
}
},
addIssueAssignee
(
issue
,
assignee
)
{
if
(
!
issue
.
findAssignee
(
assignee
))
{
issue
.
assignees
.
push
(
new
ListAssignee
(
assignee
));
}
},
setIssueAssignees
(
issue
,
assignees
)
{
issue
.
assignees
=
[...
assignees
];
},
removeIssueLabels
(
issue
,
labels
)
{
labels
.
forEach
(
issue
.
removeLabel
.
bind
(
issue
));
},
bulkUpdate
(
issueIds
,
extraData
=
{})
{
const
data
=
{
update
:
Object
.
assign
(
extraData
,
{
issuable_ids
:
issueIds
.
join
(
'
,
'
),
}),
};
return
axios
.
post
(
this
.
state
.
endpoints
.
bulkUpdatePath
,
data
);
},
getIssueInfo
(
endpoint
)
{
return
axios
.
get
(
endpoint
);
},
toggleIssueSubscription
(
endpoint
)
{
return
axios
.
post
(
endpoint
);
},
recentBoards
()
{
return
axios
.
get
(
this
.
state
.
endpoints
.
recentBoardsEndpoint
);
},
setCurrentBoard
(
board
)
{
this
.
state
.
currentBoard
=
board
;
},
toggleMultiSelect
(
issue
)
{
const
selectedIssueIds
=
this
.
multiSelect
.
list
.
map
((
issue
)
=>
issue
.
id
);
const
index
=
selectedIssueIds
.
indexOf
(
issue
.
id
);
if
(
index
===
-
1
)
{
this
.
multiSelect
.
list
.
push
(
issue
);
return
;
}
this
.
multiSelect
.
list
=
[
...
this
.
multiSelect
.
list
.
slice
(
0
,
index
),
...
this
.
multiSelect
.
list
.
slice
(
index
+
1
),
];
},
removeIssueAssignee
(
issue
,
removeAssignee
)
{
if
(
removeAssignee
)
{
issue
.
assignees
=
issue
.
assignees
.
filter
((
assignee
)
=>
assignee
.
id
!==
removeAssignee
.
id
);
}
},
findIssueAssignee
(
issue
,
findAssignee
)
{
return
issue
.
assignees
.
find
((
assignee
)
=>
assignee
.
id
===
findAssignee
.
id
);
},
clearMultiSelect
()
{
this
.
multiSelect
.
list
=
[];
},
removeAllIssueAssignees
(
issue
)
{
issue
.
assignees
=
[];
},
addIssueMilestone
(
issue
,
milestone
)
{
const
miletoneId
=
issue
.
milestone
?
issue
.
milestone
.
id
:
null
;
if
(
IS_EE
&&
milestone
.
id
!==
miletoneId
)
{
issue
.
milestone
=
new
ListMilestone
(
milestone
);
}
},
setIssueLoadingState
(
issue
,
key
,
value
)
{
issue
.
isLoading
[
key
]
=
value
;
},
updateIssueData
(
issue
,
newData
)
{
Object
.
assign
(
issue
,
newData
);
},
setIssueFetchingState
(
issue
,
key
,
value
)
{
issue
.
isFetching
[
key
]
=
value
;
},
removeIssueMilestone
(
issue
,
removeMilestone
)
{
if
(
IS_EE
&&
removeMilestone
&&
removeMilestone
.
id
===
issue
.
milestone
.
id
)
{
issue
.
milestone
=
{};
}
},
refreshIssueData
(
issue
,
obj
)
{
const
convertedObj
=
convertObjectPropsToCamelCase
(
obj
,
{
dropKeys
:
[
'
issue_sidebar_endpoint
'
,
'
real_path
'
,
'
webUrl
'
],
});
convertedObj
.
sidebarInfoEndpoint
=
obj
.
issue_sidebar_endpoint
;
issue
.
path
=
obj
.
real_path
||
obj
.
webUrl
;
issue
.
project_id
=
obj
.
project_id
;
Object
.
assign
(
issue
,
convertedObj
);
if
(
obj
.
project
)
{
issue
.
project
=
new
IssueProject
(
obj
.
project
);
}
if
(
obj
.
milestone
)
{
issue
.
milestone
=
new
ListMilestone
(
obj
.
milestone
);
issue
.
milestone_id
=
obj
.
milestone
.
id
;
}
if
(
obj
.
labels
)
{
issue
.
labels
=
obj
.
labels
.
map
((
label
)
=>
new
ListLabel
(
label
));
}
if
(
obj
.
assignees
)
{
issue
.
assignees
=
obj
.
assignees
.
map
((
a
)
=>
new
ListAssignee
(
a
));
}
},
addIssueLabel
(
issue
,
label
)
{
if
(
!
issue
.
findLabel
(
label
))
{
issue
.
labels
.
push
(
new
ListLabel
(
label
));
}
},
updateIssue
(
issue
)
{
const
data
=
{
issue
:
{
milestone_id
:
issue
.
milestone
?
issue
.
milestone
.
id
:
null
,
due_date
:
issue
.
dueDate
,
assignee_ids
:
issue
.
assignees
.
length
>
0
?
issue
.
assignees
.
map
(({
id
})
=>
id
)
:
[
0
],
label_ids
:
issue
.
labels
.
length
>
0
?
issue
.
labels
.
map
(({
id
})
=>
id
)
:
[
''
],
},
};
return
axios
.
patch
(
`
${
issue
.
path
}
.json`
,
data
).
then
(({
data
:
body
=
{}
}
=
{})
=>
{
/**
* Since post implementation of Scoped labels, server can reject
* same key-ed labels. To keep the UI and server Model consistent,
* we're just assigning labels that server echo's back to us when we
* PATCH the said object.
*/
if
(
body
)
{
issue
.
labels
=
convertObjectPropsToCamelCase
(
body
.
labels
,
{
deep
:
true
});
}
});
},
};
BoardsStoreEE
.
initEESpecific
(
boardsStore
);
export
default
boardsStore
;
app/assets/javascripts/boards/stores/boards_store_ee.js
deleted
100644 → 0
View file @
d98c7844
// this is just to make ee_else_ce happy and will be cleaned up in https://gitlab.com/gitlab-org/gitlab-foss/issues/59807
export
default
{
initEESpecific
()
{},
};
app/views/shared/boards/_show.html.haml
View file @
78ef57fa
...
...
@@ -18,5 +18,5 @@
=
render
'shared/issuable/search_bar'
,
type: :boards
,
board:
board
#board-app
.boards-app.position-relative
{
"v-cloak"
=>
"true"
,
data:
board_data
,
":class"
=>
"{ 'is-compact': detailIssueVisible }"
}
%board-content
{
":
lists"
=>
"state.lists"
,
":
disabled"
=>
"disabled"
}
%board-content
{
":disabled"
=>
"disabled"
}
%board-settings-sidebar
ee/app/assets/javascripts/boards/components/boards_list_selector/assignees_list_item.vue
deleted
100644 → 0
View file @
d98c7844
<
script
>
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
{
sprintf
,
__
}
from
'
~/locale
'
;
export
default
{
components
:
{
GlButton
,
},
props
:
{
item
:
{
type
:
Object
,
required
:
true
,
},
},
computed
:
{
avatarAltText
()
{
return
sprintf
(
__
(
"
%{name}'s avatar
"
),
{
name
:
this
.
item
.
name
,
});
},
},
methods
:
{
handleItemClick
()
{
this
.
$emit
(
'
onItemSelect
'
,
this
.
item
);
},
},
};
</
script
>
<
template
>
<li
class=
"filter-dropdown-item"
@
click=
"handleItemClick"
>
<gl-button
variant=
"link"
category=
"primary"
class=
"dropdown-user"
>
<div
class=
"avatar-container s32 flex-shrink-0"
>
<img
:alt=
"avatarAltText"
:src=
"item.avatar_url"
class=
"avatar s32 lazy"
/>
</div>
<div
class=
"text-truncate dropdown-user-details"
>
<div
class=
"text-truncate"
>
{{
item
.
name
}}
</div>
<div
class=
"text-truncate dropdown-light-content"
>
@
{{
item
.
username
}}
</div>
</div>
</gl-button>
</li>
</
template
>
ee/app/assets/javascripts/boards/components/boards_list_selector/index.js
deleted
100644 → 0
View file @
d98c7844
import
Vue
from
'
vue
'
;
import
vuexStore
from
'
~/boards/stores
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
{
fullMilestoneId
,
fullUserId
}
from
'
../../boards_util
'
;
import
ListContainer
from
'
./list_container.vue
'
;
export
default
Vue
.
extend
({
components
:
{
ListContainer
,
},
props
:
{
listPath
:
{
type
:
String
,
required
:
true
,
},
listType
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
loading
:
true
,
store
:
boardsStore
,
vuexStore
,
};
},
mounted
()
{
this
.
loadList
();
},
methods
:
{
loadList
()
{
return
this
.
store
.
loadList
(
this
.
listPath
,
this
.
listType
).
then
(()
=>
{
this
.
loading
=
false
;
});
},
filterItems
(
term
,
items
)
{
const
query
=
term
.
toLowerCase
();
return
items
.
filter
((
item
)
=>
{
const
name
=
item
.
name
?
item
.
name
.
toLowerCase
()
:
item
.
title
.
toLowerCase
();
const
foundName
=
name
.
indexOf
(
query
)
>
-
1
;
if
(
this
.
listType
===
'
milestones
'
)
{
return
foundName
;
}
const
username
=
item
.
username
.
toLowerCase
();
return
foundName
||
username
.
indexOf
(
query
)
>
-
1
;
});
},
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
.
getListByTitle
(
item
.
title
))
{
if
(
this
.
listType
===
'
milestones
'
)
{
this
.
vuexStore
.
dispatch
(
'
createList
'
,
{
milestoneId
:
fullMilestoneId
(
item
.
id
)
});
}
else
if
(
this
.
listType
===
'
assignees
'
)
{
this
.
vuexStore
.
dispatch
(
'
createList
'
,
{
assigneeId
:
fullUserId
(
item
.
id
)
});
}
}
},
},
render
(
createElement
)
{
return
createElement
(
'
list-container
'
,
{
props
:
{
loading
:
this
.
loading
,
items
:
this
.
store
.
state
[
this
.
listType
],
listType
:
this
.
listType
,
},
on
:
{
onItemSelect
:
this
.
handleItemClick
,
},
});
},
});
ee/app/assets/javascripts/boards/components/boards_list_selector/list_container.vue
deleted
100644 → 0
View file @
d98c7844
<
script
>
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
ListContent
from
'
./list_content.vue
'
;
import
ListFilter
from
'
./list_filter.vue
'
;
export
default
{
components
:
{
ListFilter
,
ListContent
,
GlLoadingIcon
,
},
props
:
{
loading
:
{
type
:
Boolean
,
required
:
true
,
},
items
:
{
type
:
Array
,
required
:
true
,
},
listType
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
query
:
''
,
};
},
computed
:
{
filteredItems
()
{
if
(
!
this
.
query
)
return
this
.
items
;
const
query
=
this
.
query
.
toLowerCase
();
return
this
.
items
.
filter
((
item
)
=>
{
const
name
=
item
.
name
?
item
.
name
.
toLowerCase
()
:
item
.
title
.
toLowerCase
();
if
(
this
.
listType
===
'
milestones
'
)
{
return
name
.
indexOf
(
query
)
>
-
1
;
}
const
username
=
item
.
username
.
toLowerCase
();
return
name
.
indexOf
(
query
)
>
-
1
||
username
.
indexOf
(
query
)
>
-
1
;
});
},
},
methods
:
{
handleSearch
(
query
)
{
this
.
query
=
query
;
},
handleItemClick
(
item
)
{
this
.
$emit
(
'
onItemSelect
'
,
item
);
},
},
};
</
script
>
<
template
>
<div
class=
"dropdown-assignees-list"
>
<div
v-if=
"loading"
class=
"dropdown-loading"
><gl-loading-icon
size=
"sm"
/></div>
<list-filter
@
onSearchInput=
"handleSearch"
/>
<list-content
v-if=
"!loading"
:items=
"filteredItems"
:list-type=
"listType"
@
onItemSelect=
"handleItemClick"
/>
</div>
</
template
>
ee/app/assets/javascripts/boards/components/boards_list_selector/list_content.vue
deleted
100644 → 0
View file @
d98c7844
<
script
>
import
AssigneesListItem
from
'
./assignees_list_item.vue
'
;
import
MilestoneListItem
from
'
./milestones_list_item.vue
'
;
export
default
{
props
:
{
items
:
{
type
:
Array
,
required
:
true
,
},
listType
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
listContentComponent
()
{
return
this
.
listType
===
'
assignees
'
?
AssigneesListItem
:
MilestoneListItem
;
},
},
methods
:
{
handleItemClick
(
item
)
{
this
.
$emit
(
'
onItemSelect
'
,
item
);
},
},
};
</
script
>
<
template
>
<div
class=
"dropdown-content"
>
<ul>
<component
:is=
"listContentComponent"
v-for=
"item in items"
:key=
"item.id"
:item=
"item"
@
onItemSelect=
"handleItemClick"
/>
</ul>
</div>
</
template
>
ee/app/assets/javascripts/boards/components/boards_list_selector/list_filter.vue
deleted
100644 → 0
View file @
d98c7844
<
script
>
import
{
GlIcon
}
from
'
@gitlab/ui
'
;
export
default
{
components
:
{
GlIcon
,
},
data
()
{
return
{
query
:
''
,
};
},
methods
:
{
handleInputChange
()
{
this
.
$emit
(
'
onSearchInput
'
,
this
.
query
);
},
handleInputClear
()
{
this
.
query
=
''
;
this
.
handleInputChange
();
},
},
};
</
script
>
<
template
>
<div
:class=
"
{ 'has-value': !!query }" class="dropdown-input">
<input
v-model.trim=
"query"
:placeholder=
"__('Search')"
type=
"search"
class=
"dropdown-input-field"
@
keyup=
"handleInputChange"
/>
<gl-icon
name=
"search"
class=
"dropdown-input-search"
data-hidden=
"true"
/>
<gl-icon
name=
"close"
class=
"dropdown-input-clear"
@
click=
"handleInputClear"
/>
</div>
</
template
>
ee/app/assets/javascripts/boards/components/boards_list_selector/milestones_list_item.vue
deleted
100644 → 0
View file @
d98c7844
<
script
>
import
{
GlButton
}
from
'
@gitlab/ui
'
;
export
default
{
components
:
{
GlButton
,
},
props
:
{
item
:
{
type
:
Object
,
required
:
true
,
},
},
methods
:
{
handleItemClick
()
{
this
.
$emit
(
'
onItemSelect
'
,
this
.
item
);
},
},
};
</
script
>
<
template
>
<li>
<gl-button
category=
"tertiary"
class=
"gl-rounded-0!"
@
click=
"handleItemClick"
>
<span
class=
"gl-white-space-normal"
>
{{
item
.
title
}}
</span>
</gl-button>
</li>
</
template
>
ee/app/assets/javascripts/boards/ee_functions.js
deleted
100644 → 0
View file @
d98c7844
export
const
setWeightFetchingState
=
(
issue
,
value
)
=>
{
issue
.
setFetchingState
(
'
weight
'
,
value
);
};
export
const
setEpicFetchingState
=
(
issue
,
value
)
=>
{
issue
.
setFetchingState
(
'
epic
'
,
value
);
};
export
const
getMilestoneTitle
=
(
$boardApp
)
=>
({
milestoneTitle
:
$boardApp
.
dataset
.
boardMilestoneTitle
,
});
ee/app/assets/javascripts/boards/models/issue.js
deleted
100644 → 0
View file @
d98c7844
import
ListIssue
from
'
~/boards/models/issue
'
;
import
IssueProject
from
'
~/boards/models/project
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
class
ListIssueEE
extends
ListIssue
{
constructor
(
obj
)
{
super
(
obj
,
{
IssueProject
,
});
this
.
weight
=
obj
.
weight
;
if
(
obj
.
project
)
{
this
.
project
=
new
IssueProject
(
obj
.
project
);
}
}
updateEpic
(
newEpic
)
{
boardsStore
.
updateIssueEpic
(
this
,
newEpic
);
}
}
window
.
ListIssue
=
ListIssueEE
;
export
default
ListIssueEE
;
ee/app/assets/javascripts/boards/models/list.js
deleted
100644 → 0
View file @
d98c7844
/* eslint-disable no-param-reassign */
import
ListAssignee
from
'
~/boards/models/assignee
'
;
import
List
from
'
~/boards/models/list
'
;
import
ListMilestone
from
'
~/boards/models/milestone
'
;
class
ListEE
extends
List
{
constructor
(...
args
)
{
super
(...
args
);
this
.
totalWeight
=
args
[
0
]?.
totalWeight
||
0
;
}
getIssues
(...
args
)
{
return
super
.
getIssues
(...
args
).
then
((
data
)
=>
{
this
.
totalWeight
=
data
.
total_weight
;
});
}
addIssue
(
issue
,
...
args
)
{
super
.
addIssue
(
issue
,
...
args
);
if
(
issue
.
weight
)
{
this
.
totalWeight
+=
issue
.
weight
;
}
}
removeIssue
(
issue
,
...
args
)
{
if
(
issue
.
weight
)
{
this
.
totalWeight
-=
issue
.
weight
;
}
super
.
removeIssue
(
issue
,
...
args
);
}
addWeight
(
weight
)
{
this
.
totalWeight
+=
weight
;
}
onNewIssueResponse
(
issue
,
data
)
{
issue
.
milestone
=
data
.
milestone
?
new
ListMilestone
(
data
.
milestone
)
:
data
.
milestone
;
issue
.
assignees
=
Array
.
isArray
(
data
.
assignees
)
?
data
.
assignees
.
map
((
assignee
)
=>
new
ListAssignee
(
assignee
))
:
data
.
assignees
;
issue
.
labels
=
data
.
labels
;
super
.
onNewIssueResponse
(
issue
,
data
);
}
}
window
.
List
=
ListEE
;
export
default
ListEE
;
ee/app/assets/javascripts/boards/stores/boards_store_ee.js
deleted
100644 → 0
View file @
d98c7844
/* eslint-disable class-methods-use-this, no-param-reassign */
/*
no-param-reassign is disabled because one method of BoardsStoreEE
modify the passed parameter in conformity with non-ee BoardsStore.
*/
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
import
sidebarEventHub
from
'
~/sidebar/event_hub
'
;
const
NO_ITERATION_TITLE
=
'
No+Iteration
'
;
const
NO_MILESTONE_TITLE
=
'
No+Milestone
'
;
class
BoardsStoreEE
{
initEESpecific
(
boardsStore
)
{
this
.
$boardApp
=
document
.
getElementById
(
'
board-app
'
);
this
.
store
=
boardsStore
;
this
.
store
.
loadList
=
(
listPath
,
listType
)
=>
this
.
loadList
(
listPath
,
listType
);
const
superSetCurrentBoard
=
this
.
store
.
setCurrentBoard
.
bind
(
this
.
store
);
this
.
store
.
setCurrentBoard
=
(
board
)
=>
{
superSetCurrentBoard
(
board
);
this
.
store
.
state
.
assignees
=
[];
this
.
store
.
state
.
milestones
=
[];
};
const
baseCreate
=
this
.
store
.
create
.
bind
(
this
.
store
);
this
.
store
.
create
=
()
=>
{
baseCreate
();
if
(
this
.
$boardApp
)
{
const
{
dataset
:
{
boardMilestoneId
,
boardMilestoneTitle
,
boardIterationTitle
,
boardIterationId
,
boardAssigneeUsername
,
labels
,
boardWeight
,
weightFeatureAvailable
,
scopedLabels
,
},
}
=
this
.
$boardApp
;
this
.
store
.
boardConfig
=
{
milestoneId
:
parseInt
(
boardMilestoneId
,
10
),
milestoneTitle
:
boardMilestoneTitle
||
''
,
iterationId
:
parseInt
(
boardIterationId
,
10
),
iterationTitle
:
boardIterationTitle
||
''
,
assigneeUsername
:
boardAssigneeUsername
,
labels
:
JSON
.
parse
(
labels
||
[]),
weight
:
parseInt
(
boardWeight
,
10
),
};
this
.
store
.
cantEdit
=
[];
this
.
store
.
weightFeatureAvailable
=
parseBoolean
(
weightFeatureAvailable
);
this
.
store
.
scopedLabels
=
{
enabled
:
parseBoolean
(
scopedLabels
),
};
}
};
this
.
store
.
updateFiltersUrl
=
(
replaceState
=
false
)
=>
{
if
(
!
this
.
store
.
filter
.
path
)
{
return
;
}
if
(
replaceState
)
{
window
.
history
.
replaceState
(
null
,
null
,
`?
${
this
.
store
.
filter
.
path
}
`
);
}
else
{
window
.
history
.
pushState
(
null
,
null
,
`?
${
this
.
store
.
filter
.
path
}
`
);
}
};
this
.
store
.
updateIssueEpic
=
this
.
updateIssueEpic
;
sidebarEventHub
.
$on
(
'
updateWeight
'
,
this
.
updateWeight
.
bind
(
this
));
Object
.
assign
(
this
.
store
,
{
updateWeight
(
endpoint
,
weight
=
null
)
{
return
axios
.
put
(
endpoint
,
{
weight
,
});
},
});
}
initBoardFilters
()
{
const
updateFilterPath
=
(
key
,
value
)
=>
{
if
(
!
value
)
return
;
const
querystring
=
`
${
key
}
=
${
value
}
`
;
this
.
store
.
filter
.
path
=
[
querystring
]
.
concat
(
this
.
store
.
filter
.
path
.
split
(
'
&
'
)
.
filter
((
param
)
=>
param
.
match
(
new
RegExp
(
`^
${
key
}
=(.*)$`
,
'
g
'
))
===
null
),
)
.
join
(
'
&
'
);
};
let
{
milestoneTitle
}
=
this
.
store
.
boardConfig
;
if
(
this
.
store
.
boardConfig
.
milestoneId
===
0
)
{
milestoneTitle
=
NO_MILESTONE_TITLE
;
}
else
{
milestoneTitle
=
encodeURIComponent
(
milestoneTitle
);
}
if
(
milestoneTitle
)
{
updateFilterPath
(
'
milestone_title
'
,
milestoneTitle
);
this
.
store
.
cantEdit
.
push
(
'
milestone
'
);
}
let
{
iterationTitle
}
=
this
.
store
.
boardConfig
;
if
(
this
.
store
.
boardConfig
.
iterationId
===
0
)
{
iterationTitle
=
NO_ITERATION_TITLE
;
}
else
{
iterationTitle
=
encodeURIComponent
(
iterationTitle
);
}
if
(
iterationTitle
)
{
updateFilterPath
(
'
iteration_id
'
,
iterationTitle
);
this
.
store
.
cantEdit
.
push
(
'
iteration
'
);
}
let
{
weight
}
=
this
.
store
.
boardConfig
;
if
(
weight
!==
-
1
)
{
if
(
weight
===
0
)
{
weight
=
'
0
'
;
}
if
(
weight
===
-
2
)
{
/* eslint-disable-next-line @gitlab/require-i18n-strings */
weight
=
'
None
'
;
}
updateFilterPath
(
'
weight
'
,
weight
);
}
updateFilterPath
(
'
assignee_username
'
,
this
.
store
.
boardConfig
.
assigneeUsername
);
if
(
this
.
store
.
boardConfig
.
assigneeUsername
)
{
this
.
store
.
cantEdit
.
push
(
'
assignee
'
);
}
const
filterPath
=
this
.
store
.
filter
.
path
.
split
(
'
&
'
);
this
.
store
.
boardConfig
.
labels
.
forEach
((
label
)
=>
{
const
labelTitle
=
encodeURIComponent
(
label
.
title
);
const
param
=
`label_name[]=
${
labelTitle
}
`
;
const
labelIndex
=
filterPath
.
indexOf
(
param
);
if
(
labelIndex
===
-
1
)
{
filterPath
.
push
(
param
);
}
this
.
store
.
cantEdit
.
push
({
name
:
'
label
'
,
value
:
label
.
title
,
});
});
this
.
store
.
filter
.
path
=
filterPath
.
join
(
'
&
'
);
this
.
store
.
updateFiltersUrl
(
true
);
}
setMaxIssueCountOnList
(
id
,
maxIssueCount
)
{
this
.
store
.
findList
(
'
id
'
,
id
).
maxIssueCount
=
maxIssueCount
;
}
updateIssueEpic
(
issue
,
newEpic
)
{
issue
.
epic
=
newEpic
;
}
updateWeight
({
id
,
value
:
newWeight
})
{
const
{
issue
}
=
this
.
store
.
detail
;
if
(
issue
.
id
===
id
&&
issue
.
sidebarInfoEndpoint
)
{
issue
.
setLoadingState
(
'
weight
'
,
true
);
this
.
store
.
updateWeight
(
issue
.
sidebarInfoEndpoint
,
newWeight
)
.
then
((
res
)
=>
res
.
data
)
.
then
((
data
)
=>
{
const
lists
=
issue
.
getLists
();
const
oldWeight
=
issue
.
weight
;
const
weightDiff
=
newWeight
-
oldWeight
;
issue
.
setLoadingState
(
'
weight
'
,
false
);
issue
.
updateData
({
weight
:
data
.
weight
,
});
lists
.
forEach
((
list
)
=>
{
list
.
addWeight
(
weightDiff
);
});
})
.
catch
(()
=>
{
issue
.
setLoadingState
(
'
weight
'
,
false
);
createFlash
({
message
:
__
(
'
An error occurred when updating the issue weight
'
),
});
});
}
}
loadList
(
listPath
,
listType
)
{
if
(
this
.
store
.
state
[
listType
].
length
)
{
return
Promise
.
resolve
();
}
return
axios
.
get
(
listPath
)
.
then
(({
data
})
=>
{
this
.
store
.
state
[
listType
]
=
data
;
})
.
catch
(()
=>
{
createFlash
({
message
:
sprintf
(
__
(
'
Something went wrong while fetching %{listType} list
'
),
{
listType
,
}),
});
});
}
}
export
default
new
BoardsStoreEE
();
ee/app/assets/javascripts/epic/models/label.js
View file @
78ef57fa
// This file is duplicated in ~/boards/models/label.js
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
export
default
class
ListLabel
{
...
...
ee/app/assets/javascripts/epic_boards/index.js
View file @
78ef57fa
...
...
@@ -92,11 +92,8 @@ export default () => {
state
:
{},
loading
:
0
,
allowSubEpics
:
parseBoolean
(
$boardApp
.
dataset
.
subEpicsFeatureAvailable
),
boardsEndpoint
:
$boardApp
.
dataset
.
boardsEndpoint
,
recentBoardsEndpoint
:
$boardApp
.
dataset
.
recentBoardsEndpoint
,
listsEndpoint
:
$boardApp
.
dataset
.
listsEndpoint
,
disabled
:
parseBoolean
(
$boardApp
.
dataset
.
disabled
),
bulkUpdatePath
:
$boardApp
.
dataset
.
bulkUpdatePath
,
parent
:
$boardApp
.
dataset
.
parent
,
detailIssueVisible
:
false
,
};
...
...
ee/app/assets/javascripts/vue_shared/components/sidebar/epics_select/store/actions.js
View file @
78ef57fa
import
Api
from
'
ee/api
'
;
import
{
noneEpic
}
from
'
ee/vue_shared/constants
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
createFlash
from
'
~/flash
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
formatDate
,
timeFor
}
from
'
~/lib/utils/datetime_utility
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
{
s__
}
from
'
~/locale
'
;
import
*
as
types
from
'
./mutation_types
'
;
...
...
@@ -59,43 +57,8 @@ export const fetchEpics = ({ state, dispatch }, search = '') => {
export
const
requestIssueUpdate
=
({
commit
})
=>
commit
(
types
.
REQUEST_ISSUE_UPDATE
);
export
const
receiveIssueUpdateSuccess
=
({
state
,
commit
},
{
data
,
epic
,
isRemoval
=
false
})
=>
{
/*
If EpicsSelect is loaded within Boards, -
we need to update "boardsStore.issue.detail.epic" which has -
a differently formatted timestamp that includes '<strong>' tag.
However, "data.epic" in the response of the API POST doesn't have '<strong>' tag.
("epic" param is also in a different format).
*/
function
insertStrongTag
(
humanReadableTimestamp
)
{
if
(
humanReadableTimestamp
===
__
(
'
Past due
'
))
{
return
`<strong>
${
humanReadableTimestamp
}
</strong>`
;
}
// Insert strong tag for for any number in the string.
// I.e., "3 days remaining" or "Осталось 3 дней"
// A similar transformation is done in the backend:
// app/serializers/entity_date_helper.rb
return
humanReadableTimestamp
.
replace
(
/
\d
+/
,
'
<strong>$&</strong>
'
);
}
// Verify if update was successful
if
(
data
.
epic
.
id
===
epic
.
id
&&
data
.
issue
.
id
===
state
.
issueId
)
{
if
(
boardsStore
.
detail
.
issue
.
updateEpic
)
{
const
formattedEpic
=
isRemoval
?
{
epic_issue_id
:
noneEpic
.
id
}
:
{
epic_issue_id
:
data
.
id
,
group_id
:
data
.
epic
.
group_id
,
human_readable_end_date
:
formatDate
(
data
.
epic
.
end_date
,
'
mmm d, yyyy
'
),
human_readable_timestamp
:
insertStrongTag
(
timeFor
(
data
.
epic
.
end_date
)),
id
:
data
.
epic
.
id
,
iid
:
data
.
epic
.
iid
,
title
:
data
.
epic
.
title
,
url
:
`/groups/
${
data
.
epic
.
web_url
.
replace
(
/.+groups
\/
/
,
''
)}
`
,
};
boardsStore
.
detail
.
issue
.
updateEpic
(
formattedEpic
);
}
commit
(
types
.
RECEIVE_ISSUE_UPDATE_SUCCESS
,
{
selectedEpic
:
isRemoval
?
noneEpic
:
epic
,
selectedEpicIssueId
:
data
.
id
,
...
...
ee/spec/frontend/boards/components/board_list_selector/assignees_list_item_spec.js
deleted
100644 → 0
View file @
d98c7844
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
AssigneesListItem
from
'
ee/boards/components/boards_list_selector/assignees_list_item.vue
'
;
import
{
mockAssigneesList
}
from
'
jest/boards/mock_data
'
;
describe
(
'
AssigneesListItem
'
,
()
=>
{
const
assignee
=
mockAssigneesList
[
0
];
let
wrapper
;
beforeEach
(()
=>
{
wrapper
=
shallowMount
(
AssigneesListItem
,
{
propsData
:
{
item
:
assignee
,
},
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
renders component container element with class `filter-dropdown-item`
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.filter-dropdown-item
'
).
exists
()).
toBe
(
true
);
});
it
(
'
emits `onItemSelect` event on component click and sends `assignee` as event param
'
,
()
=>
{
wrapper
.
find
(
'
.filter-dropdown-item
'
).
trigger
(
'
click
'
);
expect
(
wrapper
.
emitted
().
onItemSelect
[
0
]).
toEqual
([
assignee
]);
});
describe
(
'
avatar
'
,
()
=>
{
it
(
'
has alt text
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.avatar
'
).
attributes
(
'
alt
'
)).
toBe
(
`
${
assignee
.
name
}
's avatar`
);
});
it
(
'
has src url
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.avatar
'
).
attributes
(
'
src
'
)).
toBe
(
assignee
.
avatar_url
);
});
});
describe
(
'
user details
'
,
()
=>
{
it
(
'
shows assignee name
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.dropdown-user-details
'
).
text
()).
toContain
(
assignee
.
name
);
});
it
(
'
shows assignee username
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.dropdown-user-details .dropdown-light-content
'
).
text
()).
toContain
(
`@
${
assignee
.
username
}
`
,
);
});
});
});
ee/spec/frontend/boards/components/board_list_selector/board_list_selector_spec.js
deleted
100644 → 0
View file @
d98c7844
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
BoardListSelector
from
'
ee/boards/components/boards_list_selector/
'
;
import
mountComponent
from
'
helpers/vue_mount_component_helper
'
;
import
{
mockAssigneesList
}
from
'
jest/boards/mock_data
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
{
createStore
}
from
'
~/boards/stores
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
jest
.
mock
(
'
~/flash
'
);
describe
(
'
BoardListSelector
'
,
()
=>
{
const
dummyEndpoint
=
`
${
TEST_HOST
}
/users.json`
;
const
createComponent
=
()
=>
mountComponent
(
BoardListSelector
,
{
listPath
:
dummyEndpoint
,
listType
:
'
assignees
'
,
});
let
vm
;
let
mock
;
boardsStore
.
create
();
boardsStore
.
state
.
assignees
=
[];
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
vm
=
createComponent
();
vm
.
vuexStore
=
createStore
();
});
afterEach
(()
=>
{
vm
.
$destroy
();
mock
.
restore
();
});
describe
(
'
data
'
,
()
=>
{
it
(
'
returns default data props
'
,
()
=>
{
expect
(
vm
.
loading
).
toBe
(
true
);
expect
(
vm
.
store
).
toBe
(
boardsStore
);
});
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
loadList
'
,
()
=>
{
it
(
'
calls axios.get and sets response to store.state.assignees
'
,
(
done
)
=>
{
mock
.
onGet
(
dummyEndpoint
).
reply
(
200
,
mockAssigneesList
);
boardsStore
.
state
.
assignees
=
[];
vm
.
loadList
()
.
then
(()
=>
{
expect
(
vm
.
loading
).
toBe
(
false
);
expect
(
vm
.
store
.
state
.
assignees
.
length
).
toBe
(
mockAssigneesList
.
length
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
does not call axios.get when store.state.assignees is not empty
'
,
(
done
)
=>
{
jest
.
spyOn
(
axios
,
'
get
'
).
mockReturnValue
(
Promise
.
resolve
());
boardsStore
.
state
.
assignees
=
mockAssigneesList
;
vm
.
loadList
()
.
then
(()
=>
{
expect
(
axios
.
get
).
not
.
toHaveBeenCalled
();
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
calls axios.get and shows Flash error when request fails
'
,
(
done
)
=>
{
mock
.
onGet
(
dummyEndpoint
).
replyOnce
(
500
,
{});
boardsStore
.
state
.
assignees
=
[];
vm
.
loadList
()
.
then
(()
=>
{
expect
(
vm
.
loading
).
toBe
(
false
);
expect
(
createFlash
).
toHaveBeenCalledWith
({
message
:
'
Something went wrong while fetching assignees list
'
,
});
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
describe
(
'
handleItemClick
'
,
()
=>
{
it
(
'
creates new list in a store instance
'
,
()
=>
{
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/components/board_list_selector/list_container_spec.js
deleted
100644 → 0
View file @
d98c7844
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vue
from
'
vue
'
;
import
ListContainer
from
'
ee/boards/components/boards_list_selector/list_container.vue
'
;
import
ListContent
from
'
ee/boards/components/boards_list_selector/list_content.vue
'
;
import
ListFilter
from
'
ee/boards/components/boards_list_selector/list_filter.vue
'
;
import
{
mockAssigneesList
}
from
'
jest/boards/mock_data
'
;
describe
(
'
ListContainer
'
,
()
=>
{
let
wrapper
;
beforeEach
(()
=>
{
wrapper
=
shallowMount
(
ListContainer
,
{
propsData
:
{
loading
:
false
,
items
:
mockAssigneesList
,
listType
:
'
assignees
'
,
},
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
computed
'
,
()
=>
{
describe
(
'
filteredItems
'
,
()
=>
{
it
(
'
returns assignees list as it is when `query` is empty
'
,
()
=>
{
wrapper
.
setData
({
query
:
''
});
expect
(
wrapper
.
vm
.
filteredItems
).
toHaveLength
(
mockAssigneesList
.
length
);
});
it
(
'
returns filtered assignees list as it is when `query` has name
'
,
()
=>
{
const
assignee
=
mockAssigneesList
[
0
];
wrapper
.
setData
({
query
:
assignee
.
name
});
expect
(
wrapper
.
vm
.
filteredItems
).
toHaveLength
(
1
);
expect
(
wrapper
.
vm
.
filteredItems
[
0
].
name
).
toBe
(
assignee
.
name
);
});
it
(
'
returns filtered assignees list as it is when `query` has username
'
,
()
=>
{
const
assignee
=
mockAssigneesList
[
0
];
wrapper
.
setData
({
query
:
assignee
.
username
});
expect
(
wrapper
.
vm
.
filteredItems
).
toHaveLength
(
1
);
expect
(
wrapper
.
vm
.
filteredItems
[
0
].
username
).
toBe
(
assignee
.
username
);
});
});
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
handleSearch
'
,
()
=>
{
it
(
'
sets value of param `query` to component prop `query`
'
,
()
=>
{
const
query
=
'
foobar
'
;
wrapper
.
vm
.
handleSearch
(
query
);
expect
(
wrapper
.
vm
.
query
).
toBe
(
query
);
});
});
describe
(
'
handleItemClick
'
,
()
=>
{
it
(
'
emits `onItemSelect` event on component and sends `assignee` as event param
'
,
()
=>
{
const
assignee
=
mockAssigneesList
[
0
];
wrapper
.
vm
.
handleItemClick
(
assignee
);
expect
(
wrapper
.
emitted
().
onItemSelect
[
0
]).
toEqual
([
assignee
]);
});
});
});
describe
(
'
template
'
,
()
=>
{
it
(
'
renders component container element with class `dropdown-assignees-list`
'
,
()
=>
{
expect
(
wrapper
.
classes
(
'
dropdown-assignees-list
'
)).
toBe
(
true
);
});
it
(
'
renders loading animation when prop `loading` is true
'
,
()
=>
{
wrapper
.
setProps
({
loading
:
true
});
return
Vue
.
nextTick
().
then
(()
=>
{
expect
(
wrapper
.
find
(
'
.dropdown-loading
'
).
exists
()).
toBe
(
true
);
});
});
it
(
'
renders dropdown body elements
'
,
()
=>
{
expect
(
wrapper
.
find
(
ListFilter
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
ListContent
).
exists
()).
toBe
(
true
);
});
});
});
ee/spec/frontend/boards/components/board_list_selector/list_content_spec.js
deleted
100644 → 0
View file @
d98c7844
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
ListContent
from
'
ee/boards/components/boards_list_selector/list_content.vue
'
;
import
{
mockAssigneesList
}
from
'
jest/boards/mock_data
'
;
describe
(
'
ListContent
'
,
()
=>
{
let
wrapper
;
beforeEach
(()
=>
{
wrapper
=
shallowMount
(
ListContent
,
{
propsData
:
{
items
:
mockAssigneesList
,
listType
:
'
assignees
'
,
},
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
emits `onItemSelect` event on component and sends `assignee` as event param
'
,
()
=>
{
const
assignee
=
mockAssigneesList
[
0
];
wrapper
.
vm
.
handleItemClick
(
assignee
);
expect
(
wrapper
.
emitted
().
onItemSelect
[
0
]).
toEqual
([
assignee
]);
});
it
(
'
renders component container element with class `dropdown-content`
'
,
()
=>
{
expect
(
wrapper
.
classes
(
'
dropdown-content
'
)).
toBe
(
true
);
});
it
(
'
renders UL parent element as child within container
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
ul
'
).
exists
()).
toBe
(
true
);
});
});
ee/spec/frontend/boards/components/board_list_selector/list_filter_spec.js
deleted
100644 → 0
View file @
d98c7844
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vue
from
'
vue
'
;
import
ListFilter
from
'
ee/boards/components/boards_list_selector/list_filter.vue
'
;
describe
(
'
ListFilter
'
,
()
=>
{
let
wrapper
;
beforeEach
(()
=>
{
wrapper
=
shallowMount
(
ListFilter
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
input field
'
,
()
=>
{
it
(
'
emits `onSearchInput` event on keyup and sends input text as event param
'
,
()
=>
{
const
input
=
wrapper
.
find
(
'
input
'
);
input
.
setValue
(
'
foobar
'
);
input
.
trigger
(
'
keyup
'
);
expect
(
wrapper
.
emitted
().
onSearchInput
[
0
]).
toEqual
([
'
foobar
'
]);
});
});
describe
(
'
clear button
'
,
()
=>
{
let
input
;
beforeEach
(()
=>
{
// Pre-populate input field with text
input
=
wrapper
.
find
(
'
input
'
);
input
.
setValue
(
'
foobar
'
);
input
.
trigger
(
'
keyup
'
);
});
it
(
'
clears input field and emits `onSearchInput` event with empty value
'
,
()
=>
{
expect
(
input
.
element
.
value
).
toBe
(
'
foobar
'
);
wrapper
.
find
(
'
.dropdown-input-clear
'
).
vm
.
$emit
(
'
click
'
);
return
Vue
.
nextTick
().
then
(()
=>
{
expect
(
input
.
element
.
value
).
toBe
(
''
);
expect
(
wrapper
.
emitted
().
onSearchInput
[
1
]).
toEqual
([
''
]);
});
});
});
describe
(
'
template
'
,
()
=>
{
it
(
'
renders component container element with class `dropdown-input`
'
,
()
=>
{
expect
(
wrapper
.
classes
(
'
dropdown-input
'
)).
toBe
(
true
);
});
it
(
'
renders class `has-value` on container element when prop `query` is not empty
'
,
()
=>
{
wrapper
.
setData
({
query
:
'
foobar
'
});
return
Vue
.
nextTick
().
then
(()
=>
{
expect
(
wrapper
.
classes
(
'
has-value
'
)).
toBe
(
true
);
});
});
it
(
'
removes class `has-value` from container element when prop `query` is empty
'
,
()
=>
{
wrapper
.
setData
({
query
:
''
});
return
Vue
.
nextTick
().
then
(()
=>
{
expect
(
wrapper
.
classes
(
'
has-value
'
)).
toBe
(
false
);
});
});
it
(
'
renders search input element
'
,
()
=>
{
const
inputEl
=
wrapper
.
find
(
'
input.dropdown-input-field
'
);
expect
(
inputEl
.
exists
()).
toBe
(
true
);
expect
(
inputEl
.
attributes
(
'
placeholder
'
)).
toBe
(
'
Search
'
);
});
it
(
'
renders search input icons
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.dropdown-input-search
'
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
'
.dropdown-input-clear
'
).
exists
()).
toBe
(
true
);
});
});
});
ee/spec/frontend/boards/mock_data.js
View file @
78ef57fa
/* global List */
import
Vue
from
'
vue
'
;
import
'
~/boards/models/list
'
;
export
const
mockLabel
=
{
id
:
'
gid://gitlab/GroupLabel/121
'
,
title
:
'
To Do
'
,
...
...
@@ -67,10 +62,6 @@ export const mockLists = [
},
];
export
const
mockListsWithModel
=
mockLists
.
map
((
listMock
)
=>
Vue
.
observable
(
new
List
({
...
listMock
,
doNotFetchIssues
:
true
})),
);
const
defaultDescendantCounts
=
{
openedIssues
:
0
,
closedIssues
:
0
,
...
...
ee/spec/frontend/boards/models/list_spec.js
deleted
100644 → 0
View file @
d98c7844
import
axios
from
'
axios
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
Issue
from
'
ee/boards/models/issue
'
;
import
List
from
'
ee/boards/models/list
'
;
import
{
listObj
}
from
'
jest/boards/mock_data
'
;
import
{
ListType
}
from
'
~/boards/constants
'
;
import
CeList
from
'
~/boards/models/list
'
;
describe
(
'
List model
'
,
()
=>
{
let
list
;
let
issue
;
let
axiosMock
;
beforeEach
(()
=>
{
axiosMock
=
new
MockAdapter
(
axios
);
// We need to mock axios since `new List` below makes a network request
axiosMock
.
onGet
().
replyOnce
(
200
);
});
afterEach
(()
=>
{
list
=
null
;
issue
=
null
;
axiosMock
.
restore
();
});
describe
(
'
label lists
'
,
()
=>
{
beforeEach
(()
=>
{
list
=
new
List
(
listObj
);
issue
=
new
Issue
({
title
:
'
Testing
'
,
id
:
2
,
iid
:
2
,
labels
:
[],
assignees
:
[],
weight
:
5
,
});
});
it
(
'
inits totalWeight
'
,
()
=>
{
expect
(
list
.
totalWeight
).
toBe
(
0
);
});
describe
(
'
getIssues
'
,
()
=>
{
it
(
'
calls CE getIssues
'
,
()
=>
{
const
ceGetIssues
=
jest
.
spyOn
(
CeList
.
prototype
,
'
getIssues
'
)
.
mockReturnValue
(
Promise
.
resolve
({}));
return
list
.
getIssues
().
then
(()
=>
{
expect
(
ceGetIssues
).
toHaveBeenCalled
();
});
});
it
(
'
sets total weight
'
,
()
=>
{
jest
.
spyOn
(
CeList
.
prototype
,
'
getIssues
'
).
mockReturnValue
(
Promise
.
resolve
({
total_weight
:
11
,
}),
);
return
list
.
getIssues
().
then
(()
=>
{
expect
(
list
.
totalWeight
).
toBe
(
11
);
});
});
});
describe
(
'
addIssue
'
,
()
=>
{
it
(
'
updates totalWeight
'
,
()
=>
{
list
.
addIssue
(
issue
);
expect
(
list
.
totalWeight
).
toBe
(
5
);
});
it
(
'
calls CE addIssue with all args
'
,
()
=>
{
const
ceAddIssue
=
jest
.
spyOn
(
CeList
.
prototype
,
'
addIssue
'
);
list
.
addIssue
(
issue
,
list
,
2
);
expect
(
ceAddIssue
).
toHaveBeenCalledWith
(
issue
,
list
,
2
);
});
});
describe
(
'
removeIssue
'
,
()
=>
{
beforeEach
(()
=>
{
list
.
addIssue
(
issue
);
});
it
(
'
updates totalWeight
'
,
()
=>
{
list
.
removeIssue
(
issue
);
expect
(
list
.
totalWeight
).
toBe
(
0
);
});
it
(
'
calls CE removeIssue
'
,
()
=>
{
const
ceRemoveIssue
=
jest
.
spyOn
(
CeList
.
prototype
,
'
removeIssue
'
);
list
.
removeIssue
(
issue
);
expect
(
ceRemoveIssue
).
toHaveBeenCalledWith
(
issue
);
});
});
});
describe
(
'
iteration lists
'
,
()
=>
{
const
iteration
=
{
id
:
1000
,
title
:
'
Sprint 1
'
,
webUrl
:
'
https://gitlab.com/h5bp/-/iterations/1
'
,
};
beforeEach
(()
=>
{
list
=
new
List
({
list_type
:
ListType
.
iteration
,
iteration
});
});
it
(
'
sets the iteration and title
'
,
()
=>
{
expect
(
list
.
iteration
.
id
).
toBe
(
iteration
.
id
);
expect
(
list
.
title
).
toBe
(
iteration
.
title
);
});
});
});
ee/spec/frontend/boards/stores/actions_spec.js
View file @
78ef57fa
...
...
@@ -5,7 +5,6 @@ import Vuex from 'vuex';
import
{
BoardType
,
GroupByParamType
,
listsQuery
,
issuableTypes
}
from
'
ee/boards/constants
'
;
import
epicCreateMutation
from
'
ee/boards/graphql/epic_create.mutation.graphql
'
;
import
actions
,
{
gqlClient
}
from
'
ee/boards/stores/actions
'
;
import
boardsStoreEE
from
'
ee/boards/stores/boards_store_ee
'
;
import
*
as
types
from
'
ee/boards/stores/mutation_types
'
;
import
mutations
from
'
ee/boards/stores/mutations
'
;
import
setWindowLocation
from
'
helpers/set_window_location_helper
'
;
...
...
@@ -462,16 +461,7 @@ describe('setShowLabels', () => {
});
describe
(
'
updateListWipLimit
'
,
()
=>
{
let
storeMock
;
beforeEach
(()
=>
{
storeMock
=
{
state
:
{
endpoints
:
{
listsEndpoint
:
'
/test
'
}
},
create
:
()
=>
{},
setCurrentBoard
:
()
=>
{},
};
boardsStoreEE
.
initEESpecific
(
storeMock
);
jest
.
mock
(
'
axios
'
);
axios
.
put
=
jest
.
fn
();
axios
.
put
.
mockResolvedValue
({
data
:
{}
});
...
...
ee/spec/frontend/boards/stores/boards_store_ee_spec.js
deleted
100644 → 0
View file @
d98c7844
import
AxiosMockAdapter
from
'
axios-mock-adapter
'
;
import
BoardsStoreEE
from
'
ee_else_ce/boards/stores/boards_store_ee
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
jest
.
mock
(
'
~/flash
'
);
describe
(
'
BoardsStoreEE
'
,
()
=>
{
let
setCurrentBoard
;
let
axiosMock
;
beforeEach
(()
=>
{
axiosMock
=
new
AxiosMockAdapter
(
axios
);
setCurrentBoard
=
jest
.
fn
();
// mock CE store
const
storeMock
=
{
state
:
{},
create
()
{},
setCurrentBoard
,
};
BoardsStoreEE
.
initEESpecific
(
storeMock
);
});
describe
(
'
loadList
'
,
()
=>
{
const
listPath
=
`
${
TEST_HOST
}
/list/path`
;
const
listType
=
'
D-negative
'
;
it
(
'
fetches from listPath and stores the result
'
,
()
=>
{
const
dummyResponse
=
{
uni
:
'
corn
'
};
axiosMock
.
onGet
(
listPath
).
replyOnce
(
200
,
dummyResponse
);
const
{
state
}
=
BoardsStoreEE
.
store
;
state
[
listType
]
=
[];
return
BoardsStoreEE
.
loadList
(
listPath
,
listType
).
then
(()
=>
{
expect
(
state
[
listType
]).
toEqual
(
dummyResponse
);
});
});
it
(
'
displays error if fetching fails
'
,
()
=>
{
axiosMock
.
onGet
(
listPath
).
replyOnce
(
500
);
const
{
state
}
=
BoardsStoreEE
.
store
;
state
[
listType
]
=
[];
return
BoardsStoreEE
.
loadList
(
listPath
,
listType
).
then
(()
=>
{
expect
(
state
[
listType
]).
toEqual
([]);
expect
(
createFlash
).
toHaveBeenCalled
();
});
});
it
(
'
does not make a request if response is cached
'
,
()
=>
{
const
{
state
}
=
BoardsStoreEE
.
store
;
state
[
listType
]
=
[
'
something
'
];
return
BoardsStoreEE
.
loadList
(
listPath
,
listType
).
then
(()
=>
{
expect
(
axiosMock
.
history
.
get
).
toHaveLength
(
0
);
});
});
});
describe
(
'
setCurrentBoard
'
,
()
=>
{
const
dummyBoard
=
'
skateboard
'
;
it
(
'
calls setCurrentBoard() of the base store
'
,
()
=>
{
BoardsStoreEE
.
store
.
setCurrentBoard
(
dummyBoard
);
expect
(
setCurrentBoard
).
toHaveBeenCalledWith
(
dummyBoard
);
});
it
(
'
resets assignees
'
,
()
=>
{
const
{
state
}
=
BoardsStoreEE
.
store
;
state
.
assignees
=
'
some assignees
'
;
BoardsStoreEE
.
store
.
setCurrentBoard
(
dummyBoard
);
expect
(
state
.
assignees
).
toEqual
([]);
});
it
(
'
resets milestones
'
,
()
=>
{
const
{
state
}
=
BoardsStoreEE
.
store
;
state
.
milestones
=
'
some milestones
'
;
BoardsStoreEE
.
store
.
setCurrentBoard
(
dummyBoard
);
expect
(
state
.
milestones
).
toEqual
([]);
});
});
describe
(
'
updateWeight
'
,
()
=>
{
const
dummyEndpoint
=
`
${
TEST_HOST
}
/update/weight`
;
const
dummyResponse
=
'
just another response in the network
'
;
const
weight
=
'
elephant
'
;
const
expectedRequest
=
expect
.
objectContaining
({
data
:
JSON
.
stringify
({
weight
})
});
let
requestSpy
;
beforeEach
(()
=>
{
requestSpy
=
jest
.
fn
();
axiosMock
.
onPut
(
dummyEndpoint
).
replyOnce
((
config
)
=>
requestSpy
(
config
));
});
it
(
'
makes a request to update the weight
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
200
,
dummyResponse
]);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
BoardsStoreEE
.
store
.
updateWeight
(
dummyEndpoint
,
weight
))
.
resolves
.
toEqual
(
expectedResponse
)
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
it
(
'
fails for error response
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
500
]);
return
expect
(
BoardsStoreEE
.
store
.
updateWeight
(
dummyEndpoint
,
weight
))
.
rejects
.
toThrow
()
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
});
});
ee/spec/frontend/vue_shared/components/sidebar/epics_select/store/actions_spec.js
View file @
78ef57fa
...
...
@@ -4,7 +4,6 @@ import * as types from 'ee/vue_shared/components/sidebar/epics_select/store/muta
import
createDefaultState
from
'
ee/vue_shared/components/sidebar/epics_select/store/state
'
;
import
{
noneEpic
}
from
'
ee/vue_shared/constants
'
;
import
testAction
from
'
helpers/vuex_action_helper
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
createFlash
from
'
~/flash
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
...
...
@@ -259,35 +258,6 @@ describe('EpicsSelect', () => {
);
});
it
(
'
should update the epic associated with the issue in BoardsStore if the update happened in Boards
'
,
(
done
)
=>
{
boardsStore
.
detail
.
issue
.
updateEpic
=
jest
.
fn
(()
=>
{});
state
.
issueId
=
mockIssue
.
id
;
const
mockApiData
=
{
...
mockAssignRemoveRes
};
mockApiData
.
epic
.
web_url
=
''
;
testAction
(
actions
.
receiveIssueUpdateSuccess
,
{
data
:
mockApiData
,
epic
:
normalizedEpics
[
0
],
},
state
,
[
{
type
:
types
.
RECEIVE_ISSUE_UPDATE_SUCCESS
,
payload
:
{
selectedEpic
:
normalizedEpics
[
0
],
selectedEpicIssueId
:
mockApiData
.
id
,
},
},
],
[],
done
,
);
expect
(
boardsStore
.
detail
.
issue
.
updateEpic
).
toHaveBeenCalled
();
});
it
(
'
should set updated selectedEpic with noneEpic to state when payload has matching Epic and Issue IDs and isRemoval param is true
'
,
(
done
)
=>
{
state
.
issueId
=
mockIssue
.
id
;
...
...
locale/gitlab.pot
View file @
78ef57fa
...
...
@@ -3517,12 +3517,6 @@ msgstr ""
msgid "An error occurred when removing the label."
msgstr ""
msgid "An error occurred when toggling the notification subscription"
msgstr ""
msgid "An error occurred when updating the issue weight"
msgstr ""
msgid "An error occurred when updating the title"
msgstr ""
...
...
@@ -3619,9 +3613,6 @@ msgstr ""
msgid "An error occurred while fetching reference"
msgstr ""
msgid "An error occurred while fetching sidebar data"
msgstr ""
msgid "An error occurred while fetching tags. Retry the search."
msgstr ""
...
...
@@ -31254,9 +31245,6 @@ msgstr ""
msgid "Something went wrong while exporting requirements"
msgstr ""
msgid "Something went wrong while fetching %{listType} list"
msgstr ""
msgid "Something went wrong while fetching branches"
msgstr ""
...
...
@@ -31311,9 +31299,6 @@ msgstr ""
msgid "Something went wrong while merging this merge request. Please try again."
msgstr ""
msgid "Something went wrong while moving issues."
msgstr ""
msgid "Something went wrong while obtaining the Let's Encrypt certificate."
msgstr ""
...
...
spec/frontend/boards/boards_store_spec.js
deleted
100644 → 0
View file @
d98c7844
import
AxiosMockAdapter
from
'
axios-mock-adapter
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
ListIssue
from
'
~/boards/models/issue
'
;
import
List
from
'
~/boards/models/list
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
listObj
,
listObjDuplicate
}
from
'
./mock_data
'
;
jest
.
mock
(
'
js-cookie
'
);
const
createTestIssue
=
()
=>
({
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
confidential
:
false
,
labels
:
[],
assignees
:
[],
});
describe
(
'
boardsStore
'
,
()
=>
{
const
dummyResponse
=
"
without type checking this doesn't matter
"
;
const
boardId
=
'
dummy-board-id
'
;
const
endpoints
=
{
boardsEndpoint
:
`
${
TEST_HOST
}
/boards`
,
listsEndpoint
:
`
${
TEST_HOST
}
/lists`
,
bulkUpdatePath
:
`
${
TEST_HOST
}
/bulk/update`
,
recentBoardsEndpoint
:
`
${
TEST_HOST
}
/recent/boards`
,
};
let
axiosMock
;
beforeEach
(()
=>
{
axiosMock
=
new
AxiosMockAdapter
(
axios
);
boardsStore
.
setEndpoints
({
...
endpoints
,
boardId
,
});
});
afterEach
(()
=>
{
axiosMock
.
restore
();
});
const
setupDefaultResponses
=
()
=>
{
axiosMock
.
onGet
(
`
${
endpoints
.
listsEndpoint
}
/
${
listObj
.
id
}
/issues?id=
${
listObj
.
id
}
&page=1`
)
.
reply
(
200
,
{
issues
:
[
createTestIssue
()]
});
axiosMock
.
onPost
(
endpoints
.
listsEndpoint
).
reply
(
200
,
listObj
);
axiosMock
.
onPut
();
};
describe
(
'
all
'
,
()
=>
{
it
(
'
makes a request to fetch lists
'
,
()
=>
{
axiosMock
.
onGet
(
endpoints
.
listsEndpoint
).
replyOnce
(
200
,
dummyResponse
);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
all
()).
resolves
.
toEqual
(
expectedResponse
);
});
it
(
'
fails for error response
'
,
()
=>
{
axiosMock
.
onGet
(
endpoints
.
listsEndpoint
).
replyOnce
(
500
);
return
expect
(
boardsStore
.
all
()).
rejects
.
toThrow
();
});
});
describe
(
'
createList
'
,
()
=>
{
const
entityType
=
'
moorhen
'
;
const
entityId
=
'
quack
'
;
const
expectedRequest
=
expect
.
objectContaining
({
data
:
JSON
.
stringify
({
list
:
{
[
entityType
]:
entityId
}
}),
});
let
requestSpy
;
beforeEach
(()
=>
{
requestSpy
=
jest
.
fn
();
axiosMock
.
onPost
(
endpoints
.
listsEndpoint
).
replyOnce
((
config
)
=>
requestSpy
(
config
));
});
it
(
'
makes a request to create a list
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
200
,
dummyResponse
]);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
createList
(
entityId
,
entityType
))
.
resolves
.
toEqual
(
expectedResponse
)
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
it
(
'
fails for error response
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
500
]);
return
expect
(
boardsStore
.
createList
(
entityId
,
entityType
))
.
rejects
.
toThrow
()
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
});
describe
(
'
updateList
'
,
()
=>
{
const
id
=
'
David Webb
'
;
const
position
=
'
unknown
'
;
const
collapsed
=
false
;
const
expectedRequest
=
expect
.
objectContaining
({
data
:
JSON
.
stringify
({
list
:
{
position
,
collapsed
}
}),
});
let
requestSpy
;
beforeEach
(()
=>
{
requestSpy
=
jest
.
fn
();
axiosMock
.
onPut
(
`
${
endpoints
.
listsEndpoint
}
/
${
id
}
`
).
replyOnce
((
config
)
=>
requestSpy
(
config
));
});
it
(
'
makes a request to update a list position
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
200
,
dummyResponse
]);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
updateList
(
id
,
position
,
collapsed
))
.
resolves
.
toEqual
(
expectedResponse
)
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
it
(
'
fails for error response
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
500
]);
return
expect
(
boardsStore
.
updateList
(
id
,
position
,
collapsed
))
.
rejects
.
toThrow
()
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
});
describe
(
'
destroyList
'
,
()
=>
{
const
id
=
'
-42
'
;
let
requestSpy
;
beforeEach
(()
=>
{
requestSpy
=
jest
.
fn
();
axiosMock
.
onDelete
(
`
${
endpoints
.
listsEndpoint
}
/
${
id
}
`
)
.
replyOnce
((
config
)
=>
requestSpy
(
config
));
});
it
(
'
makes a request to delete a list
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
200
,
dummyResponse
]);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
destroyList
(
id
))
.
resolves
.
toEqual
(
expectedResponse
)
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalled
();
});
});
it
(
'
fails for error response
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
500
]);
return
expect
(
boardsStore
.
destroyList
(
id
))
.
rejects
.
toThrow
()
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalled
();
});
});
});
describe
(
'
saveList
'
,
()
=>
{
let
list
;
beforeEach
(()
=>
{
list
=
new
List
(
listObj
);
setupDefaultResponses
();
});
it
(
'
makes a request to save a list
'
,
()
=>
{
const
expectedResponse
=
expect
.
objectContaining
({
issues
:
[
createTestIssue
()]
});
const
expectedListValue
=
{
id
:
listObj
.
id
,
position
:
listObj
.
position
,
type
:
listObj
.
list_type
,
label
:
listObj
.
label
,
};
expect
(
list
.
id
).
toBe
(
listObj
.
id
);
expect
(
list
.
position
).
toBe
(
listObj
.
position
);
expect
(
list
).
toMatchObject
(
expectedListValue
);
return
expect
(
boardsStore
.
saveList
(
list
)).
resolves
.
toEqual
(
expectedResponse
);
});
});
describe
(
'
getListIssues
'
,
()
=>
{
let
list
;
beforeEach
(()
=>
{
list
=
new
List
(
listObj
);
setupDefaultResponses
();
});
it
(
'
makes a request to get issues
'
,
()
=>
{
const
expectedResponse
=
expect
.
objectContaining
({
issues
:
[
createTestIssue
()]
});
expect
(
list
.
issues
).
toEqual
([]);
return
expect
(
boardsStore
.
getListIssues
(
list
,
true
)).
resolves
.
toEqual
(
expectedResponse
);
});
});
describe
(
'
getIssuesForList
'
,
()
=>
{
const
id
=
'
TOO-MUCH
'
;
const
url
=
`
${
endpoints
.
listsEndpoint
}
/
${
id
}
/issues?id=
${
id
}
`
;
it
(
'
makes a request to fetch list issues
'
,
()
=>
{
axiosMock
.
onGet
(
url
).
replyOnce
(
200
,
dummyResponse
);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
getIssuesForList
(
id
)).
resolves
.
toEqual
(
expectedResponse
);
});
it
(
'
makes a request to fetch list issues with filter
'
,
()
=>
{
const
filter
=
{
algal
:
'
scrubber
'
};
axiosMock
.
onGet
(
`
${
url
}
&algal=scrubber`
).
replyOnce
(
200
,
dummyResponse
);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
getIssuesForList
(
id
,
filter
)).
resolves
.
toEqual
(
expectedResponse
);
});
it
(
'
fails for error response
'
,
()
=>
{
axiosMock
.
onGet
(
url
).
replyOnce
(
500
);
return
expect
(
boardsStore
.
getIssuesForList
(
id
)).
rejects
.
toThrow
();
});
});
describe
(
'
moveIssue
'
,
()
=>
{
const
urlRoot
=
'
potato
'
;
const
id
=
'
over 9000
'
;
const
fromListId
=
'
left
'
;
const
toListId
=
'
right
'
;
const
moveBeforeId
=
'
up
'
;
const
moveAfterId
=
'
down
'
;
const
expectedRequest
=
expect
.
objectContaining
({
data
:
JSON
.
stringify
({
from_list_id
:
fromListId
,
to_list_id
:
toListId
,
move_before_id
:
moveBeforeId
,
move_after_id
:
moveAfterId
,
}),
});
let
requestSpy
;
beforeAll
(()
=>
{
global
.
gon
.
relative_url_root
=
urlRoot
;
});
afterAll
(()
=>
{
delete
global
.
gon
.
relative_url_root
;
});
beforeEach
(()
=>
{
requestSpy
=
jest
.
fn
();
axiosMock
.
onPut
(
`
${
urlRoot
}
/-/boards/
${
boardId
}
/issues/
${
id
}
`
)
.
replyOnce
((
config
)
=>
requestSpy
(
config
));
});
it
(
'
makes a request to move an issue between lists
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
200
,
dummyResponse
]);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
moveIssue
(
id
,
fromListId
,
toListId
,
moveBeforeId
,
moveAfterId
))
.
resolves
.
toEqual
(
expectedResponse
)
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
it
(
'
fails for error response
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
500
]);
return
expect
(
boardsStore
.
moveIssue
(
id
,
fromListId
,
toListId
,
moveBeforeId
,
moveAfterId
))
.
rejects
.
toThrow
()
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
});
describe
(
'
newIssue
'
,
()
=>
{
const
id
=
1
;
const
issue
=
{
some
:
'
issue data
'
};
const
url
=
`
${
endpoints
.
listsEndpoint
}
/
${
id
}
/issues`
;
const
expectedRequest
=
expect
.
objectContaining
({
data
:
JSON
.
stringify
({
issue
,
}),
});
let
requestSpy
;
beforeEach
(()
=>
{
requestSpy
=
jest
.
fn
();
axiosMock
.
onPost
(
url
).
replyOnce
((
config
)
=>
requestSpy
(
config
));
});
it
(
'
makes a request to create a new issue
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
200
,
dummyResponse
]);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
newIssue
(
id
,
issue
))
.
resolves
.
toEqual
(
expectedResponse
)
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
it
(
'
fails for error response
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
500
]);
return
expect
(
boardsStore
.
newIssue
(
id
,
issue
))
.
rejects
.
toThrow
()
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
});
describe
(
'
getBacklog
'
,
()
=>
{
const
urlRoot
=
'
deep
'
;
const
url
=
`
${
urlRoot
}
/-/boards/
${
boardId
}
/issues.json?not=relevant`
;
const
requestParams
=
{
not
:
'
relevant
'
,
};
beforeAll
(()
=>
{
global
.
gon
.
relative_url_root
=
urlRoot
;
});
afterAll
(()
=>
{
delete
global
.
gon
.
relative_url_root
;
});
it
(
'
makes a request to fetch backlog
'
,
()
=>
{
axiosMock
.
onGet
(
url
).
replyOnce
(
200
,
dummyResponse
);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
getBacklog
(
requestParams
)).
resolves
.
toEqual
(
expectedResponse
);
});
it
(
'
fails for error response
'
,
()
=>
{
axiosMock
.
onGet
(
url
).
replyOnce
(
500
);
return
expect
(
boardsStore
.
getBacklog
(
requestParams
)).
rejects
.
toThrow
();
});
});
describe
(
'
bulkUpdate
'
,
()
=>
{
const
issueIds
=
[
1
,
2
,
3
];
const
extraData
=
{
moar
:
'
data
'
};
const
expectedRequest
=
expect
.
objectContaining
({
data
:
JSON
.
stringify
({
update
:
{
...
extraData
,
issuable_ids
:
'
1,2,3
'
,
},
}),
});
let
requestSpy
;
beforeEach
(()
=>
{
requestSpy
=
jest
.
fn
();
axiosMock
.
onPost
(
endpoints
.
bulkUpdatePath
).
replyOnce
((
config
)
=>
requestSpy
(
config
));
});
it
(
'
makes a request to create a list
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
200
,
dummyResponse
]);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
bulkUpdate
(
issueIds
,
extraData
))
.
resolves
.
toEqual
(
expectedResponse
)
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
it
(
'
fails for error response
'
,
()
=>
{
requestSpy
.
mockReturnValue
([
500
]);
return
expect
(
boardsStore
.
bulkUpdate
(
issueIds
,
extraData
))
.
rejects
.
toThrow
()
.
then
(()
=>
{
expect
(
requestSpy
).
toHaveBeenCalledWith
(
expectedRequest
);
});
});
});
describe
(
'
getIssueInfo
'
,
()
=>
{
const
dummyEndpoint
=
`
${
TEST_HOST
}
/some/where`
;
it
(
'
makes a request to the given endpoint
'
,
()
=>
{
axiosMock
.
onGet
(
dummyEndpoint
).
replyOnce
(
200
,
dummyResponse
);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
getIssueInfo
(
dummyEndpoint
)).
resolves
.
toEqual
(
expectedResponse
);
});
it
(
'
fails for error response
'
,
()
=>
{
axiosMock
.
onGet
(
dummyEndpoint
).
replyOnce
(
500
);
return
expect
(
boardsStore
.
getIssueInfo
(
dummyEndpoint
)).
rejects
.
toThrow
();
});
});
describe
(
'
toggleIssueSubscription
'
,
()
=>
{
const
dummyEndpoint
=
`
${
TEST_HOST
}
/some/where`
;
it
(
'
makes a request to the given endpoint
'
,
()
=>
{
axiosMock
.
onPost
(
dummyEndpoint
).
replyOnce
(
200
,
dummyResponse
);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
toggleIssueSubscription
(
dummyEndpoint
)).
resolves
.
toEqual
(
expectedResponse
,
);
});
it
(
'
fails for error response
'
,
()
=>
{
axiosMock
.
onPost
(
dummyEndpoint
).
replyOnce
(
500
);
return
expect
(
boardsStore
.
toggleIssueSubscription
(
dummyEndpoint
)).
rejects
.
toThrow
();
});
});
describe
(
'
recentBoards
'
,
()
=>
{
const
url
=
`
${
endpoints
.
recentBoardsEndpoint
}
.json`
;
it
(
'
makes a request to fetch all boards
'
,
()
=>
{
axiosMock
.
onGet
(
url
).
replyOnce
(
200
,
dummyResponse
);
const
expectedResponse
=
expect
.
objectContaining
({
data
:
dummyResponse
});
return
expect
(
boardsStore
.
recentBoards
()).
resolves
.
toEqual
(
expectedResponse
);
});
it
(
'
fails for error response
'
,
()
=>
{
axiosMock
.
onGet
(
url
).
replyOnce
(
500
);
return
expect
(
boardsStore
.
recentBoards
()).
rejects
.
toThrow
();
});
});
describe
(
'
when created
'
,
()
=>
{
beforeEach
(()
=>
{
setupDefaultResponses
();
jest
.
spyOn
(
boardsStore
,
'
moveIssue
'
).
mockReturnValue
(
Promise
.
resolve
());
jest
.
spyOn
(
boardsStore
,
'
moveMultipleIssues
'
).
mockReturnValue
(
Promise
.
resolve
());
boardsStore
.
create
();
});
it
(
'
starts with a blank state
'
,
()
=>
{
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
0
);
});
describe
(
'
addList
'
,
()
=>
{
it
(
'
sorts by position
'
,
()
=>
{
boardsStore
.
addList
({
position
:
2
});
boardsStore
.
addList
({
position
:
1
});
expect
(
boardsStore
.
state
.
lists
.
map
(({
position
})
=>
position
)).
toEqual
([
1
,
2
]);
});
});
describe
(
'
toggleFilter
'
,
()
=>
{
const
dummyFilter
=
'
x=42
'
;
let
updateTokensSpy
;
beforeEach
(()
=>
{
updateTokensSpy
=
jest
.
fn
();
eventHub
.
$once
(
'
updateTokens
'
,
updateTokensSpy
);
// prevent using window.history
jest
.
spyOn
(
boardsStore
,
'
updateFiltersUrl
'
).
mockReturnValue
();
});
it
(
'
adds the filter if it is not present
'
,
()
=>
{
boardsStore
.
filter
.
path
=
'
something
'
;
boardsStore
.
toggleFilter
(
dummyFilter
);
expect
(
boardsStore
.
filter
.
path
).
toEqual
(
`something&
${
dummyFilter
}
`
);
expect
(
updateTokensSpy
).
toHaveBeenCalled
();
expect
(
boardsStore
.
updateFiltersUrl
).
toHaveBeenCalled
();
});
it
(
'
removes the filter if it is present
'
,
()
=>
{
boardsStore
.
filter
.
path
=
`something&
${
dummyFilter
}
`
;
boardsStore
.
toggleFilter
(
dummyFilter
);
expect
(
boardsStore
.
filter
.
path
).
toEqual
(
'
something
'
);
expect
(
updateTokensSpy
).
toHaveBeenCalled
();
expect
(
boardsStore
.
updateFiltersUrl
).
toHaveBeenCalled
();
});
});
describe
(
'
lists
'
,
()
=>
{
it
(
'
creates new list without persisting to DB
'
,
()
=>
{
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
0
);
boardsStore
.
addList
(
listObj
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
});
it
(
'
finds list by ID
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findList
(
'
id
'
,
listObj
.
id
);
expect
(
list
.
id
).
toBe
(
listObj
.
id
);
});
it
(
'
finds list by type
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findList
(
'
type
'
,
'
label
'
);
expect
(
list
).
toBeDefined
();
});
it
(
'
finds list by label ID
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findListByLabelId
(
listObj
.
label
.
id
);
expect
(
list
.
id
).
toBe
(
listObj
.
id
);
});
it
(
'
gets issue when new list added
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findList
(
'
id
'
,
listObj
.
id
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
return
axios
.
waitForAll
().
then
(()
=>
{
expect
(
list
.
issues
.
length
).
toBe
(
1
);
expect
(
list
.
issues
[
0
].
id
).
toBe
(
1
);
});
});
it
(
'
persists new list
'
,
()
=>
{
boardsStore
.
new
({
title
:
'
Test
'
,
list_type
:
'
label
'
,
label
:
{
id
:
1
,
title
:
'
Testing
'
,
color
:
'
red
'
,
description
:
'
testing;
'
,
},
});
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
return
axios
.
waitForAll
().
then
(()
=>
{
const
list
=
boardsStore
.
findList
(
'
id
'
,
listObj
.
id
);
expect
(
list
).
toEqual
(
expect
.
objectContaining
({
id
:
listObj
.
id
,
position
:
0
,
}),
);
});
});
it
(
'
removes list from state
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
boardsStore
.
removeList
(
listObj
.
id
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
0
);
});
it
(
'
moves the position of lists
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
boardsStore
.
moveList
(
listOne
,
[
listObjDuplicate
.
id
,
listObj
.
id
]);
expect
(
listOne
.
position
).
toBe
(
1
);
});
it
(
'
moves an issue from one list to another
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
return
axios
.
waitForAll
().
then
(()
=>
{
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
listOne
,
listTwo
,
listOne
.
findIssue
(
1
));
expect
(
listOne
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
});
});
it
(
'
moves an issue from backlog to a list
'
,
()
=>
{
const
backlog
=
boardsStore
.
addList
({
...
listObj
,
list_type
:
'
backlog
'
,
});
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
return
axios
.
waitForAll
().
then
(()
=>
{
expect
(
backlog
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
backlog
,
listTwo
,
backlog
.
findIssue
(
1
));
expect
(
backlog
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
});
});
it
(
'
moves issue to top of another list
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
return
axios
.
waitForAll
().
then
(()
=>
{
listOne
.
issues
[
0
].
id
=
2
;
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
listOne
,
listTwo
,
listOne
.
findIssue
(
2
),
0
);
expect
(
listOne
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
2
);
expect
(
listTwo
.
issues
[
0
].
id
).
toBe
(
2
);
expect
(
boardsStore
.
moveIssue
).
toHaveBeenCalledWith
(
2
,
listOne
.
id
,
listTwo
.
id
,
null
,
1
);
});
});
it
(
'
moves issue to bottom of another list
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
return
axios
.
waitForAll
().
then
(()
=>
{
listOne
.
issues
[
0
].
id
=
2
;
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
listOne
,
listTwo
,
listOne
.
findIssue
(
2
),
1
);
expect
(
listOne
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
2
);
expect
(
listTwo
.
issues
[
1
].
id
).
toBe
(
2
);
expect
(
boardsStore
.
moveIssue
).
toHaveBeenCalledWith
(
2
,
listOne
.
id
,
listTwo
.
id
,
1
,
null
);
});
});
it
(
'
moves issue in list
'
,
()
=>
{
const
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
id
:
2
,
iid
:
2
,
confidential
:
false
,
labels
:
[],
assignees
:
[],
});
const
list
=
boardsStore
.
addList
(
listObj
);
return
axios
.
waitForAll
().
then
(()
=>
{
list
.
addIssue
(
issue
);
expect
(
list
.
issues
.
length
).
toBe
(
2
);
boardsStore
.
moveIssueInList
(
list
,
issue
,
0
,
1
,
[
1
,
2
]);
expect
(
list
.
issues
[
0
].
id
).
toBe
(
2
);
expect
(
boardsStore
.
moveIssue
).
toHaveBeenCalledWith
(
2
,
null
,
null
,
1
,
null
);
});
});
});
describe
(
'
setListDetail
'
,
()
=>
{
it
(
'
sets the list detail
'
,
()
=>
{
boardsStore
.
detail
.
list
=
'
not a list
'
;
const
dummyValue
=
'
new list
'
;
boardsStore
.
setListDetail
(
dummyValue
);
expect
(
boardsStore
.
detail
.
list
).
toEqual
(
dummyValue
);
});
});
describe
(
'
clearDetailIssue
'
,
()
=>
{
it
(
'
resets issue details
'
,
()
=>
{
boardsStore
.
detail
.
issue
=
'
something
'
;
boardsStore
.
clearDetailIssue
();
expect
(
boardsStore
.
detail
.
issue
).
toEqual
({});
});
});
describe
(
'
setIssueDetail
'
,
()
=>
{
it
(
'
sets issue details
'
,
()
=>
{
boardsStore
.
detail
.
issue
=
'
some details
'
;
const
dummyValue
=
'
new details
'
;
boardsStore
.
setIssueDetail
(
dummyValue
);
expect
(
boardsStore
.
detail
.
issue
).
toEqual
(
dummyValue
);
});
});
describe
(
'
startMoving
'
,
()
=>
{
it
(
'
stores list and issue
'
,
()
=>
{
const
dummyIssue
=
'
some issue
'
;
const
dummyList
=
'
some list
'
;
boardsStore
.
startMoving
(
dummyList
,
dummyIssue
);
expect
(
boardsStore
.
moving
.
issue
).
toEqual
(
dummyIssue
);
expect
(
boardsStore
.
moving
.
list
).
toEqual
(
dummyList
);
});
});
describe
(
'
setTimeTrackingLimitToHours
'
,
()
=>
{
it
(
'
sets the timeTracking.LimitToHours option
'
,
()
=>
{
boardsStore
.
timeTracking
.
limitToHours
=
false
;
boardsStore
.
setTimeTrackingLimitToHours
(
'
true
'
);
expect
(
boardsStore
.
timeTracking
.
limitToHours
).
toEqual
(
true
);
});
});
describe
(
'
setCurrentBoard
'
,
()
=>
{
const
dummyBoard
=
'
hoverboard
'
;
it
(
'
sets the current board
'
,
()
=>
{
const
{
state
}
=
boardsStore
;
state
.
currentBoard
=
null
;
boardsStore
.
setCurrentBoard
(
dummyBoard
);
expect
(
state
.
currentBoard
).
toEqual
(
dummyBoard
);
});
});
describe
(
'
toggleMultiSelect
'
,
()
=>
{
let
basicIssueObj
;
beforeAll
(()
=>
{
basicIssueObj
=
{
id
:
987654
};
});
afterEach
(()
=>
{
boardsStore
.
clearMultiSelect
();
});
it
(
'
adds issue when not present
'
,
()
=>
{
boardsStore
.
toggleMultiSelect
(
basicIssueObj
);
const
selectedIds
=
boardsStore
.
multiSelect
.
list
.
map
(({
id
})
=>
id
);
expect
(
selectedIds
.
includes
(
basicIssueObj
.
id
)).
toEqual
(
true
);
});
it
(
'
removes issue when issue is present
'
,
()
=>
{
boardsStore
.
toggleMultiSelect
(
basicIssueObj
);
let
selectedIds
=
boardsStore
.
multiSelect
.
list
.
map
(({
id
})
=>
id
);
expect
(
selectedIds
.
includes
(
basicIssueObj
.
id
)).
toEqual
(
true
);
boardsStore
.
toggleMultiSelect
(
basicIssueObj
);
selectedIds
=
boardsStore
.
multiSelect
.
list
.
map
(({
id
})
=>
id
);
expect
(
selectedIds
.
includes
(
basicIssueObj
.
id
)).
toEqual
(
false
);
});
});
describe
(
'
clearMultiSelect
'
,
()
=>
{
it
(
'
clears all the multi selected issues
'
,
()
=>
{
const
issue1
=
{
id
:
12345
};
const
issue2
=
{
id
:
12346
};
boardsStore
.
toggleMultiSelect
(
issue1
);
boardsStore
.
toggleMultiSelect
(
issue2
);
expect
(
boardsStore
.
multiSelect
.
list
.
length
).
toEqual
(
2
);
boardsStore
.
clearMultiSelect
();
expect
(
boardsStore
.
multiSelect
.
list
.
length
).
toEqual
(
0
);
});
});
describe
(
'
moveMultipleIssuesToList
'
,
()
=>
{
it
(
'
move issues on the new index
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
return
axios
.
waitForAll
().
then
(()
=>
{
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveMultipleIssuesToList
({
listFrom
:
listOne
,
listTo
:
listTwo
,
issues
:
listOne
.
issues
,
newIndex
:
0
,
});
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
});
});
});
describe
(
'
moveMultipleIssuesInList
'
,
()
=>
{
it
(
'
moves multiple issues in list
'
,
()
=>
{
const
issueObj
=
{
title
:
'
Issue #1
'
,
id
:
12345
,
iid
:
2
,
confidential
:
false
,
labels
:
[],
assignees
:
[],
};
const
issue1
=
new
ListIssue
(
issueObj
);
const
issue2
=
new
ListIssue
({
...
issueObj
,
title
:
'
Issue #2
'
,
id
:
12346
,
});
const
list
=
boardsStore
.
addList
(
listObj
);
return
axios
.
waitForAll
().
then
(()
=>
{
list
.
addIssue
(
issue1
);
list
.
addIssue
(
issue2
);
expect
(
list
.
issues
.
length
).
toBe
(
3
);
expect
(
list
.
issues
[
0
].
id
).
not
.
toBe
(
issue2
.
id
);
boardsStore
.
moveMultipleIssuesInList
({
list
,
issues
:
[
issue1
,
issue2
],
oldIndicies
:
[
0
],
newIndex
:
1
,
idArray
:
[
1
,
12345
,
12346
],
});
expect
(
list
.
issues
[
0
].
id
).
toBe
(
issue1
.
id
);
expect
(
boardsStore
.
moveMultipleIssues
).
toHaveBeenCalledWith
({
ids
:
[
issue1
.
id
,
issue2
.
id
],
fromListId
:
null
,
toListId
:
null
,
moveBeforeId
:
1
,
moveAfterId
:
null
,
});
});
});
});
describe
(
'
addListIssue
'
,
()
=>
{
let
list
;
const
issue1
=
new
ListIssue
({
title
:
'
Testing
'
,
id
:
2
,
iid
:
2
,
confidential
:
false
,
labels
:
[
{
color
:
'
#ff0000
'
,
description
:
'
testing;
'
,
id
:
5000
,
priority
:
undefined
,
textColor
:
'
white
'
,
title
:
'
Test
'
,
},
],
assignees
:
[],
});
const
issue2
=
new
ListIssue
({
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
confidential
:
false
,
labels
:
[
{
id
:
1
,
title
:
'
test
'
,
color
:
'
red
'
,
description
:
'
testing
'
,
},
],
assignees
:
[
{
id
:
1
,
name
:
'
name
'
,
username
:
'
username
'
,
avatar_url
:
'
http://avatar_url
'
,
},
],
real_path
:
'
path/to/issue
'
,
});
beforeEach
(()
=>
{
list
=
new
List
(
listObj
);
list
.
addIssue
(
issue1
);
setupDefaultResponses
();
});
it
(
'
adds issues that are not already on the list
'
,
()
=>
{
expect
(
list
.
findIssue
(
issue2
.
id
)).
toBe
(
undefined
);
expect
(
list
.
issues
).
toEqual
([
issue1
]);
boardsStore
.
addListIssue
(
list
,
issue2
);
expect
(
list
.
findIssue
(
issue2
.
id
)).
toBe
(
issue2
);
expect
(
list
.
issues
.
length
).
toBe
(
2
);
expect
(
list
.
issues
).
toEqual
([
issue1
,
issue2
]);
});
});
describe
(
'
updateIssue
'
,
()
=>
{
let
issue
;
let
patchSpy
;
beforeEach
(()
=>
{
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
confidential
:
false
,
labels
:
[
{
id
:
1
,
title
:
'
test
'
,
color
:
'
red
'
,
description
:
'
testing
'
,
},
],
assignees
:
[
{
id
:
1
,
name
:
'
name
'
,
username
:
'
username
'
,
avatar_url
:
'
http://avatar_url
'
,
},
],
real_path
:
'
path/to/issue
'
,
});
patchSpy
=
jest
.
fn
().
mockReturnValue
([
200
,
{
labels
:
[]
}]);
axiosMock
.
onPatch
(
`path/to/issue.json`
).
reply
(({
data
})
=>
patchSpy
(
JSON
.
parse
(
data
)));
});
it
(
'
passes assignee ids when there are assignees
'
,
()
=>
{
boardsStore
.
updateIssue
(
issue
);
return
boardsStore
.
updateIssue
(
issue
).
then
(()
=>
{
expect
(
patchSpy
).
toHaveBeenCalledWith
({
issue
:
{
milestone_id
:
null
,
assignee_ids
:
[
1
],
label_ids
:
[
1
],
},
});
});
});
it
(
'
passes assignee ids of [0] when there are no assignees
'
,
()
=>
{
issue
.
removeAllAssignees
();
return
boardsStore
.
updateIssue
(
issue
).
then
(()
=>
{
expect
(
patchSpy
).
toHaveBeenCalledWith
({
issue
:
{
milestone_id
:
null
,
assignee_ids
:
[
0
],
label_ids
:
[
1
],
},
});
});
});
});
});
});
spec/frontend/boards/components/board_content_spec.js
View file @
78ef57fa
...
...
@@ -8,7 +8,7 @@ import getters from 'ee_else_ce/boards/stores/getters';
import
BoardColumn
from
'
~/boards/components/board_column.vue
'
;
import
BoardContent
from
'
~/boards/components/board_content.vue
'
;
import
BoardContentSidebar
from
'
~/boards/components/board_content_sidebar.vue
'
;
import
{
mockLists
,
mockListsWithModel
}
from
'
../mock_data
'
;
import
{
mockLists
}
from
'
../mock_data
'
;
Vue
.
use
(
Vuex
);
...
...
@@ -42,7 +42,7 @@ describe('BoardContent', () => {
});
wrapper
=
shallowMount
(
BoardContent
,
{
propsData
:
{
lists
:
mockLists
WithModel
,
lists
:
mockLists
,
disabled
:
false
,
...
props
,
},
...
...
@@ -63,7 +63,7 @@ describe('BoardContent', () => {
});
it
(
'
renders a BoardColumn component per list
'
,
()
=>
{
expect
(
wrapper
.
findAllComponents
(
BoardColumn
)).
toHaveLength
(
mockLists
WithModel
.
length
);
expect
(
wrapper
.
findAllComponents
(
BoardColumn
)).
toHaveLength
(
mockLists
.
length
);
});
it
(
'
renders BoardContentSidebar
'
,
()
=>
{
...
...
spec/frontend/boards/issue_spec.js
deleted
100644 → 0
View file @
d98c7844
/* global ListIssue */
import
'
~/boards/models/label
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
{
setMockEndpoints
,
mockIssue
}
from
'
./mock_data
'
;
describe
(
'
Issue model
'
,
()
=>
{
let
issue
;
beforeEach
(()
=>
{
setMockEndpoints
();
boardsStore
.
create
();
issue
=
new
ListIssue
(
mockIssue
);
});
it
(
'
has label
'
,
()
=>
{
expect
(
issue
.
labels
.
length
).
toBe
(
1
);
});
it
(
'
add new label
'
,
()
=>
{
issue
.
addLabel
({
id
:
2
,
title
:
'
bug
'
,
color
:
'
blue
'
,
description
:
'
bugs!
'
,
});
expect
(
issue
.
labels
.
length
).
toBe
(
2
);
});
it
(
'
does not add label if label id exists
'
,
()
=>
{
issue
.
addLabel
({
id
:
1
,
title
:
'
test 2
'
,
color
:
'
blue
'
,
description
:
'
testing
'
,
});
expect
(
issue
.
labels
.
length
).
toBe
(
1
);
expect
(
issue
.
labels
[
0
].
color
).
toBe
(
'
#F0AD4E
'
);
});
it
(
'
adds other label with same title
'
,
()
=>
{
issue
.
addLabel
({
id
:
2
,
title
:
'
test
'
,
color
:
'
blue
'
,
description
:
'
other test
'
,
});
expect
(
issue
.
labels
.
length
).
toBe
(
2
);
});
it
(
'
finds label
'
,
()
=>
{
const
label
=
issue
.
findLabel
(
issue
.
labels
[
0
]);
expect
(
label
).
toBeDefined
();
});
it
(
'
removes label
'
,
()
=>
{
const
label
=
issue
.
findLabel
(
issue
.
labels
[
0
]);
issue
.
removeLabel
(
label
);
expect
(
issue
.
labels
.
length
).
toBe
(
0
);
});
it
(
'
removes multiple labels
'
,
()
=>
{
issue
.
addLabel
({
id
:
2
,
title
:
'
bug
'
,
color
:
'
blue
'
,
description
:
'
bugs!
'
,
});
expect
(
issue
.
labels
.
length
).
toBe
(
2
);
issue
.
removeLabels
([
issue
.
labels
[
0
],
issue
.
labels
[
1
]]);
expect
(
issue
.
labels
.
length
).
toBe
(
0
);
});
it
(
'
adds assignee
'
,
()
=>
{
issue
.
addAssignee
({
id
:
2
,
name
:
'
Bruce Wayne
'
,
username
:
'
batman
'
,
avatar_url
:
'
http://batman
'
,
});
expect
(
issue
.
assignees
.
length
).
toBe
(
2
);
});
it
(
'
finds assignee
'
,
()
=>
{
const
assignee
=
issue
.
findAssignee
(
issue
.
assignees
[
0
]);
expect
(
assignee
).
toBeDefined
();
});
it
(
'
removes assignee
'
,
()
=>
{
const
assignee
=
issue
.
findAssignee
(
issue
.
assignees
[
0
]);
issue
.
removeAssignee
(
assignee
);
expect
(
issue
.
assignees
.
length
).
toBe
(
0
);
});
it
(
'
removes all assignees
'
,
()
=>
{
issue
.
removeAllAssignees
();
expect
(
issue
.
assignees
.
length
).
toBe
(
0
);
});
it
(
'
sets position to infinity if no position is stored
'
,
()
=>
{
expect
(
issue
.
position
).
toBe
(
Infinity
);
});
it
(
'
sets position
'
,
()
=>
{
const
relativePositionIssue
=
new
ListIssue
({
title
:
'
Testing
'
,
iid
:
1
,
confidential
:
false
,
relative_position
:
1
,
labels
:
[],
assignees
:
[],
});
expect
(
relativePositionIssue
.
position
).
toBe
(
1
);
});
it
(
'
updates data
'
,
()
=>
{
issue
.
updateData
({
subscribed
:
true
});
expect
(
issue
.
subscribed
).
toBe
(
true
);
});
it
(
'
sets fetching state
'
,
()
=>
{
expect
(
issue
.
isFetching
.
subscriptions
).
toBe
(
true
);
issue
.
setFetchingState
(
'
subscriptions
'
,
false
);
expect
(
issue
.
isFetching
.
subscriptions
).
toBe
(
false
);
});
it
(
'
sets loading state
'
,
()
=>
{
issue
.
setLoadingState
(
'
foo
'
,
true
);
expect
(
issue
.
isLoading
.
foo
).
toBe
(
true
);
});
describe
(
'
update
'
,
()
=>
{
it
(
'
passes update to boardsStore
'
,
()
=>
{
jest
.
spyOn
(
boardsStore
,
'
updateIssue
'
).
mockImplementation
();
issue
.
update
();
expect
(
boardsStore
.
updateIssue
).
toHaveBeenCalledWith
(
issue
);
});
});
});
spec/frontend/boards/list_spec.js
deleted
100644 → 0
View file @
d98c7844
/* global List */
/* global ListAssignee */
/* global ListIssue */
/* global ListLabel */
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
'
~/boards/models/label
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
{
ListType
}
from
'
~/boards/constants
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
listObj
,
listObjDuplicate
,
boardsMockInterceptor
}
from
'
./mock_data
'
;
describe
(
'
List model
'
,
()
=>
{
let
list
;
let
mock
;
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
mock
.
onAny
().
reply
(
boardsMockInterceptor
);
boardsStore
.
create
();
boardsStore
.
setEndpoints
({
listsEndpoint
:
'
/test/-/boards/1/lists
'
,
});
list
=
new
List
(
listObj
);
return
waitForPromises
();
});
afterEach
(()
=>
{
mock
.
restore
();
});
describe
(
'
list type
'
,
()
=>
{
const
notExpandableList
=
[
'
blank
'
];
const
table
=
Object
.
keys
(
ListType
).
map
((
k
)
=>
{
const
value
=
ListType
[
k
];
return
[
value
,
!
notExpandableList
.
includes
(
value
)];
});
it
.
each
(
table
)(
`when list_type is %s boards isExpandable is %p`
,
(
type
,
result
)
=>
{
expect
(
new
List
({
id
:
1
,
list_type
:
type
}).
isExpandable
).
toBe
(
result
);
});
});
it
(
'
gets issues when created
'
,
()
=>
{
expect
(
list
.
issues
.
length
).
toBe
(
1
);
});
it
(
'
saves list and returns ID
'
,
()
=>
{
list
=
new
List
({
title
:
'
test
'
,
label
:
{
id
:
1
,
title
:
'
test
'
,
color
:
'
#ff0000
'
,
text_color
:
'
white
'
,
},
});
return
list
.
save
().
then
(()
=>
{
expect
(
list
.
id
).
toBe
(
listObj
.
id
);
expect
(
list
.
type
).
toBe
(
'
label
'
);
expect
(
list
.
position
).
toBe
(
0
);
expect
(
list
.
label
).
toEqual
(
listObj
.
label
);
});
});
it
(
'
destroys the list
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
list
=
boardsStore
.
findList
(
'
id
'
,
listObj
.
id
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
list
.
destroy
();
return
waitForPromises
().
then
(()
=>
{
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
0
);
});
});
it
(
'
gets issue from list
'
,
()
=>
{
const
issue
=
list
.
findIssue
(
1
);
expect
(
issue
).
toBeDefined
();
});
it
(
'
removes issue
'
,
()
=>
{
const
issue
=
list
.
findIssue
(
1
);
expect
(
list
.
issues
.
length
).
toBe
(
1
);
list
.
removeIssue
(
issue
);
expect
(
list
.
issues
.
length
).
toBe
(
0
);
});
it
(
'
sends service request to update issue label
'
,
()
=>
{
const
listDup
=
new
List
(
listObjDuplicate
);
const
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
confidential
:
false
,
labels
:
[
list
.
label
,
listDup
.
label
],
assignees
:
[],
});
list
.
issues
.
push
(
issue
);
listDup
.
issues
.
push
(
issue
);
jest
.
spyOn
(
boardsStore
,
'
moveIssue
'
);
listDup
.
updateIssueLabel
(
issue
,
list
);
expect
(
boardsStore
.
moveIssue
).
toHaveBeenCalledWith
(
issue
.
id
,
list
.
id
,
listDup
.
id
,
undefined
,
undefined
,
);
});
describe
(
'
page number
'
,
()
=>
{
beforeEach
(()
=>
{
jest
.
spyOn
(
list
,
'
getIssues
'
).
mockImplementation
(()
=>
{});
list
.
issues
=
[];
});
it
(
'
increase page number if current issue count is more than the page size
'
,
()
=>
{
for
(
let
i
=
0
;
i
<
30
;
i
+=
1
)
{
list
.
issues
.
push
(
new
ListIssue
({
title
:
'
Testing
'
,
id
:
i
,
iid
:
i
,
confidential
:
false
,
labels
:
[
list
.
label
],
assignees
:
[],
}),
);
}
list
.
issuesSize
=
50
;
expect
(
list
.
issues
.
length
).
toBe
(
30
);
list
.
nextPage
();
expect
(
list
.
page
).
toBe
(
2
);
expect
(
list
.
getIssues
).
toHaveBeenCalled
();
});
it
(
'
does not increase page number if issue count is less than the page size
'
,
()
=>
{
list
.
issues
.
push
(
new
ListIssue
({
title
:
'
Testing
'
,
id
:
1
,
confidential
:
false
,
labels
:
[
list
.
label
],
assignees
:
[],
}),
);
list
.
issuesSize
=
2
;
list
.
nextPage
();
expect
(
list
.
page
).
toBe
(
1
);
expect
(
list
.
getIssues
).
toHaveBeenCalled
();
});
});
describe
(
'
newIssue
'
,
()
=>
{
beforeEach
(()
=>
{
jest
.
spyOn
(
boardsStore
,
'
newIssue
'
).
mockReturnValue
(
Promise
.
resolve
({
data
:
{
id
:
42
,
subscribed
:
false
,
assignable_labels_endpoint
:
'
/issue/42/labels
'
,
toggle_subscription_endpoint
:
'
/issue/42/subscriptions
'
,
issue_sidebar_endpoint
:
'
/issue/42/sidebar_info
'
,
},
}),
);
list
.
issues
=
[];
});
it
(
'
adds new issue to top of list
'
,
(
done
)
=>
{
const
user
=
new
ListAssignee
({
id
:
1
,
name
:
'
testing 123
'
,
username
:
'
test
'
,
avatar
:
'
test_image
'
,
});
list
.
issues
.
push
(
new
ListIssue
({
title
:
'
Testing
'
,
id
:
1
,
confidential
:
false
,
labels
:
[
new
ListLabel
(
list
.
label
)],
assignees
:
[],
}),
);
const
dummyIssue
=
new
ListIssue
({
title
:
'
new issue
'
,
id
:
2
,
confidential
:
false
,
labels
:
[
new
ListLabel
(
list
.
label
)],
assignees
:
[
user
],
subscribed
:
false
,
});
list
.
newIssue
(
dummyIssue
)
.
then
(()
=>
{
expect
(
list
.
issues
.
length
).
toBe
(
2
);
expect
(
list
.
issues
[
0
]).
toBe
(
dummyIssue
);
expect
(
list
.
issues
[
0
].
subscribed
).
toBe
(
false
);
expect
(
list
.
issues
[
0
].
assignableLabelsEndpoint
).
toBe
(
'
/issue/42/labels
'
);
expect
(
list
.
issues
[
0
].
toggleSubscriptionEndpoint
).
toBe
(
'
/issue/42/subscriptions
'
);
expect
(
list
.
issues
[
0
].
sidebarInfoEndpoint
).
toBe
(
'
/issue/42/sidebar_info
'
);
expect
(
list
.
issues
[
0
].
labels
).
toBe
(
dummyIssue
.
labels
);
expect
(
list
.
issues
[
0
].
assignees
).
toBe
(
dummyIssue
.
assignees
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
spec/frontend/boards/mock_data.js
View file @
78ef57fa
/* global List */
import
{
GlFilteredSearchToken
}
from
'
@gitlab/ui
'
;
import
{
keyBy
}
from
'
lodash
'
;
import
Vue
from
'
vue
'
;
import
'
~/boards/models/list
'
;
import
{
ListType
}
from
'
~/boards/constants
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
DEFAULT_MILESTONES_GRAPHQL
}
from
'
~/vue_shared/components/filtered_search_bar/constants
'
;
import
AuthorToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/author_token.vue
'
;
...
...
@@ -290,20 +285,6 @@ export const boardsMockInterceptor = (config) => {
return
[
200
,
body
];
};
export
const
setMockEndpoints
=
(
opts
=
{})
=>
{
const
boardsEndpoint
=
opts
.
boardsEndpoint
||
'
/test/issue-boards/-/boards.json
'
;
const
listsEndpoint
=
opts
.
listsEndpoint
||
'
/test/-/boards/1/lists
'
;
const
bulkUpdatePath
=
opts
.
bulkUpdatePath
||
''
;
const
boardId
=
opts
.
boardId
||
'
1
'
;
boardsStore
.
setEndpoints
({
boardsEndpoint
,
listsEndpoint
,
bulkUpdatePath
,
boardId
,
});
};
export
const
mockList
=
{
id
:
'
gid://gitlab/List/1
'
,
title
:
'
Open
'
,
...
...
@@ -356,10 +337,6 @@ export const mockLists = [mockList, mockLabelList];
export
const
mockListsById
=
keyBy
(
mockLists
,
'
id
'
);
export
const
mockListsWithModel
=
mockLists
.
map
((
listMock
)
=>
Vue
.
observable
(
new
List
({
...
listMock
,
doNotFetchIssues
:
true
})),
);
export
const
mockIssuesByListId
=
{
'
gid://gitlab/List/1
'
:
[
mockIssue
.
id
,
mockIssue3
.
id
,
mockIssue4
.
id
],
'
gid://gitlab/List/2
'
:
mockIssues
.
map
(({
id
})
=>
id
),
...
...
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