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
4e96d701
Commit
4e96d701
authored
Apr 05, 2022
by
Simon Knox
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'psi-delete-work' into 'master'
Delete button for tasks See merge request gitlab-org/gitlab!83353
parents
633f1054
1bcec9f2
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
325 additions
and
5 deletions
+325
-5
app/assets/javascripts/issues/show/components/description.vue
...assets/javascripts/issues/show/components/description.vue
+15
-1
app/assets/javascripts/work_items/components/work_item_actions.vue
...s/javascripts/work_items/components/work_item_actions.vue
+93
-0
app/assets/javascripts/work_items/components/work_item_detail_modal.vue
...ascripts/work_items/components/work_item_detail_modal.vue
+54
-2
app/assets/javascripts/work_items/graphql/delete_work_item.mutation.graphql
...ipts/work_items/graphql/delete_work_item.mutation.graphql
+5
-0
locale/gitlab.pot
locale/gitlab.pot
+12
-0
spec/frontend/issues/show/components/description_spec.js
spec/frontend/issues/show/components/description_spec.js
+12
-0
spec/frontend/work_items/components/work_item_actions_spec.js
.../frontend/work_items/components/work_item_actions_spec.js
+103
-0
spec/frontend/work_items/components/work_item_detail_modal_spec.js
...tend/work_items/components/work_item_detail_modal_spec.js
+15
-2
spec/frontend/work_items/mock_data.js
spec/frontend/work_items/mock_data.js
+16
-0
No files found.
app/assets/javascripts/issues/show/components/description.vue
View file @
4e96d701
<
script
>
import
{
GlSafeHtmlDirective
as
SafeHtml
,
GlModal
,
GlTooltip
,
GlModalDirective
}
from
'
@gitlab/ui
'
;
import
{
GlSafeHtmlDirective
as
SafeHtml
,
GlModal
,
GlToast
,
GlTooltip
,
GlModalDirective
,
}
from
'
@gitlab/ui
'
;
import
$
from
'
jquery
'
;
import
Vue
from
'
vue
'
;
import
{
convertToGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
TYPE_WORK_ITEM
}
from
'
~/graphql_shared/constants
'
;
import
createFlash
from
'
~/flash
'
;
...
...
@@ -14,6 +21,8 @@ import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.
import
CreateWorkItem
from
'
~/work_items/pages/create_work_item.vue
'
;
import
animateMixin
from
'
../mixins/animate
'
;
Vue
.
use
(
GlToast
);
export
default
{
directives
:
{
SafeHtml
,
...
...
@@ -246,6 +255,9 @@ export default {
this
.
$emit
(
'
updateDescription
'
,
description
);
this
.
closeCreateTaskModal
();
},
handleDeleteTask
()
{
this
.
$toast
.
show
(
s__
(
'
WorkItem|Work item deleted
'
));
},
updateWorkItemIdUrlQuery
(
workItemId
)
{
updateHistory
({
url
:
setUrlParams
({
work_item_id
:
workItemId
}),
...
...
@@ -306,8 +318,10 @@ export default {
/>
</gl-modal>
<work-item-detail-modal
:can-update=
"canUpdate"
:visible=
"showWorkItemDetailModal"
:work-item-id=
"workItemId"
@
workItemDeleted=
"handleDeleteTask"
@
close=
"closeWorkItemDetailModal"
/>
<template
v-if=
"workItemsEnabled"
>
...
...
app/assets/javascripts/work_items/components/work_item_actions.vue
0 → 100644
View file @
4e96d701
<
script
>
import
{
GlDropdown
,
GlDropdownItem
,
GlModal
,
GlModalDirective
}
from
'
@gitlab/ui
'
;
import
{
s__
}
from
'
~/locale
'
;
import
deleteWorkItemMutation
from
'
../graphql/delete_work_item.mutation.graphql
'
;
export
default
{
i18n
:
{
deleteWorkItem
:
s__
(
'
WorkItem|Delete work item
'
),
},
components
:
{
GlDropdown
,
GlDropdownItem
,
GlModal
,
},
directives
:
{
GlModal
:
GlModalDirective
,
},
props
:
{
workItemId
:
{
type
:
String
,
required
:
false
,
default
:
null
,
},
canUpdate
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
emits
:
[
'
workItemDeleted
'
,
'
error
'
],
methods
:
{
deleteWorkItem
()
{
this
.
$apollo
.
mutate
({
mutation
:
deleteWorkItemMutation
,
variables
:
{
input
:
{
id
:
this
.
workItemId
,
},
},
})
.
then
(({
data
:
{
workItemDelete
,
errors
}
})
=>
{
if
(
errors
?.
length
)
{
throw
new
Error
(
errors
[
0
].
message
);
}
if
(
workItemDelete
?.
errors
.
length
)
{
throw
new
Error
(
workItemDelete
.
errors
[
0
]);
}
this
.
$emit
(
'
workItemDeleted
'
);
})
.
catch
((
e
)
=>
{
this
.
$emit
(
'
error
'
,
e
.
message
||
s__
(
'
WorkItem|Something went wrong when deleting the work item. Please try again.
'
),
);
});
},
},
};
</
script
>
<
template
>
<div
v-if=
"canUpdate"
>
<gl-dropdown
icon=
"ellipsis_v"
text-sr-only
:text=
"__('More actions')"
category=
"tertiary"
no-caret
right
>
<gl-dropdown-item
v-gl-modal=
"'work-item-confirm-delete'"
>
{{
$options
.
i18n
.
deleteWorkItem
}}
</gl-dropdown-item>
</gl-dropdown>
<gl-modal
modal-id=
"work-item-confirm-delete"
:title=
"$options.i18n.deleteWorkItem"
:ok-title=
"$options.i18n.deleteWorkItem"
ok-variant=
"danger"
@
ok=
"deleteWorkItem"
>
{{
s__
(
'
WorkItem|Are you sure you want to delete the work item? This action cannot be reversed.
'
,
)
}}
</gl-modal>
</div>
</
template
>
app/assets/javascripts/work_items/components/work_item_detail_modal.vue
View file @
4e96d701
<
script
>
import
{
GlModal
}
from
'
@gitlab/ui
'
;
import
{
GlAlert
,
GlButton
,
GlModal
}
from
'
@gitlab/ui
'
;
import
WorkItemActions
from
'
./work_item_actions.vue
'
;
import
WorkItemDetail
from
'
./work_item_detail.vue
'
;
export
default
{
components
:
{
GlAlert
,
GlButton
,
GlModal
,
WorkItemDetail
,
WorkItemActions
,
},
props
:
{
canUpdate
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
visible
:
{
type
:
Boolean
,
required
:
true
,
...
...
@@ -18,11 +27,54 @@ export default {
default
:
null
,
},
},
emits
:
[
'
workItemDeleted
'
,
'
close
'
],
data
()
{
return
{
error
:
undefined
,
};
},
methods
:
{
handleWorkItemDeleted
()
{
this
.
$emit
(
'
workItemDeleted
'
);
this
.
closeModal
();
},
closeModal
()
{
this
.
error
=
''
;
this
.
$emit
(
'
close
'
);
},
setErrorMessage
(
message
)
{
this
.
error
=
message
;
},
},
};
</
script
>
<
template
>
<gl-modal
hide-footer
modal-id=
"work-item-detail-modal"
:visible=
"visible"
@
hide=
"$emit('close')"
>
<gl-modal
hide-footer
modal-id=
"work-item-detail-modal"
:visible=
"visible"
@
hide=
"closeModal"
>
<template
#modal-header
>
<div
class=
"gl-w-full gl-display-flex gl-align-items-center gl-justify-content-end"
>
<work-item-actions
:work-item-id=
"workItemId"
:can-update=
"canUpdate"
@
workItemDeleted=
"handleWorkItemDeleted"
@
error=
"setErrorMessage"
/>
<gl-button
category=
"tertiary"
icon=
"close"
:aria-label=
"__('Close')"
@
click=
"closeModal"
/>
</div>
</
template
>
<gl-alert
v-if=
"error"
variant=
"danger"
@
dismiss=
"error = false"
>
{{ error }}
</gl-alert>
<work-item-detail
:work-item-id=
"workItemId"
/>
</gl-modal>
</template>
<
style
>
/* hide the existing close button until we can do it
* with https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2710
*/
#work-item-detail-modal
.modal-header
>
.gl-button
{
display
:
none
;
}
</
style
>
app/assets/javascripts/work_items/graphql/delete_work_item.mutation.graphql
0 → 100644
View file @
4e96d701
mutation
deleteWorkItem
(
$input
:
WorkItemDeleteInput
!)
{
workItemDelete
(
input
:
$input
)
{
errors
}
}
locale/gitlab.pot
View file @
4e96d701
...
...
@@ -42637,12 +42637,18 @@ msgstr ""
msgid "Work in progress Limit"
msgstr ""
msgid "WorkItem|Are you sure you want to delete the work item? This action cannot be reversed."
msgstr ""
msgid "WorkItem|Convert to work item"
msgstr ""
msgid "WorkItem|Create work item"
msgstr ""
msgid "WorkItem|Delete work item"
msgstr ""
msgid "WorkItem|New Task"
msgstr ""
...
...
@@ -42652,6 +42658,9 @@ msgstr ""
msgid "WorkItem|Something went wrong when creating a work item. Please try again"
msgstr ""
msgid "WorkItem|Something went wrong when deleting the work item. Please try again."
msgstr ""
msgid "WorkItem|Something went wrong when fetching the work item. Please try again."
msgstr ""
...
...
@@ -42667,6 +42676,9 @@ msgstr ""
msgid "WorkItem|Work Items"
msgstr ""
msgid "WorkItem|Work item deleted"
msgstr ""
msgid "Would you like to create a new branch?"
msgstr ""
...
...
spec/frontend/issues/show/components/description_spec.js
View file @
4e96d701
...
...
@@ -27,6 +27,9 @@ jest.mock('~/task_list');
const
showModal
=
jest
.
fn
();
const
hideModal
=
jest
.
fn
();
const
$toast
=
{
show
:
jest
.
fn
(),
};
describe
(
'
Description component
'
,
()
=>
{
let
wrapper
;
...
...
@@ -49,6 +52,9 @@ describe('Description component', () => {
...
props
,
},
provide
,
mocks
:
{
$toast
,
},
stubs
:
{
GlModal
:
stubComponent
(
GlModal
,
{
methods
:
{
...
...
@@ -288,6 +294,12 @@ describe('Description component', () => {
expect
(
hideModal
).
toHaveBeenCalled
();
expect
(
wrapper
.
emitted
(
'
updateDescription
'
)).
toEqual
([[
newDescription
]]);
});
it
(
'
shows toast after delete success
'
,
async
()
=>
{
findWorkItemDetailModal
().
vm
.
$emit
(
'
workItemDeleted
'
);
expect
(
$toast
.
show
).
toHaveBeenCalledWith
(
'
Work item deleted
'
);
});
});
describe
(
'
work items detail
'
,
()
=>
{
...
...
spec/frontend/work_items/components/work_item_actions_spec.js
0 → 100644
View file @
4e96d701
import
{
GlDropdownItem
,
GlModal
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vue
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
WorkItemActions
from
'
~/work_items/components/work_item_actions.vue
'
;
import
deleteWorkItem
from
'
~/work_items/graphql/delete_work_item.mutation.graphql
'
;
import
{
deleteWorkItemResponse
,
deleteWorkItemFailureResponse
}
from
'
../mock_data
'
;
describe
(
'
WorkItemActions component
'
,
()
=>
{
let
wrapper
;
let
glModalDirective
;
Vue
.
use
(
VueApollo
);
const
findModal
=
()
=>
wrapper
.
findComponent
(
GlModal
);
const
findDeleteButton
=
()
=>
wrapper
.
findComponent
(
GlDropdownItem
);
const
createComponent
=
({
canUpdate
=
true
,
deleteWorkItemHandler
=
jest
.
fn
().
mockResolvedValue
(
deleteWorkItemResponse
),
}
=
{})
=>
{
glModalDirective
=
jest
.
fn
();
wrapper
=
shallowMount
(
WorkItemActions
,
{
apolloProvider
:
createMockApollo
([[
deleteWorkItem
,
deleteWorkItemHandler
]]),
propsData
:
{
workItemId
:
'
123
'
,
canUpdate
},
directives
:
{
glModal
:
{
bind
(
_
,
{
value
})
{
glModalDirective
(
value
);
},
},
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
renders modal
'
,
()
=>
{
createComponent
();
expect
(
findModal
().
exists
()).
toBe
(
true
);
expect
(
findModal
().
props
(
'
visible
'
)).
toBe
(
false
);
});
it
(
'
shows confirm modal when clicking Delete work item
'
,
()
=>
{
createComponent
();
findDeleteButton
().
vm
.
$emit
(
'
click
'
);
expect
(
glModalDirective
).
toHaveBeenCalled
();
});
it
(
'
calls delete mutation when clicking OK button
'
,
()
=>
{
const
deleteWorkItemHandler
=
jest
.
fn
().
mockResolvedValue
(
deleteWorkItemResponse
);
createComponent
({
deleteWorkItemHandler
,
});
findModal
().
vm
.
$emit
(
'
ok
'
);
expect
(
deleteWorkItemHandler
).
toHaveBeenCalled
();
expect
(
wrapper
.
emitted
(
'
error
'
)).
toBeUndefined
();
});
it
(
'
emits event after delete success
'
,
async
()
=>
{
createComponent
();
findModal
().
vm
.
$emit
(
'
ok
'
);
await
waitForPromises
();
expect
(
wrapper
.
emitted
(
'
workItemDeleted
'
)).
not
.
toBeUndefined
();
expect
(
wrapper
.
emitted
(
'
error
'
)).
toBeUndefined
();
});
it
(
'
emits error event after delete failure
'
,
async
()
=>
{
createComponent
({
deleteWorkItemHandler
:
jest
.
fn
().
mockResolvedValue
(
deleteWorkItemFailureResponse
),
});
findModal
().
vm
.
$emit
(
'
ok
'
);
await
waitForPromises
();
expect
(
wrapper
.
emitted
(
'
error
'
)[
0
]).
toEqual
([
"
The resource that you are attempting to access does not exist or you don't have permission to perform this action
"
,
]);
expect
(
wrapper
.
emitted
(
'
workItemDeleted
'
)).
toBeUndefined
();
});
it
(
'
does not render when canUpdate is false
'
,
()
=>
{
createComponent
({
canUpdate
:
false
,
});
expect
(
wrapper
.
html
()).
toBe
(
''
);
});
});
spec/frontend/work_items/components/work_item_detail_modal_spec.js
View file @
4e96d701
...
...
@@ -4,6 +4,7 @@ import Vue from 'vue';
import
VueApollo
from
'
vue-apollo
'
;
import
WorkItemDetail
from
'
~/work_items/components/work_item_detail.vue
'
;
import
WorkItemDetailModal
from
'
~/work_items/components/work_item_detail_modal.vue
'
;
import
WorkItemActions
from
'
~/work_items/components/work_item_actions.vue
'
;
describe
(
'
WorkItemDetailModal component
'
,
()
=>
{
let
wrapper
;
...
...
@@ -11,11 +12,15 @@ describe('WorkItemDetailModal component', () => {
Vue
.
use
(
VueApollo
);
const
findModal
=
()
=>
wrapper
.
findComponent
(
GlModal
);
const
findWorkItemActions
=
()
=>
wrapper
.
findComponent
(
WorkItemActions
);
const
findWorkItemDetail
=
()
=>
wrapper
.
findComponent
(
WorkItemDetail
);
const
createComponent
=
({
visible
=
true
,
workItemId
=
'
1
'
}
=
{})
=>
{
const
createComponent
=
({
visible
=
true
,
workItemId
=
'
1
'
,
canUpdate
=
false
}
=
{})
=>
{
wrapper
=
shallowMount
(
WorkItemDetailModal
,
{
propsData
:
{
visible
,
workItemId
},
propsData
:
{
visible
,
workItemId
,
canUpdate
},
stubs
:
{
GlModal
,
},
});
};
...
...
@@ -36,4 +41,12 @@ describe('WorkItemDetailModal component', () => {
expect
(
findWorkItemDetail
().
props
()).
toEqual
({
workItemId
:
'
1
'
});
});
it
(
'
shows work item actions
'
,
()
=>
{
createComponent
({
canUpdate
:
true
,
});
expect
(
findWorkItemActions
().
exists
()).
toBe
(
true
);
});
});
spec/frontend/work_items/mock_data.js
View file @
4e96d701
...
...
@@ -77,6 +77,22 @@ export const createWorkItemFromTaskMutationResponse = {
},
};
export
const
deleteWorkItemResponse
=
{
data
:
{
workItemDelete
:
{
errors
:
[],
__typename
:
'
WorkItemDeletePayload
'
}
},
};
export
const
deleteWorkItemFailureResponse
=
{
data
:
{
workItemDelete
:
null
},
errors
:
[
{
message
:
"
The resource that you are attempting to access does not exist or you don't have permission to perform this action
"
,
locations
:
[{
line
:
2
,
column
:
3
}],
path
:
[
'
workItemDelete
'
],
},
],
};
export
const
workItemTitleSubscriptionResponse
=
{
data
:
{
issuableTitleUpdated
:
{
...
...
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