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
fe340ebb
Commit
fe340ebb
authored
Aug 25, 2021
by
Peter Hegman
Committed by
Illya Klymov
Aug 25, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add spam icon to issues that are hidden
parent
d652fffc
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
197 additions
and
50 deletions
+197
-50
app/assets/javascripts/issue_show/components/app.vue
app/assets/javascripts/issue_show/components/app.vue
+18
-1
app/assets/javascripts/vue_shared/components/issuable/init_issuable_header_warning.js
...hared/components/issuable/init_issuable_header_warning.js
+11
-1
app/assets/javascripts/vue_shared/components/issuable/issuable_header_warnings.vue
...e_shared/components/issuable/issuable_header_warnings.vue
+21
-3
app/assets/stylesheets/pages/issuable.scss
app/assets/stylesheets/pages/issuable.scss
+1
-0
app/helpers/issuables_helper.rb
app/helpers/issuables_helper.rb
+2
-1
app/helpers/issues_helper.rb
app/helpers/issues_helper.rb
+9
-1
app/views/projects/issues/_issue.html.haml
app/views/projects/issues/_issue.html.haml
+1
-3
app/views/shared/issue_type/_details_header.html.haml
app/views/shared/issue_type/_details_header.html.haml
+1
-1
spec/frontend/issue_show/components/app_spec.js
spec/frontend/issue_show/components/app_spec.js
+35
-9
spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js
...ared/components/issuable/issuable_header_warnings_spec.js
+45
-29
spec/helpers/issuables_helper_spec.rb
spec/helpers/issuables_helper_spec.rb
+2
-1
spec/helpers/issues_helper_spec.rb
spec/helpers/issues_helper_spec.rb
+51
-0
No files found.
app/assets/javascripts/issue_show/components/app.vue
View file @
fe340ebb
<
script
>
import
{
GlIcon
,
GlIntersectionObserver
}
from
'
@gitlab/ui
'
;
import
{
GlIcon
,
GlIntersectionObserver
,
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
Visibility
from
'
visibilityjs
'
;
import
createFlash
from
'
~/flash
'
;
import
Poll
from
'
~/lib/utils/poll
'
;
...
...
@@ -32,6 +32,9 @@ export default {
formComponent
,
PinnedLinks
,
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
props
:
{
endpoint
:
{
required
:
true
,
...
...
@@ -183,6 +186,11 @@ export default {
required
:
false
,
default
:
true
,
},
isHidden
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
data
()
{
const
store
=
new
Store
({
...
...
@@ -508,6 +516,15 @@ export default {
<span
v-if=
"isConfidential"
data-testid=
"confidential"
class=
"issuable-warning-icon"
>
<gl-icon
name=
"eye-slash"
:aria-label=
"__('Confidential')"
/>
</span>
<span
v-if=
"isHidden"
v-gl-tooltip
:title=
"__('This issue is hidden because its author has been banned')"
data-testid=
"hidden"
class=
"issuable-warning-icon"
>
<gl-icon
name=
"spam"
/>
</span>
<p
class=
"gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0"
:title=
"state.titleText"
...
...
app/assets/javascripts/vue_shared/components/issuable/init_issuable_header_warning.js
View file @
fe340ebb
import
Vue
from
'
vue
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
IssuableHeaderWarnings
from
'
./issuable_header_warnings.vue
'
;
export
default
function
issuableHeaderWarnings
(
store
)
{
const
el
=
document
.
getElementById
(
'
js-issuable-header-warnings
'
);
if
(
!
el
)
{
return
false
;
}
const
{
hidden
}
=
el
.
dataset
;
return
new
Vue
({
el
:
document
.
getElementById
(
'
js-issuable-header-warnings
'
)
,
el
,
store
,
provide
:
{
hidden
:
parseBoolean
(
hidden
)
},
render
(
createElement
)
{
return
createElement
(
IssuableHeaderWarnings
);
},
...
...
app/assets/javascripts/vue_shared/components/issuable/issuable_header_warnings.vue
View file @
fe340ebb
<
script
>
import
{
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
GlIcon
,
GlTooltipDirective
}
from
'
@gitlab/ui
'
;
import
{
mapGetters
}
from
'
vuex
'
;
import
{
__
}
from
'
~/locale
'
;
export
default
{
components
:
{
GlIcon
,
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
inject
:
[
'
hidden
'
],
computed
:
{
...
mapGetters
([
'
getNoteableData
'
]),
isLocked
()
{
...
...
@@ -26,6 +31,12 @@ export default {
visible
:
this
.
isConfidential
,
dataTestId
:
'
confidential
'
,
},
{
iconName
:
'
spam
'
,
visible
:
this
.
hidden
,
dataTestId
:
'
hidden
'
,
tooltip
:
__
(
'
This issue is hidden because its author has been banned
'
),
},
];
},
},
...
...
@@ -35,8 +46,15 @@ export default {
<
template
>
<div
class=
"gl-display-inline-block"
>
<template
v-for=
"meta in warningIconsMeta"
>
<div
v-if=
"meta.visible"
:key=
"meta.iconName"
class=
"issuable-warning-icon inline"
>
<gl-icon
:name=
"meta.iconName"
:data-testid=
"meta.dataTestId"
class=
"icon"
/>
<div
v-if=
"meta.visible"
:key=
"meta.iconName"
v-gl-tooltip
:data-testid=
"meta.dataTestId"
:title=
"meta.tooltip || null"
class=
"issuable-warning-icon inline"
>
<gl-icon
:name=
"meta.iconName"
class=
"icon"
/>
</div>
</
template
>
</div>
...
...
app/assets/stylesheets/pages/issuable.scss
View file @
fe340ebb
...
...
@@ -7,6 +7,7 @@
text-align
:
center
;
margin-right
:
$issuable-warning-icon-margin
;
line-height
:
$gl-line-height-24
;
flex
:
0
0
auto
;
}
.limit-container-width
{
...
...
app/helpers/issuables_helper.rb
View file @
fe340ebb
...
...
@@ -256,7 +256,8 @@ module IssuablesHelper
issueType:
issuable
.
issue_type
,
zoomMeetingUrl:
ZoomMeeting
.
canonical_meeting_url
(
issuable
),
sentryIssueIdentifier:
SentryIssue
.
find_by
(
issue:
issuable
)
&
.
sentry_issue_identifier
,
# rubocop:disable CodeReuse/ActiveRecord
iid:
issuable
.
iid
.
to_s
iid:
issuable
.
iid
.
to_s
,
isHidden:
issue_hidden?
(
issuable
)
}
end
...
...
app/helpers/issues_helper.rb
View file @
fe340ebb
...
...
@@ -60,8 +60,16 @@ module IssuesHelper
sprite_icon
(
'eye-slash'
,
css_class:
'gl-vertical-align-text-bottom'
)
if
issue
.
confidential?
end
def
issue_hidden?
(
issue
)
Feature
.
enabled?
(
:ban_user_feature_flag
)
&&
issue
.
hidden?
end
def
hidden_issue_icon
(
issue
)
sprite_icon
(
'spam'
,
css_class:
'gl-vertical-align-text-bottom'
)
if
issue
.
hidden?
return
unless
issue_hidden?
(
issue
)
content_tag
(
:span
,
class:
'has-tooltip'
,
title:
_
(
'This issue is hidden because its author has been banned'
))
do
sprite_icon
(
'spam'
,
css_class:
'gl-vertical-align-text-bottom'
)
end
end
def
award_user_list
(
awards
,
current_user
,
limit:
10
)
...
...
app/views/projects/issues/_issue.html.haml
View file @
fe340ebb
...
...
@@ -12,9 +12,7 @@
-
if
issue
.
confidential?
%span
.has-tooltip
{
title:
_
(
'Confidential'
)
}
=
confidential_icon
(
issue
)
-
if
Feature
.
enabled?
(
:ban_user_feature_flag
)
&&
issue
.
hidden?
%span
.has-tooltip
{
title:
_
(
'This issue is hidden because its author has been banned'
)
}
=
hidden_issue_icon
(
issue
)
=
hidden_issue_icon
(
issue
)
=
link_to
issue
.
title
,
issue_path
(
issue
)
=
render_if_exists
'projects/issues/subepic_flag'
,
issue:
issue
-
if
issue
.
tasks?
...
...
app/views/shared/issue_type/_details_header.html.haml
View file @
fe340ebb
...
...
@@ -15,7 +15,7 @@
=
_
(
'Open'
)
.issuable-meta
#js-issuable-header-warnings
#js-issuable-header-warnings
{
data:
{
hidden:
issue_hidden?
(
issuable
).
to_s
}
}
=
issuable_meta
(
issuable
,
@project
)
%a
.btn.gl-button.btn-default.btn-icon.float-right.gl-display-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle
{
href:
"#"
}
...
...
spec/frontend/issue_show/components/app_spec.js
View file @
fe340ebb
import
{
GlIntersectionObserver
}
from
'
@gitlab/ui
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
nextTick
}
from
'
vue
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
{
mountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
'
~/behaviors/markdown/render_gfm
'
;
import
IssuableApp
from
'
~/issue_show/components/app.vue
'
;
import
DescriptionComponent
from
'
~/issue_show/components/description.vue
'
;
...
...
@@ -33,13 +34,17 @@ describe('Issuable output', () => {
let
realtimeRequestCount
=
0
;
let
wrapper
;
const
findStickyHeader
=
()
=>
wrapper
.
find
(
'
[data-testid="issue-sticky-header"]
'
);
const
findLockedBadge
=
()
=>
wrapper
.
find
(
'
[data-testid="locked"]
'
);
const
findConfidentialBadge
=
()
=>
wrapper
.
find
(
'
[data-testid="confidential"]
'
);
const
findStickyHeader
=
()
=>
wrapper
.
findByTestId
(
'
issue-sticky-header
'
);
const
findLockedBadge
=
()
=>
wrapper
.
findByTestId
(
'
locked
'
);
const
findConfidentialBadge
=
()
=>
wrapper
.
findByTestId
(
'
confidential
'
);
const
findHiddenBadge
=
()
=>
wrapper
.
findByTestId
(
'
hidden
'
);
const
findAlert
=
()
=>
wrapper
.
find
(
'
.alert
'
);
const
mountComponent
=
(
props
=
{},
options
=
{},
data
=
{})
=>
{
wrapper
=
mount
(
IssuableApp
,
{
wrapper
=
mountExtended
(
IssuableApp
,
{
directives
:
{
GlTooltip
:
createMockDirective
(),
},
propsData
:
{
...
appProps
,
...
props
},
provide
:
{
fullPath
:
'
gitlab-org/incidents
'
,
...
...
@@ -539,8 +544,8 @@ describe('Issuable output', () => {
it
.
each
`
title | isConfidential
${
'
does not show confidential badge when issue is not confidential
'
}
|
${
tru
e
}
${
'
shows confidential badge when issue is confidential
'
}
|
${
fals
e
}
${
'
does not show confidential badge when issue is not confidential
'
}
|
${
fals
e
}
${
'
shows confidential badge when issue is confidential
'
}
|
${
tru
e
}
`
(
'
$title
'
,
async
({
isConfidential
})
=>
{
wrapper
.
setProps
({
isConfidential
});
...
...
@@ -551,8 +556,8 @@ describe('Issuable output', () => {
it
.
each
`
title | isLocked
${
'
does not show locked badge when issue is not locked
'
}
|
${
tru
e
}
${
'
shows locked badge when issue is locked
'
}
|
${
fals
e
}
${
'
does not show locked badge when issue is not locked
'
}
|
${
fals
e
}
${
'
shows locked badge when issue is locked
'
}
|
${
tru
e
}
`
(
'
$title
'
,
async
({
isLocked
})
=>
{
wrapper
.
setProps
({
isLocked
});
...
...
@@ -560,6 +565,27 @@ describe('Issuable output', () => {
expect
(
findLockedBadge
().
exists
()).
toBe
(
isLocked
);
});
it
.
each
`
title | isHidden
${
'
does not show hidden badge when issue is not hidden
'
}
|
${
false
}
${
'
shows hidden badge when issue is hidden
'
}
|
${
true
}
`
(
'
$title
'
,
async
({
isHidden
})
=>
{
wrapper
.
setProps
({
isHidden
});
await
nextTick
();
const
hiddenBadge
=
findHiddenBadge
();
expect
(
hiddenBadge
.
exists
()).
toBe
(
isHidden
);
if
(
isHidden
)
{
expect
(
hiddenBadge
.
attributes
(
'
title
'
)).
toBe
(
'
This issue is hidden because its author has been banned
'
,
);
expect
(
getBinding
(
hiddenBadge
.
element
,
'
gl-tooltip
'
)).
not
.
toBeUndefined
();
}
});
});
});
...
...
spec/frontend/vue_shared/components/issuable/issuable_header_warnings_spec.js
View file @
fe340ebb
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
{
createLocalVue
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
{
createStore
as
createMrStore
}
from
'
~/mr_notes/stores
'
;
import
createIssueStore
from
'
~/notes/stores
'
;
import
IssuableHeaderWarnings
from
'
~/vue_shared/components/issuable/issuable_header_warnings.vue
'
;
...
...
@@ -12,52 +14,53 @@ localVue.use(Vuex);
describe
(
'
IssuableHeaderWarnings
'
,
()
=>
{
let
wrapper
;
let
store
;
const
findConfidentialIcon
=
()
=>
wrapper
.
find
(
'
[data-testid="confidential"]
'
);
const
findLockedIcon
=
()
=>
wrapper
.
find
(
'
[data-testid="locked"]
'
);
const
findConfidentialIcon
=
()
=>
wrapper
.
findByTestId
(
'
confidential
'
);
const
findLockedIcon
=
()
=>
wrapper
.
findByTestId
(
'
locked
'
);
const
findHiddenIcon
=
()
=>
wrapper
.
findByTestId
(
'
hidden
'
);
const
renderTestMessage
=
(
renders
)
=>
(
renders
?
'
renders
'
:
'
does not render
'
);
const
setLock
=
(
locked
)
=>
{
store
.
getters
.
getNoteableData
.
discussion_locked
=
locked
;
};
const
setConfidential
=
(
confidential
)
=>
{
store
.
getters
.
getNoteableData
.
confidential
=
confidential
;
};
const
createComponent
=
()
=>
{
wrapper
=
shallowMount
(
IssuableHeaderWarnings
,
{
store
,
localVue
});
const
createComponent
=
({
store
,
provide
})
=>
{
wrapper
=
shallowMountExtended
(
IssuableHeaderWarnings
,
{
store
,
localVue
,
provide
,
directives
:
{
GlTooltip
:
createMockDirective
(),
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
store
=
null
;
});
describe
.
each
`
issuableType
${
ISSUABLE_TYPE_ISSUE
}
|
${
ISSUABLE_TYPE_MR
}
`
(
`when issuableType=$issuableType`
,
({
issuableType
})
=>
{
beforeEach
(()
=>
{
store
=
issuableType
===
ISSUABLE_TYPE_ISSUE
?
createIssueStore
()
:
createMrStore
();
createComponent
();
});
describe
.
each
`
lockStatus | confidentialStatus
${
true
}
|
${
true
}
${
true
}
|
${
false
}
${
false
}
|
${
true
}
${
false
}
|
${
false
}
lockStatus | confidentialStatus | hiddenStatus
${
true
}
|
${
true
}
|
${
false
}
${
true
}
|
${
false
}
|
${
false
}
${
false
}
|
${
true
}
|
${
false
}
${
false
}
|
${
false
}
|
${
false
}
${
true
}
|
${
true
}
|
${
true
}
${
true
}
|
${
false
}
|
${
true
}
${
false
}
|
${
true
}
|
${
true
}
${
false
}
|
${
false
}
|
${
true
}
`
(
`when locked=$lockStatus and confidential=$confidentialStatus`
,
({
lockStatus
,
confidentialStatus
})
=>
{
`when locked=$lockStatus, confidential=$confidentialStatus, and hidden=$hiddenStatus`
,
({
lockStatus
,
confidentialStatus
,
hiddenStatus
})
=>
{
const
store
=
issuableType
===
ISSUABLE_TYPE_ISSUE
?
createIssueStore
()
:
createMrStore
();
beforeEach
(()
=>
{
setLock
(
lockStatus
);
setConfidential
(
confidentialStatus
);
store
.
getters
.
getNoteableData
.
confidential
=
confidentialStatus
;
store
.
getters
.
getNoteableData
.
discussion_locked
=
lockStatus
;
createComponent
({
store
,
provide
:
{
hidden
:
hiddenStatus
}
});
});
it
(
`
${
renderTestMessage
(
lockStatus
)}
the locked icon`
,
()
=>
{
...
...
@@ -67,6 +70,19 @@ describe('IssuableHeaderWarnings', () => {
it
(
`
${
renderTestMessage
(
confidentialStatus
)}
the confidential icon`
,
()
=>
{
expect
(
findConfidentialIcon
().
exists
()).
toBe
(
confidentialStatus
);
});
it
(
`
${
renderTestMessage
(
confidentialStatus
)}
the hidden icon`
,
()
=>
{
const
hiddenIcon
=
findHiddenIcon
();
expect
(
hiddenIcon
.
exists
()).
toBe
(
hiddenStatus
);
if
(
hiddenStatus
)
{
expect
(
hiddenIcon
.
attributes
(
'
title
'
)).
toBe
(
'
This issue is hidden because its author has been banned
'
,
);
expect
(
getBinding
(
hiddenIcon
.
element
,
'
gl-tooltip
'
)).
not
.
toBeUndefined
();
}
});
},
);
});
...
...
spec/helpers/issuables_helper_spec.rb
View file @
fe340ebb
...
...
@@ -285,7 +285,8 @@ RSpec.describe IssuablesHelper do
initialDescriptionText:
'issue text'
,
initialTaskStatus:
'0 of 0 tasks completed'
,
issueType:
'issue'
,
iid:
issue
.
iid
.
to_s
iid:
issue
.
iid
.
to_s
,
isHidden:
false
}
expect
(
helper
.
issuable_initial_data
(
issue
)).
to
match
(
hash_including
(
expected_data
))
end
...
...
spec/helpers/issues_helper_spec.rb
View file @
fe340ebb
...
...
@@ -410,4 +410,55 @@ RSpec.describe IssuesHelper do
end
end
end
describe
'#issue_hidden?'
do
context
'when issue is hidden'
do
let_it_be
(
:banned_user
)
{
build
(
:user
,
:banned
)
}
let_it_be
(
:hidden_issue
)
{
build
(
:issue
,
author:
banned_user
)
}
context
'when `ban_user_feature_flag` feature flag is enabled'
do
it
'returns `true`'
do
expect
(
helper
.
issue_hidden?
(
hidden_issue
)).
to
eq
(
true
)
end
end
context
'when `ban_user_feature_flag` feature flag is disabled'
do
before
do
stub_feature_flags
(
ban_user_feature_flag:
false
)
end
it
'returns `false`'
do
expect
(
helper
.
issue_hidden?
(
hidden_issue
)).
to
eq
(
false
)
end
end
end
context
'when issue is not hidden'
do
it
'returns `false`'
do
expect
(
helper
.
issue_hidden?
(
issue
)).
to
eq
(
false
)
end
end
end
describe
'#hidden_issue_icon'
do
let_it_be
(
:banned_user
)
{
build
(
:user
,
:banned
)
}
let_it_be
(
:hidden_issue
)
{
build
(
:issue
,
author:
banned_user
)
}
let_it_be
(
:mock_svg
)
{
'<svg></svg>'
.
html_safe
}
before
do
allow
(
helper
).
to
receive
(
:sprite_icon
).
and_return
(
mock_svg
)
end
context
'when issue is hidden'
do
it
'returns icon with tooltip'
do
expect
(
helper
.
hidden_issue_icon
(
hidden_issue
)).
to
eq
(
"<span class=
\"
has-tooltip
\"
title=
\"
This issue is hidden because its author has been banned
\"
>
#{
mock_svg
}
</span>"
)
end
end
context
'when issue is not hidden'
do
it
'returns `nil`'
do
expect
(
helper
.
hidden_issue_icon
(
issue
)).
to
be_nil
end
end
end
end
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