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
71a692d6
Commit
71a692d6
authored
Oct 08, 2021
by
David O'Regan
Committed by
Vitaly Slobodin
Oct 08, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix mark snippet as spam button
parent
2697d4ae
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
111 additions
and
33 deletions
+111
-33
app/assets/javascripts/snippets/components/snippet_header.vue
...assets/javascripts/snippets/components/snippet_header.vue
+49
-21
app/assets/javascripts/snippets/index.js
app/assets/javascripts/snippets/index.js
+2
-0
app/views/projects/snippets/show.html.haml
app/views/projects/snippets/show.html.haml
+1
-1
app/views/snippets/show.html.haml
app/views/snippets/show.html.haml
+1
-1
locale/gitlab.pot
locale/gitlab.pot
+9
-0
spec/frontend/snippets/components/show_spec.js
spec/frontend/snippets/components/show_spec.js
+11
-7
spec/frontend/snippets/components/snippet_header_spec.js
spec/frontend/snippets/components/snippet_header_spec.js
+38
-3
No files found.
app/assets/javascripts/snippets/components/snippet_header.vue
View file @
71a692d6
...
...
@@ -11,15 +11,26 @@ import {
GlButton
,
GlTooltipDirective
,
}
from
'
@gitlab/ui
'
;
import
{
isEmpty
}
from
'
lodash
'
;
import
CanCreateProjectSnippet
from
'
shared_queries/snippet/project_permissions.query.graphql
'
;
import
CanCreatePersonalSnippet
from
'
shared_queries/snippet/user_permissions.query.graphql
'
;
import
{
fetchPolicies
}
from
'
~/lib/graphql
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
joinPaths
}
from
'
~/lib/utils/url_utility
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
,
s__
,
sprintf
}
from
'
~/locale
'
;
import
TimeAgoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
createFlash
,
{
FLASH_TYPES
}
from
'
~/flash
'
;
import
DeleteSnippetMutation
from
'
../mutations/deleteSnippet.mutation.graphql
'
;
export
const
i18n
=
{
snippetSpamSuccess
:
sprintf
(
s__
(
'
Snippets|%{spammable_titlecase} was submitted to Akismet successfully.
'
),
{
spammable_titlecase
:
__
(
'
Snippet
'
)
},
),
snippetSpamFailure
:
s__
(
'
Snippets|Error with Akismet. Please check the logs for more info.
'
),
};
export
default
{
components
:
{
GlAvatar
,
...
...
@@ -54,7 +65,7 @@ export default {
},
},
},
inject
:
[
'
reportAbusePath
'
],
inject
:
[
'
reportAbusePath
'
,
'
canReportSpam
'
],
props
:
{
snippet
:
{
type
:
Object
,
...
...
@@ -63,7 +74,8 @@ export default {
},
data
()
{
return
{
isDeleting
:
false
,
isLoading
:
false
,
isSubmittingSpam
:
false
,
errorMessage
:
''
,
canCreateSnippet
:
false
,
};
...
...
@@ -105,10 +117,11 @@ export default {
category
:
'
secondary
'
,
},
{
condition
:
this
.
reportAbusePath
,
condition
:
this
.
canReportSpam
&&
!
isEmpty
(
this
.
reportAbusePath
)
,
text
:
__
(
'
Submit as spam
'
),
href
:
this
.
reportAbusePath
,
click
:
this
.
submitAsSpam
,
title
:
__
(
'
Submit as spam
'
),
loading
:
this
.
isSubmittingSpam
,
},
];
},
...
...
@@ -157,7 +170,7 @@ export default {
this
.
$refs
.
deleteModal
.
show
();
},
deleteSnippet
()
{
this
.
is
Delet
ing
=
true
;
this
.
is
Load
ing
=
true
;
this
.
$apollo
.
mutate
({
mutation
:
DeleteSnippetMutation
,
...
...
@@ -167,17 +180,34 @@ export default {
if
(
data
?.
destroySnippet
?.
errors
.
length
)
{
throw
new
Error
(
data
?.
destroySnippet
?.
errors
[
0
]);
}
this
.
isDeleting
=
false
;
this
.
errorMessage
=
undefined
;
this
.
closeDeleteModal
();
this
.
redirectToSnippets
();
})
.
catch
((
err
)
=>
{
this
.
is
Delet
ing
=
false
;
this
.
is
Load
ing
=
false
;
this
.
errorMessage
=
err
.
message
;
})
.
finally
(()
=>
{
this
.
isLoading
=
false
;
});
},
async
submitAsSpam
()
{
try
{
this
.
isSubmittingSpam
=
true
;
await
axios
.
post
(
this
.
reportAbusePath
);
createFlash
({
message
:
this
.
$options
.
i18n
.
snippetSpamSuccess
,
type
:
FLASH_TYPES
.
SUCCESS
,
});
}
catch
(
error
)
{
createFlash
({
message
:
this
.
$options
.
i18n
.
snippetSpamFailure
});
}
finally
{
this
.
isSubmittingSpam
=
false
;
}
},
},
i18n
,
};
</
script
>
<
template
>
...
...
@@ -189,9 +219,7 @@ export default {
:title=
"snippetVisibilityLevelDescription"
data-container=
"body"
>
<span
class=
"sr-only"
>
{{
s__
(
`VisibilityLevel|${visibility
}
`
)
}}
<
/span
>
<span
class=
"sr-only"
>
{{
s__
(
`VisibilityLevel|${visibility
}
`
)
}}
<
/span
>
<
gl
-
icon
:
name
=
"
visibilityLevelIcon
"
:
size
=
"
14
"
/>
<
/div
>
<
div
class
=
"
creator
"
data
-
testid
=
"
authored-message
"
>
...
...
@@ -233,6 +261,7 @@ export default {
>
<
gl
-
button
:
disabled
=
"
action.disabled
"
:
loading
=
"
action.loading
"
:
variant
=
"
action.variant
"
:
category
=
"
action.category
"
:
class
=
"
action.cssClass
"
...
...
@@ -240,9 +269,8 @@ export default {
data
-
qa
-
selector
=
"
snippet_action_button
"
:
data
-
qa
-
action
=
"
action.text
"
@
click
=
"
action.click ? action.click() : undefined
"
>
{{
action
.
text
}}
<
/gl-butto
n
>
{{
action
.
text
}}
<
/gl-button
>
<
/div
>
<
/template
>
<
/div
>
...
...
@@ -266,14 +294,14 @@ export default {
<
gl
-
modal
ref
=
"
deleteModal
"
modal
-
id
=
"
delete-modal
"
title
=
"
Example title
"
>
<
template
#
modal
-
title
>
{{
__
(
'
Delete snippet?
'
)
}}
<
/template
>
<
gl
-
alert
v
-
if
=
"
errorMessage
"
variant
=
"
danger
"
class
=
"
mb-2
"
@
dismiss
=
"
errorMessage = ''
"
>
{{
errorMessage
}}
<
/gl-alert
>
<
gl
-
alert
v
-
if
=
"
errorMessage
"
variant
=
"
danger
"
class
=
"
mb-2
"
@
dismiss
=
"
errorMessage = ''
"
>
{{
errorMessage
}}
<
/gl-alert
>
<
gl
-
sprintf
:
message
=
"
__('Are you sure you want to delete %{name
}
?')
"
>
<
template
#
name
><
strong
>
{{
snippet
.
title
}}
<
/strong></
template
>
<
template
#
name
>
<
strong
>
{{
snippet
.
title
}}
<
/strong
>
<
/template
>
<
/gl-sprintf
>
<
template
#
modal
-
footer
>
...
...
@@ -281,11 +309,11 @@ export default {
<
gl
-
button
variant
=
"
danger
"
category
=
"
primary
"
:
disabled
=
"
is
Delet
ing
"
:
disabled
=
"
is
Load
ing
"
data
-
qa
-
selector
=
"
delete_snippet_button
"
@
click
=
"
deleteSnippet
"
>
<
gl
-
loading
-
icon
v
-
if
=
"
is
Delet
ing
"
size
=
"
sm
"
inline
/>
<
gl
-
loading
-
icon
v
-
if
=
"
is
Load
ing
"
size
=
"
sm
"
inline
/>
{{
__
(
'
Delete snippet
'
)
}}
<
/gl-button
>
<
/template
>
...
...
app/assets/javascripts/snippets/index.js
View file @
71a692d6
...
...
@@ -27,6 +27,7 @@ export default function appFactory(el, Component) {
visibilityLevels
=
'
[]
'
,
selectedLevel
,
multipleLevelsRestricted
,
canReportSpam
,
reportAbusePath
,
...
restDataset
}
=
el
.
dataset
;
...
...
@@ -39,6 +40,7 @@ export default function appFactory(el, Component) {
selectedLevel
:
SNIPPET_LEVELS_MAP
[
selectedLevel
]
??
SNIPPET_VISIBILITY_PRIVATE
,
multipleLevelsRestricted
:
'
multipleLevelsRestricted
'
in
el
.
dataset
,
reportAbusePath
,
canReportSpam
,
},
render
(
createElement
)
{
return
createElement
(
Component
,
{
...
...
app/views/projects/snippets/show.html.haml
View file @
71a692d6
...
...
@@ -3,7 +3,7 @@
-
breadcrumb_title
@snippet
.
to_reference
-
page_title
"
#{
@snippet
.
title
}
(
#{
@snippet
.
to_reference
}
)"
,
_
(
"Snippets"
)
#js-snippet-view
{
data:
{
'qa-selector'
:
'snippet_view'
,
'snippet-gid'
:
@snippet
.
to_global_id
,
'report-abuse-path'
:
snippet_report_abuse_path
(
@snippet
)
}
}
#js-snippet-view
{
data:
{
'qa-selector'
:
'snippet_view'
,
'snippet-gid'
:
@snippet
.
to_global_id
,
'report-abuse-path'
:
snippet_report_abuse_path
(
@snippet
)
,
'can-report-spam'
:
@snippet
.
submittable_as_spam_by?
(
current_user
).
to_s
}
}
.row-content-block.top-block.content-component-block
=
render
'award_emoji/awards_block'
,
awardable:
@snippet
,
inline:
true
,
api_awards_path:
project_snippets_award_api_path
(
@snippet
)
...
...
app/views/snippets/show.html.haml
View file @
71a692d6
...
...
@@ -12,7 +12,7 @@
-
content_for
:prefetch_asset_tags
do
-
webpack_preload_asset_tag
(
'monaco'
,
prefetch:
true
)
#js-snippet-view
{
data:
{
'qa-selector'
:
'snippet_view'
,
'snippet-gid'
:
@snippet
.
to_global_id
,
'report-abuse-path'
:
snippet_report_abuse_path
(
@snippet
)
}
}
#js-snippet-view
{
data:
{
'qa-selector'
:
'snippet_view'
,
'snippet-gid'
:
@snippet
.
to_global_id
,
'report-abuse-path'
:
snippet_report_abuse_path
(
@snippet
)
,
'can-report-spam'
:
@snippet
.
submittable_as_spam_by?
(
current_user
).
to_s
}
}
.row-content-block.top-block.content-component-block
=
render
'award_emoji/awards_block'
,
awardable:
@snippet
,
inline:
true
...
...
locale/gitlab.pot
View file @
71a692d6
...
...
@@ -31565,6 +31565,9 @@ msgstr ""
msgid "Smartcard authentication failed: client certificate header is missing."
msgstr ""
msgid "Snippet"
msgstr ""
msgid "Snippets"
msgstr ""
...
...
@@ -31589,6 +31592,9 @@ msgstr ""
msgid "SnippetsEmptyState|There are no snippets to show."
msgstr ""
msgid "Snippets|%{spammable_titlecase} was submitted to Akismet successfully."
msgstr ""
msgid "Snippets|Add another file %{num}/%{total}"
msgstr ""
...
...
@@ -31598,6 +31604,9 @@ msgstr ""
msgid "Snippets|Description (optional)"
msgstr ""
msgid "Snippets|Error with Akismet. Please check the logs for more info."
msgstr ""
msgid "Snippets|Files"
msgstr ""
...
...
spec/frontend/snippets/components/show_spec.js
View file @
71a692d6
...
...
@@ -41,19 +41,23 @@ describe('Snippet view app', () => {
},
});
}
const
findLoadingIcon
=
()
=>
wrapper
.
findComponent
(
GlLoadingIcon
);
const
findEmbedDropdown
=
()
=>
wrapper
.
findComponent
(
EmbedDropdown
);
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
renders loader while the query is in flight
'
,
()
=>
{
createComponent
({
loading
:
true
});
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
true
);
expect
(
findLoadingIcon
(
).
exists
()).
toBe
(
true
);
});
it
(
'
renders all simple components after the query is finished
'
,
()
=>
{
it
(
'
renders all simple components
required
after the query is finished
'
,
()
=>
{
createComponent
();
expect
(
wrapper
.
find
(
SnippetHeader
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
SnippetTitle
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
Component
(
SnippetHeader
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
Component
(
SnippetTitle
).
exists
()).
toBe
(
true
);
});
it
(
'
renders embed dropdown component if visibility allows
'
,
()
=>
{
...
...
@@ -65,7 +69,7 @@ describe('Snippet view app', () => {
},
},
});
expect
(
wrapper
.
find
(
EmbedDropdown
).
exists
()).
toBe
(
true
);
expect
(
findEmbedDropdown
(
).
exists
()).
toBe
(
true
);
});
it
(
'
renders correct snippet-blob components
'
,
()
=>
{
...
...
@@ -98,7 +102,7 @@ describe('Snippet view app', () => {
},
},
});
expect
(
wrapper
.
find
(
EmbedDropdown
).
exists
()).
toBe
(
isRendered
);
expect
(
findEmbedDropdown
(
).
exists
()).
toBe
(
isRendered
);
});
});
...
...
@@ -120,7 +124,7 @@ describe('Snippet view app', () => {
},
},
});
expect
(
wrapper
.
find
(
CloneDropdownButton
).
exists
()).
toBe
(
isRendered
);
expect
(
wrapper
.
find
Component
(
CloneDropdownButton
).
exists
()).
toBe
(
isRendered
);
},
);
});
...
...
spec/frontend/snippets/components/snippet_header_spec.js
View file @
71a692d6
import
{
GlButton
,
GlModal
,
GlDropdown
}
from
'
@gitlab/ui
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
{
ApolloMutation
}
from
'
vue-apollo
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
useMockLocationHelper
}
from
'
helpers/mock_window_location_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
Blob
,
BinaryBlob
}
from
'
jest/blob/components/mock_data
'
;
import
{
differenceInMilliseconds
}
from
'
~/lib/utils/datetime_utility
'
;
import
SnippetHeader
from
'
~/snippets/components/snippet_header.vue
'
;
import
SnippetHeader
,
{
i18n
}
from
'
~/snippets/components/snippet_header.vue
'
;
import
DeleteSnippetMutation
from
'
~/snippets/mutations/deleteSnippet.mutation.graphql
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
createFlash
,
{
FLASH_TYPES
}
from
'
~/flash
'
;
jest
.
mock
(
'
~/flash
'
);
describe
(
'
Snippet header component
'
,
()
=>
{
let
wrapper
;
let
snippet
;
let
mutationTypes
;
let
mutationVariables
;
let
mock
;
let
errorMsg
;
let
err
;
const
originalRelativeUrlRoot
=
gon
.
relative_url_root
;
const
reportAbusePath
=
'
/-/snippets/42/mark_as_spam
'
;
const
canReportSpam
=
true
;
const
GlEmoji
=
{
template
:
'
<img/>
'
};
...
...
@@ -47,6 +54,7 @@ describe('Snippet header component', () => {
mocks
:
{
$apollo
},
provide
:
{
reportAbusePath
,
canReportSpam
,
...
provide
,
},
propsData
:
{
...
...
@@ -118,10 +126,13 @@ describe('Snippet header component', () => {
RESOLVE
:
jest
.
fn
(()
=>
Promise
.
resolve
({
data
:
{
destroySnippet
:
{
errors
:
[]
}
}
})),
REJECT
:
jest
.
fn
(()
=>
Promise
.
reject
(
err
)),
};
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
mock
.
restore
();
gon
.
relative_url_root
=
originalRelativeUrlRoot
;
});
...
...
@@ -186,7 +197,6 @@ describe('Snippet header component', () => {
{
category
:
'
primary
'
,
disabled
:
false
,
href
:
reportAbusePath
,
text
:
'
Submit as spam
'
,
variant
:
'
default
'
,
},
...
...
@@ -205,7 +215,6 @@ describe('Snippet header component', () => {
text
:
'
Delete
'
,
},
{
href
:
reportAbusePath
,
text
:
'
Submit as spam
'
,
title
:
'
Submit as spam
'
,
},
...
...
@@ -249,6 +258,31 @@ describe('Snippet header component', () => {
);
});
describe
(
'
submit snippet as spam
'
,
()
=>
{
beforeEach
(
async
()
=>
{
createComponent
();
});
it
.
each
`
request | variant | text
${
200
}
|
${
'
SUCCESS
'
}
|
${
i18n
.
snippetSpamSuccess
}
${
500
}
|
${
'
DANGER
'
}
|
${
i18n
.
snippetSpamFailure
}
`
(
'
renders a "$variant" flash message with "$text" message for a request with a "$request" response
'
,
async
({
request
,
variant
,
text
})
=>
{
const
submitAsSpamBtn
=
findButtons
().
at
(
2
);
mock
.
onPost
(
reportAbusePath
).
reply
(
request
);
submitAsSpamBtn
.
trigger
(
'
click
'
);
await
waitForPromises
();
expect
(
createFlash
).
toHaveBeenLastCalledWith
({
message
:
expect
.
stringContaining
(
text
),
type
:
FLASH_TYPES
[
variant
],
});
},
);
});
describe
(
'
with guest user
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
...
...
@@ -258,6 +292,7 @@ describe('Snippet header component', () => {
},
provide
:
{
reportAbusePath
:
null
,
canReportSpam
:
false
,
},
});
});
...
...
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