Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Communication: Allow user to save messages for later #9705

Merged
merged 59 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
0ad2e22
Add scroll position memorization
cremertim Oct 28, 2024
28a0dc6
Reduced timeout
cremertim Oct 28, 2024
a76e349
performance
cremertim Oct 28, 2024
9726517
Default scroll to bottom of the messages if you create a new message
cremertim Oct 29, 2024
9979342
Added test cases
cremertim Oct 29, 2024
d9b1024
First implementation via ID
cremertim Oct 29, 2024
21c0c1b
Merge branch 'develop' into bugfix/communication/adjust-channel-jumping
cremertim Oct 29, 2024
870d20f
optimization from coderabit
cremertim Oct 29, 2024
82aff7f
Merge remote-tracking branch 'origin/bugfix/communication/adjust-chan…
cremertim Oct 29, 2024
1b5de0b
Added integration Test for search
cremertim Oct 30, 2024
4ad6553
variable readonly
cremertim Oct 30, 2024
c98aef2
Added tests
cremertim Oct 30, 2024
7398ef2
Merge branch 'develop' into bugfix/communication/adjust-channel-jumping
cremertim Oct 30, 2024
674e3b5
Merge branch 'develop' into bugfix/communication/adjust-channel-jumping
cremertim Oct 30, 2024
3fb2349
Merge remote-tracking branch 'origin/develop' into bugfix/communicati…
Pablosanqt Oct 31, 2024
7d7ad2c
Small bugfix
Pablosanqt Oct 31, 2024
94e18ab
Fix for change between modules
Pablosanqt Oct 31, 2024
355a91c
Fix tests
Pablosanqt Oct 31, 2024
41d9509
Merge remote-tracking branch 'origin/develop' into bugfix/communicati…
PaRangger Nov 3, 2024
095f321
Fix for proper scroll position on load and on new message
PaRangger Nov 4, 2024
2778fcc
Merge remote-tracking branch 'origin/develop' into feature/communicat…
PaRangger Nov 4, 2024
0096502
Save Post For Later Feature
PaRangger Nov 7, 2024
41d7afa
Merge branch 'bugfix/communication/adjust-channel-jumping' into featu…
PaRangger Nov 7, 2024
2393732
Navigate to post with functionality from pr 9614
PaRangger Nov 7, 2024
90d3d65
Layouting and Scroll issue fix
PaRangger Nov 8, 2024
8fe911b
Merge remote-tracking branch 'origin/develop' into bugfix/communicati…
PaRangger Nov 8, 2024
b4da15b
Fix merge conflict issues
PaRangger Nov 8, 2024
11b0565
Merge branch 'bugfix/communication/adjust-channel-jumping' into featu…
PaRangger Nov 9, 2024
7904158
Resolve Conflict
PaRangger Nov 9, 2024
cf621a2
Client Tests
PaRangger Nov 9, 2024
4c1e409
Client test fixes
PaRangger Nov 9, 2024
f04482b
Server Tests
PaRangger Nov 10, 2024
fb13207
Merge remote-tracking branch 'origin/develop' into feature/communicat…
PaRangger Nov 10, 2024
14fa4f0
Cleanup Service
PaRangger Nov 10, 2024
fb4983e
Add missing annotations
PaRangger Nov 10, 2024
c450767
Merge remote-tracking branch 'origin/develop' into feature/communicat…
PaRangger Nov 11, 2024
fda2490
Fix Server Tests
PaRangger Nov 11, 2024
a095c98
Test fixes
PaRangger Nov 11, 2024
1cc718d
Add DomainObject to SavedPost
PaRangger Nov 11, 2024
31f3ab9
Fix server doc
PaRangger Nov 11, 2024
bcf225a
Increase coverage
PaRangger Nov 11, 2024
cebc46f
Remove junit
PaRangger Nov 11, 2024
585b9ed
Bugfix
PaRangger Nov 11, 2024
4488844
Merge branch 'bugfix/communication/fix-crashing-module-on-deleted-use…
PaRangger Nov 11, 2024
42b72ae
Changed from strings to tinyint for enums
PaRangger Nov 11, 2024
c44b968
Fix client tests
PaRangger Nov 11, 2024
431394c
Add delete notice to archived and completed tabs
PaRangger Nov 12, 2024
a61b38a
Merge branch 'develop' into feature/communication/save-message-for-later
PaRangger Nov 14, 2024
e338630
Merge remote-tracking branch 'origin/develop' into feature/communicat…
PaRangger Nov 14, 2024
a878d61
Feedback
PaRangger Nov 16, 2024
88373a0
Fix Tests
PaRangger Nov 16, 2024
5b56551
Codebunny feedback and style test fix
PaRangger Nov 16, 2024
b66ac4f
Added caching for better performance
PaRangger Nov 20, 2024
4b86594
Fix code style test
PaRangger Nov 20, 2024
bc7165c
Merge remote-tracking branch 'origin/develop' into feature/communicat…
PaRangger Nov 20, 2024
3f920cd
Feedback style
PaRangger Nov 20, 2024
0a8076d
Merge remote-tracking branch 'origin/develop' into feature/communicat…
PaRangger Nov 21, 2024
819d416
Merge remote-tracking branch 'origin/develop' into feature/communicat…
PaRangger Nov 26, 2024
238e171
Fix Merge conflict
PaRangger Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.SQLRestriction;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import de.tum.cit.aet.artemis.communication.domain.conversation.Conversation;
import de.tum.cit.aet.artemis.core.domain.Course;

/**
Expand All @@ -35,10 +39,20 @@ public class AnswerPost extends Posting {
@OneToMany(mappedBy = "answerPost", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.EAGER)
private Set<Reaction> reactions = new HashSet<>();

/***
* The value 1 represents an answer post, given by the enum {{@link PostingType}}
*/
@OneToMany(mappedBy = "postId", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
@SQLRestriction("post_type = 1")
PaRangger marked this conversation as resolved.
Show resolved Hide resolved
private Set<SavedPost> savedPosts = new HashSet<>();

@ManyToOne
@JsonIncludeProperties({ "id", "exercise", "lecture", "course", "courseWideContext", "conversation", "author" })
private Post post;

@Transient
private boolean isSaved = false;

@JsonProperty("resolvesPost")
public Boolean doesResolvePost() {
return resolvesPost;
Expand Down Expand Up @@ -76,6 +90,25 @@ public void setPost(Post post) {
this.post = post;
}

@JsonIgnore
public Set<SavedPost> getSavedPosts() {
return savedPosts;
}

@JsonProperty("isSaved")
public boolean getIsSaved() {
return isSaved;
}

public void setIsSaved(boolean isSaved) {
this.isSaved = isSaved;
}

@JsonIgnore
public Conversation getConversation() {
return getPost().getConversation();
}
PaRangger marked this conversation as resolved.
Show resolved Hide resolved

/**
* Helper method to extract the course an AnswerPost belongs to, which is found in different locations based on the parent Post's context
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.validation.constraints.Size;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.SQLRestriction;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import de.tum.cit.aet.artemis.communication.domain.conversation.Conversation;
import de.tum.cit.aet.artemis.core.domain.Course;
Expand Down Expand Up @@ -54,6 +57,13 @@ public class Post extends Posting {
@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.EAGER)
private Set<AnswerPost> answers = new HashSet<>();

/***
* The value 0 represents a post, given by the enum {{@link PostingType}}
*/
@OneToMany(mappedBy = "postId", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
@SQLRestriction("post_type = 0")
PaRangger marked this conversation as resolved.
Show resolved Hide resolved
private Set<SavedPost> savedPosts = new HashSet<>();

@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "post_tag", joinColumns = @JoinColumn(name = "post_id"))
@Column(name = "text")
Expand Down Expand Up @@ -96,6 +106,9 @@ public class Post extends Posting {
@Column(name = "vote_count")
private int voteCount;

@Transient
private boolean isSaved = false;

public Post() {
}

Expand Down Expand Up @@ -222,6 +235,20 @@ public void setVoteCount(Integer voteCount) {
this.voteCount = voteCount != null ? voteCount : 0;
}

@JsonIgnore
public Set<SavedPost> getSavedPosts() {
return savedPosts;
}

@JsonProperty("isSaved")
public boolean getIsSaved() {
return isSaved;
}

public void setIsSaved(boolean isSaved) {
this.isSaved = isSaved;
}

/**
* Helper method to extract the course a Post belongs to, which is found in different locations based on the Post's context
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import de.tum.cit.aet.artemis.communication.domain.conversation.Conversation;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.DomainObject;
import de.tum.cit.aet.artemis.core.domain.User;
Expand Down Expand Up @@ -118,4 +119,6 @@ public void setAuthorRole(UserRole authorRole) {

@Transient
public abstract Course getCoursePostingBelongsTo();

public abstract Conversation getConversation();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.tum.cit.aet.artemis.communication.domain;

import java.util.Arrays;

public enum PostingType {

POST((short) 0), ANSWER((short) 1);

private final short databaseKey;

PostingType(short databaseKey) {
this.databaseKey = databaseKey;
}

public short getDatabaseKey() {
return databaseKey;
}

public static PostingType fromDatabaseKey(short databaseKey) {
return Arrays.stream(PostingType.values()).filter(type -> type.getDatabaseKey() == databaseKey).findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown database key: " + databaseKey));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package de.tum.cit.aet.artemis.communication.domain;

import java.time.ZonedDateTime;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Enumerated;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;

import de.tum.cit.aet.artemis.core.domain.DomainObject;
import de.tum.cit.aet.artemis.core.domain.User;

@Entity
@Table(name = "saved_post")
public class SavedPost extends DomainObject {

@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;

@Column(name = "post_id", nullable = false)
private Long postId;

@Enumerated
@Column(name = "post_type", nullable = false)
private PostingType postType;

@Enumerated
@Column(name = "status", nullable = false)
private SavedPostStatus status;

@Column(name = "completed_at")
private ZonedDateTime completedAt;
PaRangger marked this conversation as resolved.
Show resolved Hide resolved

public SavedPost() {
}

public SavedPost(User user, Long postId, PostingType postType, SavedPostStatus status, ZonedDateTime completedAt) {
this.user = user;
this.postId = postId;
this.postType = postType;
this.status = status;
this.completedAt = completedAt;
}

public Long getPostId() {
return postId;
}

public void setPostId(Long postId) {
this.postId = postId;
}

public void setStatus(SavedPostStatus status) {
this.status = status;
}

public User getUser() {
return user;
}

public SavedPostStatus getStatus() {
return status;
}

public void setCompletedAt(ZonedDateTime completedAt) {
this.completedAt = completedAt;
}

public void setPostType(PostingType postType) {
this.postType = postType;
}

public PostingType getPostType() {
return postType;
}

public ZonedDateTime getCompletedAt() {
return completedAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.tum.cit.aet.artemis.communication.domain;

import java.util.Arrays;

public enum SavedPostStatus {

IN_PROGRESS((short) 0), COMPLETED((short) 1), ARCHIVED((short) 2);

private final short databaseKey;

SavedPostStatus(short databaseKey) {
this.databaseKey = databaseKey;
}

public short getDatabaseKey() {
return databaseKey;
}

public static SavedPostStatus fromDatabaseKey(short databaseKey) {
return Arrays.stream(SavedPostStatus.values()).filter(type -> type.getDatabaseKey() == databaseKey).findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown database key: " + databaseKey));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package de.tum.cit.aet.artemis.communication.dto;

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.core.domain.User;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record AuthorDTO(Long id, String name, String imageUrl) {

public AuthorDTO(User user) {
this(user.getId(), user.getName(), user.getImageUrl());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package de.tum.cit.aet.artemis.communication.dto;

import de.tum.cit.aet.artemis.communication.domain.ConversationType;
import de.tum.cit.aet.artemis.communication.domain.conversation.Channel;
import de.tum.cit.aet.artemis.communication.domain.conversation.Conversation;
import de.tum.cit.aet.artemis.communication.domain.conversation.GroupChat;

public record PostingConversationDTO(Long id, String title, ConversationType type) {

public PostingConversationDTO(Conversation conversation) {
this(conversation.getId(), determineTitle(conversation), determineType(conversation));
}

private static String determineTitle(Conversation conversation) {
if (conversation instanceof Channel) {
return ((Channel) conversation).getName();
}
else if (conversation instanceof GroupChat) {
return ((GroupChat) conversation).getName();
}
else {
return "Chat";
}
}

private static ConversationType determineType(Conversation conversation) {
if (conversation instanceof Channel) {
return ConversationType.CHANNEL;
}
else {
return ConversationType.DIRECT;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package de.tum.cit.aet.artemis.communication.dto;

import java.time.ZonedDateTime;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.communication.domain.AnswerPost;
import de.tum.cit.aet.artemis.communication.domain.Posting;
import de.tum.cit.aet.artemis.communication.domain.PostingType;
import de.tum.cit.aet.artemis.communication.domain.UserRole;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record PostingDTO(Long id, AuthorDTO author, UserRole role, ZonedDateTime creationDate, ZonedDateTime updatedDate, String content, boolean isSaved, short savedPostStatus,
List<ReactionDTO> reactions, PostingConversationDTO conversation, short postingType, Long referencePostId) {

public PostingDTO(Posting post, boolean isSaved, short savedPostStatus) {
this(post.getId(), new AuthorDTO(post.getAuthor()), post.getAuthorRole(), post.getCreationDate(), post.getUpdatedDate(), post.getContent(), isSaved, savedPostStatus,
post.getReactions().stream().map(ReactionDTO::new).toList(), new PostingConversationDTO(post.getConversation()), getSavedPostType(post).getDatabaseKey(),
getReferencePostId(post));
}

static PostingType getSavedPostType(Posting posting) {
if (posting instanceof AnswerPost) {
return PostingType.ANSWER;
}
else {
return PostingType.POST;
}
}

static Long getReferencePostId(Posting posting) {
if (posting instanceof AnswerPost) {
return ((AnswerPost) posting).getPost().getId();
}
else {
return posting.getId();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package de.tum.cit.aet.artemis.communication.dto;

import java.time.ZonedDateTime;

import de.tum.cit.aet.artemis.communication.domain.Reaction;

public record ReactionDTO(Long id, AuthorDTO user, ZonedDateTime creationDate, String emojiId) {

public ReactionDTO(Reaction reaction) {
this(reaction.getId(), new AuthorDTO(reaction.getUser()), reaction.getCreationDate(), reaction.getEmojiId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,12 @@ default AnswerPost findAnswerMessageByIdElseThrow(Long answerPostId) {
return getValueElseThrow(findById(answerPostId).filter(answerPost -> answerPost.getPost().getConversation() != null), answerPostId);
}

@NotNull
default AnswerPost findAnswerPostOrMessageByIdElseThrow(Long answerPostId) {
return getValueElseThrow(findById(answerPostId), answerPostId);
}

long countAnswerPostsByPostIdIn(List<Long> postIds);

List<AnswerPost> findByIdIn(List<Long> idList);
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ private PageImpl<Post> findPostsWithSpecification(Pageable pageable, Specificati
LEFT JOIN FETCH p.conversation
LEFT JOIN FETCH p.reactions
LEFT JOIN FETCH p.tags
LEFT JOIN FETCH p.savedPosts
LEFT JOIN FETCH p.answers a
LEFT JOIN FETCH a.reactions
LEFT JOIN FETCH a.post
Expand Down
Loading
Loading