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
79e09244
Commit
79e09244
authored
Jan 05, 2021
by
sstern
Committed by
Paul Slaughter
Jan 31, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add iterations to bulk edit on project and group
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51657
parent
d225db93
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
573 additions
and
14 deletions
+573
-14
app/assets/javascripts/issuable_bulk_update_actions.js
app/assets/javascripts/issuable_bulk_update_actions.js
+1
-0
app/assets/javascripts/issuable_bulk_update_sidebar.js
app/assets/javascripts/issuable_bulk_update_sidebar.js
+10
-0
app/views/shared/issuable/_bulk_update_sidebar.html.haml
app/views/shared/issuable/_bulk_update_sidebar.html.haml
+3
-0
doc/user/group/bulk_editing/index.md
doc/user/group/bulk_editing/index.md
+9
-7
doc/user/project/bulk_editing.md
doc/user/project/bulk_editing.md
+10
-7
ee/app/assets/javascripts/sidebar/components/iteration_dropdown.vue
...ets/javascripts/sidebar/components/iteration_dropdown.vue
+110
-0
ee/app/assets/javascripts/vue_shared/components/sidebar/iterations_dropdown_bundle.js
...e_shared/components/sidebar/iterations_dropdown_bundle.js
+54
-0
ee/app/views/shared/_iterations_dropdown.html.haml
ee/app/views/shared/_iterations_dropdown.html.haml
+8
-0
ee/app/views/shared/issuable/_group_bulk_update_sidebar.html.haml
...iews/shared/issuable/_group_bulk_update_sidebar.html.haml
+3
-0
ee/changelogs/unreleased/ss-add-iteration-to-bulk-edit-issues.yml
...elogs/unreleased/ss-add-iteration-to-bulk-edit-issues.yml
+5
-0
ee/spec/features/issues/bulk_assignment_iteration_spec.rb
ee/spec/features/issues/bulk_assignment_iteration_spec.rb
+105
-0
ee/spec/frontend/sidebar/components/__snapshots__/iteration_dropdown_spec.js.snap
.../components/__snapshots__/iteration_dropdown_spec.js.snap
+25
-0
ee/spec/frontend/sidebar/components/iteration_dropdown_spec.js
...ec/frontend/sidebar/components/iteration_dropdown_spec.js
+227
-0
locale/gitlab.pot
locale/gitlab.pot
+3
-0
No files found.
app/assets/javascripts/issuable_bulk_update_actions.js
View file @
79e09244
...
@@ -50,6 +50,7 @@ export default {
...
@@ -50,6 +50,7 @@ export default {
subscription_event
:
this
.
form
.
find
(
'
input[name="update[subscription_event]"]
'
).
val
(),
subscription_event
:
this
.
form
.
find
(
'
input[name="update[subscription_event]"]
'
).
val
(),
health_status
:
this
.
form
.
find
(
'
input[name="update[health_status]"]
'
).
val
(),
health_status
:
this
.
form
.
find
(
'
input[name="update[health_status]"]
'
).
val
(),
epic_id
:
this
.
form
.
find
(
'
input[name="update[epic_id]"]
'
).
val
(),
epic_id
:
this
.
form
.
find
(
'
input[name="update[epic_id]"]
'
).
val
(),
sprint_id
:
this
.
form
.
find
(
'
input[name="update[iteration_id]"]
'
).
val
(),
add_label_ids
:
[],
add_label_ids
:
[],
remove_label_ids
:
[],
remove_label_ids
:
[],
},
},
...
...
app/assets/javascripts/issuable_bulk_update_sidebar.js
View file @
79e09244
...
@@ -79,6 +79,16 @@ export default class IssuableBulkUpdateSidebar {
...
@@ -79,6 +79,16 @@ export default class IssuableBulkUpdateSidebar {
})
})
.
catch
(()
=>
{});
.
catch
(()
=>
{});
}
}
if
(
IS_EE
)
{
import
(
'
ee/vue_shared/components/sidebar/iterations_dropdown_bundle
'
)
.
then
(({
default
:
iterationsDropdown
})
=>
{
iterationsDropdown
();
})
.
catch
((
e
)
=>
{
throw
e
;
});
}
}
}
setupBulkUpdateActions
()
{
setupBulkUpdateActions
()
{
...
...
app/views/shared/issuable/_bulk_update_sidebar.html.haml
View file @
79e09244
-
type
=
local_assigns
.
fetch
(
:type
)
-
type
=
local_assigns
.
fetch
(
:type
)
-
bulk_issue_health_status_flag
=
type
==
:issues
&&
@project
&
.
group
&
.
feature_available?
(
:issuable_health_status
)
-
bulk_issue_health_status_flag
=
type
==
:issues
&&
@project
&
.
group
&
.
feature_available?
(
:issuable_health_status
)
-
epic_bulk_edit_flag
=
@project
&
.
group
&
.
feature_available?
(
:epics
)
&&
type
==
:issues
-
epic_bulk_edit_flag
=
@project
&
.
group
&
.
feature_available?
(
:epics
)
&&
type
==
:issues
-
bulk_iterations_flag
=
@project
.
feature_available?
(
:iterations
)
&&
@project
&
.
group
.
present?
&&
type
==
:issues
%aside
.issues-bulk-update.js-right-sidebar.right-sidebar
{
"aria-live"
=>
"polite"
,
data:
{
'signed-in'
:
current_user
.
present?
}
}
%aside
.issues-bulk-update.js-right-sidebar.right-sidebar
{
"aria-live"
=>
"polite"
,
data:
{
'signed-in'
:
current_user
.
present?
}
}
.issuable-sidebar.hidden
.issuable-sidebar.hidden
...
@@ -41,6 +42,8 @@
...
@@ -41,6 +42,8 @@
=
_
(
'Milestone'
)
=
_
(
'Milestone'
)
.filter-item
.filter-item
=
dropdown_tag
(
_
(
"Select milestone"
),
options:
{
title:
_
(
"Assign milestone"
),
toggle_class:
"js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update"
,
filter:
true
,
dropdown_class:
"dropdown-menu-selectable dropdown-menu-milestone"
,
placeholder:
_
(
"Search milestones"
),
data:
{
show_no:
true
,
field_name:
"update[milestone_id]"
,
project_id:
@project
.
id
,
use_id:
true
,
default_label:
_
(
"Milestone"
)
}
})
=
dropdown_tag
(
_
(
"Select milestone"
),
options:
{
title:
_
(
"Assign milestone"
),
toggle_class:
"js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update"
,
filter:
true
,
dropdown_class:
"dropdown-menu-selectable dropdown-menu-milestone"
,
placeholder:
_
(
"Search milestones"
),
data:
{
show_no:
true
,
field_name:
"update[milestone_id]"
,
project_id:
@project
.
id
,
use_id:
true
,
default_label:
_
(
"Milestone"
)
}
})
-
if
bulk_iterations_flag
=
render_if_exists
'shared/iterations_dropdown'
,
path:
@project
.
group
.
full_path
.block
.block
.title
.title
=
_
(
'Labels'
)
=
_
(
'Labels'
)
...
...
doc/user/group/bulk_editing/index.md
View file @
79e09244
...
@@ -20,19 +20,21 @@ Only the items visible on the current page are selected for bulk editing (up to
...
@@ -20,19 +20,21 @@ Only the items visible on the current page are selected for bulk editing (up to
## Bulk edit issues at the group level
## Bulk edit issues at the group level
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7249) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.1.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7249) in GitLab 12.1.
> - Assigning epic ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210470) in GitLab 13.2.
> - Editing health status [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218395) in GitLab 13.2.
> - Editing iteration [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196806) in GitLab 13.9.
NOTE:
NOTE:
You need a permission level of
[
Reporter or higher
](
../../permissions.md
)
to manage issues.
You need a permission level of
[
Reporter or higher
](
../../permissions.md
)
to manage issues.
When bulk editing issues in a group, you can edit the following attributes:
When bulk editing issues in a group, you can edit the following attributes:
-
Epic (
[
introduced
](
https://gitlab.com/gitlab-org/gitlab/-/issues/210470
)
in
-
[
Epic
](
../epics/index.md
)
[
GitLab Premium
](
https://about.gitlab.com/pricing/
)
13.2.)
**(PREMIUM)**
-
[
Milestone
](
../../project/milestones/index.md
)
-
Milestone
-
[
Labels
](
../../project/labels.md
)
-
Labels
-
[
Health status
](
../../project/issues/index.md#health-status
)
-
Health status (
[
introduced
](
https://gitlab.com/gitlab-org/gitlab/-/issues/218395
)
in
-
[
Iteration
](
../iterations/index.md
)
[
GitLab Ultimate
](
https://about.gitlab.com/pricing/
)
13.2.)
**(ULTIMATE)**
To update multiple project issues at the same time:
To update multiple project issues at the same time:
...
...
doc/user/project/bulk_editing.md
View file @
79e09244
...
@@ -21,6 +21,10 @@ Only the items visible on the current page are selected for bulk editing (up to
...
@@ -21,6 +21,10 @@ Only the items visible on the current page are selected for bulk editing (up to
## Bulk edit issues at the project level
## Bulk edit issues at the project level
> - Assigning epic ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210470) in GitLab 13.2.
> - Editing health status [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218395) in GitLab 13.2.
> - Editing iteration [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196806) in GitLab 13.9.
NOTE:
NOTE:
You need a permission level of
[
Reporter or higher
](
../permissions.md
)
to manage issues.
You need a permission level of
[
Reporter or higher
](
../permissions.md
)
to manage issues.
...
@@ -28,13 +32,12 @@ When bulk editing issues in a project, you can edit the following attributes:
...
@@ -28,13 +32,12 @@ When bulk editing issues in a project, you can edit the following attributes:
-
Status (open/closed)
-
Status (open/closed)
-
Assignee
-
Assignee
-
Epic (
[
introduced
](
https://gitlab.com/gitlab-org/gitlab/-/issues/210470
)
in
-
[
Epic
](
../group/epics/index.md
)
[
GitLab Premium
](
https://about.gitlab.com/pricing/
)
13.2.)
**(PREMIUM)**
-
[
Milestone
](
milestones/index.md
)
-
Milestone
-
[
Labels
](
labels.md
)
-
Labels
-
[
Health status
](
issues/index.md#health-status
)
-
Health status (
[
introduced
](
https://gitlab.com/gitlab-org/gitlab/-/issues/218395
)
in
-
Notification subscription
[
GitLab Ultimate
](
https://about.gitlab.com/pricing/
)
13.2.)
**(ULTIMATE)**
-
[
Iteration
](
../group/iterations/index.md
)
-
Subscriptions
To update multiple project issues at the same time:
To update multiple project issues at the same time:
...
...
ee/app/assets/javascripts/sidebar/components/iteration_dropdown.vue
0 → 100644
View file @
79e09244
<
script
>
import
{
GlDropdown
,
GlDropdownItem
,
GlSearchBoxByType
,
GlDropdownSectionHeader
,
GlTooltipDirective
,
GlLoadingIcon
,
}
from
'
@gitlab/ui
'
;
import
groupIterationsQuery
from
'
../queries/group_iterations.query.graphql
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
iterationSelectTextMap
,
iterationDisplayState
}
from
'
../constants
'
;
export
default
{
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
components
:
{
GlDropdown
,
GlDropdownItem
,
GlSearchBoxByType
,
GlDropdownSectionHeader
,
GlLoadingIcon
,
},
apollo
:
{
iterations
:
{
query
:
groupIterationsQuery
,
debounce
:
250
,
variables
()
{
const
search
=
this
.
searchTerm
?
`"
${
this
.
searchTerm
}
"`
:
''
;
return
{
fullPath
:
this
.
fullPath
,
title
:
search
,
state
:
iterationDisplayState
,
};
},
update
(
data
)
{
// TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/220379
return
data
.
group
?.
iterations
?.
nodes
||
[];
},
result
({
data
})
{
const
nodes
=
data
.
group
?.
iterations
?.
nodes
||
[];
this
.
iterations
=
iterationSelectTextMap
.
noIterationItem
.
concat
(
nodes
);
},
skip
()
{
return
!
this
.
shouldFetch
;
},
},
},
props
:
{
fullPath
:
{
required
:
true
,
type
:
String
,
},
},
data
()
{
return
{
searchTerm
:
''
,
iterations
:
[],
currentIteration
:
null
,
shouldFetch
:
false
,
};
},
computed
:
{
title
()
{
return
this
.
currentIteration
?.
title
||
__
(
'
Select iteration
'
);
},
},
methods
:
{
onClick
(
iteration
)
{
if
(
iteration
.
id
===
this
.
currentIteration
?.
id
)
{
this
.
currentIteration
=
null
;
}
else
{
this
.
currentIteration
=
iteration
;
}
this
.
$emit
(
'
onIterationSelect
'
,
this
.
currentIteration
);
},
isIterationChecked
(
id
)
{
return
id
===
this
.
currentIteration
?.
id
;
},
onDropdownShow
()
{
this
.
shouldFetch
=
true
;
},
},
};
</
script
>
<
template
>
<div
data-qa-selector=
"iteration_container"
>
<gl-dropdown
:text=
"title"
class=
"gl-w-full"
@
show=
"onDropdownShow"
>
<gl-dropdown-section-header
class=
"gl-display-flex! gl-justify-content-center"
>
{{
__
(
'
Assign Iteration
'
)
}}
</gl-dropdown-section-header>
<gl-search-box-by-type
v-model=
"searchTerm"
/>
<gl-loading-icon
v-if=
"$apollo.loading"
/>
<gl-dropdown-item
v-for=
"iterationItem in iterations"
v-else
:key=
"iterationItem.id"
:is-check-item=
"true"
:is-checked=
"isIterationChecked(iterationItem.id)"
@
click=
"onClick(iterationItem)"
>
{{
iterationItem
.
title
}}
</gl-dropdown-item
>
</gl-dropdown>
</div>
</
template
>
ee/app/assets/javascripts/vue_shared/components/sidebar/iterations_dropdown_bundle.js
0 → 100644
View file @
79e09244
import
Vue
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
IterationDropdown
from
'
ee/sidebar/components/iteration_dropdown.vue
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
Vue
.
use
(
VueApollo
);
export
default
function
()
{
const
el
=
document
.
querySelector
(
'
#js-iteration-dropdown
'
);
const
iterationField
=
document
.
getElementById
(
'
issue_iteration_id
'
);
if
(
!
el
||
!
iterationField
)
{
return
false
;
}
const
apolloProvider
=
new
VueApollo
({
defaultClient
:
createDefaultClient
(),
});
const
{
fullPath
}
=
el
.
dataset
;
return
new
Vue
({
el
,
apolloProvider
,
methods
:
{
getIdForIteration
(
iteration
)
{
const
noChangeIterationValue
=
''
;
const
unSetIterationValue
=
'
0
'
;
if
(
iteration
===
null
)
{
return
noChangeIterationValue
;
}
else
if
(
iteration
.
id
===
null
)
{
return
unSetIterationValue
;
}
return
getIdFromGraphQLId
(
iteration
.
id
);
},
handleIterationSelect
(
iteration
)
{
iterationField
.
setAttribute
(
'
value
'
,
this
.
getIdForIteration
(
iteration
));
},
},
render
(
createElement
)
{
return
createElement
(
IterationDropdown
,
{
props
:
{
fullPath
,
},
on
:
{
onIterationSelect
:
this
.
handleIterationSelect
.
bind
(
this
),
},
});
},
});
}
ee/app/views/shared/_iterations_dropdown.html.haml
0 → 100644
View file @
79e09244
-
path
=
local_assigns
.
fetch
(
:path
)
.block
.title
=
_
(
'Iteration'
)
.filter-item
#js-iteration-dropdown
{
data:
{
full_path:
path
}
}
%input
{
id:
'issue_iteration_id'
,
type:
'hidden'
,
name:
'update[iteration_id]'
}
ee/app/views/shared/issuable/_group_bulk_update_sidebar.html.haml
View file @
79e09244
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
-
type
=
local_assigns
.
fetch
(
:type
)
-
type
=
local_assigns
.
fetch
(
:type
)
-
bulk_issue_health_status_flag
=
type
==
:issues
&&
group
&
.
feature_available?
(
:issuable_health_status
)
-
bulk_issue_health_status_flag
=
type
==
:issues
&&
group
&
.
feature_available?
(
:issuable_health_status
)
-
epic_bulk_edit_flag
=
type
==
:issues
&&
group
&
.
feature_available?
(
:epics
)
-
epic_bulk_edit_flag
=
type
==
:issues
&&
group
&
.
feature_available?
(
:epics
)
-
bulk_edit_iterations
=
group
.
feature_available?
(
:iterations
)
&&
type
==
:issues
%aside
.issues-bulk-update.js-right-sidebar.right-sidebar
{
'aria-live'
=>
'polite'
,
data:
{
'signed-in'
:
current_user
.
present?
}
}
%aside
.issues-bulk-update.js-right-sidebar.right-sidebar
{
'aria-live'
=>
'polite'
,
data:
{
'signed-in'
:
current_user
.
present?
}
}
.issuable-sidebar.hidden
.issuable-sidebar.hidden
...
@@ -23,6 +24,8 @@
...
@@ -23,6 +24,8 @@
=
_
(
'Milestone'
)
=
_
(
'Milestone'
)
.filter-item
.filter-item
=
dropdown_tag
(
_
(
'Select milestone'
),
options:
{
title:
_
(
'Assign milestone'
),
toggle_class:
'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update'
,
filter:
true
,
dropdown_class:
'dropdown-menu-selectable dropdown-menu-milestone'
,
placeholder:
_
(
'Search milestones'
),
data:
{
show_no:
true
,
field_name:
'update[milestone_id]'
,
group_id:
group
&
.
id
,
use_id:
true
,
default_label:
_
(
'Milestone'
)
}
})
=
dropdown_tag
(
_
(
'Select milestone'
),
options:
{
title:
_
(
'Assign milestone'
),
toggle_class:
'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update'
,
filter:
true
,
dropdown_class:
'dropdown-menu-selectable dropdown-menu-milestone'
,
placeholder:
_
(
'Search milestones'
),
data:
{
show_no:
true
,
field_name:
'update[milestone_id]'
,
group_id:
group
&
.
id
,
use_id:
true
,
default_label:
_
(
'Milestone'
)
}
})
-
if
bulk_edit_iterations
=
render
"shared/iterations_dropdown"
,
path:
group
.
full_path
.block
.block
.title
.title
=
_
(
'Labels'
)
=
_
(
'Labels'
)
...
...
ee/changelogs/unreleased/ss-add-iteration-to-bulk-edit-issues.yml
0 → 100644
View file @
79e09244
---
title
:
Add iteration to bulk issue edit sidebar
merge_request
:
51657
author
:
type
:
changed
ee/spec/features/issues/bulk_assignment_iteration_spec.rb
0 → 100644
View file @
79e09244
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
'Issues > Iteration bulk assignment'
do
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:group
)
{
create
(
:group
,
:public
)
}
let_it_be
(
:project
)
{
create
(
:project
,
:public
,
group:
group
)
}
let_it_be
(
:project_without_group
)
{
create
(
:project
,
:public
)
}
let_it_be
(
:issue1
)
{
create
(
:issue
,
project:
project
,
title:
"Issue 1"
)
}
let_it_be
(
:issue2
)
{
create
(
:issue
,
project:
project
,
title:
"Issue 2"
)
}
let_it_be
(
:issue3
)
{
create
(
:issue
,
project:
project_without_group
,
title:
"Issue 3"
)
}
let_it_be
(
:iteration
)
{
create
(
:iteration
,
group:
group
,
title:
"Iteration 1"
)
}
shared_examples
'cannot find iterations when project does not have a group'
do
|
context
|
context
'cannot find iteration when group does not belong to project'
,
:js
do
before
do
project_without_group
.
add_maintainer
(
user
)
enable_bulk_update
(
context
)
end
it
'cannot find iteration dropdown'
do
expect
(
page
).
not_to
have_selector
(
'[data-qa-selector="iteration_container"]'
)
end
end
end
shared_examples
'bulk edit iteration'
do
|
context
|
context
'iteration'
,
:js
do
before
do
enable_bulk_update
(
context
)
end
context
'to all issues'
do
before
do
check
'check-all-issues'
open_iteration_dropdown
[
'Iteration 1'
]
update_issues
end
it
'updates the iteration'
do
aggregate_failures
'each issue in list'
do
expect
(
issue1
.
reload
.
iteration
.
name
).
to
eq
'Iteration 1'
expect
(
issue2
.
reload
.
iteration
.
name
).
to
eq
'Iteration 1'
end
end
end
end
context
'cannot find iteration when iterations is off'
,
:js
do
before
do
stub_licensed_features
(
iterations:
false
)
enable_bulk_update
(
context
)
end
it
'cannot find iteration dropdown'
do
expect
(
page
).
not_to
have_selector
(
'[data-qa-selector="iteration_container"]'
)
end
end
end
context
'as an allowed user'
,
:js
do
before
do
group
.
add_maintainer
(
user
)
sign_in
user
end
context
'at group level'
do
it_behaves_like
'bulk edit iteration'
,
:group
end
context
'at project level'
do
it_behaves_like
'bulk edit iteration'
,
:project
it_behaves_like
'cannot find iterations when project does not have a group'
,
:project_without_group
end
end
def
enable_bulk_update
(
context
)
if
context
==
:project
visit
project_issues_path
(
project
)
elsif
context
==
:project_without_group
visit
project_issues_path
(
project_without_group
)
else
visit
issues_group_path
(
group
)
end
click_button
'Edit issues'
end
def
open_iteration_dropdown
(
items
=
[])
page
.
within
(
'.issues-bulk-update'
)
do
click_button
'Select iteration'
items
.
map
do
|
item
|
find
(
'.dropdown-item'
,
text:
item
).
click
end
end
end
def
update_issues
find
(
'.update-selected-issues'
).
click
wait_for_requests
end
end
ee/spec/frontend/sidebar/components/__snapshots__/iteration_dropdown_spec.js.snap
0 → 100644
View file @
79e09244
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`IterationDropdown default shows gl-dropdown 1`] = `
<gl-dropdown-stub
category="primary"
class="gl-w-full"
headertext=""
hideheaderborder="true"
size="medium"
text="Select iteration"
variant="default"
>
<gl-dropdown-section-header-stub
class="gl-display-flex! gl-justify-content-center"
>
Assign Iteration
</gl-dropdown-section-header-stub>
<gl-search-box-by-type-stub
clearbuttontitle="Clear"
value=""
/>
</gl-dropdown-stub>
`;
ee/spec/frontend/sidebar/components/iteration_dropdown_spec.js
0 → 100644
View file @
79e09244
import
{
GlDropdownItem
,
GlLoadingIcon
,
GlDropdown
,
GlSearchBoxByType
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
IterationDropdown
from
'
ee/sidebar/components/iteration_dropdown.vue
'
;
import
groupIterationsQuery
from
'
ee/sidebar/queries/group_iterations.query.graphql
'
;
import
{
iterationSelectTextMap
}
from
'
ee/sidebar/constants
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
VueApollo
);
const
TEST_SEARCH
=
'
search
'
;
const
TEST_FULL_PATH
=
'
gitlab-test/test
'
;
const
TEST_ITERATIONS
=
[
{
id
:
'
1
'
,
title
:
'
Test Title
'
,
webUrl
:
''
,
state
:
''
,
},
{
id
:
'
2
'
,
title
:
'
Another Test Title
'
,
webUrl
:
''
,
state
:
''
,
},
];
describe
(
'
IterationDropdown
'
,
()
=>
{
let
wrapper
;
let
fakeApollo
;
let
groupIterationsSpy
;
beforeEach
(()
=>
{
groupIterationsSpy
=
jest
.
fn
().
mockResolvedValue
({
data
:
{
group
:
{
iterations
:
{
nodes
:
TEST_ITERATIONS
,
},
},
},
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
const
waitForDebounce
=
async
()
=>
{
await
wrapper
.
vm
.
$nextTick
();
jest
.
runOnlyPendingTimers
();
};
const
findDropdownItems
=
()
=>
wrapper
.
findAll
(
GlDropdownItem
);
const
findDropdownItemWithText
=
(
text
)
=>
findDropdownItems
().
wrappers
.
find
((
x
)
=>
x
.
text
()
===
text
);
const
findDropdownItemsData
=
()
=>
findDropdownItems
().
wrappers
.
map
((
x
)
=>
({
isCheckItem
:
x
.
props
(
'
isCheckItem
'
),
isChecked
:
x
.
props
(
'
isChecked
'
),
text
:
x
.
text
(),
}));
const
selectDropdownItemAndWait
=
async
(
text
)
=>
{
const
item
=
findDropdownItemWithText
(
text
);
item
.
vm
.
$emit
(
'
click
'
);
await
wrapper
.
vm
.
$nextTick
();
};
const
findDropdown
=
()
=>
wrapper
.
find
(
GlDropdown
);
const
showDropdownAndWait
=
async
()
=>
{
findDropdown
().
vm
.
$emit
(
'
show
'
);
await
waitForDebounce
();
};
const
isLoading
=
()
=>
wrapper
.
find
(
GlLoadingIcon
).
exists
();
const
createComponent
=
({
mountFn
=
shallowMount
}
=
{})
=>
{
fakeApollo
=
createMockApollo
([[
groupIterationsQuery
,
groupIterationsSpy
]]);
wrapper
=
mountFn
(
IterationDropdown
,
{
localVue
,
apolloProvider
:
fakeApollo
,
propsData
:
{
fullPath
:
TEST_FULL_PATH
,
},
});
};
describe
(
'
default
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
});
it
(
'
does not show loading
'
,
()
=>
{
expect
(
isLoading
()).
toBe
(
false
);
});
it
(
'
shows gl-dropdown
'
,
()
=>
{
expect
(
wrapper
.
find
(
GlDropdown
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
GlDropdown
).
element
).
toMatchSnapshot
();
});
});
describe
(
'
when dropdown opens and query is loading
'
,
()
=>
{
beforeEach
(
async
()
=>
{
// return promise that doesn't resolve to force loading state
groupIterationsSpy
.
mockReturnValue
(
new
Promise
(()
=>
{}));
createComponent
();
await
showDropdownAndWait
();
});
it
(
'
shows loading
'
,
()
=>
{
expect
(
isLoading
()).
toBe
(
true
);
});
it
(
'
calls groupIterations query
'
,
()
=>
{
expect
(
groupIterationsSpy
).
toHaveBeenCalledTimes
(
1
);
expect
(
groupIterationsSpy
).
toHaveBeenCalledWith
({
fullPath
:
TEST_FULL_PATH
,
state
:
'
opened
'
,
title
:
''
,
});
});
});
describe
(
'
when dropdown opens and query responds
'
,
()
=>
{
beforeEach
(
async
()
=>
{
createComponent
();
await
showDropdownAndWait
();
});
it
(
'
does not show loading
'
,
()
=>
{
expect
(
isLoading
()).
toBe
(
false
);
});
it
(
'
shows dropdown items
'
,
()
=>
{
const
result
=
iterationSelectTextMap
.
noIterationItem
.
concat
(
TEST_ITERATIONS
);
expect
(
findDropdownItemsData
()).
toEqual
(
result
.
map
((
x
)
=>
({
isCheckItem
:
true
,
isChecked
:
false
,
text
:
x
.
title
,
})),
);
});
it
(
'
does not re-query if opened again
'
,
async
()
=>
{
groupIterationsSpy
.
mockClear
();
await
showDropdownAndWait
();
expect
(
groupIterationsSpy
).
not
.
toHaveBeenCalled
();
});
describe
.
each
([
0
,
1
,
2
])(
'
when item %s is selected
'
,
(
index
)
=>
{
const
allIterations
=
iterationSelectTextMap
.
noIterationItem
.
concat
(
TEST_ITERATIONS
);
const
selected
=
allIterations
[
index
];
const
asNotChecked
=
({
title
})
=>
({
isCheckItem
:
true
,
isChecked
:
false
,
text
:
title
});
beforeEach
(
async
()
=>
{
await
selectDropdownItemAndWait
(
selected
.
title
);
});
it
(
'
shows item as checked
'
,
()
=>
{
const
prevSelected
=
allIterations
.
slice
(
0
,
index
);
const
afterSelected
=
allIterations
.
slice
(
index
+
1
);
expect
(
findDropdownItemsData
()).
toEqual
([
...
prevSelected
.
map
(
asNotChecked
),
{
isCheckItem
:
true
,
isChecked
:
true
,
text
:
selected
.
title
,
},
...
afterSelected
.
map
(
asNotChecked
),
]);
});
it
(
'
emits event
'
,
()
=>
{
expect
(
wrapper
.
emitted
(
'
onIterationSelect
'
)).
toEqual
([[
selected
]]);
});
describe
(
'
when item is clicked again
'
,
()
=>
{
beforeEach
(
async
()
=>
{
await
selectDropdownItemAndWait
(
selected
.
title
);
});
it
(
'
shows item as unchecked
'
,
()
=>
{
expect
(
findDropdownItemsData
()).
toEqual
(
allIterations
.
map
(
asNotChecked
));
});
it
(
'
emits event
'
,
()
=>
{
expect
(
wrapper
.
emitted
(
'
onIterationSelect
'
).
length
).
toBe
(
2
);
expect
(
wrapper
.
emitted
(
'
onIterationSelect
'
)[
1
]).
toEqual
([
null
]);
});
});
});
});
describe
(
'
when dropdown opens and search is set
'
,
()
=>
{
beforeEach
(
async
()
=>
{
createComponent
();
await
showDropdownAndWait
();
groupIterationsSpy
.
mockClear
();
wrapper
.
find
(
GlSearchBoxByType
).
vm
.
$emit
(
'
input
'
,
TEST_SEARCH
);
await
waitForDebounce
();
});
it
(
'
adds the search to the query
'
,
()
=>
{
expect
(
groupIterationsSpy
).
toHaveBeenCalledWith
({
fullPath
:
TEST_FULL_PATH
,
state
:
'
opened
'
,
title
:
`"
${
TEST_SEARCH
}
"`
,
});
});
});
});
locale/gitlab.pot
View file @
79e09244
...
@@ -25996,6 +25996,9 @@ msgstr ""
...
@@ -25996,6 +25996,9 @@ msgstr ""
msgid "Select health status"
msgid "Select health status"
msgstr ""
msgstr ""
msgid "Select iteration"
msgstr ""
msgid "Select label"
msgid "Select label"
msgstr ""
msgstr ""
...
...
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