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
b6a019ff
Commit
b6a019ff
authored
Dec 16, 2021
by
Coung Ngo
Committed by
Natalia Tepluhina
Dec 16, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add `Delete issue` option in issue page ellipsis dropdown
parent
c8b5da56
Changes
19
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
395 additions
and
160 deletions
+395
-160
app/assets/javascripts/issues/show/components/app.vue
app/assets/javascripts/issues/show/components/app.vue
+1
-21
app/assets/javascripts/issues/show/components/delete_issue_modal.vue
...javascripts/issues/show/components/delete_issue_modal.vue
+71
-0
app/assets/javascripts/issues/show/components/edit_actions.vue
...ssets/javascripts/issues/show/components/edit_actions.vue
+19
-34
app/assets/javascripts/issues/show/components/form.vue
app/assets/javascripts/issues/show/components/form.vue
+5
-0
app/assets/javascripts/issues/show/components/header_actions.vue
...ets/javascripts/issues/show/components/header_actions.vue
+65
-12
app/assets/javascripts/issues/show/incident.js
app/assets/javascripts/issues/show/incident.js
+2
-0
app/assets/javascripts/issues/show/issue.js
app/assets/javascripts/issues/show/issue.js
+2
-0
app/helpers/issues_helper.rb
app/helpers/issues_helper.rb
+2
-0
doc/user/project/issues/img/delete_issue_v13_11.png
doc/user/project/issues/img/delete_issue_v13_11.png
+0
-0
doc/user/project/issues/index.md
doc/user/project/issues/index.md
+1
-1
doc/user/project/issues/managing_issues.md
doc/user/project/issues/managing_issues.md
+15
-4
locale/gitlab.pot
locale/gitlab.pot
+0
-3
spec/features/issues/issue_header_spec.rb
spec/features/issues/issue_header_spec.rb
+6
-3
spec/frontend/issues/show/components/app_spec.js
spec/frontend/issues/show/components/app_spec.js
+0
-38
spec/frontend/issues/show/components/delete_issue_modal_spec.js
...rontend/issues/show/components/delete_issue_modal_spec.js
+108
-0
spec/frontend/issues/show/components/edit_actions_spec.js
spec/frontend/issues/show/components/edit_actions_spec.js
+34
-16
spec/frontend/issues/show/components/form_spec.js
spec/frontend/issues/show/components/form_spec.js
+1
-0
spec/frontend/issues/show/components/header_actions_spec.js
spec/frontend/issues/show/components/header_actions_spec.js
+61
-28
spec/helpers/issues_helper_spec.rb
spec/helpers/issues_helper_spec.rb
+2
-0
No files found.
app/assets/javascripts/issues/show/components/app.vue
View file @
b6a019ff
...
@@ -289,13 +289,11 @@ export default {
...
@@ -289,13 +289,11 @@ export default {
window
.
addEventListener
(
'
beforeunload
'
,
this
.
handleBeforeUnloadEvent
);
window
.
addEventListener
(
'
beforeunload
'
,
this
.
handleBeforeUnloadEvent
);
eventHub
.
$on
(
'
delete.issuable
'
,
this
.
deleteIssuable
);
eventHub
.
$on
(
'
update.issuable
'
,
this
.
updateIssuable
);
eventHub
.
$on
(
'
update.issuable
'
,
this
.
updateIssuable
);
eventHub
.
$on
(
'
close.form
'
,
this
.
closeForm
);
eventHub
.
$on
(
'
close.form
'
,
this
.
closeForm
);
eventHub
.
$on
(
'
open.form
'
,
this
.
openForm
);
eventHub
.
$on
(
'
open.form
'
,
this
.
openForm
);
},
},
beforeDestroy
()
{
beforeDestroy
()
{
eventHub
.
$off
(
'
delete.issuable
'
,
this
.
deleteIssuable
);
eventHub
.
$off
(
'
update.issuable
'
,
this
.
updateIssuable
);
eventHub
.
$off
(
'
update.issuable
'
,
this
.
updateIssuable
);
eventHub
.
$off
(
'
close.form
'
,
this
.
closeForm
);
eventHub
.
$off
(
'
close.form
'
,
this
.
closeForm
);
eventHub
.
$off
(
'
open.form
'
,
this
.
openForm
);
eventHub
.
$off
(
'
open.form
'
,
this
.
openForm
);
...
@@ -418,25 +416,6 @@ export default {
...
@@ -418,25 +416,6 @@ export default {
});
});
},
},
deleteIssuable
(
payload
)
{
return
this
.
service
.
deleteIssuable
(
payload
)
.
then
((
res
)
=>
res
.
data
)
.
then
((
data
)
=>
{
// Stop the poll so we don't get 404's with the issuable not existing
this
.
poll
.
stop
();
visitUrl
(
data
.
web_url
);
})
.
catch
(()
=>
{
createFlash
({
message
:
sprintf
(
__
(
'
Error deleting %{issuableType}
'
),
{
issuableType
:
this
.
issuableType
,
}),
});
});
},
hideStickyHeader
()
{
hideStickyHeader
()
{
this
.
isStickyHeaderShowing
=
false
;
this
.
isStickyHeaderShowing
=
false
;
},
},
...
@@ -475,6 +454,7 @@ export default {
...
@@ -475,6 +454,7 @@ export default {
<div>
<div>
<div
v-if=
"canUpdate && showForm"
>
<div
v-if=
"canUpdate && showForm"
>
<form-component
<form-component
:endpoint=
"endpoint"
:form-state=
"formState"
:form-state=
"formState"
:initial-description-text=
"initialDescriptionText"
:initial-description-text=
"initialDescriptionText"
:can-destroy=
"canDestroy"
:can-destroy=
"canDestroy"
...
...
app/assets/javascripts/issues/show/components/delete_issue_modal.vue
0 → 100644
View file @
b6a019ff
<
script
>
import
{
GlModal
}
from
'
@gitlab/ui
'
;
import
csrf
from
'
~/lib/utils/csrf
'
;
import
{
capitalizeFirstCharacter
}
from
'
~/lib/utils/text_utility
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
export
default
{
actionCancel
:
{
text
:
__
(
'
Cancel
'
)
},
csrf
,
components
:
{
GlModal
,
},
props
:
{
issuePath
:
{
type
:
String
,
required
:
true
,
},
issueType
:
{
type
:
String
,
required
:
true
,
},
modalId
:
{
type
:
String
,
required
:
true
,
},
title
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
actionPrimary
()
{
return
{
attributes
:
{
variant
:
'
danger
'
},
text
:
this
.
title
,
};
},
bodyText
()
{
return
this
.
issueType
.
toLowerCase
()
===
'
epic
'
?
__
(
'
Delete this epic and all descendants?
'
)
:
sprintf
(
__
(
'
%{issuableType} will be removed! Are you sure?
'
),
{
issuableType
:
capitalizeFirstCharacter
(
this
.
issueType
),
});
},
},
methods
:
{
submitForm
()
{
this
.
$emit
(
'
delete
'
);
this
.
$refs
.
form
.
submit
();
},
},
};
</
script
>
<
template
>
<gl-modal
:action-cancel=
"$options.actionCancel"
:action-primary=
"actionPrimary"
:modal-id=
"modalId"
size=
"sm"
:title=
"title"
@
primary=
"submitForm"
>
<form
ref=
"form"
:action=
"issuePath"
method=
"post"
>
<input
type=
"hidden"
name=
"_method"
value=
"delete"
/>
<input
type=
"hidden"
name=
"authenticity_token"
:value=
"$options.csrf.token"
/>
<input
type=
"hidden"
name=
"destroy_confirm"
value=
"true"
/>
{{
bodyText
}}
</form>
</gl-modal>
</
template
>
app/assets/javascripts/issues/show/components/edit_actions.vue
View file @
b6a019ff
<
script
>
<
script
>
import
{
GlButton
,
GlModal
,
GlModal
Directive
}
from
'
@gitlab/ui
'
;
import
{
GlButton
,
GlModalDirective
}
from
'
@gitlab/ui
'
;
import
{
uniqueId
}
from
'
lodash
'
;
import
{
uniqueId
}
from
'
lodash
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
import
Tracking
from
'
~/tracking
'
;
import
eventHub
from
'
../event_hub
'
;
import
eventHub
from
'
../event_hub
'
;
import
updateMixin
from
'
../mixins/update
'
;
import
updateMixin
from
'
../mixins/update
'
;
import
getIssueStateQuery
from
'
../queries/get_issue_state.query.graphql
'
;
import
getIssueStateQuery
from
'
../queries/get_issue_state.query.graphql
'
;
import
DeleteIssueModal
from
'
./delete_issue_modal.vue
'
;
const
issuableTypes
=
{
const
issuableTypes
=
{
issue
:
__
(
'
Issue
'
),
issue
:
__
(
'
Issue
'
),
...
@@ -12,20 +14,26 @@ const issuableTypes = {
...
@@ -12,20 +14,26 @@ const issuableTypes = {
incident
:
__
(
'
Incident
'
),
incident
:
__
(
'
Incident
'
),
};
};
const
trackingMixin
=
Tracking
.
mixin
({
label
:
'
delete_issue
'
});
export
default
{
export
default
{
components
:
{
components
:
{
DeleteIssueModal
,
GlButton
,
GlButton
,
GlModal
,
},
},
directives
:
{
directives
:
{
GlModal
:
GlModalDirective
,
GlModal
:
GlModalDirective
,
},
},
mixins
:
[
updateMixin
],
mixins
:
[
trackingMixin
,
updateMixin
],
props
:
{
props
:
{
canDestroy
:
{
canDestroy
:
{
type
:
Boolean
,
type
:
Boolean
,
required
:
true
,
required
:
true
,
},
},
endpoint
:
{
required
:
true
,
type
:
String
,
},
formState
:
{
formState
:
{
type
:
Object
,
type
:
Object
,
required
:
true
,
required
:
true
,
...
@@ -65,27 +73,9 @@ export default {
...
@@ -65,27 +73,9 @@ export default {
issuableType
:
this
.
typeToShow
.
toLowerCase
(),
issuableType
:
this
.
typeToShow
.
toLowerCase
(),
});
});
},
},
deleteIssuableModalText
()
{
return
this
.
issuableType
===
'
epic
'
?
__
(
'
Delete this epic and all descendants?
'
)
:
sprintf
(
__
(
'
%{issuableType} will be removed! Are you sure?
'
),
{
issuableType
:
this
.
typeToShow
,
});
},
isSubmitEnabled
()
{
isSubmitEnabled
()
{
return
this
.
formState
.
title
.
trim
()
!==
''
;
return
this
.
formState
.
title
.
trim
()
!==
''
;
},
},
modalActionProps
()
{
return
{
primary
:
{
text
:
this
.
deleteIssuableButtonText
,
attributes
:
[{
variant
:
'
danger
'
},
{
loading
:
this
.
deleteLoading
}],
},
cancel
:
{
text
:
__
(
'
Cancel
'
),
},
};
},
shouldShowDeleteButton
()
{
shouldShowDeleteButton
()
{
return
this
.
canDestroy
&&
this
.
showDeleteButton
;
return
this
.
canDestroy
&&
this
.
showDeleteButton
;
},
},
...
@@ -101,7 +91,7 @@ export default {
...
@@ -101,7 +91,7 @@ export default {
},
},
deleteIssuable
()
{
deleteIssuable
()
{
this
.
deleteLoading
=
true
;
this
.
deleteLoading
=
true
;
eventHub
.
$emit
(
'
delete.issuable
'
,
{
destroy_confirm
:
true
}
);
eventHub
.
$emit
(
'
delete.issuable
'
);
},
},
},
},
};
};
...
@@ -135,22 +125,17 @@ export default {
...
@@ -135,22 +125,17 @@ export default {
variant=
"danger"
variant=
"danger"
class=
"qa-delete-button"
class=
"qa-delete-button"
data-testid=
"issuable-delete-button"
data-testid=
"issuable-delete-button"
@
click=
"track('click_button')"
>
>
{{
deleteIssuableButtonText
}}
{{
deleteIssuableButtonText
}}
</gl-button>
</gl-button>
<gl-modal
<delete-issue-modal
ref=
"removeModal"
:issue-path=
"endpoint"
:issue-type=
"typeToShow"
:modal-id=
"modalId"
:modal-id=
"modalId"
size=
"sm"
:title=
"deleteIssuableButtonText"
:action-primary=
"modalActionProps.primary"
@
delete=
"deleteIssuable"
:action-cancel=
"modalActionProps.cancel"
/>
@
primary=
"deleteIssuable"
>
<template
#modal-title
>
{{
deleteIssuableButtonText
}}
</
template
>
<div>
<p
class=
"gl-mb-1"
>
{{ deleteIssuableModalText }}
</p>
</div>
</gl-modal>
</div>
</div>
</div>
</div>
</
template
>
</
template
>
app/assets/javascripts/issues/show/components/form.vue
View file @
b6a019ff
...
@@ -26,6 +26,10 @@ export default {
...
@@ -26,6 +26,10 @@ export default {
type
:
Boolean
,
type
:
Boolean
,
required
:
true
,
required
:
true
,
},
},
endpoint
:
{
type
:
String
,
required
:
true
,
},
formState
:
{
formState
:
{
type
:
Object
,
type
:
Object
,
required
:
true
,
required
:
true
,
...
@@ -213,6 +217,7 @@ export default {
...
@@ -213,6 +217,7 @@ export default {
:enable-autocomplete=
"enableAutocomplete"
:enable-autocomplete=
"enableAutocomplete"
/>
/>
<edit-actions
<edit-actions
:endpoint=
"endpoint"
:form-state=
"formState"
:form-state=
"formState"
:can-destroy=
"canDestroy"
:can-destroy=
"canDestroy"
:show-delete-button=
"showDeleteButton"
:show-delete-button=
"showDeleteButton"
...
...
app/assets/javascripts/issues/show/components/header_actions.vue
View file @
b6a019ff
<
script
>
<
script
>
import
{
GlButton
,
GlDropdown
,
GlDropdownItem
,
GlLink
,
GlModal
}
from
'
@gitlab/ui
'
;
import
{
GlButton
,
GlDropdown
,
GlDropdownDivider
,
GlDropdownItem
,
GlLink
,
GlModal
,
GlModalDirective
,
}
from
'
@gitlab/ui
'
;
import
{
mapActions
,
mapGetters
,
mapState
}
from
'
vuex
'
;
import
{
mapActions
,
mapGetters
,
mapState
}
from
'
vuex
'
;
import
createFlash
,
{
FLASH_TYPES
}
from
'
~/flash
'
;
import
createFlash
,
{
FLASH_TYPES
}
from
'
~/flash
'
;
import
{
EVENT_ISSUABLE_VUE_APP_CHANGE
}
from
'
~/issuable/constants
'
;
import
{
EVENT_ISSUABLE_VUE_APP_CHANGE
}
from
'
~/issuable/constants
'
;
...
@@ -10,23 +18,21 @@ import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
...
@@ -10,23 +18,21 @@ import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import
{
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
{
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
{
s__
,
__
,
sprintf
}
from
'
~/locale
'
;
import
{
s__
,
__
,
sprintf
}
from
'
~/locale
'
;
import
eventHub
from
'
~/notes/event_hub
'
;
import
eventHub
from
'
~/notes/event_hub
'
;
import
Tracking
from
'
~/tracking
'
;
import
promoteToEpicMutation
from
'
../queries/promote_to_epic.mutation.graphql
'
;
import
promoteToEpicMutation
from
'
../queries/promote_to_epic.mutation.graphql
'
;
import
updateIssueMutation
from
'
../queries/update_issue.mutation.graphql
'
;
import
updateIssueMutation
from
'
../queries/update_issue.mutation.graphql
'
;
import
DeleteIssueModal
from
'
./delete_issue_modal.vue
'
;
const
trackingMixin
=
Tracking
.
mixin
({
label
:
'
delete_issue
'
});
export
default
{
export
default
{
components
:
{
GlButton
,
GlDropdown
,
GlDropdownItem
,
GlLink
,
GlModal
,
},
actionCancel
:
{
actionCancel
:
{
text
:
__
(
'
Cancel
'
),
text
:
__
(
'
Cancel
'
),
},
},
actionPrimary
:
{
actionPrimary
:
{
text
:
__
(
'
Yes, close issue
'
),
text
:
__
(
'
Yes, close issue
'
),
},
},
deleteModalId
:
'
delete-modal-id
'
,
i18n
:
{
i18n
:
{
promoteErrorMessage
:
__
(
promoteErrorMessage
:
__
(
'
Something went wrong while promoting the issue to an epic. Please try again.
'
,
'
Something went wrong while promoting the issue to an epic. Please try again.
'
,
...
@@ -35,10 +41,26 @@ export default {
...
@@ -35,10 +41,26 @@ export default {
'
The issue was successfully promoted to an epic. Redirecting to epic...
'
,
'
The issue was successfully promoted to an epic. Redirecting to epic...
'
,
),
),
},
},
components
:
{
DeleteIssueModal
,
GlButton
,
GlDropdown
,
GlDropdownDivider
,
GlDropdownItem
,
GlLink
,
GlModal
,
},
directives
:
{
GlModal
:
GlModalDirective
,
},
mixins
:
[
trackingMixin
],
inject
:
{
inject
:
{
canCreateIssue
:
{
canCreateIssue
:
{
default
:
false
,
default
:
false
,
},
},
canDestroyIssue
:
{
default
:
false
,
},
canPromoteToEpic
:
{
canPromoteToEpic
:
{
default
:
false
,
default
:
false
,
},
},
...
@@ -57,6 +79,9 @@ export default {
...
@@ -57,6 +79,9 @@ export default {
isIssueAuthor
:
{
isIssueAuthor
:
{
default
:
false
,
default
:
false
,
},
},
issuePath
:
{
default
:
''
,
},
issueType
:
{
issueType
:
{
default
:
IssuableType
.
Issue
,
default
:
IssuableType
.
Issue
,
},
},
...
@@ -92,6 +117,9 @@ export default {
...
@@ -92,6 +117,9 @@ export default {
?
sprintf
(
__
(
'
Reopen %{issueType}
'
),
{
issueType
:
this
.
issueTypeText
})
?
sprintf
(
__
(
'
Reopen %{issueType}
'
),
{
issueType
:
this
.
issueTypeText
})
:
sprintf
(
__
(
'
Close %{issueType}
'
),
{
issueType
:
this
.
issueTypeText
});
:
sprintf
(
__
(
'
Close %{issueType}
'
),
{
issueType
:
this
.
issueTypeText
});
},
},
deleteButtonText
()
{
return
sprintf
(
__
(
'
Delete %{issuableType}
'
),
{
issuableType
:
this
.
issueTypeText
});
},
qaSelector
()
{
qaSelector
()
{
return
this
.
isClosed
?
'
reopen_issue_button
'
:
'
close_issue_button
'
;
return
this
.
isClosed
?
'
reopen_issue_button
'
:
'
close_issue_button
'
;
},
},
...
@@ -141,8 +169,7 @@ export default {
...
@@ -141,8 +169,7 @@ export default {
})
})
.
then
(({
data
})
=>
{
.
then
(({
data
})
=>
{
if
(
data
.
updateIssue
.
errors
.
length
)
{
if
(
data
.
updateIssue
.
errors
.
length
)
{
createFlash
({
message
:
data
.
updateIssue
.
errors
.
join
(
'
.
'
)
});
throw
new
Error
();
return
;
}
}
const
payload
=
{
const
payload
=
{
...
@@ -175,8 +202,7 @@ export default {
...
@@ -175,8 +202,7 @@ export default {
})
})
.
then
(({
data
})
=>
{
.
then
(({
data
})
=>
{
if
(
data
.
promoteToEpic
.
errors
.
length
)
{
if
(
data
.
promoteToEpic
.
errors
.
length
)
{
createFlash
({
message
:
data
.
promoteToEpic
.
errors
.
join
(
'
;
'
)
});
throw
new
Error
();
return
;
}
}
createFlash
({
createFlash
({
...
@@ -228,6 +254,16 @@ export default {
...
@@ -228,6 +254,16 @@ export default {
>
>
{{
__
(
'
Submit as spam
'
)
}}
{{
__
(
'
Submit as spam
'
)
}}
</gl-dropdown-item>
</gl-dropdown-item>
<template
v-if=
"canDestroyIssue"
>
<gl-dropdown-divider
/>
<gl-dropdown-item
v-gl-modal=
"$options.deleteModalId"
variant=
"danger"
@
click=
"track('click_dropdown')"
>
{{
deleteButtonText
}}
</gl-dropdown-item>
</
template
>
</gl-dropdown>
</gl-dropdown>
<gl-button
<gl-button
...
@@ -271,6 +307,16 @@ export default {
...
@@ -271,6 +307,16 @@ export default {
>
>
{{ __('Submit as spam') }}
{{ __('Submit as spam') }}
</gl-dropdown-item>
</gl-dropdown-item>
<
template
v-if=
"canDestroyIssue"
>
<gl-dropdown-divider
/>
<gl-dropdown-item
v-gl-modal=
"$options.deleteModalId"
variant=
"danger"
@
click=
"track('click_dropdown')"
>
{{
deleteButtonText
}}
</gl-dropdown-item>
</
template
>
</gl-dropdown>
</gl-dropdown>
<gl-modal
<gl-modal
...
@@ -288,5 +334,12 @@ export default {
...
@@ -288,5 +334,12 @@ export default {
</li>
</li>
</ul>
</ul>
</gl-modal>
</gl-modal>
<delete-issue-modal
:issue-path=
"issuePath"
:issue-type=
"issueType"
:modal-id=
"$options.deleteModalId"
:title=
"deleteButtonText"
/>
</div>
</div>
</template>
</template>
app/assets/javascripts/issues/show/incident.js
View file @
b6a019ff
...
@@ -81,12 +81,14 @@ export function initIncidentHeaderActions(store) {
...
@@ -81,12 +81,14 @@ export function initIncidentHeaderActions(store) {
store
,
store
,
provide
:
{
provide
:
{
canCreateIssue
:
parseBoolean
(
el
.
dataset
.
canCreateIncident
),
canCreateIssue
:
parseBoolean
(
el
.
dataset
.
canCreateIncident
),
canDestroyIssue
:
parseBoolean
(
el
.
dataset
.
canDestroyIssue
),
canPromoteToEpic
:
parseBoolean
(
el
.
dataset
.
canPromoteToEpic
),
canPromoteToEpic
:
parseBoolean
(
el
.
dataset
.
canPromoteToEpic
),
canReopenIssue
:
parseBoolean
(
el
.
dataset
.
canReopenIssue
),
canReopenIssue
:
parseBoolean
(
el
.
dataset
.
canReopenIssue
),
canReportSpam
:
parseBoolean
(
el
.
dataset
.
canReportSpam
),
canReportSpam
:
parseBoolean
(
el
.
dataset
.
canReportSpam
),
canUpdateIssue
:
parseBoolean
(
el
.
dataset
.
canUpdateIssue
),
canUpdateIssue
:
parseBoolean
(
el
.
dataset
.
canUpdateIssue
),
iid
:
el
.
dataset
.
iid
,
iid
:
el
.
dataset
.
iid
,
isIssueAuthor
:
parseBoolean
(
el
.
dataset
.
isIssueAuthor
),
isIssueAuthor
:
parseBoolean
(
el
.
dataset
.
isIssueAuthor
),
issuePath
:
el
.
dataset
.
issuePath
,
issueType
:
el
.
dataset
.
issueType
,
issueType
:
el
.
dataset
.
issueType
,
newIssuePath
:
el
.
dataset
.
newIssuePath
,
newIssuePath
:
el
.
dataset
.
newIssuePath
,
projectPath
:
el
.
dataset
.
projectPath
,
projectPath
:
el
.
dataset
.
projectPath
,
...
...
app/assets/javascripts/issues/show/issue.js
View file @
b6a019ff
...
@@ -66,12 +66,14 @@ export function initIssueHeaderActions(store) {
...
@@ -66,12 +66,14 @@ export function initIssueHeaderActions(store) {
store
,
store
,
provide
:
{
provide
:
{
canCreateIssue
:
parseBoolean
(
el
.
dataset
.
canCreateIssue
),
canCreateIssue
:
parseBoolean
(
el
.
dataset
.
canCreateIssue
),
canDestroyIssue
:
parseBoolean
(
el
.
dataset
.
canDestroyIssue
),
canPromoteToEpic
:
parseBoolean
(
el
.
dataset
.
canPromoteToEpic
),
canPromoteToEpic
:
parseBoolean
(
el
.
dataset
.
canPromoteToEpic
),
canReopenIssue
:
parseBoolean
(
el
.
dataset
.
canReopenIssue
),
canReopenIssue
:
parseBoolean
(
el
.
dataset
.
canReopenIssue
),
canReportSpam
:
parseBoolean
(
el
.
dataset
.
canReportSpam
),
canReportSpam
:
parseBoolean
(
el
.
dataset
.
canReportSpam
),
canUpdateIssue
:
parseBoolean
(
el
.
dataset
.
canUpdateIssue
),
canUpdateIssue
:
parseBoolean
(
el
.
dataset
.
canUpdateIssue
),
iid
:
el
.
dataset
.
iid
,
iid
:
el
.
dataset
.
iid
,
isIssueAuthor
:
parseBoolean
(
el
.
dataset
.
isIssueAuthor
),
isIssueAuthor
:
parseBoolean
(
el
.
dataset
.
isIssueAuthor
),
issuePath
:
el
.
dataset
.
issuePath
,
issueType
:
el
.
dataset
.
issueType
,
issueType
:
el
.
dataset
.
issueType
,
newIssuePath
:
el
.
dataset
.
newIssuePath
,
newIssuePath
:
el
.
dataset
.
newIssuePath
,
projectPath
:
el
.
dataset
.
projectPath
,
projectPath
:
el
.
dataset
.
projectPath
,
...
...
app/helpers/issues_helper.rb
View file @
b6a019ff
...
@@ -193,11 +193,13 @@ module IssuesHelper
...
@@ -193,11 +193,13 @@ module IssuesHelper
{
{
can_create_issue:
show_new_issue_link?
(
project
).
to_s
,
can_create_issue:
show_new_issue_link?
(
project
).
to_s
,
can_create_incident:
create_issue_type_allowed?
(
project
,
:incident
).
to_s
,
can_create_incident:
create_issue_type_allowed?
(
project
,
:incident
).
to_s
,
can_destroy_issue:
can?
(
current_user
,
:"destroy_
#{
issuable
.
to_ability_name
}
"
,
issuable
).
to_s
,
can_reopen_issue:
can?
(
current_user
,
:reopen_issue
,
issuable
).
to_s
,
can_reopen_issue:
can?
(
current_user
,
:reopen_issue
,
issuable
).
to_s
,
can_report_spam:
issuable
.
submittable_as_spam_by?
(
current_user
).
to_s
,
can_report_spam:
issuable
.
submittable_as_spam_by?
(
current_user
).
to_s
,
can_update_issue:
can?
(
current_user
,
:update_issue
,
issuable
).
to_s
,
can_update_issue:
can?
(
current_user
,
:update_issue
,
issuable
).
to_s
,
iid:
issuable
.
iid
,
iid:
issuable
.
iid
,
is_issue_author:
(
issuable
.
author
==
current_user
).
to_s
,
is_issue_author:
(
issuable
.
author
==
current_user
).
to_s
,
issue_path:
issuable_path
(
issuable
),
issue_type:
issuable_display_type
(
issuable
),
issue_type:
issuable_display_type
(
issuable
),
new_issue_path:
new_project_issue_path
(
project
,
new_issuable_params
),
new_issue_path:
new_project_issue_path
(
project
,
new_issuable_params
),
project_path:
project
.
full_path
,
project_path:
project
.
full_path
,
...
...
doc/user/project/issues/img/delete_issue_v13_11.png
deleted
100644 → 0
View file @
c8b5da56
38 KB
doc/user/project/issues/index.md
View file @
b6a019ff
...
@@ -34,7 +34,7 @@ To learn how the GitLab Strategic Marketing department uses GitLab issues with [
...
@@ -34,7 +34,7 @@ To learn how the GitLab Strategic Marketing department uses GitLab issues with [
-
[
Edit issues
](
managing_issues.md#edit-an-issue
)
-
[
Edit issues
](
managing_issues.md#edit-an-issue
)
-
[
Move issues
](
managing_issues.md#moving-issues
)
-
[
Move issues
](
managing_issues.md#moving-issues
)
-
[
Close issues
](
managing_issues.md#closing-issues
)
-
[
Close issues
](
managing_issues.md#closing-issues
)
-
[
Delete issues
](
managing_issues.md#delet
ing-issues
)
-
[
Delete issues
](
managing_issues.md#delet
e-an-issue
)
-
[
Promote issues
](
managing_issues.md#promote-an-issue-to-an-epic
)
-
[
Promote issues
](
managing_issues.md#promote-an-issue-to-an-epic
)
-
[
Set a due date
](
due_dates.md
)
-
[
Set a due date
](
due_dates.md
)
-
[
Import issues
](
csv_import.md
)
-
[
Import issues
](
csv_import.md
)
...
...
doc/user/project/issues/managing_issues.md
View file @
b6a019ff
...
@@ -439,12 +439,23 @@ can change an issue's type. To do this, edit the issue and select an issue type
...
@@ -439,12 +439,23 @@ can change an issue's type. To do this, edit the issue and select an issue type
![
Change the issue type
](
img/issue_type_change_v13_12.png
)
![
Change the issue type
](
img/issue_type_change_v13_12.png
)
## Delet
ing issues
## Delet
e an issue
Users with the
[
Owner role
](
../../permissions.md
)
can delete an issue by
> Deleting from the vertical ellipsis menu [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299933) in GitLab 14.6.
editing it and selecting
**Delete issue**
.
![
delete issue - button
](
img/delete_issue_v13_11.png
)
Prerequisites:
-
You must have the
[
Owner role
](
../../permissions.md
)
for a project.
To delete an issue:
1.
In an issue, select the vertical ellipsis (
**{ellipsis_v}**
).
1.
Select
**Delete issue**
.
Alternatively:
1.
In an issue, select
**Edit title and description**
(
**{pencil}**
).
1.
Select
**Delete issue**
.
## Promote an issue to an epic **(PREMIUM)**
## Promote an issue to an epic **(PREMIUM)**
...
...
locale/gitlab.pot
View file @
b6a019ff
...
@@ -13708,9 +13708,6 @@ msgstr ""
...
@@ -13708,9 +13708,6 @@ msgstr ""
msgid "Error creating the snippet"
msgid "Error creating the snippet"
msgstr ""
msgstr ""
msgid "Error deleting %{issuableType}"
msgstr ""
msgid "Error deleting project. Check logs for error details."
msgid "Error deleting project. Check logs for error details."
msgstr ""
msgstr ""
...
...
spec/features/issues/issue_header_spec.rb
View file @
b6a019ff
...
@@ -4,7 +4,8 @@ require 'spec_helper'
...
@@ -4,7 +4,8 @@ require 'spec_helper'
RSpec
.
describe
'issue header'
,
:js
do
RSpec
.
describe
'issue header'
,
:js
do
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:project
)
{
create
(
:project
)
}
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:project
)
{
create
(
:project
,
group:
group
)
}
let_it_be
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let_it_be
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let_it_be
(
:closed_issue
)
{
create
(
:issue
,
:closed
,
project:
project
)
}
let_it_be
(
:closed_issue
)
{
create
(
:issue
,
:closed
,
project:
project
)
}
let_it_be
(
:closed_locked_issue
)
{
create
(
:issue
,
:closed
,
:locked
,
project:
project
)
}
let_it_be
(
:closed_locked_issue
)
{
create
(
:issue
,
:closed
,
:locked
,
project:
project
)
}
...
@@ -12,7 +13,7 @@ RSpec.describe 'issue header', :js do
...
@@ -12,7 +13,7 @@ RSpec.describe 'issue header', :js do
context
'when user has permission to update'
do
context
'when user has permission to update'
do
before
do
before
do
project
.
add_maintai
ner
(
user
)
group
.
add_ow
ner
(
user
)
sign_in
(
user
)
sign_in
(
user
)
end
end
...
@@ -24,9 +25,10 @@ RSpec.describe 'issue header', :js do
...
@@ -24,9 +25,10 @@ RSpec.describe 'issue header', :js do
click_button
'Issue actions'
click_button
'Issue actions'
end
end
it
'
only shows the "New issue" and "Report abus
e" items'
,
:aggregate_failures
do
it
'
shows the "New issue", "Report abuse", and "Delete issu
e" items'
,
:aggregate_failures
do
expect
(
page
).
to
have_link
'New issue'
expect
(
page
).
to
have_link
'New issue'
expect
(
page
).
to
have_link
'Report abuse'
expect
(
page
).
to
have_link
'Report abuse'
expect
(
page
).
to
have_button
'Delete issue'
expect
(
page
).
not_to
have_link
'Submit as spam'
expect
(
page
).
not_to
have_link
'Submit as spam'
end
end
end
end
...
@@ -116,6 +118,7 @@ RSpec.describe 'issue header', :js do
...
@@ -116,6 +118,7 @@ RSpec.describe 'issue header', :js do
expect
(
page
).
to
have_link
'New issue'
expect
(
page
).
to
have_link
'New issue'
expect
(
page
).
to
have_link
'Report abuse'
expect
(
page
).
to
have_link
'Report abuse'
expect
(
page
).
not_to
have_link
'Submit as spam'
expect
(
page
).
not_to
have_link
'Submit as spam'
expect
(
page
).
not_to
have_button
'Delete issue'
end
end
end
end
...
...
spec/frontend/issues/show/components/app_spec.js
View file @
b6a019ff
...
@@ -326,44 +326,6 @@ describe('Issuable output', () => {
...
@@ -326,44 +326,6 @@ describe('Issuable output', () => {
});
});
});
});
describe
(
'
deleteIssuable
'
,
()
=>
{
it
(
'
changes URL when deleted
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
service
,
'
deleteIssuable
'
).
mockResolvedValue
({
data
:
{
web_url
:
'
/test
'
,
},
});
return
wrapper
.
vm
.
deleteIssuable
().
then
(()
=>
{
expect
(
visitUrl
).
toHaveBeenCalledWith
(
'
/test
'
);
});
});
it
(
'
stops polling when deleting
'
,
()
=>
{
const
spy
=
jest
.
spyOn
(
wrapper
.
vm
.
poll
,
'
stop
'
);
jest
.
spyOn
(
wrapper
.
vm
.
service
,
'
deleteIssuable
'
).
mockResolvedValue
({
data
:
{
web_url
:
'
/test
'
,
},
});
return
wrapper
.
vm
.
deleteIssuable
().
then
(()
=>
{
expect
(
spy
).
toHaveBeenCalledWith
();
});
});
it
(
'
closes form on error
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
service
,
'
deleteIssuable
'
).
mockRejectedValue
();
return
wrapper
.
vm
.
deleteIssuable
().
then
(()
=>
{
expect
(
eventHub
.
$emit
).
not
.
toHaveBeenCalledWith
(
'
close.form
'
);
expect
(
document
.
querySelector
(
'
.flash-container .flash-text
'
).
innerText
.
trim
()).
toBe
(
'
Error deleting issue
'
,
);
});
});
});
describe
(
'
updateAndShowForm
'
,
()
=>
{
describe
(
'
updateAndShowForm
'
,
()
=>
{
it
(
'
shows locked warning if form is open & data is different
'
,
()
=>
{
it
(
'
shows locked warning if form is open & data is different
'
,
()
=>
{
return
wrapper
.
vm
return
wrapper
.
vm
...
...
spec/frontend/issues/show/components/delete_issue_modal_spec.js
0 → 100644
View file @
b6a019ff
import
{
GlModal
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
DeleteIssueModal
from
'
~/issues/show/components/delete_issue_modal.vue
'
;
jest
.
mock
(
'
~/lib/utils/csrf
'
,
()
=>
({
token
:
'
mock-csrf-token
'
}));
describe
(
'
DeleteIssueModal component
'
,
()
=>
{
let
wrapper
;
const
defaultProps
=
{
issuePath
:
'
gitlab-org/gitlab-test/-/issues/1
'
,
issueType
:
'
issue
'
,
modalId
:
'
modal-id
'
,
title
:
'
Delete issue
'
,
};
const
findForm
=
()
=>
wrapper
.
find
(
'
form
'
);
const
findModal
=
()
=>
wrapper
.
findComponent
(
GlModal
);
const
mountComponent
=
(
props
=
{})
=>
shallowMount
(
DeleteIssueModal
,
{
propsData
:
{
...
defaultProps
,
...
props
}
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
modal
'
,
()
=>
{
it
(
'
renders
'
,
()
=>
{
wrapper
=
mountComponent
();
expect
(
findModal
().
props
()).
toMatchObject
({
actionCancel
:
DeleteIssueModal
.
actionCancel
,
actionPrimary
:
{
attributes
:
{
variant
:
'
danger
'
},
text
:
defaultProps
.
title
,
},
modalId
:
defaultProps
.
modalId
,
size
:
'
sm
'
,
title
:
defaultProps
.
title
,
});
});
describe
(
'
when "primary" event is emitted
'
,
()
=>
{
let
formSubmitSpy
;
beforeEach
(()
=>
{
wrapper
=
mountComponent
();
formSubmitSpy
=
jest
.
spyOn
(
wrapper
.
vm
.
$refs
.
form
,
'
submit
'
);
findModal
().
vm
.
$emit
(
'
primary
'
);
});
it
(
'
"delete" event is emitted by DeleteIssueModal
'
,
()
=>
{
expect
(
wrapper
.
emitted
(
'
delete
'
)).
toEqual
([[]]);
});
it
(
'
submits the form
'
,
()
=>
{
expect
(
formSubmitSpy
).
toHaveBeenCalled
();
});
});
});
describe
(
'
form
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
mountComponent
();
});
it
(
'
renders with action and method
'
,
()
=>
{
expect
(
findForm
().
attributes
()).
toEqual
({
action
:
defaultProps
.
issuePath
,
method
:
'
post
'
,
});
});
it
(
'
contains form data
'
,
()
=>
{
const
formData
=
wrapper
.
findAll
(
'
input
'
).
wrappers
.
reduce
(
(
acc
,
input
)
=>
({
...
acc
,
[
input
.
element
.
name
]:
input
.
element
.
value
,
}),
{},
);
expect
(
formData
).
toEqual
({
_method
:
'
delete
'
,
authenticity_token
:
'
mock-csrf-token
'
,
destroy_confirm
:
'
true
'
,
});
});
});
describe
(
'
body text
'
,
()
=>
{
describe
(
'
when issue type is not epic
'
,
()
=>
{
it
(
'
renders
'
,
()
=>
{
wrapper
=
mountComponent
();
expect
(
findForm
().
text
()).
toBe
(
'
Issue will be removed! Are you sure?
'
);
});
});
describe
(
'
when issue type is epic
'
,
()
=>
{
it
(
'
renders
'
,
()
=>
{
wrapper
=
mountComponent
({
issueType
:
'
epic
'
});
expect
(
findForm
().
text
()).
toBe
(
'
Delete this epic and all descendants?
'
);
});
});
});
});
spec/frontend/issues/show/components/edit_actions_spec.js
View file @
b6a019ff
import
{
GlButton
,
GlModal
}
from
'
@gitlab/ui
'
;
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
{
createLocalVue
}
from
'
@vue/test-utils
'
;
import
Vue
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
{
mockTracking
}
from
'
helpers/tracking_helper
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
IssuableEditActions
from
'
~/issues/show/components/edit_actions.vue
'
;
import
IssuableEditActions
from
'
~/issues/show/components/edit_actions.vue
'
;
import
DeleteIssueModal
from
'
~/issues/show/components/delete_issue_modal.vue
'
;
import
eventHub
from
'
~/issues/show/event_hub
'
;
import
eventHub
from
'
~/issues/show/event_hub
'
;
import
{
import
{
getIssueStateQueryResponse
,
getIssueStateQueryResponse
,
updateIssueStateQueryResponse
,
updateIssueStateQueryResponse
,
}
from
'
../mock_data/apollo_mock
'
;
}
from
'
../mock_data/apollo_mock
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
VueApollo
);
describe
(
'
Edit Actions component
'
,
()
=>
{
describe
(
'
Edit Actions component
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
let
fakeApollo
;
let
fakeApollo
;
let
mockIssueStateData
;
let
mockIssueStateData
;
Vue
.
use
(
VueApollo
);
const
mockResolvers
=
{
const
mockResolvers
=
{
Query
:
{
Query
:
{
issueState
()
{
issueState
()
{
...
@@ -43,6 +43,7 @@ describe('Edit Actions component', () => {
...
@@ -43,6 +43,7 @@ describe('Edit Actions component', () => {
title
:
'
GitLab Issue
'
,
title
:
'
GitLab Issue
'
,
},
},
canDestroy
:
true
,
canDestroy
:
true
,
endpoint
:
'
gitlab-org/gitlab-test/-/issues/1
'
,
issuableType
:
'
issue
'
,
issuableType
:
'
issue
'
,
...
props
,
...
props
,
},
},
...
@@ -56,11 +57,7 @@ describe('Edit Actions component', () => {
...
@@ -56,11 +57,7 @@ describe('Edit Actions component', () => {
});
});
};
};
async
function
deleteIssuable
(
localWrapper
)
{
const
findModal
=
()
=>
wrapper
.
findComponent
(
DeleteIssueModal
);
localWrapper
.
findComponent
(
GlModal
).
vm
.
$emit
(
'
primary
'
);
}
const
findModal
=
()
=>
wrapper
.
findComponent
(
GlModal
);
const
findEditButtons
=
()
=>
wrapper
.
findAllComponents
(
GlButton
);
const
findEditButtons
=
()
=>
wrapper
.
findAllComponents
(
GlButton
);
const
findDeleteButton
=
()
=>
wrapper
.
findByTestId
(
'
issuable-delete-button
'
);
const
findDeleteButton
=
()
=>
wrapper
.
findByTestId
(
'
issuable-delete-button
'
);
const
findSaveButton
=
()
=>
wrapper
.
findByTestId
(
'
issuable-save-button
'
);
const
findSaveButton
=
()
=>
wrapper
.
findByTestId
(
'
issuable-save-button
'
);
...
@@ -123,9 +120,30 @@ describe('Edit Actions component', () => {
...
@@ -123,9 +120,30 @@ describe('Edit Actions component', () => {
});
});
});
});
describe
(
'
renders create modal with the correct information
'
,
()
=>
{
describe
(
'
delete issue button
'
,
()
=>
{
it
(
'
renders correct modal id
'
,
()
=>
{
let
trackingSpy
;
expect
(
findModal
().
attributes
(
'
modalid
'
)).
toBe
(
modalId
);
beforeEach
(()
=>
{
trackingSpy
=
mockTracking
(
undefined
,
wrapper
.
element
,
jest
.
spyOn
);
});
it
(
'
tracks clicking on button
'
,
()
=>
{
findDeleteButton
().
vm
.
$emit
(
'
click
'
);
expect
(
trackingSpy
).
toHaveBeenCalledWith
(
undefined
,
'
click_button
'
,
{
label
:
'
delete_issue
'
,
});
});
});
describe
(
'
delete issue modal
'
,
()
=>
{
it
(
'
renders
'
,
()
=>
{
expect
(
findModal
().
props
()).
toEqual
({
issuePath
:
'
gitlab-org/gitlab-test/-/issues/1
'
,
issueType
:
'
Issue
'
,
modalId
,
title
:
'
Delete issue
'
,
});
});
});
});
});
...
@@ -141,8 +159,8 @@ describe('Edit Actions component', () => {
...
@@ -141,8 +159,8 @@ describe('Edit Actions component', () => {
it
(
'
sends the `delete.issuable` event when clicking the delete confirm button
'
,
async
()
=>
{
it
(
'
sends the `delete.issuable` event when clicking the delete confirm button
'
,
async
()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledTimes
(
0
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledTimes
(
0
);
await
deleteIssuable
(
wrapper
);
findModal
().
vm
.
$emit
(
'
delete
'
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
delete.issuable
'
,
{
destroy_confirm
:
true
}
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
delete.issuable
'
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledTimes
(
1
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledTimes
(
1
);
});
});
});
});
...
...
spec/frontend/issues/show/components/form_spec.js
View file @
b6a019ff
...
@@ -13,6 +13,7 @@ describe('Inline edit form component', () => {
...
@@ -13,6 +13,7 @@ describe('Inline edit form component', () => {
let
wrapper
;
let
wrapper
;
const
defaultProps
=
{
const
defaultProps
=
{
canDestroy
:
true
,
canDestroy
:
true
,
endpoint
:
'
gitlab-org/gitlab-test/-/issues/1
'
,
formState
:
{
formState
:
{
title
:
'
b
'
,
title
:
'
b
'
,
description
:
'
a
'
,
description
:
'
a
'
,
...
...
spec/frontend/issues/show/components/header_actions_spec.js
View file @
b6a019ff
import
{
GlButton
,
GlDropdown
,
GlDropdownItem
,
GlLink
,
GlModal
}
from
'
@gitlab/ui
'
;
import
{
GlButton
,
GlDropdown
,
GlDropdownItem
,
GlLink
,
GlModal
}
from
'
@gitlab/ui
'
;
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vue
from
'
vue
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
Vuex
from
'
vuex
'
;
import
{
mockTracking
}
from
'
helpers/tracking_helper
'
;
import
createFlash
,
{
FLASH_TYPES
}
from
'
~/flash
'
;
import
createFlash
,
{
FLASH_TYPES
}
from
'
~/flash
'
;
import
{
IssuableType
}
from
'
~/vue_shared/issuable/show/constants
'
;
import
{
IssuableType
}
from
'
~/vue_shared/issuable/show/constants
'
;
import
DeleteIssueModal
from
'
~/issues/show/components/delete_issue_modal.vue
'
;
import
HeaderActions
from
'
~/issues/show/components/header_actions.vue
'
;
import
HeaderActions
from
'
~/issues/show/components/header_actions.vue
'
;
import
{
IssuableStatus
}
from
'
~/issues/constants
'
;
import
{
IssuableStatus
}
from
'
~/issues/constants
'
;
import
{
IssueStateEvent
}
from
'
~/issues/show/constants
'
;
import
{
IssueStateEvent
}
from
'
~/issues/show/constants
'
;
...
@@ -19,18 +22,20 @@ describe('HeaderActions component', () => {
...
@@ -19,18 +22,20 @@ describe('HeaderActions component', () => {
let
wrapper
;
let
wrapper
;
let
visitUrlSpy
;
let
visitUrlSpy
;
const
localVue
=
createLocalVue
(
);
Vue
.
use
(
Vuex
);
localVue
.
use
(
Vuex
);
const
store
=
createStore
();
const
store
=
createStore
();
const
defaultProps
=
{
const
defaultProps
=
{
canCreateIssue
:
true
,
canCreateIssue
:
true
,
canDestroyIssue
:
true
,
canPromoteToEpic
:
true
,
canPromoteToEpic
:
true
,
canReopenIssue
:
true
,
canReopenIssue
:
true
,
canReportSpam
:
true
,
canReportSpam
:
true
,
canUpdateIssue
:
true
,
canUpdateIssue
:
true
,
iid
:
'
32
'
,
iid
:
'
32
'
,
isIssueAuthor
:
true
,
isIssueAuthor
:
true
,
issuePath
:
'
gitlab-org/gitlab-test/-/issues/1
'
,
issueType
:
IssuableType
.
Issue
,
issueType
:
IssuableType
.
Issue
,
newIssuePath
:
'
gitlab-org/gitlab-test/-/issues/new
'
,
newIssuePath
:
'
gitlab-org/gitlab-test/-/issues/new
'
,
projectPath
:
'
gitlab-org/gitlab-test
'
,
projectPath
:
'
gitlab-org/gitlab-test
'
,
...
@@ -61,17 +66,12 @@ describe('HeaderActions component', () => {
...
@@ -61,17 +66,12 @@ describe('HeaderActions component', () => {
},
},
};
};
const
findToggleIssueStateButton
=
()
=>
wrapper
.
find
(
GlButton
);
const
findToggleIssueStateButton
=
()
=>
wrapper
.
findComponent
(
GlButton
);
const
findDropdownAt
=
(
index
)
=>
wrapper
.
findAllComponents
(
GlDropdown
).
at
(
index
);
const
findDropdownAt
=
(
index
)
=>
wrapper
.
findAll
(
GlDropdown
).
at
(
index
);
const
findMobileDropdownItems
=
()
=>
findDropdownAt
(
0
).
findAllComponents
(
GlDropdownItem
);
const
findDesktopDropdownItems
=
()
=>
findDropdownAt
(
1
).
findAllComponents
(
GlDropdownItem
);
const
findMobileDropdownItems
=
()
=>
findDropdownAt
(
0
).
findAll
(
GlDropdownItem
);
const
findModal
=
()
=>
wrapper
.
findComponent
(
GlModal
);
const
findModalLinkAt
=
(
index
)
=>
findModal
().
findAllComponents
(
GlLink
).
at
(
index
);
const
findDesktopDropdownItems
=
()
=>
findDropdownAt
(
1
).
findAll
(
GlDropdownItem
);
const
findModal
=
()
=>
wrapper
.
find
(
GlModal
);
const
findModalLinkAt
=
(
index
)
=>
findModal
().
findAll
(
GlLink
).
at
(
index
);
const
mountComponent
=
({
const
mountComponent
=
({
props
=
{},
props
=
{},
...
@@ -87,7 +87,6 @@ describe('HeaderActions component', () => {
...
@@ -87,7 +87,6 @@ describe('HeaderActions component', () => {
});
});
return
shallowMount
(
HeaderActions
,
{
return
shallowMount
(
HeaderActions
,
{
localVue
,
store
,
store
,
provide
:
{
provide
:
{
...
defaultProps
,
...
defaultProps
,
...
@@ -168,17 +167,19 @@ describe('HeaderActions component', () => {
...
@@ -168,17 +167,19 @@ describe('HeaderActions component', () => {
${
'
desktop dropdown
'
}
|
${
false
}
|
${
findDesktopDropdownItems
}
${
'
desktop dropdown
'
}
|
${
false
}
|
${
findDesktopDropdownItems
}
`
(
'
$description
'
,
({
isCloseIssueItemVisible
,
findDropdownItems
})
=>
{
`
(
'
$description
'
,
({
isCloseIssueItemVisible
,
findDropdownItems
})
=>
{
describe
.
each
`
describe
.
each
`
description | itemText | isItemVisible | canUpdateIssue | canCreateIssue | isIssueAuthor | canReportSpam | canPromoteToEpic
description | itemText | isItemVisible | canUpdateIssue | canCreateIssue | isIssueAuthor | canReportSpam | canPromoteToEpic | canDestroyIssue
${
`when user can update
${
issueType
}
`
}
|
${
`Close
${
issueType
}
`
}
|
${
isCloseIssueItemVisible
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
`when user can update
${
issueType
}
`
}
|
${
`Close
${
issueType
}
`
}
|
${
isCloseIssueItemVisible
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
`when user cannot update
${
issueType
}
`
}
|
${
`Close
${
issueType
}
`
}
|
${
false
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
`when user cannot update
${
issueType
}
`
}
|
${
`Close
${
issueType
}
`
}
|
${
false
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
`when user can create
${
issueType
}
`
}
|
${
`New
${
issueType
}
`
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
`when user can create
${
issueType
}
`
}
|
${
`New
${
issueType
}
`
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
`when user cannot create
${
issueType
}
`
}
|
${
`New
${
issueType
}
`
}
|
${
false
}
|
${
true
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
${
`when user cannot create
${
issueType
}
`
}
|
${
`New
${
issueType
}
`
}
|
${
false
}
|
${
true
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
'
when user can promote to epic
'
}
|
${
'
Promote to epic
'
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
'
when user can promote to epic
'
}
|
${
'
Promote to epic
'
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
'
when user cannot promote to epic
'
}
|
${
'
Promote to epic
'
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
false
}
${
'
when user cannot promote to epic
'
}
|
${
'
Promote to epic
'
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
false
}
|
${
true
}
${
'
when user can report abuse
'
}
|
${
'
Report abuse
'
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
false
}
|
${
true
}
|
${
true
}
${
'
when user can report abuse
'
}
|
${
'
Report abuse
'
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
${
'
when user cannot report abuse
'
}
|
${
'
Report abuse
'
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
'
when user cannot report abuse
'
}
|
${
'
Report abuse
'
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
'
when user can submit as spam
'
}
|
${
'
Submit as spam
'
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
'
when user can submit as spam
'
}
|
${
'
Submit as spam
'
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
'
when user cannot submit as spam
'
}
|
${
'
Submit as spam
'
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
false
}
|
${
true
}
${
'
when user cannot submit as spam
'
}
|
${
'
Submit as spam
'
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
false
}
|
${
true
}
|
${
true
}
${
`when user can delete
${
issueType
}
`
}
|
${
`Delete
${
issueType
}
`
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
${
`when user cannot delete
${
issueType
}
`
}
|
${
`Delete
${
issueType
}
`
}
|
${
false
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
true
}
|
${
false
}
`
(
`
(
'
$description
'
,
'
$description
'
,
({
({
...
@@ -189,6 +190,7 @@ describe('HeaderActions component', () => {
...
@@ -189,6 +190,7 @@ describe('HeaderActions component', () => {
isIssueAuthor
,
isIssueAuthor
,
canReportSpam
,
canReportSpam
,
canPromoteToEpic
,
canPromoteToEpic
,
canDestroyIssue
,
})
=>
{
})
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
wrapper
=
mountComponent
({
wrapper
=
mountComponent
({
...
@@ -199,6 +201,7 @@ describe('HeaderActions component', () => {
...
@@ -199,6 +201,7 @@ describe('HeaderActions component', () => {
issueType
,
issueType
,
canReportSpam
,
canReportSpam
,
canPromoteToEpic
,
canPromoteToEpic
,
canDestroyIssue
,
},
},
});
});
});
});
...
@@ -215,6 +218,23 @@ describe('HeaderActions component', () => {
...
@@ -215,6 +218,23 @@ describe('HeaderActions component', () => {
});
});
});
});
describe
(
'
delete issue button
'
,
()
=>
{
let
trackingSpy
;
beforeEach
(()
=>
{
wrapper
=
mountComponent
();
trackingSpy
=
mockTracking
(
undefined
,
wrapper
.
element
,
jest
.
spyOn
);
});
it
(
'
tracks clicking on button
'
,
()
=>
{
findDesktopDropdownItems
().
at
(
3
).
vm
.
$emit
(
'
click
'
);
expect
(
trackingSpy
).
toHaveBeenCalledWith
(
undefined
,
'
click_dropdown
'
,
{
label
:
'
delete_issue
'
,
});
});
});
describe
(
'
when "Promote to epic" button is clicked
'
,
()
=>
{
describe
(
'
when "Promote to epic" button is clicked
'
,
()
=>
{
describe
(
'
when response is successful
'
,
()
=>
{
describe
(
'
when response is successful
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
...
@@ -268,7 +288,7 @@ describe('HeaderActions component', () => {
...
@@ -268,7 +288,7 @@ describe('HeaderActions component', () => {
it
(
'
shows an error message
'
,
()
=>
{
it
(
'
shows an error message
'
,
()
=>
{
expect
(
createFlash
).
toHaveBeenCalledWith
({
expect
(
createFlash
).
toHaveBeenCalledWith
({
message
:
promoteToEpicMutationErrorResponse
.
data
.
promoteToEpic
.
errors
.
join
(
'
;
'
)
,
message
:
HeaderActions
.
i18n
.
promoteErrorMessage
,
});
});
});
});
});
});
...
@@ -294,7 +314,7 @@ describe('HeaderActions component', () => {
...
@@ -294,7 +314,7 @@ describe('HeaderActions component', () => {
});
});
});
});
describe
(
'
modal
'
,
()
=>
{
describe
(
'
blocked by issues
modal
'
,
()
=>
{
const
blockedByIssues
=
[
const
blockedByIssues
=
[
{
iid
:
13
,
web_url
:
'
gitlab-org/gitlab-test/-/issues/13
'
},
{
iid
:
13
,
web_url
:
'
gitlab-org/gitlab-test/-/issues/13
'
},
{
iid
:
79
,
web_url
:
'
gitlab-org/gitlab-test/-/issues/79
'
},
{
iid
:
79
,
web_url
:
'
gitlab-org/gitlab-test/-/issues/79
'
},
...
@@ -346,4 +366,17 @@ describe('HeaderActions component', () => {
...
@@ -346,4 +366,17 @@ describe('HeaderActions component', () => {
});
});
});
});
});
});
describe
(
'
delete issue modal
'
,
()
=>
{
it
(
'
renders
'
,
()
=>
{
wrapper
=
mountComponent
();
expect
(
wrapper
.
findComponent
(
DeleteIssueModal
).
props
()).
toEqual
({
issuePath
:
defaultProps
.
issuePath
,
issueType
:
defaultProps
.
issueType
,
modalId
:
HeaderActions
.
deleteModalId
,
title
:
'
Delete issue
'
,
});
});
});
});
});
spec/helpers/issues_helper_spec.rb
View file @
b6a019ff
...
@@ -278,11 +278,13 @@ RSpec.describe IssuesHelper do
...
@@ -278,11 +278,13 @@ RSpec.describe IssuesHelper do
it
'returns expected result'
do
it
'returns expected result'
do
expected
=
{
expected
=
{
can_create_issue:
'true'
,
can_create_issue:
'true'
,
can_destroy_issue:
'true'
,
can_reopen_issue:
'true'
,
can_reopen_issue:
'true'
,
can_report_spam:
'false'
,
can_report_spam:
'false'
,
can_update_issue:
'true'
,
can_update_issue:
'true'
,
iid:
issue
.
iid
,
iid:
issue
.
iid
,
is_issue_author:
'false'
,
is_issue_author:
'false'
,
issue_path:
issue_path
(
issue
),
issue_type:
'issue'
,
issue_type:
'issue'
,
new_issue_path:
new_project_issue_path
(
project
,
{
issue:
{
description:
"Related to
\#
#{
issue
.
iid
}
.
\n\n
"
}
}),
new_issue_path:
new_project_issue_path
(
project
,
{
issue:
{
description:
"Related to
\#
#{
issue
.
iid
}
.
\n\n
"
}
}),
project_path:
project
.
full_path
,
project_path:
project
.
full_path
,
...
...
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