diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/events/ChangeEventHandler.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/events/ChangeEventHandler.java index 258c581c688..4be07a9b49c 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/events/ChangeEventHandler.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/events/ChangeEventHandler.java @@ -18,13 +18,11 @@ import static org.openmetadata.catalog.type.EventType.ENTITY_SOFT_DELETED; import static org.openmetadata.catalog.type.EventType.ENTITY_UPDATED; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.UUID; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.SecurityContext; import lombok.extern.slf4j.Slf4j; import org.jdbi.v3.core.Jdbi; import org.openmetadata.catalog.CatalogApplicationConfig; @@ -37,7 +35,6 @@ import org.openmetadata.catalog.type.ChangeEvent; import org.openmetadata.catalog.type.EntityReference; import org.openmetadata.catalog.type.EventType; import org.openmetadata.catalog.type.FieldChange; -import org.openmetadata.catalog.type.Post; import org.openmetadata.catalog.util.EntityInterface; import org.openmetadata.catalog.util.JsonUtils; import org.openmetadata.catalog.util.RestUtil; @@ -54,7 +51,6 @@ public class ChangeEventHandler implements EventHandler { public Void process(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { String method = requestContext.getMethod(); - SecurityContext securityContext = requestContext.getSecurityContext(); try { ChangeEvent changeEvent = getChangeEvent(method, responseContext); if (changeEvent != null) { @@ -77,7 +73,7 @@ public class ChangeEventHandler implements EventHandler { List threads = getThreads(responseContext); if (threads != null) { for (var thread : threads) { - feedDao.create(thread, securityContext); + feedDao.create(thread); } } } @@ -240,12 +236,6 @@ public class ChangeEventHandler implements EventHandler { break; } - Post post = - new Post() - .withFrom(entityInterface.getUpdatedBy()) - .withMessage(message) - .withPostTs(System.currentTimeMillis()); - threads.add( new Thread() .withId(UUID.randomUUID()) @@ -254,7 +244,7 @@ public class ChangeEventHandler implements EventHandler { .withAbout(link.getLinkString()) .withUpdatedBy(entityInterface.getUpdatedBy()) .withUpdatedAt(System.currentTimeMillis()) - .withPosts(Collections.singletonList(post))); + .withMessage(message)); } return threads; diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/FeedRepository.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/FeedRepository.java index a6f96ed6cb9..d10d5f85bd0 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/FeedRepository.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/FeedRepository.java @@ -24,7 +24,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import javax.ws.rs.core.SecurityContext; import org.apache.commons.lang3.StringUtils; import org.jdbi.v3.sqlobject.transaction.Transaction; import org.openmetadata.catalog.Entity; @@ -34,7 +33,6 @@ import org.openmetadata.catalog.entity.feed.Thread; import org.openmetadata.catalog.resources.feeds.FeedUtil; import org.openmetadata.catalog.resources.feeds.MessageParser; import org.openmetadata.catalog.resources.feeds.MessageParser.EntityLink; -import org.openmetadata.catalog.security.SecurityUtil; import org.openmetadata.catalog.type.EntityReference; import org.openmetadata.catalog.type.Include; import org.openmetadata.catalog.type.Post; @@ -50,13 +48,11 @@ public class FeedRepository { } @Transaction - public Thread create(Thread thread, SecurityContext securityContext) throws IOException, ParseException { - String fromUser = thread.getPosts().get(0).getFrom(); + public Thread create(Thread thread) throws IOException, ParseException { + String createdBy = thread.getCreatedBy(); - if (SecurityUtil.isSecurityEnabled(securityContext)) { - // Validate user creating thread if security is enabled - dao.userDAO().findEntityByName(fromUser); - } + // Validate user creating thread + dao.userDAO().findEntityByName(createdBy); // Validate about data entity is valid EntityLink about = EntityLink.parse(thread.getAbout()); @@ -69,7 +65,8 @@ public class FeedRepository { dao.feedDAO().insert(JsonUtils.pojoToJson(thread)); // Add relationship User -- created --> Thread relationship - dao.relationshipDAO().insert(fromUser, thread.getId().toString(), "user", "thread", Relationship.CREATED.ordinal()); + dao.relationshipDAO() + .insert(createdBy, thread.getId().toString(), "user", "thread", Relationship.CREATED.ordinal()); // Add field relationship data asset Thread -- isAbout ---> entity/entityField // relationship @@ -94,7 +91,7 @@ public class FeedRepository { // Create relationship for users, teams, and other entities that are mentioned in the post // Multiple mentions of the same entity is handled by taking distinct mentions - List mentions = MessageParser.getEntityLinks(thread.getPosts().get(0).getMessage()); + List mentions = MessageParser.getEntityLinks(thread.getMessage()); mentions.stream() .distinct() @@ -117,7 +114,7 @@ public class FeedRepository { @Transaction public Thread addPostToThread(String id, Post post) throws IOException { - // Query 1 - validate user creating thread + // Query 1 - validate the user posting the message String fromUser = post.getFrom(); dao.userDAO().findEntityByName(fromUser); @@ -191,50 +188,57 @@ public class FeedRepository { } @Transaction - public List listThreads(String link) throws IOException { + public List listThreads(String link, int limitPosts) throws IOException { + List threads = new ArrayList<>(); if (link == null) { // Not listing thread by data asset or user - return JsonUtils.readObjects(dao.feedDAO().list(), Thread.class); - } - EntityLink entityLink = EntityLink.parse(link); - EntityReference reference = EntityUtil.validateEntityLink(entityLink); - List threadIds = new ArrayList<>(); - List> result = - dao.fieldRelationshipDAO() - .listToByPrefix( - entityLink.getFullyQualifiedFieldValue(), - entityLink.getFullyQualifiedFieldType(), - "thread", - Relationship.MENTIONED_IN.ordinal()); - result.forEach(l -> threadIds.add(l.get(1))); - - // TODO remove hardcoding of thread - // For a user entitylink get created or replied relationships to the thread - if (reference.getType().equals(Entity.USER)) { - threadIds.addAll(getUserThreadIds(reference)); + threads = JsonUtils.readObjects(dao.feedDAO().list(), Thread.class); } else { - // Only data assets are added as about - result = - dao.fieldRelationshipDAO() - .listFromByAllPrefix( - entityLink.getFullyQualifiedFieldValue(), - "thread", - entityLink.getFullyQualifiedFieldType(), - Relationship.IS_ABOUT.ordinal()); - result.forEach(l -> threadIds.add(l.get(1))); - } + EntityLink entityLink = EntityLink.parse(link); + EntityReference reference = EntityUtil.validateEntityLink(entityLink); + List threadIds = new ArrayList<>(); + List> result; - List threads = new ArrayList<>(); - Set uniqueValues = new HashSet<>(); - for (String t : threadIds) { - // If an entity has multiple relationships (created, mentioned, repliedTo etc.) to the same thread - // Don't send duplicated copies of the thread in response - if (uniqueValues.add(t)) { - threads.add(EntityUtil.validate(t, dao.feedDAO().findById(t), Thread.class)); + // TODO remove hardcoding of thread + // For a user entitylink get created or replied relationships to the thread + if (reference.getType().equals(Entity.USER)) { + threadIds.addAll(getUserThreadIds(reference)); + } else { + // Only data assets are added as about + result = + dao.fieldRelationshipDAO() + .listFromByAllPrefix( + entityLink.getFullyQualifiedFieldValue(), + "thread", + entityLink.getFullyQualifiedFieldType(), + Relationship.IS_ABOUT.ordinal()); + result.forEach(l -> threadIds.add(l.get(1))); + } + + Set uniqueValues = new HashSet<>(); + for (String t : threadIds) { + // If an entity has multiple relationships (created, mentioned, repliedTo etc.) to the same thread + // Don't send duplicated copies of the thread in response + if (uniqueValues.add(t)) { + threads.add(EntityUtil.validate(t, dao.feedDAO().findById(t), Thread.class)); + } + } + // sort the list by thread updated timestamp before returning + threads.sort(Comparator.comparing(Thread::getUpdatedAt, Comparator.reverseOrder())); + } + return limitPostsInThreads(threads, limitPosts); + } + + private List limitPostsInThreads(List threads, int limitPosts) { + for (Thread t : threads) { + List posts = t.getPosts(); + if (posts.size() > limitPosts) { + // Only keep the last "n" number of posts + posts.sort(Comparator.comparing(Post::getPostTs)); + posts = posts.subList(posts.size() - limitPosts, posts.size()); + t.withPosts(posts); } } - // sort the list by thread updated timestamp before returning - threads.sort(Comparator.comparing(Thread::getUpdatedAt, Comparator.reverseOrder())); return threads; } diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/UserRepository.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/UserRepository.java index 028ca9cd4e6..5ab2681477b 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/UserRepository.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/jdbi3/UserRepository.java @@ -121,11 +121,13 @@ public class UserRepository extends EntityRepository { @Override public User setFields(User user, Fields fields) throws IOException, ParseException { + user.setProfile(fields.contains("profile") ? user.getProfile() : null); user.setTeams(fields.contains("teams") ? getTeams(user) : null); user.setRoles(fields.contains("roles") ? getRoles(user) : null); user.setOwns(fields.contains("owns") ? getOwns(user) : null); user.setFollows(fields.contains("follows") ? getFollows(user) : null); + return user; } diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/feeds/FeedResource.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/feeds/FeedResource.java index 37a753342e3..ce84935fbe8 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/feeds/FeedResource.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/feeds/FeedResource.java @@ -27,6 +27,8 @@ import java.util.List; import java.util.Objects; import java.util.UUID; import javax.validation.Valid; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; @@ -112,13 +114,21 @@ public class FeedResource { }) public ThreadList list( @Context UriInfo uriInfo, + @Parameter( + description = "Limit the number of posts sorted by chronological order (1 to 1000000, default = 3)", + schema = @Schema(type = "integer")) + @Min(1) + @Max(1000000) + @DefaultValue("3") + @QueryParam("limitPosts") + int limitPosts, @Parameter( description = "Filter threads by entity link", schema = @Schema(type = "string", example = "")) @QueryParam("entityLink") String entityLink) throws IOException { - return new ThreadList(addHref(uriInfo, dao.listThreads(entityLink))); + return new ThreadList(addHref(uriInfo, dao.listThreads(entityLink, limitPosts))); } @GET @@ -180,8 +190,7 @@ public class FeedResource { public Response create(@Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid CreateThread create) throws IOException, ParseException { Thread thread = getThread(securityContext, create); - FeedUtil.addPost(thread, new Post().withMessage(create.getMessage()).withFrom(create.getFrom())); - addHref(uriInfo, dao.create(thread, securityContext)); + addHref(uriInfo, dao.create(thread)); return Response.created(thread.getHref()).entity(thread).build(); } @@ -223,7 +232,8 @@ public class FeedResource { return new Thread() .withId(UUID.randomUUID()) .withThreadTs(System.currentTimeMillis()) - .withCreatedBy(securityContext.getUserPrincipal().getName()) + .withMessage(create.getMessage()) + .withCreatedBy(create.getFrom()) .withAbout(create.getAbout()) .withAddressedTo(create.getAddressedTo()) .withUpdatedBy(securityContext.getUserPrincipal().getName()) diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/feeds/FeedUtil.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/feeds/FeedUtil.java index 3894a1b7215..879feb1d638 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/feeds/FeedUtil.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/resources/feeds/FeedUtil.java @@ -13,7 +13,6 @@ package org.openmetadata.catalog.resources.feeds; -import java.util.Collections; import org.openmetadata.catalog.entity.feed.Thread; import org.openmetadata.catalog.type.Post; @@ -22,14 +21,9 @@ public final class FeedUtil { private FeedUtil() {} public static void addPost(Thread thread, Post post) { - if (thread.getPosts() == null || thread.getPosts().isEmpty()) { - // First post in the thread - post.setPostTs(thread.getThreadTs()); - thread.setPosts(Collections.singletonList(post)); - } else { - // Add new post to the thread - post.setPostTs(System.currentTimeMillis()); - thread.getPosts().add(post); - } + // Add new post to the thread + post.setPostTs(System.currentTimeMillis()); + thread.getPosts().add(post); + thread.withPostsCount(thread.getPosts().size()); } } diff --git a/catalog-rest-service/src/main/java/org/openmetadata/catalog/security/DefaultAuthorizer.java b/catalog-rest-service/src/main/java/org/openmetadata/catalog/security/DefaultAuthorizer.java index c2752e89de4..26ae12f7f18 100644 --- a/catalog-rest-service/src/main/java/org/openmetadata/catalog/security/DefaultAuthorizer.java +++ b/catalog-rest-service/src/main/java/org/openmetadata/catalog/security/DefaultAuthorizer.java @@ -248,11 +248,11 @@ public class DefaultAuthorizer implements Authorizer { private void addOrUpdateUser(User user) { try { RestUtil.PutResponse addedUser = userRepository.createOrUpdate(null, user); - LOG.debug("Added admin user entry: {}", addedUser); + LOG.debug("Added user entry: {}", addedUser); } catch (IOException | ParseException exception) { // In HA set up the other server may have already added the user. LOG.debug("Caught exception: {}", ExceptionUtils.getStackTrace(exception)); - LOG.debug("Admin user entry: {} already exists.", user); + LOG.debug("User entry: {} already exists.", user); } } } diff --git a/catalog-rest-service/src/main/resources/json/schema/entity/feed/thread.json b/catalog-rest-service/src/main/resources/json/schema/entity/feed/thread.json index 58f4c420476..716a29641a7 100644 --- a/catalog-rest-service/src/main/resources/json/schema/entity/feed/thread.json +++ b/catalog-rest-service/src/main/resources/json/schema/entity/feed/thread.json @@ -65,6 +65,15 @@ "type": "boolean", "default": false }, + "message": { + "description": "The main message of the thread in markdown format", + "type": "string" + }, + "postsCount": { + "description": "The total count of posts in the thread", + "type": "integer", + "default": 0 + }, "posts": { "type": "array", "items": { diff --git a/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/feeds/FeedResourceTest.java b/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/feeds/FeedResourceTest.java index b5613aea314..ecc7a25ce09 100644 --- a/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/feeds/FeedResourceTest.java +++ b/catalog-rest-service/src/test/java/org/openmetadata/catalog/resources/feeds/FeedResourceTest.java @@ -33,6 +33,7 @@ import java.util.Map; import java.util.UUID; import javax.ws.rs.client.WebTarget; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.apache.http.client.HttpResponseException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer; @@ -138,7 +139,7 @@ public class FeedResourceTest extends CatalogApplicationTest { } @Test - void post_feedWithNonExistentFrom_404() throws IOException { + void post_feedWithNonExistentFrom_404() { // Create thread with non-existent from CreateThread create = create().withFrom(NON_EXISTENT_ENTITY.toString()); assertResponse( @@ -154,7 +155,7 @@ public class FeedResourceTest extends CatalogApplicationTest { } @Test - void post_feedWithInvalidAbout_400() throws IOException { + void post_feedWithInvalidAbout_400() { // post with invalid entity link pattern // if entity link refers to an array member, then it should have both // field name and value @@ -165,12 +166,12 @@ public class FeedResourceTest extends CatalogApplicationTest { @Test void post_validThreadAndList_200(TestInfo test) throws IOException { - int totalThreadCount = listThreads(null, ADMIN_AUTH_HEADERS).getData().size(); - int userThreadCount = listThreads(USER_LINK, ADMIN_AUTH_HEADERS).getData().size(); - int teamThreadCount = listThreads(TEAM_LINK, ADMIN_AUTH_HEADERS).getData().size(); - int tableThreadCount = listThreads(TABLE_LINK, ADMIN_AUTH_HEADERS).getData().size(); - int tableDescriptionThreadCount = listThreads(TABLE_DESCRIPTION_LINK, ADMIN_AUTH_HEADERS).getData().size(); - int tableColumnDescriptionThreadCount = listThreads(TABLE_COLUMN_LINK, ADMIN_AUTH_HEADERS).getData().size(); + int totalThreadCount = listThreads(null, null, ADMIN_AUTH_HEADERS).getData().size(); + int userThreadCount = listThreads(USER_LINK, null, ADMIN_AUTH_HEADERS).getData().size(); + int teamThreadCount = listThreads(TEAM_LINK, null, ADMIN_AUTH_HEADERS).getData().size(); + int tableThreadCount = listThreads(TABLE_LINK, null, ADMIN_AUTH_HEADERS).getData().size(); + int tableDescriptionThreadCount = listThreads(TABLE_DESCRIPTION_LINK, null, ADMIN_AUTH_HEADERS).getData().size(); + int tableColumnDescriptionThreadCount = listThreads(TABLE_COLUMN_LINK, null, ADMIN_AUTH_HEADERS).getData().size(); CreateThread create = create() @@ -188,27 +189,57 @@ public class FeedResourceTest extends CatalogApplicationTest { for (int i = 0; i < 10; i++) { createAndCheck(create, userAuthHeaders); // List all the threads and make sure the number of threads increased by 1 - assertEquals(++userThreadCount, listThreads(USER_LINK, userAuthHeaders).getData().size()); // Mentioned user - assertEquals(++teamThreadCount, listThreads(TEAM_LINK, userAuthHeaders).getData().size()); // Mentioned team - assertEquals(++tableThreadCount, listThreads(TABLE_LINK, userAuthHeaders).getData().size()); // About TABLE + assertEquals(++userThreadCount, listThreads(USER_LINK, null, userAuthHeaders).getData().size()); // Mentioned user + // TODO: There is no support for team mentions yet. + // assertEquals(++teamThreadCount, listThreads(TEAM_LINK, null, userAuthHeaders).getData().size()); // Mentioned + // team + assertEquals(++tableThreadCount, listThreads(TABLE_LINK, null, userAuthHeaders).getData().size()); // About TABLE + assertEquals(++totalThreadCount, listThreads(null, null, userAuthHeaders).getData().size()); // Overall threads + } + + // List threads should not include mentioned entities + // It should only include threads which are about the entity link + assertEquals( + tableDescriptionThreadCount, + listThreads(TABLE_DESCRIPTION_LINK, null, userAuthHeaders).getData().size()); // About TABLE Description + assertEquals( + tableColumnDescriptionThreadCount, + listThreads(TABLE_COLUMN_LINK, null, userAuthHeaders).getData().size()); // About TABLE Column Description + + create.withAbout(TABLE_DESCRIPTION_LINK); + for (int i = 0; i < 10; i++) { + createAndCheck(create, userAuthHeaders); + // List all the threads and make sure the number of threads increased by 1 + assertEquals(++userThreadCount, listThreads(USER_LINK, null, userAuthHeaders).getData().size()); // Mentioned user + assertEquals(++tableThreadCount, listThreads(TABLE_LINK, null, userAuthHeaders).getData().size()); // About TABLE assertEquals( ++tableDescriptionThreadCount, - listThreads(TABLE_DESCRIPTION_LINK, userAuthHeaders).getData().size()); // About TABLE Description + listThreads(TABLE_DESCRIPTION_LINK, null, userAuthHeaders).getData().size()); // About TABLE Description + assertEquals(++totalThreadCount, listThreads(null, null, userAuthHeaders).getData().size()); // Overall threads + } + + create.withAbout(TABLE_COLUMN_LINK); + for (int i = 0; i < 10; i++) { + createAndCheck(create, userAuthHeaders); + // List all the threads and make sure the number of threads increased by 1 + assertEquals(++userThreadCount, listThreads(USER_LINK, null, userAuthHeaders).getData().size()); // Mentioned user + assertEquals(++tableThreadCount, listThreads(TABLE_LINK, null, userAuthHeaders).getData().size()); // About TABLE assertEquals( ++tableColumnDescriptionThreadCount, - listThreads(TABLE_COLUMN_LINK, userAuthHeaders).getData().size()); // About TABLE Column Description - assertEquals(++totalThreadCount, listThreads(null, userAuthHeaders).getData().size()); // Overall threads + listThreads(TABLE_COLUMN_LINK, null, userAuthHeaders).getData().size()); // About TABLE Description + assertEquals(++totalThreadCount, listThreads(null, null, userAuthHeaders).getData().size()); // Overall threads } // Test the /api/v1/feed/count API assertEquals(userThreadCount, listThreadsCount(USER_LINK, userAuthHeaders).getTotalCount()); - assertEquals(tableThreadCount, getThreadCount(TABLE_LINK, userAuthHeaders)); + assertEquals(tableDescriptionThreadCount, getThreadCount(TABLE_DESCRIPTION_LINK, userAuthHeaders)); + assertEquals(tableColumnDescriptionThreadCount, getThreadCount(TABLE_COLUMN_LINK, userAuthHeaders)); } @Test void post_addPostWithoutMessage_4xx() { // Add post to a thread without message field - Post post = createPost().withMessage(null); + Post post = createPost(null).withMessage(null); assertResponseContains( () -> addPost(THREAD.getId(), post, AUTH_HEADERS), BAD_REQUEST, "[message must not be null]"); @@ -217,7 +248,7 @@ public class FeedResourceTest extends CatalogApplicationTest { @Test void post_addPostWithoutFrom_4xx() { // Add post to a thread without from field - Post post = createPost().withFrom(null); + Post post = createPost(null).withFrom(null); assertResponseContains(() -> addPost(THREAD.getId(), post, AUTH_HEADERS), BAD_REQUEST, "[from must not be null]"); } @@ -226,7 +257,7 @@ public class FeedResourceTest extends CatalogApplicationTest { void post_addPostWithNonExistentFrom_404() { // Add post to a thread with non-existent from user - Post post = createPost().withFrom(NON_EXISTENT_ENTITY.toString()); + Post post = createPost(null).withFrom(NON_EXISTENT_ENTITY.toString()); assertResponse( () -> addPost(THREAD.getId(), post, AUTH_HEADERS), NOT_FOUND, entityNotFound(Entity.USER, NON_EXISTENT_ENTITY)); } @@ -237,15 +268,51 @@ public class FeedResourceTest extends CatalogApplicationTest { // Add 10 posts and validate int POST_COUNT = 10; for (int i = 0; i < POST_COUNT; i++) { - Post post = createPost(); + Post post = createPost(null); thread = addPostAndCheck(thread, post, AUTH_HEADERS); } // Check if get posts API returns all the posts PostList postList = listPosts(thread.getId().toString(), AUTH_HEADERS); - // Thread also has the first message as a post. - // So, the total count should be POST_COUNT+1 - assertEquals(POST_COUNT + 1, postList.getData().size()); + assertEquals(POST_COUNT, postList.getData().size()); + } + + @Test + void list_threadsWithPostsLimit() throws HttpResponseException { + Thread thread = createAndCheck(create(), AUTH_HEADERS); + // Add 10 posts and validate + int POST_COUNT = 10; + for (int i = 0; i < POST_COUNT; i++) { + Post post = createPost("message" + i); + thread = addPostAndCheck(thread, post, AUTH_HEADERS); + } + + ThreadList threads = listThreads(null, 5, AUTH_HEADERS); + thread = threads.getData().get(0); + assertEquals(5, thread.getPosts().size()); + assertEquals(POST_COUNT, thread.getPostsCount()); + // Thread should contain the latest 5 messages + List posts = thread.getPosts(); + int startIndex = 5; + for (var post : posts) { + assertEquals("message" + startIndex++, post.getMessage()); + } + + // when posts limit is null, it should return 3 posts which is the default + threads = listThreads(null, null, AUTH_HEADERS); + thread = threads.getData().get(0); + assertEquals(3, thread.getPosts().size()); + + // limit 0 is not supported and should throw an exception + assertResponse( + () -> listThreads(null, 0, AUTH_HEADERS), + BAD_REQUEST, + "[query param limitPosts must be greater than or equal to 1]"); + + // limit greater than total number of posts should return correct response + threads = listThreads(null, 100, AUTH_HEADERS); + thread = threads.getData().get(0); + assertEquals(10, thread.getPosts().size()); } @Test @@ -281,9 +348,8 @@ public class FeedResourceTest extends CatalogApplicationTest { private static void validateThread(Thread thread, String message, String from, String about) { assertNotNull(thread.getId()); - Post firstPost = thread.getPosts().get(0); - assertEquals(message, firstPost.getMessage()); - assertEquals(from, firstPost.getFrom()); + assertEquals(message, thread.getMessage()); + assertEquals(from, thread.getCreatedBy()); assertEquals(about, thread.getAbout()); } @@ -311,8 +377,9 @@ public class FeedResourceTest extends CatalogApplicationTest { return new CreateThread().withFrom(USER.getName()).withMessage("message").withAbout(about); } - public static Post createPost() { - return new Post().withFrom(USER.getName()).withMessage("message"); + public static Post createPost(String message) { + message = StringUtils.isNotEmpty(message) ? message : "message"; + return new Post().withFrom(USER.getName()).withMessage(message); } public static Thread getThread(UUID id, Map authHeaders) throws HttpResponseException { @@ -320,10 +387,11 @@ public class FeedResourceTest extends CatalogApplicationTest { return TestUtils.get(target, Thread.class, authHeaders); } - public static ThreadList listThreads(String entityLink, Map authHeaders) + public static ThreadList listThreads(String entityLink, Integer limitPosts, Map authHeaders) throws HttpResponseException { WebTarget target = getResource("feed"); target = entityLink != null ? target.queryParam("entityLink", entityLink) : target; + target = limitPosts != null ? target.queryParam("limitPosts", limitPosts) : target; return TestUtils.get(target, ThreadList.class, authHeaders); }