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
f606e491
Commit
f606e491
authored
Jan 14, 2021
by
Tristan Read
Committed by
Olena Horal-Koretska
Jan 14, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add delete metric image API endpoint
- Add changelog file
parent
846e04c8
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
370 additions
and
19 deletions
+370
-19
changelogs/unreleased/tr-metric-image-delete.yml
changelogs/unreleased/tr-metric-image-delete.yml
+5
-0
doc/api/issues.md
doc/api/issues.md
+23
-0
ee/app/assets/javascripts/api.js
ee/app/assets/javascripts/api.js
+11
-0
ee/app/assets/javascripts/issue_show/components/incidents/metrics_image.vue
...scripts/issue_show/components/incidents/metrics_image.vue
+62
-2
ee/app/assets/javascripts/issue_show/components/incidents/metrics_tab.vue
...vascripts/issue_show/components/incidents/metrics_tab.vue
+1
-1
ee/app/assets/javascripts/issue_show/components/incidents/service.js
...ts/javascripts/issue_show/components/incidents/service.js
+5
-0
ee/app/assets/javascripts/issue_show/components/incidents/store/actions.js
...ascripts/issue_show/components/incidents/store/actions.js
+12
-1
ee/app/assets/javascripts/issue_show/components/incidents/store/mutation_types.js
...s/issue_show/components/incidents/store/mutation_types.js
+2
-0
ee/app/assets/javascripts/issue_show/components/incidents/store/mutations.js
...cripts/issue_show/components/incidents/store/mutations.js
+4
-0
ee/lib/ee/api/issues.rb
ee/lib/ee/api/issues.rb
+19
-0
ee/spec/frontend/issue_show/components/incidents/__snapshots__/metrics_image_spec.js.snap
...onents/incidents/__snapshots__/metrics_image_spec.js.snap
+16
-1
ee/spec/frontend/issue_show/components/incidents/metrics_image_spec.js
...end/issue_show/components/incidents/metrics_image_spec.js
+86
-10
ee/spec/frontend/issue_show/components/incidents/metrics_tab_spec.js
...ntend/issue_show/components/incidents/metrics_tab_spec.js
+1
-1
ee/spec/frontend/issue_show/components/incidents/store/actions_spec.js
...end/issue_show/components/incidents/store/actions_spec.js
+21
-1
ee/spec/frontend/issue_show/components/incidents/store/mutation_spec.js
...nd/issue_show/components/incidents/store/mutation_spec.js
+17
-2
ee/spec/requests/api/issues_spec.rb
ee/spec/requests/api/issues_spec.rb
+76
-0
locale/gitlab.pot
locale/gitlab.pot
+9
-0
No files found.
changelogs/unreleased/tr-metric-image-delete.yml
0 → 100644
View file @
f606e491
---
title
:
Add delete metric image REST API endpoint
merge_request
:
50043
author
:
type
:
added
doc/api/issues.md
View file @
f606e491
...
@@ -2211,3 +2211,26 @@ Example response:
...
@@ -2211,3 +2211,26 @@ Example response:
}
}
]
]
```
```
## Delete metric image
Available only for Incident issues.
```
plaintext
DELETE /projects/:id/issues/:issue_iid/metric_images/:image_id
```
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
owned by the authenticated user |
|
`issue_iid`
| integer | yes | The internal ID of a project's issue |
|
`image_id`
| integer | yes | The ID of the image |
```
shell
curl
--header
"PRIVATE-TOKEN: <your_access_token>"
--request
DELETE
"https://gitlab.example.com/api/v4/projects/5/issues/93/metric_images/1"
```
Can return the following status codes:
-
`204 No Content`
, if the image was deleted successfully.
-
`400 Bad Request`
, if the image could not be deleted.
ee/app/assets/javascripts/api.js
View file @
f606e491
...
@@ -46,6 +46,8 @@ export default {
...
@@ -46,6 +46,8 @@ export default {
projectDeploymentFrequencyAnalyticsPath
:
projectDeploymentFrequencyAnalyticsPath
:
'
/api/:version/projects/:id/analytics/deployment_frequency
'
,
'
/api/:version/projects/:id/analytics/deployment_frequency
'
,
issueMetricImagesPath
:
'
/api/:version/projects/:id/issues/:issue_iid/metric_images
'
,
issueMetricImagesPath
:
'
/api/:version/projects/:id/issues/:issue_iid/metric_images
'
,
issueMetricSingleImagePath
:
'
/api/:version/projects/:id/issues/:issue_iid/metric_images/:image_id
'
,
userSubscription
(
namespaceId
)
{
userSubscription
(
namespaceId
)
{
const
url
=
Api
.
buildUrl
(
this
.
subscriptionPath
).
replace
(
'
:id
'
,
encodeURIComponent
(
namespaceId
));
const
url
=
Api
.
buildUrl
(
this
.
subscriptionPath
).
replace
(
'
:id
'
,
encodeURIComponent
(
namespaceId
));
...
@@ -367,4 +369,13 @@ export default {
...
@@ -367,4 +369,13 @@ export default {
return
axios
.
post
(
metricImagesUrl
,
formData
,
options
);
return
axios
.
post
(
metricImagesUrl
,
formData
,
options
);
},
},
deleteMetricImage
({
issueIid
,
id
,
imageId
})
{
const
individualMetricImageUrl
=
Api
.
buildUrl
(
this
.
issueMetricSingleImagePath
)
.
replace
(
'
:id
'
,
encodeURIComponent
(
id
))
.
replace
(
'
:issue_iid
'
,
encodeURIComponent
(
issueIid
))
.
replace
(
'
:image_id
'
,
encodeURIComponent
(
imageId
));
return
axios
.
delete
(
individualMetricImageUrl
);
},
};
};
ee/app/assets/javascripts/issue_show/components/incidents/metrics_image.vue
View file @
f606e491
<
script
>
<
script
>
import
{
GlButton
,
GlCard
,
GlIcon
,
GlLink
}
from
'
@gitlab/ui
'
;
import
{
GlButton
,
GlCard
,
GlIcon
,
GlLink
,
GlModal
,
GlSprintf
}
from
'
@gitlab/ui
'
;
import
{
mapActions
}
from
'
vuex
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
export
default
{
export
default
{
i18n
:
{
modalDelete
:
__
(
'
Delete
'
),
modalDescription
:
s__
(
'
Incident|Are you sure you wish to delete this image?
'
),
modalCancel
:
__
(
'
Cancel
'
),
modalTitle
:
s__
(
'
Incident|Deleting %{filename}
'
),
},
components
:
{
components
:
{
GlButton
,
GlButton
,
GlCard
,
GlCard
,
GlIcon
,
GlIcon
,
GlLink
,
GlLink
,
GlModal
,
GlSprintf
,
},
},
inject
:
[
'
canUpdate
'
],
props
:
{
props
:
{
id
:
{
id
:
{
type
:
Number
,
type
:
Number
,
...
@@ -30,9 +41,22 @@ export default {
...
@@ -30,9 +41,22 @@ export default {
data
()
{
data
()
{
return
{
return
{
isCollapsed
:
false
,
isCollapsed
:
false
,
isDeleting
:
false
,
modalVisible
:
false
,
};
};
},
},
computed
:
{
computed
:
{
actionPrimaryProps
()
{
return
{
text
:
this
.
$options
.
i18n
.
modalDelete
,
attributes
:
{
loading
:
this
.
isDeleting
,
disabled
:
this
.
isDeleting
,
category
:
'
primary
'
,
variant
:
'
danger
'
,
},
};
},
arrowIconName
()
{
arrowIconName
()
{
return
this
.
isCollapsed
?
'
chevron-right
'
:
'
chevron-down
'
;
return
this
.
isCollapsed
?
'
chevron-right
'
:
'
chevron-down
'
;
},
},
...
@@ -46,9 +70,19 @@ export default {
...
@@ -46,9 +70,19 @@ export default {
},
},
},
},
methods
:
{
methods
:
{
...
mapActions
([
'
deleteImage
'
]),
toggleCollapsed
()
{
toggleCollapsed
()
{
this
.
isCollapsed
=
!
this
.
isCollapsed
;
this
.
isCollapsed
=
!
this
.
isCollapsed
;
},
},
async
onDelete
()
{
try
{
this
.
isDeleting
=
true
;
await
this
.
deleteImage
(
this
.
id
);
}
finally
{
this
.
isDeleting
=
false
;
this
.
modalVisible
=
false
;
}
},
},
},
};
};
</
script
>
</
script
>
...
@@ -59,9 +93,28 @@ export default {
...
@@ -59,9 +93,28 @@ export default {
header-class=
"gl-display-flex gl-align-items-center gl-border-b-0 gl-py-3"
header-class=
"gl-display-flex gl-align-items-center gl-border-b-0 gl-py-3"
:body-class=
"bodyClass"
:body-class=
"bodyClass"
>
>
<gl-modal
body-class=
"gl-pb-0! gl-min-h-6!"
modal-id=
"delete-metric-modal"
size=
"sm"
:visible=
"modalVisible"
:action-primary=
"actionPrimaryProps"
:action-cancel=
"
{ text: $options.i18n.modalCancel }"
@primary.prevent="onDelete"
@hidden="modalVisible = false"
>
<template
#modal-title
>
<gl-sprintf
:message=
"$options.i18n.modalTitle"
>
<template
#filename
>
{{
filename
}}
</
template
>
</gl-sprintf>
</template>
<p>
{{ $options.i18n.modalDescription }}
</p>
</gl-modal>
<
template
#header
>
<
template
#header
>
<div
class=
"gl-w-full gl-display-flex gl-flex-direction-row gl-justify-content-space-between"
>
<div
class=
"gl-w-full gl-display-flex gl-flex-direction-row gl-justify-content-space-between"
>
<div
class=
"gl-display-flex gl-flex-direction-row"
>
<div
class=
"gl-display-flex gl-flex-direction-row
gl-align-items-center gl-w-full
"
>
<gl-button
<gl-button
class=
"collapsible-card-btn gl-display-flex gl-text-decoration-none gl-reset-color! gl-hover-text-blue-800! gl-shadow-none!"
class=
"collapsible-card-btn gl-display-flex gl-text-decoration-none gl-reset-color! gl-hover-text-blue-800! gl-shadow-none!"
:aria-label=
"filename"
:aria-label=
"filename"
...
@@ -76,6 +129,13 @@ export default {
...
@@ -76,6 +129,13 @@ export default {
{{
filename
}}
{{
filename
}}
</gl-link>
</gl-link>
<span
v-else
>
{{
filename
}}
</span>
<span
v-else
>
{{
filename
}}
</span>
<gl-button
v-if=
"canUpdate"
class=
"gl-ml-auto"
icon=
"remove"
data-testid=
"delete-button"
@
click=
"modalVisible = true"
/>
</div>
</div>
</div>
</div>
</
template
>
</
template
>
...
...
ee/app/assets/javascripts/issue_show/components/incidents/metrics_tab.vue
View file @
f606e491
...
@@ -89,7 +89,7 @@ export default {
...
@@ -89,7 +89,7 @@ export default {
:action-cancel=
"
{ text: $options.i18n.modalCancel }"
:action-cancel=
"
{ text: $options.i18n.modalCancel }"
:title="$options.i18n.modalTitle"
:title="$options.i18n.modalTitle"
:visible="modalVisible"
:visible="modalVisible"
@
canceled
="clearInputs"
@
hidden
="clearInputs"
@primary.prevent="onUpload"
@primary.prevent="onUpload"
>
>
<p>
{{
$options
.
i18n
.
modalDescription
}}
</p>
<p>
{{
$options
.
i18n
.
modalDescription
}}
</p>
...
...
ee/app/assets/javascripts/issue_show/components/incidents/service.js
View file @
f606e491
...
@@ -10,3 +10,8 @@ export const uploadMetricImage = async (payload) => {
...
@@ -10,3 +10,8 @@ export const uploadMetricImage = async (payload) => {
const
response
=
await
Api
.
uploadIssueMetricImage
(
payload
);
const
response
=
await
Api
.
uploadIssueMetricImage
(
payload
);
return
convertObjectPropsToCamelCase
(
response
.
data
);
return
convertObjectPropsToCamelCase
(
response
.
data
);
};
};
export
const
deleteMetricImage
=
async
(
payload
)
=>
{
const
response
=
await
Api
.
deleteMetricImage
(
payload
);
return
convertObjectPropsToCamelCase
(
response
.
data
);
};
ee/app/assets/javascripts/issue_show/components/incidents/store/actions.js
View file @
f606e491
import
{
s__
}
from
'
~/locale
'
;
import
{
s__
}
from
'
~/locale
'
;
import
createFlash
from
'
~/flash
'
;
import
createFlash
from
'
~/flash
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
{
getMetricImages
,
uploadMetricImage
}
from
'
../service
'
;
import
{
deleteMetricImage
,
getMetricImages
,
uploadMetricImage
}
from
'
../service
'
;
export
const
fetchMetricImages
=
async
({
state
,
commit
})
=>
{
export
const
fetchMetricImages
=
async
({
state
,
commit
})
=>
{
commit
(
types
.
REQUEST_METRIC_IMAGES
);
commit
(
types
.
REQUEST_METRIC_IMAGES
);
...
@@ -31,6 +31,17 @@ export const uploadImage = async ({ state, commit }, { files, url }) => {
...
@@ -31,6 +31,17 @@ export const uploadImage = async ({ state, commit }, { files, url }) => {
}
}
};
};
export
const
deleteImage
=
async
({
state
,
commit
},
imageId
)
=>
{
const
{
issueIid
,
projectId
}
=
state
;
try
{
await
deleteMetricImage
({
imageId
,
id
:
projectId
,
issueIid
});
commit
(
types
.
RECEIVE_METRIC_DELETE_SUCCESS
,
imageId
);
}
catch
(
error
)
{
createFlash
({
message
:
s__
(
'
Incidents|There was an issue deleting the image.
'
)
});
}
};
export
const
setInitialData
=
({
commit
},
data
)
=>
{
export
const
setInitialData
=
({
commit
},
data
)
=>
{
commit
(
types
.
SET_INITIAL_DATA
,
data
);
commit
(
types
.
SET_INITIAL_DATA
,
data
);
};
};
ee/app/assets/javascripts/issue_show/components/incidents/store/mutation_types.js
View file @
f606e491
...
@@ -6,4 +6,6 @@ export const REQUEST_METRIC_UPLOAD = 'REQUEST_METRIC_UPLOAD';
...
@@ -6,4 +6,6 @@ export const REQUEST_METRIC_UPLOAD = 'REQUEST_METRIC_UPLOAD';
export
const
RECEIVE_METRIC_UPLOAD_SUCCESS
=
'
RECEIVE_METRIC_UPLOAD_SUCCESS
'
;
export
const
RECEIVE_METRIC_UPLOAD_SUCCESS
=
'
RECEIVE_METRIC_UPLOAD_SUCCESS
'
;
export
const
RECEIVE_METRIC_UPLOAD_ERROR
=
'
RECEIVE_METRIC_UPLOAD_ERROR
'
;
export
const
RECEIVE_METRIC_UPLOAD_ERROR
=
'
RECEIVE_METRIC_UPLOAD_ERROR
'
;
export
const
RECEIVE_METRIC_DELETE_SUCCESS
=
'
RECEIVE_METRIC_DELETE_SUCCESS
'
;
export
const
SET_INITIAL_DATA
=
'
SET_INITIAL_DATA
'
;
export
const
SET_INITIAL_DATA
=
'
SET_INITIAL_DATA
'
;
ee/app/assets/javascripts/issue_show/components/incidents/store/mutations.js
View file @
f606e491
...
@@ -21,6 +21,10 @@ export default {
...
@@ -21,6 +21,10 @@ export default {
[
types
.
RECEIVE_METRIC_UPLOAD_ERROR
](
state
)
{
[
types
.
RECEIVE_METRIC_UPLOAD_ERROR
](
state
)
{
state
.
isUploadingImage
=
false
;
state
.
isUploadingImage
=
false
;
},
},
[
types
.
RECEIVE_METRIC_DELETE_SUCCESS
](
state
,
imageId
)
{
const
metricIndex
=
state
.
metricImages
.
findIndex
((
image
)
=>
image
.
id
===
imageId
);
state
.
metricImages
.
splice
(
metricIndex
,
1
);
},
[
types
.
SET_INITIAL_DATA
](
state
,
{
issueIid
,
projectId
})
{
[
types
.
SET_INITIAL_DATA
](
state
,
{
issueIid
,
projectId
})
{
state
.
issueIid
=
issueIid
;
state
.
issueIid
=
issueIid
;
state
.
projectId
=
projectId
;
state
.
projectId
=
projectId
;
...
...
ee/lib/ee/api/issues.rb
View file @
f606e491
...
@@ -70,6 +70,25 @@ module EE
...
@@ -70,6 +70,25 @@ module EE
render_api_error!
(
'Issue not found'
,
404
)
render_api_error!
(
'Issue not found'
,
404
)
end
end
end
end
desc
'Remove a metric image for an issue'
do
success
Entities
::
IssuableMetricImage
end
params
do
requires
:metric_image_id
,
type:
Integer
,
desc:
'The ID of metric image'
end
delete
':metric_image_id'
do
issue
=
find_project_issue
(
params
[
:issue_iid
])
metric_image
=
issue
.
metric_images
.
find_by_id
(
params
[
:metric_image_id
])
render_api_error!
(
'Metric image not found'
,
404
)
unless
metric_image
if
metric_image
&
.
destroy
no_content!
else
render_api_error!
(
'Metric image could not be deleted'
,
400
)
end
end
end
end
end
end
...
...
ee/spec/frontend/issue_show/components/incidents/__snapshots__/metrics_image_spec.js.snap
View file @
f606e491
...
@@ -7,7 +7,22 @@ exports[`Metrics upload item render the metrics image component 1`] = `
...
@@ -7,7 +7,22 @@ exports[`Metrics upload item render the metrics image component 1`] = `
footerclass=""
footerclass=""
headerclass="gl-display-flex gl-align-items-center gl-border-b-0 gl-py-3"
headerclass="gl-display-flex gl-align-items-center gl-border-b-0 gl-py-3"
>
>
<gl-modal-stub
actioncancel="[object Object]"
actionprimary="[object Object]"
body-class="gl-pb-0! gl-min-h-6!"
dismisslabel="Close"
modalclass=""
modalid="delete-metric-modal"
size="sm"
titletag="h4"
>
<p>
Are you sure you wish to delete this image?
</p>
</gl-modal-stub>
<div
<div
class="gl-display-flex gl-flex-direction-column"
class="gl-display-flex gl-flex-direction-column"
data-testid="metric-image-body"
data-testid="metric-image-body"
...
...
ee/spec/frontend/issue_show/components/incidents/metrics_image_spec.js
View file @
f606e491
import
{
shallowMount
,
mount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
{
GlLink
}
from
'
@gitlab/ui
'
;
import
{
createLocalVue
,
shallowMount
,
mount
}
from
'
@vue/test-utils
'
;
import
{
GlLink
,
GlModal
}
from
'
@gitlab/ui
'
;
import
merge
from
'
lodash/merge
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
createStore
from
'
ee/issue_show/components/incidents/store
'
;
import
MetricsImage
from
'
ee/issue_show/components/incidents/metrics_image.vue
'
;
import
MetricsImage
from
'
ee/issue_show/components/incidents/metrics_image.vue
'
;
const
defaultProps
=
{
const
defaultProps
=
{
...
@@ -9,16 +12,32 @@ const defaultProps = {
...
@@ -9,16 +12,32 @@ const defaultProps = {
filename
:
'
test_file_name
'
,
filename
:
'
test_file_name
'
,
};
};
const
mockEvent
=
{
preventDefault
:
jest
.
fn
()
};
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
Metrics upload item
'
,
()
=>
{
describe
(
'
Metrics upload item
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
let
store
;
const
mountComponent
=
(
propsData
=
{},
mountMethod
=
mount
)
=>
{
const
mountComponent
=
(
options
=
{},
mountMethod
=
mount
)
=>
{
wrapper
=
mountMethod
(
MetricsImage
,
{
store
=
createStore
();
propsData
:
{
...
defaultProps
,
wrapper
=
mountMethod
(
...
propsData
,
MetricsImage
,
},
merge
(
});
{
localVue
,
store
,
propsData
:
{
...
defaultProps
,
},
provide
:
{
canUpdate
:
true
},
},
options
,
),
);
};
};
afterEach
(()
=>
{
afterEach
(()
=>
{
...
@@ -31,6 +50,12 @@ describe('Metrics upload item', () => {
...
@@ -31,6 +50,12 @@ describe('Metrics upload item', () => {
const
findImageLink
=
()
=>
wrapper
.
find
(
GlLink
);
const
findImageLink
=
()
=>
wrapper
.
find
(
GlLink
);
const
findCollapseButton
=
()
=>
wrapper
.
find
(
'
[data-testid="collapse-button"]
'
);
const
findCollapseButton
=
()
=>
wrapper
.
find
(
'
[data-testid="collapse-button"]
'
);
const
findMetricImageBody
=
()
=>
wrapper
.
find
(
'
[data-testid="metric-image-body"]
'
);
const
findMetricImageBody
=
()
=>
wrapper
.
find
(
'
[data-testid="metric-image-body"]
'
);
const
findModal
=
()
=>
wrapper
.
find
(
GlModal
);
const
findDeleteButton
=
()
=>
wrapper
.
find
(
'
[data-testid="delete-button"]
'
);
const
closeModal
=
()
=>
findModal
().
vm
.
$emit
(
'
hidden
'
);
const
submitModal
=
()
=>
findModal
().
vm
.
$emit
(
'
primary
'
,
mockEvent
);
const
deleteImage
=
()
=>
findDeleteButton
().
vm
.
$emit
(
'
click
'
);
it
(
'
render the metrics image component
'
,
()
=>
{
it
(
'
render the metrics image component
'
,
()
=>
{
mountComponent
({},
shallowMount
);
mountComponent
({},
shallowMount
);
...
@@ -40,7 +65,7 @@ describe('Metrics upload item', () => {
...
@@ -40,7 +65,7 @@ describe('Metrics upload item', () => {
it
(
'
shows a link with the correct url
'
,
()
=>
{
it
(
'
shows a link with the correct url
'
,
()
=>
{
const
testUrl
=
'
test_url
'
;
const
testUrl
=
'
test_url
'
;
mountComponent
({
url
:
testUrl
});
mountComponent
({
propsData
:
{
url
:
testUrl
}
});
expect
(
findImageLink
().
attributes
(
'
href
'
)).
toBe
(
testUrl
);
expect
(
findImageLink
().
attributes
(
'
href
'
)).
toBe
(
testUrl
);
expect
(
findImageLink
().
text
()).
toBe
(
defaultProps
.
filename
);
expect
(
findImageLink
().
text
()).
toBe
(
defaultProps
.
filename
);
...
@@ -63,4 +88,55 @@ describe('Metrics upload item', () => {
...
@@ -63,4 +88,55 @@ describe('Metrics upload item', () => {
expect
(
findMetricImageBody
().
isVisible
()).
toBe
(
false
);
expect
(
findMetricImageBody
().
isVisible
()).
toBe
(
false
);
});
});
});
});
describe
(
'
delete functionality
'
,
()
=>
{
it
(
'
should open the modal when clicked
'
,
async
()
=>
{
mountComponent
({
stubs
:
{
GlModal
:
true
}
});
deleteImage
();
await
waitForPromises
();
expect
(
findModal
().
attributes
(
'
visible
'
)).
toBe
(
'
true
'
);
});
describe
(
'
when the modal is open
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
(
{
data
()
{
return
{
modalVisible
:
true
};
},
},
shallowMount
,
);
});
it
(
'
should close the modal when cancelled
'
,
async
()
=>
{
closeModal
();
await
waitForPromises
();
expect
(
findModal
().
attributes
(
'
visible
'
)).
toBeFalsy
();
});
it
(
'
should delete the image when selected
'
,
async
()
=>
{
const
dispatchSpy
=
jest
.
spyOn
(
store
,
'
dispatch
'
).
mockImplementation
(
jest
.
fn
());
submitModal
();
await
waitForPromises
();
expect
(
dispatchSpy
).
toHaveBeenCalledWith
(
'
deleteImage
'
,
defaultProps
.
id
);
});
});
describe
(
'
canUpdate permission
'
,
()
=>
{
it
(
'
delete button is hidden when user lacks update permissions
'
,
()
=>
{
mountComponent
({
provide
:
{
canUpdate
:
false
}
});
expect
(
findDeleteButton
().
exists
()).
toBe
(
false
);
});
});
});
});
});
ee/spec/frontend/issue_show/components/incidents/metrics_tab_spec.js
View file @
f606e491
...
@@ -58,7 +58,7 @@ describe('Metrics tab', () => {
...
@@ -58,7 +58,7 @@ describe('Metrics tab', () => {
const
findImages
=
()
=>
wrapper
.
findAll
(
MetricsImage
);
const
findImages
=
()
=>
wrapper
.
findAll
(
MetricsImage
);
const
findModal
=
()
=>
wrapper
.
find
(
GlModal
);
const
findModal
=
()
=>
wrapper
.
find
(
GlModal
);
const
submitModal
=
()
=>
findModal
().
vm
.
$emit
(
'
primary
'
,
mockEvent
);
const
submitModal
=
()
=>
findModal
().
vm
.
$emit
(
'
primary
'
,
mockEvent
);
const
cancelModal
=
()
=>
findModal
().
vm
.
$emit
(
'
canceled
'
);
const
cancelModal
=
()
=>
findModal
().
vm
.
$emit
(
'
hidden
'
);
describe
(
'
empty state
'
,
()
=>
{
describe
(
'
empty state
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
...
...
ee/spec/frontend/issue_show/components/incidents/store/actions_spec.js
View file @
f606e491
...
@@ -4,7 +4,11 @@ import testAction from 'helpers/vuex_action_helper';
...
@@ -4,7 +4,11 @@ import testAction from 'helpers/vuex_action_helper';
import
createStore
from
'
ee/issue_show/components/incidents/store
'
;
import
createStore
from
'
ee/issue_show/components/incidents/store
'
;
import
*
as
actions
from
'
ee/issue_show/components/incidents/store/actions
'
;
import
*
as
actions
from
'
ee/issue_show/components/incidents/store/actions
'
;
import
*
as
types
from
'
ee/issue_show/components/incidents/store/mutation_types
'
;
import
*
as
types
from
'
ee/issue_show/components/incidents/store/mutation_types
'
;
import
{
getMetricImages
,
uploadMetricImage
}
from
'
ee/issue_show/components/incidents/service
'
;
import
{
getMetricImages
,
uploadMetricImage
,
deleteMetricImage
,
}
from
'
ee/issue_show/components/incidents/service
'
;
import
createFlash
from
'
~/flash
'
;
import
createFlash
from
'
~/flash
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
fileList
,
initialData
}
from
'
../mock_data
'
;
import
{
fileList
,
initialData
}
from
'
../mock_data
'
;
...
@@ -13,6 +17,7 @@ jest.mock('~/flash');
...
@@ -13,6 +17,7 @@ jest.mock('~/flash');
jest
.
mock
(
'
ee/issue_show/components/incidents/service
'
,
()
=>
({
jest
.
mock
(
'
ee/issue_show/components/incidents/service
'
,
()
=>
({
getMetricImages
:
jest
.
fn
(),
getMetricImages
:
jest
.
fn
(),
uploadMetricImage
:
jest
.
fn
(),
uploadMetricImage
:
jest
.
fn
(),
deleteMetricImage
:
jest
.
fn
(),
}));
}));
const
defaultState
=
{
const
defaultState
=
{
...
@@ -100,6 +105,21 @@ describe('Metrics tab store actions', () => {
...
@@ -100,6 +105,21 @@ describe('Metrics tab store actions', () => {
});
});
});
});
describe
(
'
deleting a metric image
'
,
()
=>
{
const
payload
=
fileList
[
0
].
id
;
it
(
'
should call success action when deleting an image
'
,
()
=>
{
deleteMetricImage
.
mockImplementation
(()
=>
Promise
.
resolve
());
testAction
(
actions
.
deleteImage
,
payload
,
state
,
[
{
type
:
types
.
RECEIVE_METRIC_DELETE_SUCCESS
,
payload
,
},
]);
});
});
describe
(
'
initial data
'
,
()
=>
{
describe
(
'
initial data
'
,
()
=>
{
it
(
'
should set the initial data correctly
'
,
()
=>
{
it
(
'
should set the initial data correctly
'
,
()
=>
{
testAction
(
actions
.
setInitialData
,
initialData
,
state
,
[
testAction
(
actions
.
setInitialData
,
initialData
,
state
,
[
...
...
ee/spec/frontend/issue_show/components/incidents/store/mutation_spec.js
View file @
f606e491
...
@@ -10,8 +10,9 @@ const defaultState = {
...
@@ -10,8 +10,9 @@ const defaultState = {
};
};
const
testImages
=
[
const
testImages
=
[
{
filename
:
'
test.filename
'
,
id
:
0
,
filePath
:
'
test/file/path
'
,
url
:
null
},
{
filename
:
'
test.filename
'
,
id
:
5
,
filePath
:
'
test/file/path
'
,
url
:
null
},
{
filename
:
'
second.filename
'
,
id
:
1
,
filePath
:
'
second/file/path
'
,
url
:
'
test/url
'
},
{
filename
:
'
second.filename
'
,
id
:
6
,
filePath
:
'
second/file/path
'
,
url
:
'
test/url
'
},
{
filename
:
'
third.filename
'
,
id
:
7
,
filePath
:
'
third/file/path
'
,
url
:
'
test/url
'
},
];
];
describe
(
'
Metric images mutations
'
,
()
=>
{
describe
(
'
Metric images mutations
'
,
()
=>
{
...
@@ -100,6 +101,20 @@ describe('Metric images mutations', () => {
...
@@ -100,6 +101,20 @@ describe('Metric images mutations', () => {
});
});
});
});
describe
(
'
RECEIVE_METRIC_DELETE_SUCCESS
'
,
()
=>
{
const
deletedImageId
=
testImages
[
1
].
id
;
const
expectedResult
=
[
testImages
[
0
],
testImages
[
2
]];
beforeEach
(()
=>
{
createState
({
metricImages
:
[...
testImages
]
});
mutations
[
types
.
RECEIVE_METRIC_DELETE_SUCCESS
](
state
,
deletedImageId
);
});
it
(
'
should remove the correct metric image
'
,
()
=>
{
expect
(
state
.
metricImages
).
toEqual
(
expectedResult
);
});
});
describe
(
'
SET_INITIAL_DATA
'
,
()
=>
{
describe
(
'
SET_INITIAL_DATA
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
mutations
[
types
.
SET_INITIAL_DATA
](
state
,
initialData
);
mutations
[
types
.
SET_INITIAL_DATA
](
state
,
initialData
);
...
...
ee/spec/requests/api/issues_spec.rb
View file @
f606e491
...
@@ -696,6 +696,82 @@ RSpec.describe API::Issues, :mailer do
...
@@ -696,6 +696,82 @@ RSpec.describe API::Issues, :mailer do
end
end
end
end
describe
'DELETE /projects/:id/issues/:issue_id/metric_images/:metric_image_id'
do
using
RSpec
::
Parameterized
::
TableSyntax
let_it_be
(
:project
)
do
create
(
:project
,
:private
,
creator_id:
user
.
id
,
namespace:
user
.
namespace
)
end
let!
(
:image
)
{
create
(
:issuable_metric_image
,
issue:
issue
)
}
subject
{
delete
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
iid
}
/metric_images/
#{
image
.
id
}
"
,
user2
)
}
shared_examples
'can_delete_metric_image'
do
it
'can delete the metric images'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
:no_content
)
expect
{
image
.
reload
}.
to
raise_error
(
ActiveRecord
::
RecordNotFound
)
end
end
shared_examples
'unauthorized_delete'
do
it
'cannot delete the metric image'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
expect
(
image
.
reload
).
to
eq
(
image
)
end
end
where
(
:user_role
,
:own_issue
,
:issue_confidential
,
:expected_status
)
do
:not_member
|
false
|
false
|
:unauthorized_delete
:not_member
|
true
|
false
|
:unauthorized_delete
:not_member
|
true
|
true
|
:unauthorized_delete
:guest
|
false
|
true
|
:unauthorized_delete
:guest
|
true
|
false
|
:can_delete_metric_image
:guest
|
false
|
false
|
:can_delete_metric_image
:reporter
|
true
|
false
|
:can_delete_metric_image
:reporter
|
false
|
false
|
:can_delete_metric_image
end
with_them
do
before
do
stub_licensed_features
(
incident_metric_upload:
true
)
project
.
send
(
"add_
#{
user_role
}
"
,
user2
)
unless
user_role
==
:not_member
end
let!
(
:issue
)
do
author
=
own_issue
?
user2
:
user
confidential
=
issue_confidential
create
(
:incident
,
project:
project
,
confidential:
confidential
,
author:
author
)
end
it_behaves_like
"
#{
params
[
:expected_status
]
}
"
end
context
'user has access'
do
let
(
:issue
)
{
create
(
:incident
,
project:
project
)
}
before
do
project
.
add_reporter
(
user2
)
end
context
'metric image not found'
do
subject
{
delete
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
iid
}
/metric_images/
#{
non_existing_record_id
}
"
,
user2
)
}
it
'returns an error'
do
subject
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
expect
(
json_response
[
'message'
]).
to
eq
(
'Metric image not found'
)
end
end
end
end
private
private
def
epic_issue_response_for
(
epic_issue
)
def
epic_issue_response_for
(
epic_issue
)
...
...
locale/gitlab.pot
View file @
f606e491
...
@@ -15042,6 +15042,9 @@ msgstr ""
...
@@ -15042,6 +15042,9 @@ msgstr ""
msgid "Incidents|Must start with http or https"
msgid "Incidents|Must start with http or https"
msgstr ""
msgstr ""
msgid "Incidents|There was an issue deleting the image."
msgstr ""
msgid "Incidents|There was an issue loading metric images."
msgid "Incidents|There was an issue loading metric images."
msgstr ""
msgstr ""
...
@@ -15054,6 +15057,12 @@ msgstr ""
...
@@ -15054,6 +15057,12 @@ msgstr ""
msgid "Incident|Alert details"
msgid "Incident|Alert details"
msgstr ""
msgstr ""
msgid "Incident|Are you sure you wish to delete this image?"
msgstr ""
msgid "Incident|Deleting %{filename}"
msgstr ""
msgid "Incident|Metrics"
msgid "Incident|Metrics"
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