Skip to content

Commit 9d657bc

Browse files
sanyavertoletCheshiriksorchestr7
authored
FossGraph comments improvements (#2260)
* FossGraph comments improvements ### What's done: * Forbid creating blank comments * Supported hiding comments if there are more then 5 comments present * Fixed visuals * Made fossGraph viewable for unauthorized users --------- Co-authored-by: Vladislav Frolov <50615459+Cheshiriks@users.noreply.github.com> Co-authored-by: Andrey Kuleshov <andrewkuleshov7@gmail.com>
1 parent 51f2f6f commit 9d657bc

8 files changed

Lines changed: 115 additions & 90 deletions

File tree

authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ class WebSecurityConfig(
111111
"/api/$v1/contests/*/*/best",
112112
"/demo/api/*/run",
113113
"/api/$v1/vulnerabilities/get-all-public",
114+
// `fossGraphView` is public page
115+
"/api/$v1/vulnerabilities/by-name-with-description",
116+
"/api/$v1/comments/get-all",
114117
)
115118
}
116119
}

save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/UsersDetailsController.kt

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ import com.saveourtool.save.domain.Role
99
import com.saveourtool.save.domain.UserSaveStatus
1010
import com.saveourtool.save.entities.User
1111
import com.saveourtool.save.info.UserInfo
12-
import com.saveourtool.save.utils.StringResponse
13-
import com.saveourtool.save.utils.blockingToFlux
14-
import com.saveourtool.save.utils.orNotFound
15-
import com.saveourtool.save.utils.switchIfEmptyToResponseException
12+
import com.saveourtool.save.utils.*
1613
import com.saveourtool.save.v1
1714

1815
import org.springframework.http.HttpStatus
@@ -41,15 +38,14 @@ class UsersDetailsController(
4138
*/
4239
@GetMapping("/{userName}")
4340
@PreAuthorize("permitAll()")
44-
fun findByName(@PathVariable userName: String): Mono<UserInfo> =
45-
userRepository.findByName(userName)
46-
?.toMonoOrNotFound()?.map { it.toUserInfo() }
47-
?: run {
48-
originalLoginRepository.findByName(userName)
49-
.toMonoOrNotFound()
50-
.map { it.user }
51-
.map { it.toUserInfo() }
52-
}
41+
fun findByName(@PathVariable userName: String): Mono<UserInfo> = userRepository.findByName(userName)
42+
?.toMonoOrNotFound()?.map { it.toUserInfo() }
43+
?: run {
44+
blockingToMono { originalLoginRepository.findByName(userName) }
45+
.orNotFound()
46+
.map { it.user }
47+
.map { it.toUserInfo() }
48+
}
5349

5450
/**
5551
* @return list of [UserInfo] info about user's

save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/vulnerability/VulnerabilityController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class VulnerabilityController(
111111
@RequestParam name: String,
112112
): Mono<VulnerabilityDto> = blockingToMono {
113113
vulnerabilityService.getVulnerabilityWithDescriptionByName(name)
114-
}
114+
}.switchIfEmptyToNotFound { "Could not find vulnerability with name $name" }
115115

116116
@GetMapping("/by-user-and-active")
117117
@Operation(

save-backend/src/main/kotlin/com/saveourtool/save/backend/service/vulnerability/VulnerabilityService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class VulnerabilityService(
133133
* @param name name of vulnerability
134134
* @return vulnerability dto by name with description
135135
*/
136-
fun getVulnerabilityWithDescriptionByName(name: String): VulnerabilityDto = vulnerabilityRepository.findByName(name)?.toDtoWithDescription() ?: VulnerabilityDto.empty
136+
fun getVulnerabilityWithDescriptionByName(name: String): VulnerabilityDto? = vulnerabilityRepository.findByName(name)?.toDtoWithDescription()
137137

138138
/**
139139
* @param vulnerabilityDto dto of new vulnerability

save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/Forum.kt

Lines changed: 56 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package com.saveourtool.save.frontend.components.basic
44

55
import com.saveourtool.save.entities.CommentDto
66
import com.saveourtool.save.frontend.components.inputform.InputTypes
7+
import com.saveourtool.save.frontend.externals.fontawesome.faPaperPlane
78
import com.saveourtool.save.frontend.utils.*
89
import com.saveourtool.save.frontend.utils.AVATAR_PLACEHOLDER
910
import com.saveourtool.save.info.UserInfo
@@ -19,6 +20,7 @@ import react.dom.html.ReactHTML.div
1920
import react.dom.html.ReactHTML.form
2021
import react.dom.html.ReactHTML.h1
2122
import react.dom.html.ReactHTML.img
23+
import react.dom.html.ReactHTML.span
2224
import react.dom.html.ReactHTML.textarea
2325
import react.useState
2426
import web.cssom.*
@@ -37,7 +39,6 @@ import kotlinx.serialization.json.Json
3739
)
3840
val newCommentWindow: FC<NewCommentWindowProps> = FC { props ->
3941
val (comment, setComment) = useState(CommentDto.empty)
40-
val (user, setUser) = useStateFromProps(props.currentUserInfo ?: UserInfo("Unknown"))
4142

4243
val enrollRequest = useDeferredRequest {
4344
val commentNew = comment.copy(section = window.location.hash)
@@ -50,71 +51,78 @@ val newCommentWindow: FC<NewCommentWindowProps> = FC { props ->
5051
)
5152
if (response.ok) {
5253
props.addComment()
54+
setComment(CommentDto.empty)
5355
}
5456
}
5557

5658
div {
57-
className = ClassName("row no-gutters mx-auto border-secondary")
58-
renderLeftColumn(
59-
user.avatar,
60-
user.name,
61-
user.rating,
62-
)
59+
className = ClassName("shadow col mx-auto border border-secondary card card-body p-0")
6360
div {
64-
className = ClassName("card col-10 input-group needs-validation")
65-
textarea {
66-
className = ClassName("form-control")
67-
style = jso {
68-
width = "100%".unsafeCast<Width>()
69-
height = "100%".unsafeCast<Height>()
70-
}
71-
onChange = { event ->
72-
setComment { it.copy(message = event.target.value) }
61+
className = ClassName("row no-gutters mx-auto input-group px-0 shadow-none")
62+
renderLeftColumn(
63+
props.currentUserInfo.avatar,
64+
props.currentUserInfo.name,
65+
props.currentUserInfo.rating,
66+
"#e1e9ed"
67+
)
68+
div {
69+
className = ClassName("col")
70+
textarea {
71+
className = ClassName("form-control p-3 border-0")
72+
style = jso {
73+
width = "100%".unsafeCast<Width>()
74+
height = "100%".unsafeCast<Height>()
75+
}
76+
onChange = { event -> setComment { it.copy(message = event.target.value) } }
77+
value = comment.message
78+
ariaDescribedBy = "${InputTypes.COMMENT.name}Span"
79+
rows = 5
80+
id = InputTypes.COMMENT.name
81+
required = true
82+
placeholder = "Write a comment"
7383
}
74-
ariaDescribedBy = "${InputTypes.COMMENT.name}Span"
75-
rows = 5
76-
id = InputTypes.COMMENT.name
77-
required = true
7884
}
7985
}
80-
}
81-
div {
82-
className = ClassName("d-flex justify-content-end")
83-
buttonBuilder(label = "Comment") {
84-
enrollRequest()
86+
div {
87+
className = ClassName("d-flex justify-content-end p-2")
88+
style = jso { background = "#e1e9ed".unsafeCast<Background>() }
89+
buttonBuilder(
90+
faPaperPlane,
91+
isDisabled = comment.message.isBlank(),
92+
classes = "rounded-circle btn-sm",
93+
isOutline = true,
94+
) {
95+
enrollRequest()
96+
}
8597
}
8698
}
8799
}
88100

89101
/**
90-
* @return a function component
102+
* [FC] for comment displaying
91103
*/
92-
@Suppress(
93-
"GENERIC_VARIABLE_WRONG_DECLARATION",
94-
"MAGIC_NUMBER",
95-
)
104+
@Suppress("GENERIC_VARIABLE_WRONG_DECLARATION", "MAGIC_NUMBER")
96105
val commentWindow: FC<CommentWindowProps> = FC { props ->
97-
98-
val columnCard = cardComponent(isBordered = false, hasBg = true, isNoPadding = false, isPaddingBottomNull = true, isFilling = true)
99-
100106
div {
101-
className = ClassName("row no-gutters mx-auto border-secondary")
107+
className = ClassName("shadow input-group row no-gutters mx-auto border-secondary")
102108
renderLeftColumn(
103109
props.comment.userAvatar,
104110
props.comment.userName,
105111
props.comment.userRating,
106112
)
107113
div {
108-
className = ClassName("card col-10 text-left border-secondary")
114+
className = ClassName("shadow-none card col-10 text-left border-0")
109115
val comment = props.comment
110116
div {
111-
className = ClassName("col")
112-
style = jso {
113-
background = "#F1F1F1".unsafeCast<Background>()
117+
className = ClassName("flex-wrap")
118+
style = jso { background = "#f1f1f1".unsafeCast<Background>() }
119+
span {
120+
className = ClassName("ml-1")
121+
+(comment.createDate?.toUnixCalendarFormat(TimeZone.currentSystemDefault()) ?: "Unknown")
114122
}
115-
+(comment.createDate?.toUnixCalendarFormat(TimeZone.currentSystemDefault()) ?: "Unknown")
116123
}
117-
columnCard {
124+
div {
125+
className = ClassName("shadow-none card card-body border-0")
118126
markdown(comment.message.split("\n").joinToString("\n\n"))
119127
}
120128
}
@@ -143,40 +151,36 @@ external interface NewCommentWindowProps : PropsWithChildren {
143151
/**
144152
* Information about current user
145153
*/
146-
var currentUserInfo: UserInfo?
154+
var currentUserInfo: UserInfo
147155
}
148156

149-
@Suppress(
150-
"MAGIC_NUMBER",
151-
)
157+
@Suppress("MAGIC_NUMBER")
152158
private fun ChildrenBuilder.renderLeftColumn(
153159
userAvatar: String?,
154160
name: String,
155161
rating: Long,
162+
color: String = "#f1f1f1",
156163
) {
157164
val (avatar, setAvatar) = useState(userAvatar?.let { "/api/$v1/avatar$it" } ?: "img/undraw_profile.svg")
158165

159166
div {
160-
className = ClassName("card card-body col-2 border-secondary")
167+
className = ClassName("input-group-prepend col-2")
161168
style = jso {
162-
background = "#e1e9ed".unsafeCast<Background>()
169+
background = color.unsafeCast<Background>()
163170
}
164171
div {
165172
className = ClassName("mb-0 font-weight-bold text-gray-800")
166173
form {
167174
div {
168-
className = ClassName("row justify-content-center g-3 ml-3 mr-3 pb-2 pt-2 border-bottom")
175+
className = ClassName("row justify-content-center g-3 ml-3 mr-3 pb-2 pt-2 border-bottom-0")
169176
div {
170177
className = ClassName("md-4 pl-0 pr-0")
171178
img {
172-
className =
173-
ClassName("avatar avatar-user width-full border color-bg-default rounded-circle")
179+
className = ClassName("avatar avatar-user width-full border color-bg-default rounded-circle")
174180
src = avatar
175181
height = 80.0
176182
width = 80.0
177-
onError = {
178-
setAvatar(AVATAR_PLACEHOLDER)
179-
}
183+
onError = { setAvatar(AVATAR_PLACEHOLDER) }
180184
}
181185
}
182186
div {

save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/fossgraph/FossGraphView.kt

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ val fossGraph: FC<FossGraphViewProps> = FC { props ->
4949
val navigate = useNavigate()
5050

5151
val (vulnerability, setVulnerability) = useState(VulnerabilityDto.empty)
52-
val (user, setUser) = useState(props.currentUserInfo)
5352
val (selectedMenu, setSelectedMenu) = useState(VulnerabilityTab.INFO)
5453

5554
val enrollUpdateRequest = useDeferredRequest {
@@ -87,15 +86,6 @@ val fossGraph: FC<FossGraphViewProps> = FC { props ->
8786
}
8887

8988
setVulnerability(vulnerabilityNew)
90-
91-
val userInfo: UserInfo = get(
92-
url = "$apiUrl/users/${props.currentUserInfo?.name}",
93-
headers = jsonHeaders,
94-
loadingHandler = ::noopLoadingHandler,
95-
)
96-
.decodeFromJsonString()
97-
98-
setUser(userInfo)
9989
}
10090

10191
displayModal(
@@ -118,7 +108,7 @@ val fossGraph: FC<FossGraphViewProps> = FC { props ->
118108
className = ClassName("")
119109

120110
val isSuperAdmin = props.currentUserInfo?.globalRole?.isHigherOrEqualThan(Role.SUPER_ADMIN) == true
121-
val isOwner = user?.id == vulnerability.userId
111+
val isOwner = props.currentUserInfo?.id == vulnerability.userId
122112

123113
div {
124114
className = ClassName("d-flex align-items-center justify-content-center mb-4")
@@ -226,7 +216,7 @@ val fossGraph: FC<FossGraphViewProps> = FC { props ->
226216
}
227217
VulnerabilityTab.COMMENTS -> vulnerabilityCommentTab {
228218
this.vulnerability = vulnerability
229-
this.currentUserInfo = user
219+
this.currentUserInfo = props.currentUserInfo
230220
}
231221
}
232222
}

save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/fossgraph/VulnerabilityCommentTab.kt

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,31 @@ import com.saveourtool.save.frontend.components.basic.newCommentWindow
99
import com.saveourtool.save.frontend.utils.*
1010
import com.saveourtool.save.info.UserInfo
1111

12+
import js.core.jso
1213
import react.FC
1314
import react.Props
1415
import react.dom.html.ReactHTML.div
16+
import react.dom.html.ReactHTML.h4
1517
import react.useState
1618
import web.cssom.ClassName
19+
import web.cssom.Cursor
1720

1821
import kotlinx.browser.window
1922

20-
val vulnerabilityCommentTab: FC<VulnerabilityCommentTabProps> = FC { props ->
23+
private const val DEFAULT_MAX_COMMENT_AMOUNT = 5
2124

25+
val vulnerabilityCommentTab: FC<VulnerabilityCommentTabProps> = FC { props ->
2226
val (comments, setComments) = useState(emptyList<CommentDto>())
23-
val (user, setUser) = useStateFromProps(props.currentUserInfo ?: UserInfo("Unknown"))
27+
val (isAllCommentsShown, setIsAllCommentsShown) = useState(false)
28+
29+
val display: List<CommentDto>.() -> Unit = {
30+
forEach { message ->
31+
div {
32+
className = ClassName("col-12 mt-4")
33+
commentWindow { comment = message }
34+
}
35+
}
36+
}
2437

2538
useRequest {
2639
val newComments = post(
@@ -48,21 +61,36 @@ val vulnerabilityCommentTab: FC<VulnerabilityCommentTabProps> = FC { props ->
4861
setComments(newComments)
4962
}
5063

51-
div {
52-
className = ClassName("col-12 mt-4")
53-
newCommentWindow {
54-
addComment = { enrollCommentsRequest() }
55-
currentUserInfo = user
64+
if (comments.size > DEFAULT_MAX_COMMENT_AMOUNT && !isAllCommentsShown) {
65+
comments.take(DEFAULT_MAX_COMMENT_AMOUNT / 2).display()
66+
div {
67+
className = ClassName("col-12 mt-4")
68+
onClick = { setIsAllCommentsShown(true) }
69+
style = jso { cursor = "pointer".unsafeCast<Cursor>() }
70+
h4 {
71+
className = ClassName("text-center card p-2 shadow")
72+
+"Show all ${comments.size} comments"
73+
}
5674
}
75+
comments.takeLast(DEFAULT_MAX_COMMENT_AMOUNT / 2).display()
76+
} else {
77+
comments.display()
5778
}
5879

59-
comments.forEach { message ->
80+
props.currentUserInfo?.let { userInfo ->
6081
div {
61-
className = ClassName("col-12 mt-4")
62-
commentWindow {
63-
comment = message
82+
className = ClassName("col-12 mt-5")
83+
newCommentWindow {
84+
addComment = { enrollCommentsRequest() }
85+
currentUserInfo = userInfo
6486
}
6587
}
88+
} ?: div {
89+
className = ClassName("col-12 mt-4")
90+
h4 {
91+
className = ClassName("text-center card p-2 shadow")
92+
+"Authorize in order to write comments"
93+
}
6694
}
6795
}
6896

save-frontend/src/main/kotlin/com/saveourtool/save/frontend/externals/fontawesome/Icons.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,7 @@ external val faGlobe: FontAwesomeIconModule
227227
@JsModule("@fortawesome/free-solid-svg-icons/faLink")
228228
@JsNonModule
229229
external val faLink: FontAwesomeIconModule
230+
231+
@JsModule("@fortawesome/free-solid-svg-icons/faPaperPlane")
232+
@JsNonModule
233+
external val faPaperPlane: FontAwesomeIconModule

0 commit comments

Comments
 (0)