From 4b75397fb0d0b69e4ceb64837ccb5456ad0472a4 Mon Sep 17 00:00:00 2001 From: Tim Cremer Date: Tue, 29 Oct 2024 19:08:10 +0100 Subject: [PATCH 1/3] Allow users to search for answer posts --- .../communication/repository/MessageSpecs.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/repository/MessageSpecs.java b/src/main/java/de/tum/cit/aet/artemis/communication/repository/MessageSpecs.java index 938eb9d7a8d3..e7635ab28ae8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/repository/MessageSpecs.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/repository/MessageSpecs.java @@ -39,8 +39,8 @@ public static Specification getConversationSpecification(Long conversation } /** - * Specification which filters Messages according to a search string in a match-all-manner - * message is only kept if the search string (which is not a #id pattern) is included in the message content (all strings lowercased) + * Specification which filters Messages and answer posts according to a search string in a match-all-manner + * message and answer post are only kept if the search string (which is not a #id pattern) is included in the message content (all strings lowercased) * * @param searchText Text to be searched within messages * @return specification used to chain DB operations @@ -60,8 +60,10 @@ else if (searchText.startsWith("#") && StringUtils.isNumeric(searchText.substrin Expression searchTextLiteral = criteriaBuilder.literal("%" + searchText.toLowerCase() + "%"); Predicate searchInMessageContent = criteriaBuilder.like(criteriaBuilder.lower(root.get(Post_.CONTENT)), searchTextLiteral); + Join answersJoin = root.join(Post_.ANSWERS, JoinType.LEFT); + Predicate searchInAnswerContent = criteriaBuilder.like(criteriaBuilder.lower(answersJoin.get(AnswerPost_.CONTENT)), searchTextLiteral); - return criteriaBuilder.and(searchInMessageContent); + return criteriaBuilder.or(searchInMessageContent, searchInAnswerContent); } }); } @@ -102,7 +104,7 @@ public static Specification getCourseWideChannelsSpecification(Long course } /** - * Specification to fetch Posts of the calling user + * Specification to fetch Posts and answer posts of the calling user * * @param filterToOwn whether only calling users own Posts should be fetched or not * @param userId id of the calling user @@ -114,7 +116,9 @@ public static Specification getOwnSpecification(boolean filterToOwn, Long return null; } else { - return criteriaBuilder.equal(root.get(Post_.AUTHOR).get(User_.ID), userId); + Join answersJoin = root.join(Post_.ANSWERS, JoinType.LEFT); + Predicate searchInAnswerContent = criteriaBuilder.equal(answersJoin.get(AnswerPost_.AUTHOR).get(User_.ID), userId); + return criteriaBuilder.or(criteriaBuilder.equal(root.get(Post_.AUTHOR).get(User_.ID), userId), searchInAnswerContent); } }); } From 0e402fb4e2389bc29e5dfce1c40c9a223df91452 Mon Sep 17 00:00:00 2001 From: Tim Cremer Date: Wed, 30 Oct 2024 09:07:40 +0100 Subject: [PATCH 2/3] Added integration Test for search --- .../communication/MessageIntegrationTest.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/test/java/de/tum/cit/aet/artemis/communication/MessageIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/communication/MessageIntegrationTest.java index 39598ae3d591..a23b10fd360d 100644 --- a/src/test/java/de/tum/cit/aet/artemis/communication/MessageIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/communication/MessageIntegrationTest.java @@ -44,6 +44,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import de.tum.cit.aet.artemis.communication.domain.AnswerPost; import de.tum.cit.aet.artemis.communication.domain.ConversationParticipant; import de.tum.cit.aet.artemis.communication.domain.DisplayPriority; import de.tum.cit.aet.artemis.communication.domain.Post; @@ -520,6 +521,51 @@ void testGetCourseWideMessagesFromOneChannel() throws Exception { assertThat(returnedPosts.size()).isLessThan(courseWidePosts.size()); } + @Test + @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") + void testGetCourseWideMessages_WithOwnWithCourseWideContentAndSearchText() throws Exception { + // conversation set will fetch all posts of conversation if the user is involved + var params = new LinkedMultiValueMap(); + var courseWidePosts = existingConversationMessages.stream().filter(post -> post.getConversation() instanceof Channel channel && channel.getIsCourseWide()).toList(); + var courseWideChannelId = courseWidePosts.getFirst().getConversation().getId(); + params.add("courseWideChannelIds", courseWideChannelId.toString()); + params.add("filterToOwn", "true"); + params.add("size", "50"); + params.add("searchText", "Content"); + + List returnedPosts = request.getList("/api/courses/" + courseId + "/messages", HttpStatus.OK, Post.class, params); + // get amount of posts with that certain + assertThat(returnedPosts).hasSize(existingCourseWideMessages.stream().filter(post -> post.getConversation().getId().equals(courseWideChannelId) + && post.getAuthor().getLogin().equals(TEST_PREFIX + "student1") && (post.getContent().contains("Content") || answerHasContext(post.getAnswers(), "Content"))) + .toList().size()); + assertThat(returnedPosts.size()).isLessThan(courseWidePosts.size()); + } + + private boolean answerHasContext(Set answers, String searchText) { + return answers.stream().anyMatch(answerPost -> answerPost.getContent().contains(searchText)); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") + void testGetCourseWideMessages_WithOwnWithCourseWideContentAndSearchTextInAnswer() throws Exception { + // conversation set will fetch all posts of conversation if the user is involved + var params = new LinkedMultiValueMap(); + var courseWidePosts = existingConversationMessages.stream().filter(post -> post.getConversation() instanceof Channel channel && channel.getIsCourseWide()).toList(); + var courseWideChannelId = courseWidePosts.getFirst().getConversation().getId(); + courseWidePosts.getFirst().getAnswers().forEach(answer -> answer.setContent("AnswerPost")); + params.add("courseWideChannelIds", courseWideChannelId.toString()); + params.add("filterToOwn", "true"); + params.add("size", "50"); + params.add("searchText", "Answer"); + + List returnedPosts = request.getList("/api/courses/" + courseId + "/messages", HttpStatus.OK, Post.class, params); + // get amount of posts with that certain + assertThat(returnedPosts).hasSize(existingCourseWideMessages.stream().filter(post -> post.getConversation().getId().equals(courseWideChannelId) + && post.getAuthor().getLogin().equals(TEST_PREFIX + "student1") && (post.getContent().contains("answer") || answerHasContext(post.getAnswers(), "Content"))) + .toList().size()); + assertThat(returnedPosts.size()).isLessThan(courseWidePosts.size()); + } + @Test @WithMockUser(username = TEST_PREFIX + "tutor1") void testEditConversationPost() throws Exception { From ed844387999fe47ac430da1bef876d2038efd9f1 Mon Sep 17 00:00:00 2001 From: Tim Cremer Date: Wed, 30 Oct 2024 11:55:53 +0100 Subject: [PATCH 3/3] Extracted predicate --- .../cit/aet/artemis/communication/repository/MessageSpecs.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/repository/MessageSpecs.java b/src/main/java/de/tum/cit/aet/artemis/communication/repository/MessageSpecs.java index e7635ab28ae8..bcce13dfedd5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/repository/MessageSpecs.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/repository/MessageSpecs.java @@ -118,7 +118,8 @@ public static Specification getOwnSpecification(boolean filterToOwn, Long else { Join answersJoin = root.join(Post_.ANSWERS, JoinType.LEFT); Predicate searchInAnswerContent = criteriaBuilder.equal(answersJoin.get(AnswerPost_.AUTHOR).get(User_.ID), userId); - return criteriaBuilder.or(criteriaBuilder.equal(root.get(Post_.AUTHOR).get(User_.ID), userId), searchInAnswerContent); + Predicate isPostOwner = criteriaBuilder.equal(root.get(Post_.AUTHOR).get(User_.ID), userId); + return criteriaBuilder.or(isPostOwner, searchInAnswerContent); } }); }