diff --git a/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/controller/FeedbackController.java b/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/controller/FeedbackController.java index c63819e9c..b64c47b1a 100644 --- a/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/controller/FeedbackController.java +++ b/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/controller/FeedbackController.java @@ -24,6 +24,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import java.security.Principal; +import java.util.HashMap; import java.util.List; import java.util.UUID; import lombok.AllArgsConstructor; @@ -76,10 +77,22 @@ public ResponseEntity> getStaffFeedbacks pageCriteria.page(), pageCriteria.pageSize()); var result = feedbackService.getStaffFeedbacks(statuses, activityId, pageCriteria); + + var latestFeedbackIdsByDeclaredActivity = new HashMap(); + var content = + result.content().stream() + .map( + feedback -> { + var declaredActivityId = feedback.getDeclaredActivity().getId(); + var latestFeedbackId = + latestFeedbackIdsByDeclaredActivity.computeIfAbsent( + declaredActivityId, id -> feedbackService.getLatestFeedback(id).getId()); + return feedbackStaffListItemDTOMapper.toDTO(feedback, latestFeedbackId); + }) + .toList(); + return ResponseEntity.ok( - new PagedResponse<>( - result.content().stream().map(feedbackStaffListItemDTOMapper::toDTO).toList(), - PageInfoDTO.fromDomain(result.pageInfo()))); + new PagedResponse<>(content, PageInfoDTO.fromDomain(result.pageInfo()))); } @PreAuthorize("hasAuthority('feedback:history:read:contextual')") diff --git a/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/dto/FeedbackStaffListItemDTO.java b/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/dto/FeedbackStaffListItemDTO.java index 99e6acf66..735b016dc 100644 --- a/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/dto/FeedbackStaffListItemDTO.java +++ b/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/dto/FeedbackStaffListItemDTO.java @@ -9,6 +9,7 @@ public record FeedbackStaffListItemDTO( UUID id, + UUID latestFeedbackId, UserInfoDTO student, ActivityContentDTO activity, @Schema(ref = "#/components/schemas/EFeedbackStatus") EFeedbackStatus status, diff --git a/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/mapper/FeedbackStaffListItemDTOMapper.java b/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/mapper/FeedbackStaffListItemDTOMapper.java index 909f50f02..8ef2d27f7 100644 --- a/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/mapper/FeedbackStaffListItemDTOMapper.java +++ b/src/main/java/fr/avenirsesr/portfolio/student/activity/application/adapter/mapper/FeedbackStaffListItemDTOMapper.java @@ -5,6 +5,7 @@ import fr.avenirsesr.portfolio.student.activity.application.adapter.dto.FeedbackStaffListItemDTO; import fr.avenirsesr.portfolio.student.activity.domain.model.Feedback; import fr.avenirsesr.portfolio.user.application.adapter.dto.UserInfoDTO; +import java.util.UUID; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -13,9 +14,10 @@ uses = {ActivityContentDtoMapper.class}) public interface FeedbackStaffListItemDTOMapper { - @Mapping(source = "declaredActivity.student.user", target = "student") - @Mapping(source = "declaredActivity.activity", target = "activity") - FeedbackStaffListItemDTO toDTO(Feedback feedback); + @Mapping(source = "feedback.declaredActivity.student.user", target = "student") + @Mapping(source = "feedback.declaredActivity.activity", target = "activity") + @Mapping(source = "latestFeedbackId", target = "latestFeedbackId") + FeedbackStaffListItemDTO toDTO(Feedback feedback, UUID latestFeedbackId); UserInfoDTO toUserInfoDTO(User user); } diff --git a/src/main/java/fr/avenirsesr/portfolio/student/activity/domain/port/input/FeedbackService.java b/src/main/java/fr/avenirsesr/portfolio/student/activity/domain/port/input/FeedbackService.java index 05e902e6c..b2bd6f460 100644 --- a/src/main/java/fr/avenirsesr/portfolio/student/activity/domain/port/input/FeedbackService.java +++ b/src/main/java/fr/avenirsesr/portfolio/student/activity/domain/port/input/FeedbackService.java @@ -43,4 +43,6 @@ Set findAttachmentIdsUsedByTraceSnapshots( List declaredActivityIds, List traceIds); FeedbackDashboardData getFeedbackDashboard(UUID activityId); + + Feedback getLatestFeedback(UUID declaredActivityId); } diff --git a/src/main/java/fr/avenirsesr/portfolio/student/activity/domain/service/FeedbackServiceImpl.java b/src/main/java/fr/avenirsesr/portfolio/student/activity/domain/service/FeedbackServiceImpl.java index 97c1dc6bc..9d00328c7 100644 --- a/src/main/java/fr/avenirsesr/portfolio/student/activity/domain/service/FeedbackServiceImpl.java +++ b/src/main/java/fr/avenirsesr/portfolio/student/activity/domain/service/FeedbackServiceImpl.java @@ -358,4 +358,9 @@ public FeedbackDashboardData getFeedbackDashboard(UUID activityId) { return new FeedbackDashboardData( newCount, pendingFeedbacks, processedFeedbacks, totalFeedbacks); } + + @Override + public Feedback getLatestFeedback(UUID declaredActivityId) { + return feedbackRepository.findAllByDeclaredActivityId(declaredActivityId).getFirst(); + } } diff --git a/src/test/java/fr/avenirsesr/portfolio/student/activity/application/adapter/controller/FeedbackControllerTest.java b/src/test/java/fr/avenirsesr/portfolio/student/activity/application/adapter/controller/FeedbackControllerTest.java index 6c1e7b5ae..008ce6fd4 100644 --- a/src/test/java/fr/avenirsesr/portfolio/student/activity/application/adapter/controller/FeedbackControllerTest.java +++ b/src/test/java/fr/avenirsesr/portfolio/student/activity/application/adapter/controller/FeedbackControllerTest.java @@ -29,6 +29,7 @@ import fr.avenirsesr.portfolio.student.activity.application.adapter.mapper.StudentFeedbackItemListDTOMapper; import fr.avenirsesr.portfolio.student.activity.domain.data.FeedbackDashboardData; import fr.avenirsesr.portfolio.student.activity.domain.data.FeedbackData; +import fr.avenirsesr.portfolio.student.activity.domain.model.DeclaredActivity; import fr.avenirsesr.portfolio.student.activity.domain.model.Feedback; import fr.avenirsesr.portfolio.student.activity.domain.model.enums.EFeedbackStatus; import fr.avenirsesr.portfolio.student.activity.domain.port.input.FeedbackService; @@ -194,12 +195,27 @@ void should_delegate_to_service_with_correct_id_and_category() { @Nested class GetStaffFeedbacks { + private Feedback feedbackWithLatestId(UUID declaredActivityId, UUID latestFeedbackId) { + Feedback feedback = mock(Feedback.class); + DeclaredActivity declaredActivity = mock(DeclaredActivity.class); + when(feedback.getDeclaredActivity()).thenReturn(declaredActivity); + when(declaredActivity.getId()).thenReturn(declaredActivityId); + + Feedback latestFeedback = mock(Feedback.class); + when(latestFeedback.getId()).thenReturn(latestFeedbackId); + when(feedbackService.getLatestFeedback(declaredActivityId)).thenReturn(latestFeedback); + + return feedback; + } + @Test void should_return_200_with_paged_result_when_no_filters() { BddLogger.given("A logged-in staff with two feedbacks and no filter applied"); - Feedback feedback1 = mock(Feedback.class); - Feedback feedback2 = mock(Feedback.class); + UUID latestFeedbackId1 = UUID.randomUUID(); + UUID latestFeedbackId2 = UUID.randomUUID(); + Feedback feedback1 = feedbackWithLatestId(UUID.randomUUID(), latestFeedbackId1); + Feedback feedback2 = feedbackWithLatestId(UUID.randomUUID(), latestFeedbackId2); FeedbackStaffListItemDTO dto1 = mock(FeedbackStaffListItemDTO.class); FeedbackStaffListItemDTO dto2 = mock(FeedbackStaffListItemDTO.class); @@ -207,8 +223,8 @@ void should_return_200_with_paged_result_when_no_filters() { new PagedResult<>(List.of(feedback1, feedback2), new PageInfo(0, 8, 2)); when(feedbackService.getStaffFeedbacks(isNull(), isNull(), any())).thenReturn(pagedResult); - when(feedbackStaffListItemDTOMapper.toDTO(feedback1)).thenReturn(dto1); - when(feedbackStaffListItemDTOMapper.toDTO(feedback2)).thenReturn(dto2); + when(feedbackStaffListItemDTOMapper.toDTO(feedback1, latestFeedbackId1)).thenReturn(dto1); + when(feedbackStaffListItemDTOMapper.toDTO(feedback2, latestFeedbackId2)).thenReturn(dto2); BddLogger.when("getStaffFeedbacks is called without any filter"); ResponseEntity> response = @@ -221,15 +237,16 @@ void should_return_200_with_paged_result_when_no_filters() { assertThat(response.getBody().page().totalElements()).isEqualTo(2); verify(feedbackService).getStaffFeedbacks(isNull(), isNull(), any()); - verify(feedbackStaffListItemDTOMapper).toDTO(feedback1); - verify(feedbackStaffListItemDTOMapper).toDTO(feedback2); + verify(feedbackStaffListItemDTOMapper).toDTO(feedback1, latestFeedbackId1); + verify(feedbackStaffListItemDTOMapper).toDTO(feedback2, latestFeedbackId2); } @Test void should_forward_statuses_filter_to_service() { BddLogger.given("A logged-in staff requesting only IN_PROCESS feedbacks"); - Feedback feedback = mock(Feedback.class); + UUID latestFeedbackId = UUID.randomUUID(); + Feedback feedback = feedbackWithLatestId(UUID.randomUUID(), latestFeedbackId); FeedbackStaffListItemDTO dto = mock(FeedbackStaffListItemDTO.class); PagedResult pagedResult = @@ -239,7 +256,7 @@ void should_forward_statuses_filter_to_service() { when(feedbackService.getStaffFeedbacks(eq(statuses), isNull(), any())) .thenReturn(pagedResult); - when(feedbackStaffListItemDTOMapper.toDTO(feedback)).thenReturn(dto); + when(feedbackStaffListItemDTOMapper.toDTO(feedback, latestFeedbackId)).thenReturn(dto); BddLogger.when("getStaffFeedbacks is called with status=IN_PROCESS"); ResponseEntity> response = @@ -256,8 +273,10 @@ void should_forward_statuses_filter_to_service() { void should_forward_multiple_statuses_filter_to_service() { BddLogger.given("A logged-in staff requesting feedbacks with multiple statuses"); - Feedback feedback1 = mock(Feedback.class); - Feedback feedback2 = mock(Feedback.class); + UUID latestFeedbackId1 = UUID.randomUUID(); + UUID latestFeedbackId2 = UUID.randomUUID(); + Feedback feedback1 = feedbackWithLatestId(UUID.randomUUID(), latestFeedbackId1); + Feedback feedback2 = feedbackWithLatestId(UUID.randomUUID(), latestFeedbackId2); FeedbackStaffListItemDTO dto1 = mock(FeedbackStaffListItemDTO.class); FeedbackStaffListItemDTO dto2 = mock(FeedbackStaffListItemDTO.class); @@ -268,8 +287,8 @@ void should_forward_multiple_statuses_filter_to_service() { when(feedbackService.getStaffFeedbacks(eq(statuses), isNull(), any())) .thenReturn(pagedResult); - when(feedbackStaffListItemDTOMapper.toDTO(feedback1)).thenReturn(dto1); - when(feedbackStaffListItemDTOMapper.toDTO(feedback2)).thenReturn(dto2); + when(feedbackStaffListItemDTOMapper.toDTO(feedback1, latestFeedbackId1)).thenReturn(dto1); + when(feedbackStaffListItemDTOMapper.toDTO(feedback2, latestFeedbackId2)).thenReturn(dto2); BddLogger.when("getStaffFeedbacks is called with multiple statuses"); ResponseEntity> response = @@ -289,7 +308,8 @@ void should_forward_activity_id_filter_to_service() { BddLogger.given("A logged-in staff requesting feedbacks for a specific activity"); UUID activityId = UUID.randomUUID(); - Feedback feedback = mock(Feedback.class); + UUID latestFeedbackId = UUID.randomUUID(); + Feedback feedback = feedbackWithLatestId(UUID.randomUUID(), latestFeedbackId); FeedbackStaffListItemDTO dto = mock(FeedbackStaffListItemDTO.class); PagedResult pagedResult = @@ -297,7 +317,7 @@ void should_forward_activity_id_filter_to_service() { when(feedbackService.getStaffFeedbacks(isNull(), eq(activityId), any())) .thenReturn(pagedResult); - when(feedbackStaffListItemDTOMapper.toDTO(feedback)).thenReturn(dto); + when(feedbackStaffListItemDTOMapper.toDTO(feedback, latestFeedbackId)).thenReturn(dto); BddLogger.when("getStaffFeedbacks is called with activityId"); ResponseEntity> response = diff --git a/src/test/java/fr/avenirsesr/portfolio/student/activity/domain/service/FeedbackServiceImplTest.java b/src/test/java/fr/avenirsesr/portfolio/student/activity/domain/service/FeedbackServiceImplTest.java index 0c24d320f..26dfd1908 100644 --- a/src/test/java/fr/avenirsesr/portfolio/student/activity/domain/service/FeedbackServiceImplTest.java +++ b/src/test/java/fr/avenirsesr/portfolio/student/activity/domain/service/FeedbackServiceImplTest.java @@ -1512,6 +1512,44 @@ void should_throw_UserIsNotStaffException_when_logged_in_user_is_not_staff() { } } + @Nested + class GetLatestFeedback { + @Test + void should_return_the_first_feedback_returned_by_the_repository() { + BddLogger.given( + "A declared activity with several feedbacks, the repository already sorting them by" + + " createdAt descending"); + UUID declaredActivityId = UUID.randomUUID(); + Feedback latestFeedback = mock(Feedback.class); + Feedback olderFeedback = mock(Feedback.class); + + when(feedbackRepository.findAllByDeclaredActivityId(declaredActivityId)) + .thenReturn(List.of(latestFeedback, olderFeedback)); + + BddLogger.when("getLatestFeedback is called"); + Feedback result = service.getLatestFeedback(declaredActivityId); + + BddLogger.then("The first feedback of the repository result (the most recent) is returned"); + assertThat(result).isEqualTo(latestFeedback); + } + + @Test + void should_pass_the_declared_activity_id_to_the_repository() { + BddLogger.given("A declared activity ID"); + UUID declaredActivityId = UUID.randomUUID(); + Feedback feedback = mock(Feedback.class); + + when(feedbackRepository.findAllByDeclaredActivityId(declaredActivityId)) + .thenReturn(List.of(feedback)); + + BddLogger.when("getLatestFeedback is called"); + service.getLatestFeedback(declaredActivityId); + + BddLogger.then("The repository is called with the correct declaredActivityId"); + verify(feedbackRepository).findAllByDeclaredActivityId(declaredActivityId); + } + } + @Nested class GetFeedbackDashboardData {