From bff9f7f264b43e09ef000f989b4facc8b1564d82 Mon Sep 17 00:00:00 2001 From: StaNov Date: Fri, 3 Jan 2025 15:19:02 +0100 Subject: [PATCH 01/26] FE: Basic notifications dropdown --- .../component/layout/TopBar/Notifications.tsx | 118 ++++++++++++++++++ webapp/src/component/layout/TopBar/TopBar.tsx | 2 + 2 files changed, 120 insertions(+) create mode 100644 webapp/src/component/layout/TopBar/Notifications.tsx diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx new file mode 100644 index 0000000000..118eb659f4 --- /dev/null +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -0,0 +1,118 @@ +import { default as React, FunctionComponent, useState } from 'react'; +import { + IconButton, + List, ListItem, + ListItemButton, + ListItemText, + styled, +} from '@mui/material'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import { useHistory } from 'react-router-dom'; +import { Bell01 } from '@untitled-ui/icons-react'; +import { T, useTranslate } from '@tolgee/react'; + +const StyledMenu = styled(Menu)` + .MuiPaper-root { + margin-top: 5px; + } +`; + +const StyledIconButton = styled(IconButton)` + width: 40px; + height: 40px; + + img { + user-drag: none; + } +`; + +export const Notifications: FunctionComponent<{ className?: string }> = () => { + const { t } = useTranslate(); + + const handleOpen = (event: React.MouseEvent) => { + // @ts-ignore + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const [anchorEl, setAnchorEl] = useState(null); + + const history = useHistory(); + + const notifications = [ + { + id: 1, + linkedEntityId: 1000000001, + linkedEntityName: 'Task One', + }, + { + id: 2, + linkedEntityId: 1000000002, + linkedEntityName: 'Task Two', + }, + { + id: 3, + linkedEntityId: 1000000003, + linkedEntityName: 'Task Three', + }, + ]; + + return ( + <> + + + + + + {notifications.map((notification) => ( + { + handleClose(); + history.push( + `/projects/1/languages/language/${notification.linkedEntityId}` + ); + }} + > + + + ))} + {notifications?.length === 0 && ( + + + + )} + + + + ); +}; diff --git a/webapp/src/component/layout/TopBar/TopBar.tsx b/webapp/src/component/layout/TopBar/TopBar.tsx index 9aa38d1707..f232c2d808 100644 --- a/webapp/src/component/layout/TopBar/TopBar.tsx +++ b/webapp/src/component/layout/TopBar/TopBar.tsx @@ -12,6 +12,7 @@ import { UserMenu } from '../../security/UserMenu/UserMenu'; import { AdminInfo } from './AdminInfo'; import { QuickStartTopBarButton } from '../QuickStartGuide/QuickStartTopBarButton'; import { LanguageMenu } from 'tg.component/layout/TopBar/LanguageMenu'; +import { Notifications } from 'tg.component/layout/TopBar/Notifications'; export const TOP_BAR_HEIGHT = 52; @@ -121,6 +122,7 @@ export const TopBar: React.FC = ({ debuggingCustomerAccount={isDebuggingCustomerAccount} /> + {user && } {!hideQuickStart && } {!user && } {user && } From 6bb4a37c4bc62e095427071de638f30b2e12dc0b Mon Sep 17 00:00:00 2001 From: StaNov Date: Fri, 3 Jan 2025 15:55:21 +0100 Subject: [PATCH 02/26] FE + BE: Notifications getting fetched from BE --- .../v2/controllers/NotificationsController.kt | 47 ++++++++++++++++ .../component/layout/TopBar/Notifications.tsx | 24 ++------ webapp/src/service/apiSchema.generated.ts | 55 +++++++++++++++++++ 3 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt new file mode 100644 index 0000000000..5c68bd73fe --- /dev/null +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt @@ -0,0 +1,47 @@ +package io.tolgee.ee.api.v2.controllers + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import io.tolgee.security.authentication.AllowApiAccess +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.CrossOrigin +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@CrossOrigin(origins = ["*"]) +@RequestMapping( + value = [ + "/v2/notifications", + ], +) +@Tag(name = "Notifications", description = "Manipulates notifications") +class NotificationsController { + + @GetMapping + @Operation(summary = "Get notifications") + @AllowApiAccess + fun getNotifications(): ResponseEntity { + return ResponseEntity.ok( + NotificationsResponse( + listOf( + Notification(1, 1000000001, "Task One"), + Notification(2, 1000000002, "Task Two"), + Notification(3, 1000000003, "Task Three"), + Notification(4, 1000000004, "Task Four"), + ) + ) + ) + } +} + +data class NotificationsResponse( + val notifications: List, +) + +data class Notification( + val id: Long, + val linkedEntityId: Long, + val linkedEntityName: String, +) diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index 118eb659f4..756d4024f6 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -9,6 +9,7 @@ import { import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import { useHistory } from 'react-router-dom'; +import { useApiQuery } from 'tg.service/http/useQueryApi'; import { Bell01 } from '@untitled-ui/icons-react'; import { T, useTranslate } from '@tolgee/react'; @@ -43,23 +44,10 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { const history = useHistory(); - const notifications = [ - { - id: 1, - linkedEntityId: 1000000001, - linkedEntityName: 'Task One', - }, - { - id: 2, - linkedEntityId: 1000000002, - linkedEntityName: 'Task Two', - }, - { - id: 3, - linkedEntityId: 1000000003, - linkedEntityName: 'Task Three', - }, - ]; + const notifications = useApiQuery({ + url: '/v2/notifications', + method: 'get', + }); return ( <> @@ -89,7 +77,7 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { }} > - {notifications.map((notification) => ( + {notifications.data?.notifications.map((notification) => ( Date: Mon, 6 Jan 2025 14:49:47 +0100 Subject: [PATCH 03/26] BE: Fetching notifications from database --- .../kotlin/io/tolgee/model/Notification.kt | 31 +++++++++++++++++++ .../repository/NotificationRepository.kt | 27 ++++++++++++++++ .../io/tolgee/service/NotificationService.kt | 21 +++++++++++++ .../main/resources/db/changelog/schema.xml | 29 +++++++++++++++++ .../v2/controllers/NotificationsController.kt | 30 +++++++----------- .../component/layout/TopBar/Notifications.tsx | 8 ++--- webapp/src/service/apiSchema.generated.ts | 10 +++--- 7 files changed, 129 insertions(+), 27 deletions(-) create mode 100644 backend/data/src/main/kotlin/io/tolgee/model/Notification.kt create mode 100644 backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt create mode 100644 backend/data/src/main/kotlin/io/tolgee/service/NotificationService.kt diff --git a/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt b/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt new file mode 100644 index 0000000000..52a94aaa11 --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt @@ -0,0 +1,31 @@ +package io.tolgee.model + +import io.tolgee.model.task.Task +import jakarta.persistence.* +import java.util.* + +@Entity +@Table( + indexes = [ + Index(columnList = "user_id"), + ], +) +class Notification( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long = 0L, + + @ManyToOne(fetch = FetchType.LAZY) + var user: UserAccount, + + var createdAt: Date = Date(), + + @ManyToOne(fetch = FetchType.LAZY) + var project: Project? = null, + + @ManyToOne(fetch = FetchType.LAZY) + var originatingUser: UserAccount? = null, + + @ManyToOne(fetch = FetchType.LAZY) + var linkedTask: Task? = null, +) diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt new file mode 100644 index 0000000000..ceda5245bc --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt @@ -0,0 +1,27 @@ +package io.tolgee.repository + +import io.tolgee.model.Notification +import org.springframework.context.annotation.Lazy +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.stereotype.Repository + +@Repository +@Lazy +interface NotificationRepository : JpaRepository { + @Query( + """ + SELECT n + FROM Notification n + LEFT JOIN FETCH n.project + LEFT JOIN FETCH n.user AS u + LEFT JOIN FETCH n.originatingUser + LEFT JOIN FETCH n.linkedTask + WHERE u.id = :userId + ORDER BY n.id DESC + """, + ) + fun fetchNotificationsByUserId( + userId: Long, + ): List +} diff --git a/backend/data/src/main/kotlin/io/tolgee/service/NotificationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/NotificationService.kt new file mode 100644 index 0000000000..f35aed146a --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/service/NotificationService.kt @@ -0,0 +1,21 @@ +package io.tolgee.service + +import io.tolgee.repository.NotificationRepository +import org.springframework.stereotype.Service + +@Service +class NotificationService( + private val notificationRepository: NotificationRepository, +) { + fun getNotifications(userId: Long): List { + return notificationRepository.fetchNotificationsByUserId(userId) + .map { NotificationModel(it.id, it.linkedTask?.project?.id, it.linkedTask?.number, it.linkedTask?.name) } + } +} + +data class NotificationModel( + val id: Long, + val linkedProjectId: Long?, + val linkedTaskNumber: Long?, + val linkedTaskName: String?, +) diff --git a/backend/data/src/main/resources/db/changelog/schema.xml b/backend/data/src/main/resources/db/changelog/schema.xml index 9194b20b8d..f97e80b6ae 100644 --- a/backend/data/src/main/resources/db/changelog/schema.xml +++ b/backend/data/src/main/resources/db/changelog/schema.xml @@ -4052,4 +4052,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt index 5c68bd73fe..38f4fa1af7 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt @@ -3,7 +3,9 @@ package io.tolgee.ee.api.v2.controllers import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import io.tolgee.security.authentication.AllowApiAccess -import org.springframework.http.ResponseEntity +import io.tolgee.security.authentication.AuthenticationFacade +import io.tolgee.service.NotificationModel +import io.tolgee.service.NotificationService import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -17,31 +19,21 @@ import org.springframework.web.bind.annotation.RestController ], ) @Tag(name = "Notifications", description = "Manipulates notifications") -class NotificationsController { +class NotificationsController( + private val notificationService: NotificationService, + private val authenticationFacade: AuthenticationFacade, +) { @GetMapping @Operation(summary = "Get notifications") @AllowApiAccess - fun getNotifications(): ResponseEntity { - return ResponseEntity.ok( - NotificationsResponse( - listOf( - Notification(1, 1000000001, "Task One"), - Notification(2, 1000000002, "Task Two"), - Notification(3, 1000000003, "Task Three"), - Notification(4, 1000000004, "Task Four"), - ) - ) + fun getNotifications(): NotificationsResponse { + return NotificationsResponse( + notificationService.getNotifications(authenticationFacade.authenticatedUser.id) ) } } data class NotificationsResponse( - val notifications: List, -) - -data class Notification( - val id: Long, - val linkedEntityId: Long, - val linkedEntityName: String, + val notifications: List, ) diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index 756d4024f6..a089290623 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -47,7 +47,7 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { const notifications = useApiQuery({ url: '/v2/notifications', method: 'get', - }); + }).data?.notifications; return ( <> @@ -77,14 +77,14 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { }} > - {notifications.data?.notifications.map((notification) => ( + {notifications?.map((notification, i) => ( { handleClose(); history.push( - `/projects/1/languages/language/${notification.linkedEntityId}` + `/projects/${notification.linkedProjectId}/task?number=${notification.linkedTaskNumber}` ); }} > diff --git a/webapp/src/service/apiSchema.generated.ts b/webapp/src/service/apiSchema.generated.ts index 37b4e88008..c406a51133 100644 --- a/webapp/src/service/apiSchema.generated.ts +++ b/webapp/src/service/apiSchema.generated.ts @@ -4798,15 +4798,17 @@ export interface components { projectsWithDirectPermission: components["schemas"]["SimpleProjectModel"][]; avatar?: components["schemas"]["Avatar"]; }; - Notification: { + NotificationModel: { /** Format: int64 */ id: number; /** Format: int64 */ - linkedEntityId: number; - linkedEntityName: string; + linkedProjectId?: number; + /** Format: int64 */ + linkedTaskNumber?: number; + linkedTaskName?: string; }; NotificationsResponse: { - notifications: components["schemas"]["Notification"][]; + notifications: components["schemas"]["NotificationModel"][]; }; ApiKeyWithLanguagesModel: { /** From 410e9451d0d63a3c1115592018069606df5b8cf2 Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 7 Jan 2025 11:51:12 +0100 Subject: [PATCH 04/26] BE: Creating notifications on task assignee updated --- .../iterceptor/ActivityDatabaseInterceptor.kt | 2 +- .../iterceptor/PreCommitEventPublisher.kt | 8 ++- .../io/tolgee/events/OnEntityPreUpdate.kt | 2 + ...TaskAssigneeUpdatedNotificationListener.kt | 53 +++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt index 3ee1c35bd7..bdc9a51409 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt @@ -61,7 +61,7 @@ class ActivityDatabaseInterceptor : Interceptor, Logging { propertyNames: Array?, types: Array?, ): Boolean { - preCommitEventsPublisher.onUpdate(entity) + preCommitEventsPublisher.onUpdate(entity, previousState, propertyNames) interceptedEventsManager.onFieldModificationsActivity( entity, currentState, diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt index b18f25c064..49fbd196e1 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt @@ -12,8 +12,12 @@ class PreCommitEventPublisher(private val applicationContext: ApplicationContext applicationContext.publishEvent(OnEntityPrePersist(this, entity)) } - fun onUpdate(entity: Any?) { - applicationContext.publishEvent(OnEntityPreUpdate(this, entity)) + fun onUpdate( + entity: Any?, + previousState: Array?, + propertyNames: Array? + ) { + applicationContext.publishEvent(OnEntityPreUpdate(this, entity, previousState, propertyNames)) } fun onDelete(entity: Any?) { diff --git a/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt b/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt index 72b2802384..1c87043a64 100644 --- a/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt +++ b/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt @@ -6,4 +6,6 @@ import org.springframework.context.ApplicationEvent class OnEntityPreUpdate( override val source: PreCommitEventPublisher, override val entity: Any?, + val previousState: Array?, + val propertyNames: Array?, ) : ApplicationEvent(source), EntityPreCommitEvent diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt new file mode 100644 index 0000000000..be6035e908 --- /dev/null +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt @@ -0,0 +1,53 @@ +package io.tolgee.ee.service + +import io.tolgee.events.OnEntityPreUpdate +import io.tolgee.model.Notification +import io.tolgee.model.UserAccount +import io.tolgee.model.task.Task +import io.tolgee.repository.NotificationRepository +import io.tolgee.util.Logging +import io.tolgee.util.logger +import org.springframework.context.event.EventListener +import org.springframework.stereotype.Component + +@Component +class TaskAssigneeUpdatedNotificationListener( + private val notificationRepository: NotificationRepository, +) : Logging { + @EventListener + fun onTaskAssigneeChange(event: OnEntityPreUpdate) { + val entity = event.entity + + if (entity !is Task) + return + + getNewAssignees(event, entity).forEach { + notificationRepository.save( + Notification( + user = it, + linkedTask = entity, + originatingUser = entity.author, + project = entity.project, + ) + ) + } + } + + private fun getNewAssignees(event: OnEntityPreUpdate, entity: Task) : List { + val assigneesIndex = event.propertyNames?.indexOf("assignees") + + if (assigneesIndex == null || assigneesIndex == -1) { + logger.warn("'assignees' not found in 'propertyNames': ${event.propertyNames}") + return emptyList() + } + + val previousAssignees = event.previousState?.get(assigneesIndex) + + if (previousAssignees !is Iterable<*>) { + logger.warn("'previousAssignees' is not iterable: $previousAssignees") + return emptyList() + } + + return entity.assignees.filterNot { it in previousAssignees } + } +} From 67e21e317cbfe81e584fb38245fd840c8a543f6c Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 7 Jan 2025 14:06:10 +0100 Subject: [PATCH 05/26] BE: Move stuff around to the correct places --- .../v2/controllers/NotificationController.kt | 18 +++++++----------- .../notification/NotificationResponse.kt | 7 +++++++ .../service/notification/NotificationModel.kt | 8 ++++++++ .../{ => notification}/NotificationService.kt | 9 +-------- 4 files changed, 23 insertions(+), 19 deletions(-) rename ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt => backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt (70%) create mode 100644 backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationResponse.kt create mode 100644 backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt rename backend/data/src/main/kotlin/io/tolgee/service/{ => notification}/NotificationService.kt (73%) diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt similarity index 70% rename from ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt rename to backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt index 38f4fa1af7..b2725856c4 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/api/v2/controllers/NotificationsController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt @@ -1,11 +1,11 @@ -package io.tolgee.ee.api.v2.controllers +package io.tolgee.api.v2.controllers import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag +import io.tolgee.hateoas.notification.NotificationResponse import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authentication.AuthenticationFacade -import io.tolgee.service.NotificationModel -import io.tolgee.service.NotificationService +import io.tolgee.service.notification.NotificationService import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -19,21 +19,17 @@ import org.springframework.web.bind.annotation.RestController ], ) @Tag(name = "Notifications", description = "Manipulates notifications") -class NotificationsController( +class NotificationController( private val notificationService: NotificationService, private val authenticationFacade: AuthenticationFacade, ) { @GetMapping - @Operation(summary = "Get notifications") + @Operation(summary = "Gets notifications of the currently logged in user, newest is first.") @AllowApiAccess - fun getNotifications(): NotificationsResponse { - return NotificationsResponse( + fun getNotifications(): NotificationResponse { + return NotificationResponse( notificationService.getNotifications(authenticationFacade.authenticatedUser.id) ) } } - -data class NotificationsResponse( - val notifications: List, -) diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationResponse.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationResponse.kt new file mode 100644 index 0000000000..7d41e47acb --- /dev/null +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationResponse.kt @@ -0,0 +1,7 @@ +package io.tolgee.hateoas.notification + +import io.tolgee.service.notification.NotificationModel + +data class NotificationResponse( + val notifications: List, +) diff --git a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt new file mode 100644 index 0000000000..627bad5b5c --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt @@ -0,0 +1,8 @@ +package io.tolgee.service.notification + +data class NotificationModel( + val id: Long, + val linkedProjectId: Long?, + val linkedTaskNumber: Long?, + val linkedTaskName: String?, +) diff --git a/backend/data/src/main/kotlin/io/tolgee/service/NotificationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt similarity index 73% rename from backend/data/src/main/kotlin/io/tolgee/service/NotificationService.kt rename to backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt index f35aed146a..8be5f44fb5 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/NotificationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt @@ -1,4 +1,4 @@ -package io.tolgee.service +package io.tolgee.service.notification import io.tolgee.repository.NotificationRepository import org.springframework.stereotype.Service @@ -12,10 +12,3 @@ class NotificationService( .map { NotificationModel(it.id, it.linkedTask?.project?.id, it.linkedTask?.number, it.linkedTask?.name) } } } - -data class NotificationModel( - val id: Long, - val linkedProjectId: Long?, - val linkedTaskNumber: Long?, - val linkedTaskName: String?, -) From 874bada8c24915897294d145417fd5568bace1a9 Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 7 Jan 2025 14:19:44 +0100 Subject: [PATCH 06/26] Prettier --- webapp/src/component/layout/TopBar/Notifications.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index a089290623..f02e773032 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -1,17 +1,16 @@ import { default as React, FunctionComponent, useState } from 'react'; import { IconButton, - List, ListItem, + List, + ListItem, ListItemButton, - ListItemText, styled, } from '@mui/material'; import Menu from '@mui/material/Menu'; -import MenuItem from '@mui/material/MenuItem'; import { useHistory } from 'react-router-dom'; import { useApiQuery } from 'tg.service/http/useQueryApi'; import { Bell01 } from '@untitled-ui/icons-react'; -import { T, useTranslate } from '@tolgee/react'; +import { T } from '@tolgee/react'; const StyledMenu = styled(Menu)` .MuiPaper-root { @@ -29,8 +28,6 @@ const StyledIconButton = styled(IconButton)` `; export const Notifications: FunctionComponent<{ className?: string }> = () => { - const { t } = useTranslate(); - const handleOpen = (event: React.MouseEvent) => { // @ts-ignore setAnchorEl(event.currentTarget); From 123807e12655b9d3c1645cec0cfcabc1823b3c14 Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 7 Jan 2025 14:20:59 +0100 Subject: [PATCH 07/26] ktlint --- .../api/v2/controllers/NotificationController.kt | 3 +-- .../activity/iterceptor/PreCommitEventPublisher.kt | 2 +- .../src/main/kotlin/io/tolgee/model/Notification.kt | 5 ----- .../io/tolgee/repository/NotificationRepository.kt | 4 +--- .../service/TaskAssigneeUpdatedNotificationListener.kt | 10 +++++++--- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt index b2725856c4..f13db70b60 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt @@ -23,13 +23,12 @@ class NotificationController( private val notificationService: NotificationService, private val authenticationFacade: AuthenticationFacade, ) { - @GetMapping @Operation(summary = "Gets notifications of the currently logged in user, newest is first.") @AllowApiAccess fun getNotifications(): NotificationResponse { return NotificationResponse( - notificationService.getNotifications(authenticationFacade.authenticatedUser.id) + notificationService.getNotifications(authenticationFacade.authenticatedUser.id), ) } } diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt index 49fbd196e1..14048260e4 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt @@ -15,7 +15,7 @@ class PreCommitEventPublisher(private val applicationContext: ApplicationContext fun onUpdate( entity: Any?, previousState: Array?, - propertyNames: Array? + propertyNames: Array?, ) { applicationContext.publishEvent(OnEntityPreUpdate(this, entity, previousState, propertyNames)) } diff --git a/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt b/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt index 52a94aaa11..f582295ef2 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt @@ -14,18 +14,13 @@ class Notification( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = 0L, - @ManyToOne(fetch = FetchType.LAZY) var user: UserAccount, - var createdAt: Date = Date(), - @ManyToOne(fetch = FetchType.LAZY) var project: Project? = null, - @ManyToOne(fetch = FetchType.LAZY) var originatingUser: UserAccount? = null, - @ManyToOne(fetch = FetchType.LAZY) var linkedTask: Task? = null, ) diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt index ceda5245bc..8debb0a378 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt @@ -21,7 +21,5 @@ interface NotificationRepository : JpaRepository { ORDER BY n.id DESC """, ) - fun fetchNotificationsByUserId( - userId: Long, - ): List + fun fetchNotificationsByUserId(userId: Long): List } diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt index be6035e908..ea60b42b24 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt @@ -18,8 +18,9 @@ class TaskAssigneeUpdatedNotificationListener( fun onTaskAssigneeChange(event: OnEntityPreUpdate) { val entity = event.entity - if (entity !is Task) + if (entity !is Task) { return + } getNewAssignees(event, entity).forEach { notificationRepository.save( @@ -28,12 +29,15 @@ class TaskAssigneeUpdatedNotificationListener( linkedTask = entity, originatingUser = entity.author, project = entity.project, - ) + ), ) } } - private fun getNewAssignees(event: OnEntityPreUpdate, entity: Task) : List { + private fun getNewAssignees( + event: OnEntityPreUpdate, + entity: Task, + ): List { val assigneesIndex = event.propertyNames?.indexOf("assignees") if (assigneesIndex == null || assigneesIndex == -1) { From 4c23e163c0e6a9ffd5707337947e95718c43ae9b Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 7 Jan 2025 15:38:57 +0100 Subject: [PATCH 08/26] Notification derives from StandardAuditModel --- .../kotlin/io/tolgee/model/Notification.kt | 20 +++++++++---------- ...TaskAssigneeUpdatedNotificationListener.kt | 12 +++++------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt b/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt index f582295ef2..51e51fbdbc 100644 --- a/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt +++ b/backend/data/src/main/kotlin/io/tolgee/model/Notification.kt @@ -2,7 +2,6 @@ package io.tolgee.model import io.tolgee.model.task.Task import jakarta.persistence.* -import java.util.* @Entity @Table( @@ -10,17 +9,16 @@ import java.util.* Index(columnList = "user_id"), ], ) -class Notification( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long = 0L, +class Notification : StandardAuditModel() { @ManyToOne(fetch = FetchType.LAZY) - var user: UserAccount, - var createdAt: Date = Date(), + lateinit var user: UserAccount + @ManyToOne(fetch = FetchType.LAZY) - var project: Project? = null, + var project: Project? = null + @ManyToOne(fetch = FetchType.LAZY) - var originatingUser: UserAccount? = null, + var originatingUser: UserAccount? = null + @ManyToOne(fetch = FetchType.LAZY) - var linkedTask: Task? = null, -) + var linkedTask: Task? = null +} diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt index ea60b42b24..d9abee66c5 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt @@ -24,12 +24,12 @@ class TaskAssigneeUpdatedNotificationListener( getNewAssignees(event, entity).forEach { notificationRepository.save( - Notification( - user = it, - linkedTask = entity, - originatingUser = entity.author, - project = entity.project, - ), + Notification().apply { + user = it + linkedTask = entity + originatingUser = entity.author + project = entity.project + }, ) } } From e77626a3ae7ed554025ba52f6f3eaba77c1b4b46 Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 7 Jan 2025 15:47:26 +0100 Subject: [PATCH 09/26] linkedProjectId -> projectId --- .../kotlin/io/tolgee/service/notification/NotificationModel.kt | 2 +- webapp/src/component/layout/TopBar/Notifications.tsx | 2 +- webapp/src/service/apiSchema.generated.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt index 627bad5b5c..434045220b 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt @@ -2,7 +2,7 @@ package io.tolgee.service.notification data class NotificationModel( val id: Long, - val linkedProjectId: Long?, + val projectId: Long?, val linkedTaskNumber: Long?, val linkedTaskName: String?, ) diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index f02e773032..81a46a3f51 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -81,7 +81,7 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { onClick={() => { handleClose(); history.push( - `/projects/${notification.linkedProjectId}/task?number=${notification.linkedTaskNumber}` + `/projects/${notification.projectId}/task?number=${notification.linkedTaskNumber}` ); }} > diff --git a/webapp/src/service/apiSchema.generated.ts b/webapp/src/service/apiSchema.generated.ts index c406a51133..89b3316d4e 100644 --- a/webapp/src/service/apiSchema.generated.ts +++ b/webapp/src/service/apiSchema.generated.ts @@ -4802,7 +4802,7 @@ export interface components { /** Format: int64 */ id: number; /** Format: int64 */ - linkedProjectId?: number; + projectId?: number; /** Format: int64 */ linkedTaskNumber?: number; linkedTaskName?: string; From 93f8739078a138bf0cd97537b54e4c3193eda597 Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 7 Jan 2025 15:54:35 +0100 Subject: [PATCH 10/26] Schema updated --- backend/data/src/main/resources/db/changelog/schema.xml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/data/src/main/resources/db/changelog/schema.xml b/backend/data/src/main/resources/db/changelog/schema.xml index f97e80b6ae..2a9117ed0c 100644 --- a/backend/data/src/main/resources/db/changelog/schema.xml +++ b/backend/data/src/main/resources/db/changelog/schema.xml @@ -4054,10 +4054,15 @@ - + - + + + + + + From 11970509b52b3d82f4faf0f0664a0b2c2f4498a6 Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 7 Jan 2025 16:27:04 +0100 Subject: [PATCH 11/26] BE: Move creating notifications on task assignee updated to AssigneeNotificationService --- .../iterceptor/ActivityDatabaseInterceptor.kt | 2 +- .../iterceptor/PreCommitEventPublisher.kt | 8 +-- .../io/tolgee/events/OnEntityPreUpdate.kt | 2 - .../notification/NotificationService.kt | 5 ++ .../ee/service/AssigneeNotificationService.kt | 10 ++++ ...TaskAssigneeUpdatedNotificationListener.kt | 57 ------------------- 6 files changed, 18 insertions(+), 66 deletions(-) delete mode 100644 ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt index bdc9a51409..3ee1c35bd7 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/ActivityDatabaseInterceptor.kt @@ -61,7 +61,7 @@ class ActivityDatabaseInterceptor : Interceptor, Logging { propertyNames: Array?, types: Array?, ): Boolean { - preCommitEventsPublisher.onUpdate(entity, previousState, propertyNames) + preCommitEventsPublisher.onUpdate(entity) interceptedEventsManager.onFieldModificationsActivity( entity, currentState, diff --git a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt index 14048260e4..b18f25c064 100644 --- a/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt +++ b/backend/data/src/main/kotlin/io/tolgee/activity/iterceptor/PreCommitEventPublisher.kt @@ -12,12 +12,8 @@ class PreCommitEventPublisher(private val applicationContext: ApplicationContext applicationContext.publishEvent(OnEntityPrePersist(this, entity)) } - fun onUpdate( - entity: Any?, - previousState: Array?, - propertyNames: Array?, - ) { - applicationContext.publishEvent(OnEntityPreUpdate(this, entity, previousState, propertyNames)) + fun onUpdate(entity: Any?) { + applicationContext.publishEvent(OnEntityPreUpdate(this, entity)) } fun onDelete(entity: Any?) { diff --git a/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt b/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt index 1c87043a64..72b2802384 100644 --- a/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt +++ b/backend/data/src/main/kotlin/io/tolgee/events/OnEntityPreUpdate.kt @@ -6,6 +6,4 @@ import org.springframework.context.ApplicationEvent class OnEntityPreUpdate( override val source: PreCommitEventPublisher, override val entity: Any?, - val previousState: Array?, - val propertyNames: Array?, ) : ApplicationEvent(source), EntityPreCommitEvent diff --git a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt index 8be5f44fb5..0aae2c5ec5 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt @@ -1,5 +1,6 @@ package io.tolgee.service.notification +import io.tolgee.model.Notification import io.tolgee.repository.NotificationRepository import org.springframework.stereotype.Service @@ -11,4 +12,8 @@ class NotificationService( return notificationRepository.fetchNotificationsByUserId(userId) .map { NotificationModel(it.id, it.linkedTask?.project?.id, it.linkedTask?.number, it.linkedTask?.name) } } + + fun createNotification(notification: Notification) { + notificationRepository.save(notification) + } } diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt index 56cf668675..9474efd0bc 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt @@ -4,9 +4,12 @@ import io.sentry.Sentry import io.tolgee.component.FrontendUrlProvider import io.tolgee.component.email.TolgeeEmailSender import io.tolgee.dtos.misc.EmailParams +import io.tolgee.model.Notification import io.tolgee.model.UserAccount import io.tolgee.model.enums.TaskType import io.tolgee.model.task.Task +import io.tolgee.repository.NotificationRepository +import io.tolgee.service.notification.NotificationService import io.tolgee.util.Logging import io.tolgee.util.logger import org.springframework.stereotype.Component @@ -15,11 +18,18 @@ import org.springframework.stereotype.Component class AssigneeNotificationService( private val tolgeeEmailSender: TolgeeEmailSender, private val frontendUrlProvider: FrontendUrlProvider, + private val notificationService: NotificationService, ) : Logging { fun notifyNewAssignee( user: UserAccount, task: Task, ) { + notificationService.createNotification(Notification().apply { + this.user = user + this.linkedTask = task + this.project = task.project + this.originatingUser = task.author + }) val taskUrl = getTaskUrl(task.project.id, task.number) val myTasksUrl = getMyTasksUrl() diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt deleted file mode 100644 index d9abee66c5..0000000000 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskAssigneeUpdatedNotificationListener.kt +++ /dev/null @@ -1,57 +0,0 @@ -package io.tolgee.ee.service - -import io.tolgee.events.OnEntityPreUpdate -import io.tolgee.model.Notification -import io.tolgee.model.UserAccount -import io.tolgee.model.task.Task -import io.tolgee.repository.NotificationRepository -import io.tolgee.util.Logging -import io.tolgee.util.logger -import org.springframework.context.event.EventListener -import org.springframework.stereotype.Component - -@Component -class TaskAssigneeUpdatedNotificationListener( - private val notificationRepository: NotificationRepository, -) : Logging { - @EventListener - fun onTaskAssigneeChange(event: OnEntityPreUpdate) { - val entity = event.entity - - if (entity !is Task) { - return - } - - getNewAssignees(event, entity).forEach { - notificationRepository.save( - Notification().apply { - user = it - linkedTask = entity - originatingUser = entity.author - project = entity.project - }, - ) - } - } - - private fun getNewAssignees( - event: OnEntityPreUpdate, - entity: Task, - ): List { - val assigneesIndex = event.propertyNames?.indexOf("assignees") - - if (assigneesIndex == null || assigneesIndex == -1) { - logger.warn("'assignees' not found in 'propertyNames': ${event.propertyNames}") - return emptyList() - } - - val previousAssignees = event.previousState?.get(assigneesIndex) - - if (previousAssignees !is Iterable<*>) { - logger.warn("'previousAssignees' is not iterable: $previousAssignees") - return emptyList() - } - - return entity.assignees.filterNot { it in previousAssignees } - } -} From 3848eb181388a9fd4becf587b0c28a21a8323fe3 Mon Sep 17 00:00:00 2001 From: StaNov Date: Wed, 8 Jan 2025 14:23:37 +0100 Subject: [PATCH 12/26] API task model --- .../v2/controllers/NotificationController.kt | 20 +++++++++--- .../notification/NotificationEnhancer.kt | 16 ++++++++++ .../hateoas/notification/NotificationModel.kt | 11 +++++++ .../NotificationModelAssembler.kt | 31 +++++++++++++++++++ .../notification/NotificationResponse.kt | 7 ----- .../repository/NotificationRepository.kt | 4 ++- .../service/notification/NotificationModel.kt | 8 ----- .../notification/NotificationService.kt | 7 +++-- .../ee/component/TaskNotificationEnhancer.kt | 25 +++++++++++++++ .../io/tolgee/ee/service/TaskService.kt | 5 +++ .../component/layout/TopBar/Notifications.tsx | 7 +++-- webapp/src/service/apiSchema.generated.ts | 23 ++++++++++---- 12 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt create mode 100644 backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt create mode 100644 backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt delete mode 100644 backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationResponse.kt delete mode 100644 backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt create mode 100644 ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt diff --git a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt index f13db70b60..9dbfe46de1 100644 --- a/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt +++ b/backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/NotificationController.kt @@ -2,10 +2,17 @@ package io.tolgee.api.v2.controllers import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag -import io.tolgee.hateoas.notification.NotificationResponse +import io.tolgee.hateoas.notification.NotificationEnhancer +import io.tolgee.hateoas.notification.NotificationModel +import io.tolgee.hateoas.notification.NotificationModelAssembler +import io.tolgee.model.Notification import io.tolgee.security.authentication.AllowApiAccess import io.tolgee.security.authentication.AuthenticationFacade import io.tolgee.service.notification.NotificationService +import org.springdoc.core.annotations.ParameterObject +import org.springframework.data.domain.Pageable +import org.springframework.data.web.PagedResourcesAssembler +import org.springframework.hateoas.PagedModel import org.springframework.web.bind.annotation.CrossOrigin import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -22,13 +29,16 @@ import org.springframework.web.bind.annotation.RestController class NotificationController( private val notificationService: NotificationService, private val authenticationFacade: AuthenticationFacade, + private val enhancers: List, + private val pagedResourcesAssembler: PagedResourcesAssembler, ) { @GetMapping @Operation(summary = "Gets notifications of the currently logged in user, newest is first.") @AllowApiAccess - fun getNotifications(): NotificationResponse { - return NotificationResponse( - notificationService.getNotifications(authenticationFacade.authenticatedUser.id), - ) + fun getNotifications( + @ParameterObject pageable: Pageable, + ): PagedModel { + val notifications = notificationService.getNotifications(authenticationFacade.authenticatedUser.id, pageable) + return pagedResourcesAssembler.toModel(notifications, NotificationModelAssembler(enhancers, notifications)) } } diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt new file mode 100644 index 0000000000..0e2dd5e9e3 --- /dev/null +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt @@ -0,0 +1,16 @@ +package io.tolgee.hateoas.notification + +import io.tolgee.model.Notification + +/** + * Dynamic component enhancing notifications by additional information if eligible, + * e.g. adding linked task. + */ +fun interface NotificationEnhancer { + + /** + * Takes list of input Notification and output NotificationModel. + * It iterates over the pairs and alters the output NotificationModel by enhancing it of the new information. + */ + fun enhanceNotifications(notifications: Collection>) +} diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt new file mode 100644 index 0000000000..c4411c8765 --- /dev/null +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt @@ -0,0 +1,11 @@ +package io.tolgee.hateoas.notification + +import io.tolgee.hateoas.task.TaskModel +import org.springframework.hateoas.RepresentationModel +import java.io.Serializable + +data class NotificationModel( + val id: Long, + val projectId: Long?, + var linkedTask: TaskModel? = null, +) : RepresentationModel(), Serializable diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt new file mode 100644 index 0000000000..26313bfc16 --- /dev/null +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt @@ -0,0 +1,31 @@ +package io.tolgee.hateoas.notification + +import io.tolgee.api.v2.controllers.NotificationController +import io.tolgee.model.Notification +import org.springframework.data.domain.Page +import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport + +class NotificationModelAssembler( + private val enhancers: List, + private val notifications: Page, +) : RepresentationModelAssemblerSupport( + NotificationController::class.java, + NotificationModel::class.java, + ) { + private val toReturn = run { + val notificationsWithModel = notifications.content.map { notification -> + notification to NotificationModel( + id = notification.id, + projectId = notification.project?.id, + ) + } + enhancers.forEach { enhancer -> + enhancer.enhanceNotifications(notificationsWithModel) + } + notificationsWithModel.toMap() + } + + override fun toModel(view: Notification): NotificationModel { + return toReturn[view] ?: throw IllegalStateException("Notification $view was not found") + } +} diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationResponse.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationResponse.kt deleted file mode 100644 index 7d41e47acb..0000000000 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationResponse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.tolgee.hateoas.notification - -import io.tolgee.service.notification.NotificationModel - -data class NotificationResponse( - val notifications: List, -) diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt index 8debb0a378..907f16c7d8 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt @@ -2,6 +2,8 @@ package io.tolgee.repository import io.tolgee.model.Notification import org.springframework.context.annotation.Lazy +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository @@ -21,5 +23,5 @@ interface NotificationRepository : JpaRepository { ORDER BY n.id DESC """, ) - fun fetchNotificationsByUserId(userId: Long): List + fun fetchNotificationsByUserId(userId: Long, pageable: Pageable): Page } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt deleted file mode 100644 index 434045220b..0000000000 --- a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationModel.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.tolgee.service.notification - -data class NotificationModel( - val id: Long, - val projectId: Long?, - val linkedTaskNumber: Long?, - val linkedTaskName: String?, -) diff --git a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt index 0aae2c5ec5..11bb75700a 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt @@ -2,15 +2,16 @@ package io.tolgee.service.notification import io.tolgee.model.Notification import io.tolgee.repository.NotificationRepository +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service @Service class NotificationService( private val notificationRepository: NotificationRepository, ) { - fun getNotifications(userId: Long): List { - return notificationRepository.fetchNotificationsByUserId(userId) - .map { NotificationModel(it.id, it.linkedTask?.project?.id, it.linkedTask?.number, it.linkedTask?.name) } + fun getNotifications(userId: Long, pageable: Pageable): Page { + return notificationRepository.fetchNotificationsByUserId(userId, pageable) } fun createNotification(notification: Notification) { diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt new file mode 100644 index 0000000000..b8c02ee897 --- /dev/null +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt @@ -0,0 +1,25 @@ +package io.tolgee.ee.component + +import io.tolgee.ee.api.v2.hateoas.assemblers.TaskModelAssembler +import io.tolgee.ee.service.TaskService +import io.tolgee.hateoas.notification.NotificationEnhancer +import io.tolgee.hateoas.notification.NotificationModel +import io.tolgee.model.Notification +import org.springframework.stereotype.Component + +@Component +class TaskNotificationEnhancer( + private val taskService: TaskService, + private val taskModelAssembler: TaskModelAssembler, +) : NotificationEnhancer { + override fun enhanceNotifications(notifications: Collection>) { + val taskIds = notifications.mapNotNull { it.first.linkedTask?.id } + val convertedTasks = taskService.getTasksWithScope(taskIds) + + notifications.forEach { (source, target) -> + target.linkedTask = + convertedTasks.find { it.project.id == source.project?.id && it.number == source.linkedTask?.number } + ?.let { taskModelAssembler.toModel(it) } + } + } +} diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt index 17ad905d26..07c7ee44cb 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt @@ -468,6 +468,11 @@ class TaskService( } } + fun getTasksWithScope(taskIds: List): List { + val tasks = taskRepository.findAllById(taskIds) + return getTasksWithScope(tasks) + } + private fun getTasksWithScope(tasks: Collection): List { val scopes = taskRepository.getTasksScopes(tasks) return tasks.map { task -> diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index 81a46a3f51..0a5abf67fb 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -44,7 +44,8 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { const notifications = useApiQuery({ url: '/v2/notifications', method: 'get', - }).data?.notifications; + query: { size: 10000 }, + }).data?._embedded?.notificationModelList; return ( <> @@ -81,13 +82,13 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { onClick={() => { handleClose(); history.push( - `/projects/${notification.projectId}/task?number=${notification.linkedTaskNumber}` + `/projects/${notification.projectId}/task?number=${notification.linkedTask?.number}` ); }} > ))} diff --git a/webapp/src/service/apiSchema.generated.ts b/webapp/src/service/apiSchema.generated.ts index 89b3316d4e..b24fac81bb 100644 --- a/webapp/src/service/apiSchema.generated.ts +++ b/webapp/src/service/apiSchema.generated.ts @@ -4803,12 +4803,13 @@ export interface components { id: number; /** Format: int64 */ projectId?: number; - /** Format: int64 */ - linkedTaskNumber?: number; - linkedTaskName?: string; + linkedTask?: components["schemas"]["TaskModel"]; }; - NotificationsResponse: { - notifications: components["schemas"]["NotificationModel"][]; + PagedModelNotificationModel: { + _embedded?: { + notificationModelList?: components["schemas"]["NotificationModel"][]; + }; + page?: components["schemas"]["PageMetadata"]; }; ApiKeyWithLanguagesModel: { /** @@ -18060,11 +18061,21 @@ export interface operations { }; }; getNotifications: { + parameters: { + query: { + /** Zero-based page index (0..N) */ + page?: number; + /** The size of the page to be returned */ + size?: number; + /** Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: string[]; + }; + }; responses: { /** OK */ 200: { content: { - "application/json": components["schemas"]["NotificationsResponse"]; + "application/json": components["schemas"]["PagedModelNotificationModel"]; }; }; /** Bad Request */ From d4086b8952b992dcc1aa6bcb89a943e04deeca5f Mon Sep 17 00:00:00 2001 From: StaNov Date: Wed, 8 Jan 2025 14:51:24 +0100 Subject: [PATCH 13/26] Fetching project instead of just ID --- .../hateoas/notification/NotificationModel.kt | 4 +++- .../NotificationModelAssembler.kt | 1 - .../ProjectNotificationEnhancer.kt | 21 +++++++++++++++++++ .../tolgee/service/project/ProjectService.kt | 4 ++++ .../component/layout/TopBar/Notifications.tsx | 2 +- webapp/src/service/apiSchema.generated.ts | 3 +-- 6 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt index c4411c8765..2ea3535396 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt @@ -1,11 +1,13 @@ package io.tolgee.hateoas.notification +import io.tolgee.hateoas.project.ProjectModel +import io.tolgee.hateoas.project.SimpleProjectModel import io.tolgee.hateoas.task.TaskModel import org.springframework.hateoas.RepresentationModel import java.io.Serializable data class NotificationModel( val id: Long, - val projectId: Long?, + var project: SimpleProjectModel? = null, var linkedTask: TaskModel? = null, ) : RepresentationModel(), Serializable diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt index 26313bfc16..6593fb12e1 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt @@ -16,7 +16,6 @@ class NotificationModelAssembler( val notificationsWithModel = notifications.content.map { notification -> notification to NotificationModel( id = notification.id, - projectId = notification.project?.id, ) } enhancers.forEach { enhancer -> diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt new file mode 100644 index 0000000000..dfb00ede89 --- /dev/null +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt @@ -0,0 +1,21 @@ +package io.tolgee.hateoas.notification + +import io.tolgee.hateoas.project.SimpleProjectModelAssembler +import io.tolgee.model.Notification +import io.tolgee.service.project.ProjectService +import org.springframework.stereotype.Component + +@Component +class ProjectNotificationEnhancer( + private val projectService: ProjectService, + private val simpleProjectModelAssembler: SimpleProjectModelAssembler +) : NotificationEnhancer { + override fun enhanceNotifications(notifications: Collection>) { + val projectIds = notifications.mapNotNull { it.first.project?.id }.distinct() + val projects = projectService.findAll(projectIds).associateBy { it.id } + + notifications.forEach { (source, target) -> + target.project = source.project?.id.let { projects[it] }?.let { simpleProjectModelAssembler.toModel(it) } + } + } +} diff --git a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt index bbbb6f18c3..6638c53a55 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectService.kt @@ -136,6 +136,10 @@ class ProjectService( return projectRepository.find(id) } + fun findAll(ids: List): List { + return projectRepository.findAllById(ids) + } + @Transactional fun getView(id: Long): ProjectWithLanguagesView { val perms = permissionService.getProjectPermissionData(id, authenticationFacade.authenticatedUser.id) diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index 0a5abf67fb..7a6a477f03 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -82,7 +82,7 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { onClick={() => { handleClose(); history.push( - `/projects/${notification.projectId}/task?number=${notification.linkedTask?.number}` + `/projects/${notification.project?.id}/task?number=${notification.linkedTask?.number}` ); }} > diff --git a/webapp/src/service/apiSchema.generated.ts b/webapp/src/service/apiSchema.generated.ts index b24fac81bb..8ea03a698e 100644 --- a/webapp/src/service/apiSchema.generated.ts +++ b/webapp/src/service/apiSchema.generated.ts @@ -4801,8 +4801,7 @@ export interface components { NotificationModel: { /** Format: int64 */ id: number; - /** Format: int64 */ - projectId?: number; + project?: components["schemas"]["SimpleProjectModel"]; linkedTask?: components["schemas"]["TaskModel"]; }; PagedModelNotificationModel: { From 67393e11961d6e50fe1dedf4088ccf6878eb94ec Mon Sep 17 00:00:00 2001 From: StaNov Date: Wed, 8 Jan 2025 14:55:03 +0100 Subject: [PATCH 14/26] ktlint --- .../notification/NotificationEnhancer.kt | 1 - .../hateoas/notification/NotificationModel.kt | 1 - .../NotificationModelAssembler.kt | 29 ++++++++++--------- .../ProjectNotificationEnhancer.kt | 2 +- .../repository/NotificationRepository.kt | 5 +++- .../notification/NotificationService.kt | 5 +++- .../ee/service/AssigneeNotificationService.kt | 15 +++++----- 7 files changed, 33 insertions(+), 25 deletions(-) diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt index 0e2dd5e9e3..0d0335f431 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt @@ -7,7 +7,6 @@ import io.tolgee.model.Notification * e.g. adding linked task. */ fun interface NotificationEnhancer { - /** * Takes list of input Notification and output NotificationModel. * It iterates over the pairs and alters the output NotificationModel by enhancing it of the new information. diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt index 2ea3535396..4fd5bd7cba 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModel.kt @@ -1,6 +1,5 @@ package io.tolgee.hateoas.notification -import io.tolgee.hateoas.project.ProjectModel import io.tolgee.hateoas.project.SimpleProjectModel import io.tolgee.hateoas.task.TaskModel import org.springframework.hateoas.RepresentationModel diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt index 6593fb12e1..06bb08f3da 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt @@ -6,25 +6,28 @@ import org.springframework.data.domain.Page import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport class NotificationModelAssembler( - private val enhancers: List, - private val notifications: Page, + private val enhancers: List, + private val notifications: Page, ) : RepresentationModelAssemblerSupport( NotificationController::class.java, NotificationModel::class.java, ) { - private val toReturn = run { - val notificationsWithModel = notifications.content.map { notification -> - notification to NotificationModel( - id = notification.id, + private val toReturn = + run { + val notificationsWithModel = + notifications.content.map { notification -> + notification to + NotificationModel( + id = notification.id, ) } - enhancers.forEach { enhancer -> - enhancer.enhanceNotifications(notificationsWithModel) - } - notificationsWithModel.toMap() + enhancers.forEach { enhancer -> + enhancer.enhanceNotifications(notificationsWithModel) + } + notificationsWithModel.toMap() } - override fun toModel(view: Notification): NotificationModel { - return toReturn[view] ?: throw IllegalStateException("Notification $view was not found") - } + override fun toModel(view: Notification): NotificationModel { + return toReturn[view] ?: throw IllegalStateException("Notification $view was not found") + } } diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt index dfb00ede89..152e08dfa9 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt @@ -8,7 +8,7 @@ import org.springframework.stereotype.Component @Component class ProjectNotificationEnhancer( private val projectService: ProjectService, - private val simpleProjectModelAssembler: SimpleProjectModelAssembler + private val simpleProjectModelAssembler: SimpleProjectModelAssembler, ) : NotificationEnhancer { override fun enhanceNotifications(notifications: Collection>) { val projectIds = notifications.mapNotNull { it.first.project?.id }.distinct() diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt index 907f16c7d8..8489b17751 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt @@ -23,5 +23,8 @@ interface NotificationRepository : JpaRepository { ORDER BY n.id DESC """, ) - fun fetchNotificationsByUserId(userId: Long, pageable: Pageable): Page + fun fetchNotificationsByUserId( + userId: Long, + pageable: Pageable, + ): Page } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt index 11bb75700a..6676153713 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt @@ -10,7 +10,10 @@ import org.springframework.stereotype.Service class NotificationService( private val notificationRepository: NotificationRepository, ) { - fun getNotifications(userId: Long, pageable: Pageable): Page { + fun getNotifications( + userId: Long, + pageable: Pageable, + ): Page { return notificationRepository.fetchNotificationsByUserId(userId, pageable) } diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt index 9474efd0bc..bb5f2f3fc0 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt @@ -8,7 +8,6 @@ import io.tolgee.model.Notification import io.tolgee.model.UserAccount import io.tolgee.model.enums.TaskType import io.tolgee.model.task.Task -import io.tolgee.repository.NotificationRepository import io.tolgee.service.notification.NotificationService import io.tolgee.util.Logging import io.tolgee.util.logger @@ -24,12 +23,14 @@ class AssigneeNotificationService( user: UserAccount, task: Task, ) { - notificationService.createNotification(Notification().apply { - this.user = user - this.linkedTask = task - this.project = task.project - this.originatingUser = task.author - }) + notificationService.createNotification( + Notification().apply { + this.user = user + this.linkedTask = task + this.project = task.project + this.originatingUser = task.author + }, + ) val taskUrl = getTaskUrl(task.project.id, task.number) val myTasksUrl = getMyTasksUrl() From d8e776f14fd69b67ff2114b876b66ab79c26146e Mon Sep 17 00:00:00 2001 From: StaNov Date: Thu, 9 Jan 2025 13:04:12 +0100 Subject: [PATCH 15/26] Change collection of pairs to map --- .../hateoas/notification/NotificationEnhancer.kt | 2 +- .../notification/NotificationModelAssembler.kt | 15 +++++++-------- .../notification/ProjectNotificationEnhancer.kt | 4 ++-- .../ee/component/TaskNotificationEnhancer.kt | 4 ++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt index 0d0335f431..2518693234 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationEnhancer.kt @@ -11,5 +11,5 @@ fun interface NotificationEnhancer { * Takes list of input Notification and output NotificationModel. * It iterates over the pairs and alters the output NotificationModel by enhancing it of the new information. */ - fun enhanceNotifications(notifications: Collection>) + fun enhanceNotifications(notifications: Map) } diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt index 06bb08f3da..fb86ac9e2c 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/NotificationModelAssembler.kt @@ -12,22 +12,21 @@ class NotificationModelAssembler( NotificationController::class.java, NotificationModel::class.java, ) { - private val toReturn = + private val prefetchedNotifications = run { val notificationsWithModel = - notifications.content.map { notification -> - notification to - NotificationModel( - id = notification.id, - ) + notifications.content.associateWith { notification -> + NotificationModel( + id = notification.id, + ) } enhancers.forEach { enhancer -> enhancer.enhanceNotifications(notificationsWithModel) } - notificationsWithModel.toMap() + notificationsWithModel } override fun toModel(view: Notification): NotificationModel { - return toReturn[view] ?: throw IllegalStateException("Notification $view was not found") + return prefetchedNotifications[view] ?: throw IllegalStateException("Notification $view was not found") } } diff --git a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt index 152e08dfa9..316580b221 100644 --- a/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt +++ b/backend/api/src/main/kotlin/io/tolgee/hateoas/notification/ProjectNotificationEnhancer.kt @@ -10,8 +10,8 @@ class ProjectNotificationEnhancer( private val projectService: ProjectService, private val simpleProjectModelAssembler: SimpleProjectModelAssembler, ) : NotificationEnhancer { - override fun enhanceNotifications(notifications: Collection>) { - val projectIds = notifications.mapNotNull { it.first.project?.id }.distinct() + override fun enhanceNotifications(notifications: Map) { + val projectIds = notifications.mapNotNull { (source, _) -> source.project?.id }.distinct() val projects = projectService.findAll(projectIds).associateBy { it.id } notifications.forEach { (source, target) -> diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt index b8c02ee897..7fac9af139 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt @@ -12,8 +12,8 @@ class TaskNotificationEnhancer( private val taskService: TaskService, private val taskModelAssembler: TaskModelAssembler, ) : NotificationEnhancer { - override fun enhanceNotifications(notifications: Collection>) { - val taskIds = notifications.mapNotNull { it.first.linkedTask?.id } + override fun enhanceNotifications(notifications: Map) { + val taskIds = notifications.mapNotNull { (source, _) -> source.linkedTask?.id } val convertedTasks = taskService.getTasksWithScope(taskIds) notifications.forEach { (source, target) -> From 75d89d3947ce92c1aa90d8f6f57edfda0546f725 Mon Sep 17 00:00:00 2001 From: StaNov Date: Wed, 8 Jan 2025 16:14:28 +0100 Subject: [PATCH 16/26] Make link from notification copiable --- .../component/layout/TopBar/Notifications.tsx | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index 7a6a477f03..0c2b43ceec 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -75,23 +75,26 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { }} > - {notifications?.map((notification, i) => ( - { - handleClose(); - history.push( - `/projects/${notification.project?.id}/task?number=${notification.linkedTask?.number}` - ); - }} - > - - - ))} + {notifications?.map((notification, i) => { + const destinationUrl = `/projects/${notification.project?.id}/task?number=${notification.linkedTask?.number}`; + return ( + { + event.preventDefault(); + handleClose(); + history.push(destinationUrl); + }} + > + + + ); + })} {notifications?.length === 0 && ( From 08cb0a4eb5cb221aa55d251db55970020bc08cf9 Mon Sep 17 00:00:00 2001 From: StaNov Date: Thu, 9 Jan 2025 10:48:01 +0100 Subject: [PATCH 17/26] Empty notifications fix --- webapp/src/component/layout/TopBar/Notifications.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index 0c2b43ceec..a7c1e4ecbc 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -45,7 +45,9 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { url: '/v2/notifications', method: 'get', query: { size: 10000 }, - }).data?._embedded?.notificationModelList; + }).data; + + const notificationsData = notifications?._embedded?.notificationModelList; return ( <> @@ -75,12 +77,12 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { }} > - {notifications?.map((notification, i) => { + {notificationsData?.map((notification, i) => { const destinationUrl = `/projects/${notification.project?.id}/task?number=${notification.linkedTask?.number}`; return ( { event.preventDefault(); @@ -95,7 +97,7 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { ); })} - {notifications?.length === 0 && ( + {notifications?.page?.totalElements === 0 && ( From 9313757126479b3c752c2c62dacc66bae29d0d9a Mon Sep 17 00:00:00 2001 From: StaNov Date: Thu, 9 Jan 2025 11:16:31 +0100 Subject: [PATCH 18/26] Cypress identifiers --- e2e/cypress/support/dataCyType.d.ts | 2 ++ webapp/src/component/layout/TopBar/Notifications.tsx | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/e2e/cypress/support/dataCyType.d.ts b/e2e/cypress/support/dataCyType.d.ts index 599f95b095..0956e63879 100644 --- a/e2e/cypress/support/dataCyType.d.ts +++ b/e2e/cypress/support/dataCyType.d.ts @@ -346,6 +346,8 @@ declare namespace DataCy { "namespaces-select-text-field" | "namespaces-selector" | "navigation-item" | + "notifications-button" | + "notifications-list" | "order-translation-confirmation" | "order-translation-confirmation-ok" | "order-translation-invitation-checkbox" | diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index a7c1e4ecbc..612b428fd8 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -53,16 +53,17 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { <> Date: Thu, 9 Jan 2025 12:15:53 +0100 Subject: [PATCH 19/26] Notifications header --- webapp/src/component/layout/TopBar/Notifications.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index 612b428fd8..2edfa90c65 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -27,6 +27,10 @@ const StyledIconButton = styled(IconButton)` } `; +const ListItemHeader = styled(ListItem)` + font-weight: bold; +`; + export const Notifications: FunctionComponent<{ className?: string }> = () => { const handleOpen = (event: React.MouseEvent) => { // @ts-ignore @@ -78,6 +82,9 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { }} > + + + {notificationsData?.map((notification, i) => { const destinationUrl = `/projects/${notification.project?.id}/task?number=${notification.linkedTask?.number}`; return ( From 5c462d735d606a577e6daab5130243cc405df67d Mon Sep 17 00:00:00 2001 From: StaNov Date: Thu, 9 Jan 2025 12:51:02 +0100 Subject: [PATCH 20/26] Cypress tests --- e2e/cypress/e2e/tasks/tasksNotifications.cy.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/e2e/cypress/e2e/tasks/tasksNotifications.cy.ts b/e2e/cypress/e2e/tasks/tasksNotifications.cy.ts index 656f9fee17..af556bf4ca 100644 --- a/e2e/cypress/e2e/tasks/tasksNotifications.cy.ts +++ b/e2e/cypress/e2e/tasks/tasksNotifications.cy.ts @@ -23,7 +23,7 @@ describe('tasks notifications', () => { waitForGlobalLoading(); }); - it('sends email to assignee of newly created task', () => { + it('sends email to assignee of newly created task and creates notification', () => { cy.gcy('tasks-header-add-task').click(); cy.gcy('create-task-field-name').type('New review task'); cy.gcy('create-task-field-languages').click(); @@ -33,6 +33,9 @@ describe('tasks notifications', () => { cy.gcy('assignee-search-select-popover') .contains('Organization member') .click(); + cy.gcy('assignee-search-select-popover') + .contains('Tasks test user') + .click(); dismissMenu(); cy.gcy('create-task-submit').click(); @@ -47,6 +50,12 @@ describe('tasks notifications', () => { .should('be.visible') .findDcy('task-label-name') .should('contain', 'New review task'); + dismissMenu(); + + cy.gcy('notifications-button').click(); + cy.gcy('notifications-list').contains('New review task').click(); + + cy.url().should('include', '/translations?task='); }); it('sends email to new assignee', () => { From 03bdad4cd347c7087f1e9b6ee6cf223213db44cc Mon Sep 17 00:00:00 2001 From: StaNov Date: Thu, 9 Jan 2025 16:05:27 +0100 Subject: [PATCH 21/26] Test for notification fetching --- .../controllers/NotificationControllerTest.kt | 41 +++++++++++++++++++ .../testDataBuilder/TestDataService.kt | 22 ++++++---- .../builders/NotificationBuilder.kt | 13 ++++++ .../builders/UserAccountBuilder.kt | 4 ++ .../data/NotificationsTestData.kt | 26 ++++++++++++ 5 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/NotificationControllerTest.kt create mode 100644 backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/NotificationBuilder.kt create mode 100644 backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/NotificationsTestData.kt diff --git a/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/NotificationControllerTest.kt b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/NotificationControllerTest.kt new file mode 100644 index 0000000000..7b6e59b5b4 --- /dev/null +++ b/backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/NotificationControllerTest.kt @@ -0,0 +1,41 @@ +package io.tolgee.api.v2.controllers + +import io.tolgee.development.testDataBuilder.data.NotificationsTestData +import io.tolgee.fixtures.andAssertThatJson +import io.tolgee.testing.AuthorizedControllerTest +import org.junit.jupiter.api.Test + +class NotificationControllerTest : AuthorizedControllerTest() { + @Test + fun `gets notifications from newest`() { + val testData = NotificationsTestData() + + (101L..103).forEach { i -> + executeInNewTransaction { + val task = + testData.projectBuilder.addTask { + this.name = "Notification task $i" + this.language = testData.englishLanguage + this.author = testData.originatingUser.self + this.number = i + } + + testData.userAccountBuilder.addNotification { + this.user = testData.user + this.project = testData.project + this.linkedTask = task.self + this.originatingUser = testData.originatingUser.self + } + } + } + + testDataService.saveTestData(testData.root) + loginAsUser(testData.user.username) + + performAuthGet("/v2/notifications").andAssertThatJson { + node("_embedded.notificationModelList[0].linkedTask.name").isEqualTo("Notification task 103") + node("_embedded.notificationModelList[1].linkedTask.name").isEqualTo("Notification task 102") + node("_embedded.notificationModelList[2].linkedTask.name").isEqualTo("Notification task 101") + } + } +} diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt index 4699501ec9..983b287f89 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt @@ -2,15 +2,7 @@ package io.tolgee.development.testDataBuilder import io.tolgee.activity.ActivityHolder import io.tolgee.component.eventListeners.LanguageStatsListener -import io.tolgee.development.testDataBuilder.builders.BatchJobBuilder -import io.tolgee.development.testDataBuilder.builders.ImportBuilder -import io.tolgee.development.testDataBuilder.builders.KeyBuilder -import io.tolgee.development.testDataBuilder.builders.PatBuilder -import io.tolgee.development.testDataBuilder.builders.ProjectBuilder -import io.tolgee.development.testDataBuilder.builders.TestDataBuilder -import io.tolgee.development.testDataBuilder.builders.TranslationBuilder -import io.tolgee.development.testDataBuilder.builders.UserAccountBuilder -import io.tolgee.development.testDataBuilder.builders.UserPreferencesBuilder +import io.tolgee.development.testDataBuilder.builders.* import io.tolgee.development.testDataBuilder.builders.slack.SlackUserConnectionBuilder import io.tolgee.service.TenantService import io.tolgee.service.automations.AutomationService @@ -25,6 +17,7 @@ import io.tolgee.service.key.TagService import io.tolgee.service.language.LanguageService import io.tolgee.service.machineTranslation.MtCreditBucketService import io.tolgee.service.machineTranslation.MtServiceConfigService +import io.tolgee.service.notification.NotificationService import io.tolgee.service.organization.OrganizationRoleService import io.tolgee.service.organization.OrganizationService import io.tolgee.service.project.LanguageStatsService @@ -75,6 +68,7 @@ class TestDataService( private val userPreferencesService: UserPreferencesService, private val languageStatsService: LanguageStatsService, private val patService: PatService, + private val notificationService: NotificationService, private val namespaceService: NamespaceService, private val bigMetaService: BigMetaService, private val activityHolder: ActivityHolder, @@ -114,6 +108,7 @@ class TestDataService( executeInNewTransaction(transactionManager) { saveProjectData(builder) + saveNotifications(builder) finalize() } @@ -464,6 +459,15 @@ class TestDataService( } } + private fun saveNotifications(builder: TestDataBuilder) { + builder.data.userAccounts + .flatMap { it.data.notifications } + .sortedBy { it.self.linkedTask?.name } + .forEach { + notificationService.createNotification(it.self) + } + } + private fun saveUserPreferences(data: List) { data.forEach { userPreferencesService.save(it.self) } } diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/NotificationBuilder.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/NotificationBuilder.kt new file mode 100644 index 0000000000..e7c03bc08b --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/NotificationBuilder.kt @@ -0,0 +1,13 @@ +package io.tolgee.development.testDataBuilder.builders + +import io.tolgee.development.testDataBuilder.EntityDataBuilder +import io.tolgee.model.Notification + +class NotificationBuilder( + val userAccountBuilder: UserAccountBuilder, +) : EntityDataBuilder { + override var self: Notification = + Notification().apply { + this.user = userAccountBuilder.self + } +} diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/UserAccountBuilder.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/UserAccountBuilder.kt index efe470e20a..cd70417b00 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/UserAccountBuilder.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/builders/UserAccountBuilder.kt @@ -2,6 +2,7 @@ package io.tolgee.development.testDataBuilder.builders import io.tolgee.development.testDataBuilder.FT import io.tolgee.development.testDataBuilder.builders.slack.SlackUserConnectionBuilder +import io.tolgee.model.Notification import io.tolgee.model.Pat import io.tolgee.model.UserAccount import io.tolgee.model.UserPreferences @@ -20,6 +21,7 @@ class UserAccountBuilder( var userPreferences: UserPreferencesBuilder? = null var pats: MutableList = mutableListOf() var slackUserConnections: MutableList = mutableListOf() + var notifications: MutableList = mutableListOf() } var data = DATA() @@ -37,4 +39,6 @@ class UserAccountBuilder( fun addPat(ft: FT) = addOperation(data.pats, ft) fun addSlackUserConnection(ft: FT) = addOperation(data.slackUserConnections, ft) + + fun addNotification(ft: FT) = addOperation(data.notifications, ft) } diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/NotificationsTestData.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/NotificationsTestData.kt new file mode 100644 index 0000000000..73382c89f7 --- /dev/null +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/NotificationsTestData.kt @@ -0,0 +1,26 @@ +package io.tolgee.development.testDataBuilder.data + +class NotificationsTestData : BaseTestData() { + val originatingUser = + root.addUserAccount { + name = "originating user" + username = "originatingUser" + } + + val task = + projectBuilder.addTask { + this.name = "Notification task" + this.language = englishLanguage + this.author = originatingUser.self + } + + val notificationBuilder = + userAccountBuilder.addNotification { + this.user = this@NotificationsTestData.user + this.project = this@NotificationsTestData.project + this.linkedTask = task.self + this.originatingUser = this@NotificationsTestData.originatingUser.self + } + + val notification = notificationBuilder.self +} From 73ecb4e5006787e33152bb115551b14e88037a2c Mon Sep 17 00:00:00 2001 From: StaNov Date: Thu, 9 Jan 2025 16:12:09 +0100 Subject: [PATCH 22/26] Test for notification triggering while creating a task --- .../ee/api/v2/controllers/task/TaskControllerTest.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/task/TaskControllerTest.kt b/ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/task/TaskControllerTest.kt index 2a7e7bcea0..d1b0ef3c2c 100644 --- a/ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/task/TaskControllerTest.kt +++ b/ee/backend/tests/src/test/kotlin/io/tolgee/ee/api/v2/controllers/task/TaskControllerTest.kt @@ -55,7 +55,7 @@ class TaskControllerTest : ProjectAuthControllerTest("/v2/projects/") { @Test @ProjectJWTAuthTestMethod - fun `creates new task`() { + fun `creates new task which triggers notification`() { val keys = testData.keysOutOfTask.map { it.self.id }.toMutableSet() performProjectAuthPost( "tasks", @@ -66,14 +66,14 @@ class TaskControllerTest : ProjectAuthControllerTest("/v2/projects/") { languageId = testData.englishLanguage.id, assignees = mutableSetOf( - testData.orgMember.self.id, + testData.user.id, ), keys = keys, ), ).andAssertThatJson { node("number").isNumber node("name").isEqualTo("Another task") - node("assignees[0].name").isEqualTo(testData.orgMember.self.name) + node("assignees[0].name").isEqualTo(testData.user.name) node("language.tag").isEqualTo(testData.englishLanguage.tag) node("totalItems").isEqualTo(keys.size) } @@ -81,6 +81,10 @@ class TaskControllerTest : ProjectAuthControllerTest("/v2/projects/") { performProjectAuthGet("tasks").andAssertThatJson { node("page.totalElements").isNumber.isEqualTo(BigDecimal(3)) } + + performAuthGet("/v2/notifications").andAssertThatJson { + node("_embedded.notificationModelList[0].linkedTask.name").isEqualTo("Another task") + } } @Test From 9a0458c8a4a4d24ca2deacfd6499985d4bfb9222 Mon Sep 17 00:00:00 2001 From: StaNov Date: Mon, 13 Jan 2025 15:53:48 +0100 Subject: [PATCH 23/26] FE: Scrollable notifications --- webapp/src/component/layout/TopBar/Notifications.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/webapp/src/component/layout/TopBar/Notifications.tsx b/webapp/src/component/layout/TopBar/Notifications.tsx index 2edfa90c65..5417cb5015 100644 --- a/webapp/src/component/layout/TopBar/Notifications.tsx +++ b/webapp/src/component/layout/TopBar/Notifications.tsx @@ -80,6 +80,13 @@ export const Notifications: FunctionComponent<{ className?: string }> = () => { vertical: 'top', horizontal: 'right', }} + slotProps={{ + paper: { + style: { + maxHeight: 400, + }, + }, + }} > From 072ef7b36c04c4d2f5e58a5c13559a3041590e02 Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 14 Jan 2025 14:34:38 +0100 Subject: [PATCH 24/26] Review: Save renamed --- .../io/tolgee/development/testDataBuilder/TestDataService.kt | 2 +- .../io/tolgee/service/notification/NotificationService.kt | 2 +- .../kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt index 983b287f89..306dcb15e8 100644 --- a/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/TestDataService.kt @@ -464,7 +464,7 @@ class TestDataService( .flatMap { it.data.notifications } .sortedBy { it.self.linkedTask?.name } .forEach { - notificationService.createNotification(it.self) + notificationService.save(it.self) } } diff --git a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt index 6676153713..49c9df863b 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/notification/NotificationService.kt @@ -17,7 +17,7 @@ class NotificationService( return notificationRepository.fetchNotificationsByUserId(userId, pageable) } - fun createNotification(notification: Notification) { + fun save(notification: Notification) { notificationRepository.save(notification) } } diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt index bb5f2f3fc0..bdb812e648 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/AssigneeNotificationService.kt @@ -23,7 +23,7 @@ class AssigneeNotificationService( user: UserAccount, task: Task, ) { - notificationService.createNotification( + notificationService.save( Notification().apply { this.user = user this.linkedTask = task From 2db986d2ada4662154c36fad2cf14c5a07324e4c Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 14 Jan 2025 14:34:54 +0100 Subject: [PATCH 25/26] Review: Not fetching project eagerly --- .../main/kotlin/io/tolgee/repository/NotificationRepository.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt b/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt index 8489b17751..7100f2d1da 100644 --- a/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt +++ b/backend/data/src/main/kotlin/io/tolgee/repository/NotificationRepository.kt @@ -15,7 +15,6 @@ interface NotificationRepository : JpaRepository { """ SELECT n FROM Notification n - LEFT JOIN FETCH n.project LEFT JOIN FETCH n.user AS u LEFT JOIN FETCH n.originatingUser LEFT JOIN FETCH n.linkedTask From adb5b200596ffbdeea9eaa68af269981ff84537f Mon Sep 17 00:00:00 2001 From: StaNov Date: Tue, 14 Jan 2025 14:37:29 +0100 Subject: [PATCH 26/26] Review: Handling tasks by ID instead of project ID and task number --- .../io/tolgee/ee/component/TaskNotificationEnhancer.kt | 2 +- .../src/main/kotlin/io/tolgee/ee/service/TaskService.kt | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt index 7fac9af139..17e445e187 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/component/TaskNotificationEnhancer.kt @@ -18,7 +18,7 @@ class TaskNotificationEnhancer( notifications.forEach { (source, target) -> target.linkedTask = - convertedTasks.find { it.project.id == source.project?.id && it.number == source.linkedTask?.number } + convertedTasks[source.linkedTask?.id] ?.let { taskModelAssembler.toModel(it) } } } diff --git a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt index 07c7ee44cb..901fd39109 100644 --- a/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt +++ b/ee/backend/app/src/main/kotlin/io/tolgee/ee/service/TaskService.kt @@ -468,9 +468,13 @@ class TaskService( } } - fun getTasksWithScope(taskIds: List): List { + fun getTasksWithScope(taskIds: List): Map { val tasks = taskRepository.findAllById(taskIds) - return getTasksWithScope(tasks) + val taskIdsWithProjectIdAndNumber = tasks.associate { (it.project.id to it.number) to it.id } + val tasksWithScope = getTasksWithScope(tasks) + return tasksWithScope.associateBy { + taskIdsWithProjectIdAndNumber[it.project.id to it.number] ?: throw IllegalStateException("Item not found") + } } private fun getTasksWithScope(tasks: Collection): List {