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
7523392c
Commit
7523392c
authored
Aug 12, 2021
by
Natalia Tepluhina
Committed by
Vitaly Slobodin
Aug 12, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor DropdownContentsEditView component to use GraphQL + Apollo
parent
8df6be82
Changes
16
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
407 additions
and
661 deletions
+407
-661
app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
.../javascripts/sidebar/components/labels/sidebar_labels.vue
+8
-7
app/assets/javascripts/sidebar/mount_sidebar.js
app/assets/javascripts/sidebar/mount_sidebar.js
+2
-0
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
...onents/sidebar/labels_select_widget/dropdown_contents.vue
+32
-2
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
...ar/labels_select_widget/dropdown_contents_labels_view.vue
+160
-100
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql
...labels_select_widget/graphql/project_labels.query.graphql
+12
-0
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
...nents/sidebar/labels_select_widget/labels_select_root.vue
+15
-17
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js
.../components/sidebar/labels_select_widget/store/actions.js
+0
-22
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutation_types.js
...ents/sidebar/labels_select_widget/store/mutation_types.js
+0
-8
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js
...omponents/sidebar/labels_select_widget/store/mutations.js
+0
-21
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js
...abels_select_widget/dropdown_contents_create_view_spec.js
+2
-2
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
...abels_select_widget/dropdown_contents_labels_view_spec.js
+145
-289
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js
...ts/sidebar/labels_select_widget/dropdown_contents_spec.js
+6
-1
spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
...s/sidebar/labels_select_widget/labels_select_root_spec.js
+0
-52
spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
...ared/components/sidebar/labels_select_widget/mock_data.js
+25
-0
spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js
...onents/sidebar/labels_select_widget/store/actions_spec.js
+0
-88
spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js
...ents/sidebar/labels_select_widget/store/mutations_spec.js
+0
-52
No files found.
app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
View file @
7523392c
...
...
@@ -55,12 +55,13 @@ export default {
},
getUpdateVariables
(
dropdownLabels
)
{
const
currentLabelIds
=
this
.
selectedLabels
.
map
((
label
)
=>
label
.
id
);
const
userAddedLabelIds
=
dropdownLabels
.
filter
((
label
)
=>
label
.
set
)
.
map
((
label
)
=>
label
.
id
);
const
userRemovedLabelIds
=
dropdownLabels
.
filter
((
label
)
=>
!
label
.
set
)
.
map
((
label
)
=>
label
.
id
);
const
dropdownLabelIds
=
dropdownLabels
.
map
((
label
)
=>
label
.
id
);
const
userAddedLabelIds
=
this
.
glFeatures
.
labelsWidget
?
difference
(
dropdownLabelIds
,
currentLabelIds
)
:
dropdownLabels
.
filter
((
label
)
=>
label
.
set
).
map
((
label
)
=>
label
.
id
);
const
userRemovedLabelIds
=
this
.
glFeatures
.
labelsWidget
?
difference
(
currentLabelIds
,
dropdownLabelIds
)
:
dropdownLabels
.
filter
((
label
)
=>
!
label
.
set
).
map
((
label
)
=>
label
.
id
);
const
labelIds
=
difference
(
union
(
currentLabelIds
,
userAddedLabelIds
),
userRemovedLabelIds
);
...
...
@@ -155,7 +156,7 @@ export default {
:labels-manage-path=
"labelsManagePath"
:labels-select-in-progress=
"isLabelsSelectInProgress"
:selected-labels=
"selectedLabels"
:variant=
"$options.
sidebar
"
:variant=
"$options.
variant
"
data-qa-selector=
"labels_block"
@
onDropdownClose=
"handleDropdownClose"
@
onLabelRemove=
"handleLabelRemove"
...
...
app/assets/javascripts/sidebar/mount_sidebar.js
View file @
7523392c
...
...
@@ -24,6 +24,7 @@ import SidebarDropdownWidget from '~/sidebar/components/sidebar_dropdown_widget.
import
SidebarTodoWidget
from
'
~/sidebar/components/todo_toggle/sidebar_todo_widget.vue
'
;
import
{
apolloProvider
}
from
'
~/sidebar/graphql
'
;
import
trackShowInviteMemberLink
from
'
~/sidebar/track_invite_members
'
;
import
{
DropdownVariant
}
from
'
~/vue_shared/components/sidebar/labels_select_vue/constants
'
;
import
Translate
from
'
../vue_shared/translate
'
;
import
SidebarAssignees
from
'
./components/assignees/sidebar_assignees.vue
'
;
import
CopyEmailToClipboard
from
'
./components/copy_email_to_clipboard.vue
'
;
...
...
@@ -256,6 +257,7 @@ export function mountSidebarLabels() {
allowLabelEdit
:
parseBoolean
(
el
.
dataset
.
canEdit
),
allowScopedLabels
:
parseBoolean
(
el
.
dataset
.
allowScopedLabels
),
initiallySelectedLabels
:
JSON
.
parse
(
el
.
dataset
.
selectedLabels
),
variant
:
DropdownVariant
.
Sidebar
,
},
render
:
(
createElement
)
=>
createElement
(
SidebarLabels
),
});
...
...
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
View file @
7523392c
...
...
@@ -21,9 +21,29 @@ export default {
type
:
String
,
required
:
true
,
},
selectedLabels
:
{
type
:
Array
,
required
:
true
,
},
allowMultiselect
:
{
type
:
Boolean
,
required
:
true
,
},
labelsListTitle
:
{
type
:
String
,
required
:
true
,
},
footerCreateLabelTitle
:
{
type
:
String
,
required
:
true
,
},
footerManageLabelTitle
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
...
mapState
([
'
showDropdownContentsCreateView
'
,
'
labelsListTitle
'
]),
...
mapState
([
'
showDropdownContentsCreateView
'
]),
...
mapGetters
([
'
isDropdownVariantSidebar
'
,
'
isDropdownVariantEmbedded
'
]),
dropdownContentsView
()
{
if
(
this
.
showDropdownContentsCreateView
)
{
...
...
@@ -75,6 +95,16 @@ export default {
@
click=
"toggleDropdownContents"
/>
</div>
<component
:is=
"dropdownContentsView"
@
hideCreateView=
"toggleDropdownContentsCreateView"
/>
<component
:is=
"dropdownContentsView"
:selected-labels=
"selectedLabels"
:allow-multiselect=
"allowMultiselect"
:labels-list-title=
"labelsListTitle"
:footer-create-label-title=
"footerCreateLabelTitle"
:footer-manage-label-title=
"footerManageLabelTitle"
@
hideCreateView=
"toggleDropdownContentsCreateView"
@
closeDropdown=
"$emit('closeDropdown', $event)"
@
toggleDropdownContentsCreateView=
"toggleDropdownContentsCreateView"
/>
</div>
</
template
>
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
View file @
7523392c
<
script
>
import
{
Gl
IntersectionObserver
,
Gl
LoadingIcon
,
GlSearchBoxByType
,
GlLink
}
from
'
@gitlab/ui
'
;
import
{
GlLoadingIcon
,
GlSearchBoxByType
,
GlLink
}
from
'
@gitlab/ui
'
;
import
fuzzaldrinPlus
from
'
fuzzaldrin-plus
'
;
import
{
mapState
,
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
{
debounce
}
from
'
lodash
'
;
import
createFlash
from
'
~/flash
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
DEFAULT_DEBOUNCE_AND_THROTTLE_MS
}
from
'
~/lib/utils/constants
'
;
import
{
UP_KEY_CODE
,
DOWN_KEY_CODE
,
ENTER_KEY_CODE
,
ESC_KEY_CODE
}
from
'
~/lib/utils/keycodes
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
DropdownVariant
}
from
'
./constants
'
;
import
projectLabelsQuery
from
'
./graphql/project_labels.query.graphql
'
;
import
LabelItem
from
'
./label_item.vue
'
;
export
default
{
components
:
{
GlIntersectionObserver
,
GlLoadingIcon
,
GlSearchBoxByType
,
GlLink
,
LabelItem
,
},
inject
:
[
'
projectPath
'
,
'
allowLabelCreate
'
,
'
labelsManagePath
'
,
'
variant
'
],
props
:
{
selectedLabels
:
{
type
:
Array
,
required
:
true
,
},
allowMultiselect
:
{
type
:
Boolean
,
required
:
true
,
},
labelsListTitle
:
{
type
:
String
,
required
:
true
,
},
footerCreateLabelTitle
:
{
type
:
String
,
required
:
true
,
},
footerManageLabelTitle
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
searchKey
:
''
,
labels
:
[],
currentHighlightItem
:
-
1
,
localSelectedLabels
:
[...
this
.
selectedLabels
],
};
},
apollo
:
{
labels
:
{
query
:
projectLabelsQuery
,
variables
()
{
return
{
fullPath
:
this
.
projectPath
,
searchTerm
:
this
.
searchKey
,
};
},
skip
()
{
return
this
.
searchKey
.
length
===
1
;
},
update
:
(
data
)
=>
data
.
workspace
?.
labels
?.
nodes
||
[],
async
result
()
{
if
(
this
.
$refs
.
searchInput
)
{
await
this
.
$nextTick
();
this
.
$refs
.
searchInput
.
focusInput
();
}
},
error
()
{
createFlash
({
message
:
__
(
'
Error fetching labels.
'
)
});
},
},
},
computed
:
{
...
mapState
([
'
allowLabelCreate
'
,
'
allowMultiselect
'
,
'
labelsManagePath
'
,
'
labels
'
,
'
labelsFetchInProgress
'
,
'
labelsListTitle
'
,
'
footerCreateLabelTitle
'
,
'
footerManageLabelTitle
'
,
]),
...
mapGetters
([
'
selectedLabelsList
'
,
'
isDropdownVariantSidebar
'
,
'
isDropdownVariantEmbedded
'
]),
isDropdownVariantSidebar
()
{
return
this
.
variant
===
DropdownVariant
.
Sidebar
;
},
isDropdownVariantEmbedded
()
{
return
this
.
variant
===
DropdownVariant
.
Embedded
;
},
labelsFetchInProgress
()
{
return
this
.
$apollo
.
queries
.
labels
.
loading
;
},
localSelectedLabelsIds
()
{
return
this
.
localSelectedLabels
.
map
((
label
)
=>
label
.
id
);
},
visibleLabels
()
{
if
(
this
.
searchKey
)
{
return
fuzzaldrinPlus
.
filter
(
this
.
labels
,
this
.
searchKey
,
{
...
...
@@ -55,17 +108,16 @@ export default {
}
},
},
created
()
{
this
.
debouncedSearchKeyUpdate
=
debounce
(
this
.
setSearchKey
,
DEFAULT_DEBOUNCE_AND_THROTTLE_MS
);
},
beforeDestroy
()
{
this
.
$emit
(
'
closeDropdown
'
,
this
.
localSelectedLabels
);
this
.
debouncedSearchKeyUpdate
.
cancel
();
},
methods
:
{
...
mapActions
([
'
toggleDropdownContents
'
,
'
toggleDropdownContentsCreateView
'
,
'
fetchLabels
'
,
'
receiveLabelsSuccess
'
,
'
updateSelectedLabels
'
,
'
toggleDropdownContents
'
,
]),
isLabelSelected
(
label
)
{
return
this
.
selectedLabelsList
.
includes
(
label
.
id
);
return
this
.
localSelectedLabelsIds
.
includes
(
getIdFromGraphQLId
(
label
.
id
)
);
},
/**
* This method scrolls item from dropdown into
...
...
@@ -86,23 +138,17 @@ export default {
}
}
},
handleComponentAppear
()
{
// We can avoid putting `catch` block here
// as failure is handled within actions.js already.
return
this
.
fetchLabels
().
then
(()
=>
{
this
.
$refs
.
searchInput
.
focusInput
();
updateSelectedLabels
(
label
)
{
if
(
this
.
isLabelSelected
(
label
))
{
this
.
localSelectedLabels
=
this
.
localSelectedLabels
.
filter
(
({
id
})
=>
id
!==
getIdFromGraphQLId
(
label
.
id
),
);
}
else
{
this
.
localSelectedLabels
.
push
({
...
label
,
id
:
getIdFromGraphQLId
(
label
.
id
),
});
},
/**
* We want to remove loaded labels to ensure component
* fetches fresh set of labels every time when shown.
*/
handleComponentDisappear
()
{
this
.
receiveLabelsSuccess
([]);
},
handleCreateLabelClick
()
{
this
.
receiveLabelsSuccess
([]);
this
.
toggleDropdownContentsCreateView
();
}
},
/**
* This method enables keyboard navigation support for
...
...
@@ -117,10 +163,10 @@ export default {
)
{
this
.
currentHighlightItem
+=
1
;
}
else
if
(
e
.
keyCode
===
ENTER_KEY_CODE
&&
this
.
currentHighlightItem
>
-
1
)
{
this
.
updateSelectedLabels
(
[
this
.
visibleLabels
[
this
.
currentHighlightItem
]
]);
this
.
updateSelectedLabels
(
this
.
visibleLabels
[
this
.
currentHighlightItem
]);
this
.
searchKey
=
''
;
}
else
if
(
e
.
keyCode
===
ESC_KEY_CODE
)
{
this
.
toggleDropdownContents
(
);
this
.
$emit
(
'
closeDropdown
'
,
this
.
localSelectedLabels
);
}
if
(
e
.
keyCode
!==
ESC_KEY_CODE
)
{
...
...
@@ -132,40 +178,54 @@ export default {
}
},
handleLabelClick
(
label
)
{
this
.
updateSelectedLabels
([
label
]);
if
(
!
this
.
allowMultiselect
)
this
.
toggleDropdownContents
();
this
.
updateSelectedLabels
(
label
);
if
(
!
this
.
allowMultiselect
)
{
this
.
$emit
(
'
closeDropdown
'
,
this
.
localSelectedLabels
);
}
},
setSearchKey
(
value
)
{
this
.
searchKey
=
value
;
},
},
};
</
script
>
<
template
>
<gl-intersection-observer
@
appear=
"handleComponentAppear"
@
disappear=
"handleComponentDisappear"
>
<div
class=
"labels-select-contents-list js-labels-list"
@
keydown=
"handleKeyDown"
>
<div
class=
"labels-select-contents-list js-labels-list"
data-testid=
"dropdown-wrapper"
@
keydown=
"handleKeyDown"
>
<div
class=
"dropdown-input"
@
click.stop=
"() =>
{}">
<gl-search-box-by-type
ref=
"searchInput"
v-model
=
"searchKey"
:value
=
"searchKey"
:disabled=
"labelsFetchInProgress"
data-qa-selector=
"dropdown_input_field"
data-testid=
"dropdown-input-field"
@
input=
"debouncedSearchKeyUpdate"
/>
</div>
<div
ref=
"labelsListContainer"
class=
"dropdown-content"
data-testid=
"dropdown-content"
>
<gl-loading-icon
v-if=
"labelsFetchInProgress"
class=
"labels-fetch-loading gl-align-items-center w-100 h-100
"
class=
"labels-fetch-loading gl-align-items-center gl-w-full gl-h-full
"
size=
"md"
/>
<ul
v-else
class=
"list-unstyled gl-mb-0 gl-word-break-word
"
>
<ul
v-else
class=
"list-unstyled gl-mb-0 gl-word-break-word"
data-testid=
"labels-list
"
>
<label-item
v-for=
"(label, index) in visibleLabels"
:key=
"label.id"
:label=
"label"
:is-label-set=
"label.set
"
:is-label-set=
"isLabelSelected(label)
"
:highlight=
"index === currentHighlightItem"
@
clickLabel=
"handleLabelClick(label)"
/>
<li
v-show=
"showNoMatchingResultsMessage"
class=
"gl-p-3 gl-text-center"
>
<li
v-show=
"showNoMatchingResultsMessage"
class=
"gl-p-3 gl-text-center"
data-testid=
"no-results"
>
{{
__
(
'
No matching results
'
)
}}
</li>
</ul>
...
...
@@ -178,8 +238,9 @@ export default {
<ul
class=
"list-unstyled"
>
<li
v-if=
"allowLabelCreate"
>
<gl-link
class=
"gl-display-flex w-100 flex-row text-break-word label-item"
@
click=
"handleCreateLabelClick"
class=
"gl-display-flex gl-flex-direction-row gl-w-full gl-overflow-break-word label-item"
data-testid=
"create-label-button"
@
click=
"$emit('toggleDropdownContentsCreateView')"
>
{{
footerCreateLabelTitle
}}
</gl-link>
...
...
@@ -187,7 +248,7 @@ export default {
<li>
<gl-link
:href=
"labelsManagePath"
class=
"gl-display-flex flex-row text
-break-word label-item"
class=
"gl-display-flex gl-flex-direction-row gl-w-full gl-overflow
-break-word label-item"
>
{{
footerManageLabelTitle
}}
</gl-link>
...
...
@@ -195,5 +256,4 @@ export default {
</ul>
</div>
</div>
</gl-intersection-observer>
</
template
>
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql
0 → 100644
View file @
7523392c
query
projectLabels
(
$fullPath
:
ID
!,
$searchTerm
:
String
)
{
workspace
:
project
(
fullPath
:
$fullPath
)
{
labels
(
searchTerm
:
$searchTerm
,
includeAncestorGroups
:
true
)
{
nodes
{
id
title
color
description
}
}
}
}
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
View file @
7523392c
...
...
@@ -196,23 +196,6 @@ export default {
},
methods
:
{
...
mapActions
([
'
setInitialState
'
,
'
toggleDropdownContents
'
]),
/**
* This method differentiates between
* dispatched actions and calls necessary method.
*/
handleVuexActionDispatch
(
action
,
state
)
{
if
(
action
.
type
===
'
toggleDropdownContents
'
&&
!
state
.
showDropdownButton
&&
!
state
.
showDropdownContents
)
{
let
filterFn
=
(
label
)
=>
label
.
touched
;
if
(
this
.
isDropdownVariantEmbedded
)
{
filterFn
=
(
label
)
=>
label
.
set
;
}
this
.
handleDropdownClose
(
state
.
labels
.
filter
(
filterFn
));
}
},
/**
* This method stores a mousedown event's target.
* Required by the click listener because the click
...
...
@@ -276,6 +259,9 @@ export default {
handleDropdownClose
(
labels
)
{
// Only emit label updates if there are any labels to update
// on UI.
if
(
this
.
showDropdownContents
)
{
this
.
toggleDropdownContents
();
}
if
(
labels
.
length
)
this
.
$emit
(
'
updateSelectedLabels
'
,
labels
);
this
.
$emit
(
'
onDropdownClose
'
);
},
...
...
@@ -332,8 +318,14 @@ export default {
<dropdown-contents
v-if=
"dropdownButtonVisible && showDropdownContents"
ref=
"dropdownContents"
:allow-multiselect=
"allowMultiselect"
:labels-list-title=
"labelsListTitle"
:footer-create-label-title=
"footerCreateLabelTitle"
:footer-manage-label-title=
"footerManageLabelTitle"
:render-on-top=
"!contentIsOnViewport"
:labels-create-title=
"labelsCreateTitle"
:selected-labels=
"selectedLabels"
@
closeDropdown=
"handleDropdownClose"
/>
</
template
>
<
template
v-if=
"isDropdownVariantStandalone || isDropdownVariantEmbedded"
>
...
...
@@ -341,7 +333,13 @@ export default {
<dropdown-contents
v-if=
"dropdownButtonVisible && showDropdownContents"
ref=
"dropdownContents"
:allow-multiselect=
"allowMultiselect"
:labels-list-title=
"labelsListTitle"
:footer-create-label-title=
"footerCreateLabelTitle"
:footer-manage-label-title=
"footerManageLabelTitle"
:render-on-top=
"!contentIsOnViewport"
:selected-labels=
"selectedLabels"
@
closeDropdown=
"handleDropdownClose"
/>
</
template
>
</div>
...
...
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/actions.js
View file @
7523392c
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
__
}
from
'
~/locale
'
;
import
*
as
types
from
'
./mutation_types
'
;
export
const
setInitialState
=
({
commit
},
props
)
=>
commit
(
types
.
SET_INITIAL_STATE
,
props
);
...
...
@@ -11,24 +8,5 @@ export const toggleDropdownContents = ({ commit }) => commit(types.TOGGLE_DROPDO
export
const
toggleDropdownContentsCreateView
=
({
commit
})
=>
commit
(
types
.
TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW
);
export
const
requestLabels
=
({
commit
})
=>
commit
(
types
.
REQUEST_LABELS
);
export
const
receiveLabelsSuccess
=
({
commit
},
labels
)
=>
commit
(
types
.
RECEIVE_SET_LABELS_SUCCESS
,
labels
);
export
const
receiveLabelsFailure
=
({
commit
})
=>
{
commit
(
types
.
RECEIVE_SET_LABELS_FAILURE
);
createFlash
({
message
:
__
(
'
Error fetching labels.
'
),
});
};
export
const
fetchLabels
=
({
state
,
dispatch
})
=>
{
dispatch
(
'
requestLabels
'
);
return
axios
.
get
(
state
.
labelsFetchPath
)
.
then
(({
data
})
=>
{
dispatch
(
'
receiveLabelsSuccess
'
,
data
);
})
.
catch
(()
=>
dispatch
(
'
receiveLabelsFailure
'
));
};
export
const
updateSelectedLabels
=
({
commit
},
labels
)
=>
commit
(
types
.
UPDATE_SELECTED_LABELS
,
{
labels
});
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutation_types.js
View file @
7523392c
export
const
SET_INITIAL_STATE
=
'
SET_INITIAL_STATE
'
;
export
const
REQUEST_LABELS
=
'
REQUEST_LABELS
'
;
export
const
RECEIVE_LABELS_SUCCESS
=
'
RECEIVE_LABELS_SUCCESS
'
;
export
const
RECEIVE_LABELS_FAILURE
=
'
RECEIVE_LABELS_FAILURE
'
;
export
const
REQUEST_SET_LABELS
=
'
REQUEST_SET_LABELS
'
;
export
const
RECEIVE_SET_LABELS_SUCCESS
=
'
RECEIVE_SET_LABELS_SUCCESS
'
;
export
const
RECEIVE_SET_LABELS_FAILURE
=
'
RECEIVE_SET_LABELS_FAILURE
'
;
export
const
TOGGLE_DROPDOWN_BUTTON
=
'
TOGGLE_DROPDOWN_VISIBILITY
'
;
export
const
TOGGLE_DROPDOWN_CONTENTS
=
'
TOGGLE_DROPDOWN_CONTENTS
'
;
...
...
app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/store/mutations.js
View file @
7523392c
...
...
@@ -26,27 +26,6 @@ export default {
[
types
.
TOGGLE_DROPDOWN_CONTENTS_CREATE_VIEW
](
state
)
{
state
.
showDropdownContentsCreateView
=
!
state
.
showDropdownContentsCreateView
;
},
[
types
.
REQUEST_LABELS
](
state
)
{
state
.
labelsFetchInProgress
=
true
;
},
[
types
.
RECEIVE_SET_LABELS_SUCCESS
](
state
,
labels
)
{
// Iterate over every label and add a `set` prop
// to determine whether it is already a part of
// selectedLabels array.
const
selectedLabelIds
=
state
.
selectedLabels
.
map
((
label
)
=>
label
.
id
);
state
.
labelsFetchInProgress
=
false
;
state
.
labels
=
labels
.
reduce
((
allLabels
,
label
)
=>
{
allLabels
.
push
({
...
label
,
set
:
selectedLabelIds
.
includes
(
label
.
id
),
});
return
allLabels
;
},
[]);
},
[
types
.
RECEIVE_SET_LABELS_FAILURE
](
state
)
{
state
.
labelsFetchInProgress
=
false
;
},
[
types
.
UPDATE_SELECTED_LABELS
](
state
,
{
labels
})
{
// Find the label to update from all the labels
// and change `set` prop value to represent their current state.
...
...
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js
View file @
7523392c
import
{
GlLoadingIcon
,
GlLink
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
Vue
,
{
nextTick
}
from
'
vue
'
;
import
{
nextTick
}
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
...
...
@@ -14,7 +14,7 @@ jest.mock('~/flash');
const
colors
=
Object
.
keys
(
mockSuggestedColors
);
const
localVue
=
createLocalVue
();
Vue
.
use
(
VueApollo
);
local
Vue
.
use
(
VueApollo
);
const
userRecoverableError
=
{
...
createLabelSuccessfulResponse
,
...
...
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view_spec.js
View file @
7523392c
This diff is collapsed.
Click to expand it.
spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_spec.js
View file @
7523392c
...
...
@@ -5,7 +5,7 @@ import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_w
import
DropdownContents
from
'
~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
'
;
import
labelsSelectModule
from
'
~/vue_shared/components/sidebar/labels_select_widget/store
'
;
import
{
mockConfig
}
from
'
./mock_data
'
;
import
{
mockConfig
,
mockLabels
}
from
'
./mock_data
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
...
...
@@ -19,6 +19,11 @@ const createComponent = (initialState = mockConfig, defaultProps = {}) => {
propsData
:
{
...
defaultProps
,
labelsCreateTitle
:
'
test
'
,
selectedLabels
:
mockLabels
,
allowMultiselect
:
true
,
labelsListTitle
:
'
Assign labels
'
,
footerCreateLabelTitle
:
'
create
'
,
footerManageLabelTitle
:
'
manage
'
,
},
localVue
,
store
,
...
...
spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
View file @
7523392c
...
...
@@ -50,58 +50,6 @@ describe('LabelsSelectRoot', () => {
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
handleVuexActionDispatch
'
,
()
=>
{
it
(
'
calls `handleDropdownClose` when params `action.type` is `toggleDropdownContents` and state has `showDropdownButton` & `showDropdownContents` props `false`
'
,
()
=>
{
createComponent
();
jest
.
spyOn
(
wrapper
.
vm
,
'
handleDropdownClose
'
).
mockImplementation
();
wrapper
.
vm
.
handleVuexActionDispatch
(
{
type
:
'
toggleDropdownContents
'
},
{
showDropdownButton
:
false
,
showDropdownContents
:
false
,
labels
:
[{
id
:
1
},
{
id
:
2
,
touched
:
true
}],
},
);
expect
(
wrapper
.
vm
.
handleDropdownClose
).
toHaveBeenCalledWith
(
expect
.
arrayContaining
([
{
id
:
2
,
touched
:
true
,
},
]),
);
});
it
(
'
calls `handleDropdownClose` with state.labels filterd using `set` prop when dropdown variant is `embedded`
'
,
()
=>
{
createComponent
({
...
mockConfig
,
variant
:
'
embedded
'
,
});
jest
.
spyOn
(
wrapper
.
vm
,
'
handleDropdownClose
'
).
mockImplementation
();
wrapper
.
vm
.
handleVuexActionDispatch
(
{
type
:
'
toggleDropdownContents
'
},
{
showDropdownButton
:
false
,
showDropdownContents
:
false
,
labels
:
[{
id
:
1
},
{
id
:
2
,
set
:
true
}],
},
);
expect
(
wrapper
.
vm
.
handleDropdownClose
).
toHaveBeenCalledWith
(
expect
.
arrayContaining
([
{
id
:
2
,
set
:
true
,
},
]),
);
});
});
describe
(
'
handleDropdownClose
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
...
...
spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
View file @
7523392c
...
...
@@ -48,6 +48,8 @@ export const mockConfig = {
labelsManagePath
:
'
/gitlab-org/my-project/-/labels
'
,
labelsFilterBasePath
:
'
/gitlab-org/my-project/issues
'
,
labelsFilterParam
:
'
label_name
'
,
footerCreateLabelTitle
:
'
create
'
,
footerManageLabelTitle
:
'
manage
'
,
};
export
const
mockSuggestedColors
=
{
...
...
@@ -91,3 +93,26 @@ export const createLabelSuccessfulResponse = {
},
},
};
export
const
labelsQueryResponse
=
{
data
:
{
workspace
:
{
labels
:
{
nodes
:
[
{
color
:
'
#330066
'
,
description
:
null
,
id
:
'
gid://gitlab/ProjectLabel/1
'
,
title
:
'
Label1
'
,
},
{
color
:
'
#2f7b2e
'
,
description
:
null
,
id
:
'
gid://gitlab/ProjectLabel/2
'
,
title
:
'
Label2
'
,
},
],
},
},
},
};
spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js
View file @
7523392c
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
testAction
from
'
helpers/vuex_action_helper
'
;
import
createFlash
from
'
~/flash
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
*
as
actions
from
'
~/vue_shared/components/sidebar/labels_select_widget/store/actions
'
;
import
*
as
types
from
'
~/vue_shared/components/sidebar/labels_select_widget/store/mutation_types
'
;
import
defaultState
from
'
~/vue_shared/components/sidebar/labels_select_widget/store/state
'
;
...
...
@@ -72,90 +68,6 @@ describe('LabelsSelect Actions', () => {
});
});
describe
(
'
requestLabels
'
,
()
=>
{
it
(
'
sets value of `state.labelsFetchInProgress` to `true`
'
,
(
done
)
=>
{
testAction
(
actions
.
requestLabels
,
{},
state
,
[{
type
:
types
.
REQUEST_LABELS
}],
[],
done
);
});
});
describe
(
'
receiveLabelsSuccess
'
,
()
=>
{
it
(
'
sets provided labels to `state.labels`
'
,
(
done
)
=>
{
const
labels
=
[{
id
:
1
},
{
id
:
2
},
{
id
:
3
},
{
id
:
4
}];
testAction
(
actions
.
receiveLabelsSuccess
,
labels
,
state
,
[{
type
:
types
.
RECEIVE_SET_LABELS_SUCCESS
,
payload
:
labels
}],
[],
done
,
);
});
});
describe
(
'
receiveLabelsFailure
'
,
()
=>
{
it
(
'
sets value `state.labelsFetchInProgress` to `false`
'
,
(
done
)
=>
{
testAction
(
actions
.
receiveLabelsFailure
,
{},
state
,
[{
type
:
types
.
RECEIVE_SET_LABELS_FAILURE
}],
[],
done
,
);
});
it
(
'
shows flash error
'
,
()
=>
{
actions
.
receiveLabelsFailure
({
commit
:
()
=>
{}
});
expect
(
createFlash
).
toHaveBeenCalledWith
({
message
:
'
Error fetching labels.
'
});
});
});
describe
(
'
fetchLabels
'
,
()
=>
{
let
mock
;
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
state
.
labelsFetchPath
=
'
labels.json
'
;
});
afterEach
(()
=>
{
mock
.
restore
();
});
describe
(
'
on success
'
,
()
=>
{
it
(
'
dispatches `requestLabels` & `receiveLabelsSuccess` actions
'
,
(
done
)
=>
{
const
labels
=
[{
id
:
1
},
{
id
:
2
},
{
id
:
3
},
{
id
:
4
}];
mock
.
onGet
(
/labels.json/
).
replyOnce
(
200
,
labels
);
testAction
(
actions
.
fetchLabels
,
{},
state
,
[],
[{
type
:
'
requestLabels
'
},
{
type
:
'
receiveLabelsSuccess
'
,
payload
:
labels
}],
done
,
);
});
});
describe
(
'
on failure
'
,
()
=>
{
it
(
'
dispatches `requestLabels` & `receiveLabelsFailure` actions
'
,
(
done
)
=>
{
mock
.
onGet
(
/labels.json/
).
replyOnce
(
500
,
{});
testAction
(
actions
.
fetchLabels
,
{},
state
,
[],
[{
type
:
'
requestLabels
'
},
{
type
:
'
receiveLabelsFailure
'
}],
done
,
);
});
});
});
describe
(
'
updateSelectedLabels
'
,
()
=>
{
it
(
'
updates `state.labels` based on provided `labels` param
'
,
(
done
)
=>
{
const
labels
=
[{
id
:
1
},
{
id
:
2
},
{
id
:
3
},
{
id
:
4
}];
...
...
spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js
View file @
7523392c
...
...
@@ -67,58 +67,6 @@ describe('LabelsSelect Mutations', () => {
});
});
describe
(
`
${
types
.
REQUEST_LABELS
}
`
,
()
=>
{
it
(
'
sets value of `state.labelsFetchInProgress` to true
'
,
()
=>
{
const
state
=
{
labelsFetchInProgress
:
false
,
};
mutations
[
types
.
REQUEST_LABELS
](
state
);
expect
(
state
.
labelsFetchInProgress
).
toBe
(
true
);
});
});
describe
(
`
${
types
.
RECEIVE_SET_LABELS_SUCCESS
}
`
,
()
=>
{
const
selectedLabels
=
[{
id
:
2
},
{
id
:
4
}];
const
labels
=
[{
id
:
1
},
{
id
:
2
},
{
id
:
3
},
{
id
:
4
}];
it
(
'
sets value of `state.labelsFetchInProgress` to false
'
,
()
=>
{
const
state
=
{
selectedLabels
,
labelsFetchInProgress
:
true
,
};
mutations
[
types
.
RECEIVE_SET_LABELS_SUCCESS
](
state
,
labels
);
expect
(
state
.
labelsFetchInProgress
).
toBe
(
false
);
});
it
(
'
sets provided `labels` to `state.labels` along with `set` prop based on `state.selectedLabels`
'
,
()
=>
{
const
selectedLabelIds
=
selectedLabels
.
map
((
label
)
=>
label
.
id
);
const
state
=
{
selectedLabels
,
labelsFetchInProgress
:
true
,
};
mutations
[
types
.
RECEIVE_SET_LABELS_SUCCESS
](
state
,
labels
);
state
.
labels
.
forEach
((
label
)
=>
{
if
(
selectedLabelIds
.
includes
(
label
.
id
))
{
expect
(
label
.
set
).
toBe
(
true
);
}
});
});
});
describe
(
`
${
types
.
RECEIVE_SET_LABELS_FAILURE
}
`
,
()
=>
{
it
(
'
sets value of `state.labelsFetchInProgress` to false
'
,
()
=>
{
const
state
=
{
labelsFetchInProgress
:
true
,
};
mutations
[
types
.
RECEIVE_SET_LABELS_FAILURE
](
state
);
expect
(
state
.
labelsFetchInProgress
).
toBe
(
false
);
});
});
describe
(
`
${
types
.
UPDATE_SELECTED_LABELS
}
`
,
()
=>
{
let
labels
;
...
...
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