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 forward messages #9794

Closed
wants to merge 149 commits into from

Conversation

asliayk
Copy link
Contributor

@asliayk asliayk commented Nov 16, 2024

Checklist

General

Server

  • Important: I implemented the changes with a very good performance and prevented too many (unnecessary) and too complex database calls.
  • I strictly followed the principle of data economy for all database calls.
  • I strictly followed the server coding and design guidelines.
  • I added multiple integration tests (Spring) related to the features (with a high test coverage).
  • I added pre-authorization annotations according to the guidelines and checked the course groups for all new REST Calls (security).

Client

  • Important: I implemented the changes with a very good performance, prevented too many (unnecessary) REST calls and made sure the UI is responsive, even with large data (e.g. using paging).
  • I strictly followed the principle of data economy for all client-server REST calls.
  • I strictly followed the client coding and design guidelines.
  • Following the theming guidelines, I specified colors only in the theming variable files and checked that the changes look consistent in both the light and the dark theme.
  • I added multiple integration tests (Jest) related to the features (with a high test coverage), while following the test guidelines.
  • I added multiple screenshots/screencasts of my UI changes.
  • I translated all newly inserted strings into English and German.

Motivation and Context

Currently user cannot forward messages to other channels/direct-group chats.

(Closes #9066)

Description

The forward message feature has been added. Users can now navigate to the forward message dialog by clicking on the forward message option. They can select a random number of destinations and add content to the forwarded message (similar to Slack).

forwarded_message table has been added to the database with the following fields:

  • id: id of forwarded message
  • source_id: the id of the original message
  • source_type: the type of the original message (post or answer)
  • destination_post_id: the id of the destination post (the id of newly created post with the forwarded message)
  • destination_answer_id: the id of the destination answer post (the id of newly created answer post with the forwarded message)

post and answer_post table have been updated with a new column named has_forwarded_messages.

Known Issue for a followup PR:
The server-side changes for forwarding multiple messages within a single message and forwarding a message as a reply to another message (inside an AnswerPost) are already in place, but the client-side implementation is not ready yet. To achieve this, a unique message link needs to be generated for each post/answer post, similar to the "Copy Link" feature in Slack. This will be completed in a follow-up PR.

Steps for Testing

Prerequisites:

  • 1 Instructor/Student
  • 1 Course with Communication enabled
  1. Log in to Artemis.
  2. Navigate to the Communication section of a course.
  3. Select a random message to forward or create a new one. Click on the forward message option (available on its hover bar or in the dropdown menu when opened via right-click).
  4. Add some content to the forwarded message and select random destinations to forward the message.
  5. Go to the destination channels/user chats and observe the forwarded messages.

Testserver States

Note

These badges show the state of the test servers.
Green = Currently available, Red = Currently locked
Click on the badges to get to the test servers.







Review Progress

Performance Review

  • I (as a reviewer) confirm that the client changes (in particular related to REST calls and UI responsiveness) are implemented with a very good performance even for very large courses with more than 2000 students.
  • I (as a reviewer) confirm that the server changes (in particular related to database calls) are implemented with a very good performance even for very large courses with more than 2000 students.

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Performance Tests

  • Test 1
  • Test 2

Test Coverage

Client

Class/File Line Coverage Confirmation (assert/expect)
answer-post.model.ts 100%
forwarded-message.model.ts 91.66% ✅ ❌
post.model.ts 100%
posting.model.ts 86.36% ✅ ❌
course-conversations.component.ts 91.3% ✅ ❌
forward-message-dialog.component.ts 95.79% ✅ ❌
conversation-messages.component.ts 95.53% ✅ ❌
answer-post.service.ts 100%
answer-post.component.ts 96.29% ✅ ❌
forwarded-message.service.ts 100%
forwarded-message.component.ts 90% ✅ ❌
metis-conversation.service.ts 83.47% ✅ ❌
metis.service.ts 88.47% ✅ ❌
post.service.ts 80% ✅ ❌
post.component.ts 97.08% ✅ ❌
posting-reactions-bar.component.ts 83.67% ✅ ❌
posting-thread.component.ts 100%
posting.directive.ts 93.82% ✅ ❌

Server

Class/File Line Coverage Confirmation (assert/expect)
AnswerPost.java 85% ✅ ❌
ForwardedMessage.java 42% ✅ ❌
Post.java 91% ✅ ❌
ForwardedMessageDTO.java 100%
ForwardedMessagesGroupDTO.java 100%
ForwardedMessageRepository.java not found (added)
AnswerMessageService.java 88% ✅ ❌
ConversationMessagingService.java 92% ✅ ❌
AnswerMessageResource.java 90% ✅ ❌
ConversationMessageResource.java 88% ✅ ❌
ForwardedMessageResource.java 94% ✅ ❌

Screenshots

forwarded message view
image

forward message dialog
image

Summary by CodeRabbit

  • New Features
    • Introduced robust message forwarding, allowing users to forward posts and replies to selected recipients.
    • Launched an intuitive forward message dialog featuring autocomplete for recipient selection, rich text editing, and preview capabilities.
    • Enhanced conversation navigation by clearly indicating forwarded content, ensuring users can easily trace back to original posts.
    • Updated user interface elements such as icons and tooltips to streamline message actions and improve overall messaging experience.
    • Added functionality to retrieve and manage forwarded messages, enhancing user interaction with posts.
    • Implemented new methods for batch retrieval of messages and improved data handling for forwarded content.
    • Enhanced the Post and AnswerPost classes to manage forwarded messages effectively.
    • Introduced a new ForwardedMessage entity and related DTOs for improved message management.
    • Added support for retrieving forwarded messages based on destination IDs.

asliayk and others added 30 commits October 10, 2024 20:48
…e-message-view' into feature/communication/consecutive-message-view
…-view

# Conflicts:
#	src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.ts
#	src/main/webapp/app/shared/metis/answer-post/answer-post.component.scss
Copy link
Member

@krusche krusche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add security checks (important!!!) and adapt the REST calls as specified in my inline comments. The requirement with the module prefix is new, but I'd like to address this here as well. However, the missing security checks are really problematic, please take this into account in general (we have documentation in the server coding guidelines)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (7)
src/main/webapp/app/shared/metis/metis.service.ts (1)

768-825: Refactor long method into smaller functions.

The method is quite long and handles multiple responsibilities. Consider breaking it down into smaller, more focused functions for better maintainability.

Apply this diff to extract the creation of forwarded messages into a separate method:

+private createForwardedMessageObjects(originalPosts: Posting[], createdPost: Post, sourceType: PostingType, newContent: string): ForwardedMessage[] {
+    return originalPosts.map(
+        (post) => new ForwardedMessage(undefined, post.id, sourceType, { id: createdPost.id } as Post, undefined, newContent || ''),
+    );
+}

+private updateCachedPosts(createdPost: Post, createdForwardedMessages: ForwardedMessage[], targetConversationId: number): void {
+    if (targetConversationId === this.currentConversation?.id) {
+        const existingPostIndex = this.cachedPosts.findIndex((post) => post.id === createdPost.id);
+        if (existingPostIndex === -1) {
+            this.cachedPosts = [createdPost, ...this.cachedPosts];
+        }
+
+        createdForwardedMessages.forEach((fm) => {
+            const postIndex = this.cachedPosts.findIndex((post) => post.id === fm.destinationPost?.id);
+            if (postIndex > -1) {
+                const post = this.cachedPosts[postIndex];
+                const updatedPost = { ...post, hasForwardedMessages: true };
+                this.cachedPosts[postIndex] = updatedPost;
+            }
+        });
+        this.posts$.next(this.cachedPosts);
+        this.cachedTotalNumberOfPosts += 1;
+        this.totalNumberOfPosts$.next(this.cachedTotalNumberOfPosts);
+    }
+}

 createForwardedMessages(originalPosts: Posting[], targetConversation: Conversation, isAnswer: boolean, newContent?: string): Observable<ForwardedMessage[]> {
     if (!this.courseId) {
         return throwError(() => new Error('Course ID is not set. Ensure that setCourse() is called before forwarding posts.'));
     }

     const newPost: Post = {
         content: newContent || '',
         conversation: targetConversation,
         hasForwardedMessages: true,
     };

     const sourceType = isAnswer ? PostingType.ANSWER : PostingType.POST;

     return this.postService.create(this.courseId, newPost).pipe(
         switchMap((createdPost: HttpResponse<Post>) => {
             const createdPostBody = createdPost.body!;
-            const forwardedMessages: ForwardedMessage[] = originalPosts.map(
-                (post) => new ForwardedMessage(undefined, post.id, sourceType, { id: createdPostBody.id } as Post, undefined, newContent || ''),
-            );
+            const forwardedMessages = this.createForwardedMessageObjects(originalPosts, createdPostBody, sourceType, newContent || '');

             const createForwardedMessageObservables = forwardedMessages.map((fm) =>
                 this.forwardedMessageService.createForwardedMessage(fm, this.courseId).pipe(map((res: HttpResponse<ForwardedMessage>) => res.body!)),
             );

             return forkJoin(createForwardedMessageObservables).pipe(
-                tap((createdForwardedMessages: ForwardedMessage[]) => {
-                    if (targetConversation.id === this.currentConversation?.id) {
-                        const existingPostIndex = this.cachedPosts.findIndex((post) => post.id === createdPostBody.id);
-                        if (existingPostIndex === -1) {
-                            this.cachedPosts = [createdPostBody, ...this.cachedPosts];
-                        }
-
-                        createdForwardedMessages.forEach((fm) => {
-                            const postIndex = this.cachedPosts.findIndex((post) => post.id === fm.destinationPost?.id);
-                            if (postIndex > -1) {
-                                const post = this.cachedPosts[postIndex];
-                                const updatedPost = { ...post, hasForwardedMessages: true };
-                                this.cachedPosts[postIndex] = updatedPost;
-                            }
-                        });
-                        this.posts$.next(this.cachedPosts);
-                        this.cachedTotalNumberOfPosts += 1;
-                        this.totalNumberOfPosts$.next(this.cachedTotalNumberOfPosts);
-                    }
-                }),
+                tap((createdForwardedMessages: ForwardedMessage[]) => 
+                    this.updateCachedPosts(createdPostBody, createdForwardedMessages, targetConversation.id)
+                ),
                 catchError((error) => throwError(() => error)),
             );
         }),
         catchError((error) => throwError(() => error)),
     );
 }

Also, consider extracting error messages to constants:

private static readonly COURSE_ID_NOT_SET_ERROR = 'Course ID is not set. Ensure that setCourse() is called before forwarding posts.';
src/main/java/de/tum/cit/aet/artemis/communication/web/ConversationMessageResource.java (2)

217-217: Inconsistent endpoint path naming.

The endpoint path uses inconsistent naming:

  • Uses "communication" prefix while other endpoints don't
  • Uses "messages-source-posts" instead of following the pattern of other endpoints
  • Uses hyphen instead of camelCase

Apply this diff to align with the existing endpoint naming pattern:

-    @GetMapping("communication/courses/{courseId}/messages-source-posts")
+    @GetMapping("courses/{courseId}/messages/source")

209-216: Enhance API documentation.

The JavaDoc should include information about error responses and validation requirements.

Apply this diff to improve the documentation:

     /**
      * GET /courses/{courseId}/messages/source-posts : Retrieve posts by their IDs
      *
      * @param courseId id of the course the posts belong to
      * @param postIds  list of IDs of the posts to retrieve
      * @return ResponseEntity with status 200 (OK) containing the list of posts in the response body,
-     *         or with status 400 (Bad Request) if the checks on user, course or post validity fail
+     *         or with status 400 (Bad Request) if postIds is null or empty,
+     *         or with status 404 (Not Found) if no posts exist for the given IDs,
+     *         or with status 403 (Forbidden) if the user doesn't have access to the course
      */
src/main/java/de/tum/cit/aet/artemis/communication/dto/ForwardedMessageDTO.java (1)

33-52: Consider validating entity state before returning.

The toEntity method should validate that either destinationPost or destinationAnswerPost is set, but not both.

 public ForwardedMessage toEntity() {
     ForwardedMessage message = new ForwardedMessage();
     message.setId(this.id);
     message.setSourceId(this.sourceId);
     message.setSourceType(this.sourceType);

+    // Validate that exactly one destination is set
+    if ((this.destinationPostId != null && this.destinationAnswerPostId != null) ||
+        (this.destinationPostId == null && this.destinationAnswerPostId == null)) {
+        throw new IllegalStateException("Exactly one destination must be set");
+    }

     if (this.destinationPostId != null) {
         Post post = new Post();
         post.setId(this.destinationPostId);
         message.setDestinationPost(post);
     }

     if (this.destinationAnswerPostId != null) {
         AnswerPost answerPost = new AnswerPost();
         answerPost.setId(this.destinationAnswerPostId);
         message.setDestinationAnswerPost(answerPost);
     }

     return message;
 }
src/main/webapp/app/entities/metis/forwarded-message.model.ts (1)

23-27: Simplify validation logic.

The validation can be simplified using nullish coalescing operator.

 private validateDestinations(): boolean {
-    const isDestinationPostValid = this.destinationPost !== undefined && this.destinationPost !== null;
-    const isDestinationAnswerPostValid = this.destinationAnswerPost !== undefined && this.destinationAnswerPost !== null;
-    return (isDestinationPostValid && !isDestinationAnswerPostValid) || (!isDestinationPostValid && isDestinationAnswerPostValid);
+    return (this.destinationPost ?? null) !== null !== (this.destinationAnswerPost ?? null) !== null;
 }
src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java (1)

95-123: Add pagination support for large result sets.

The endpoint should support pagination to handle large numbers of forwarded messages efficiently.

 @GetMapping("forwarded-messages")
 @EnforceAtLeastStudentInCourse
-public ResponseEntity<List<ForwardedMessagesGroupDTO>> getForwardedMessages(@RequestParam Long courseId, @RequestParam Set<Long> postingIds, @RequestParam PostingType type) {
+public ResponseEntity<Page<ForwardedMessagesGroupDTO>> getForwardedMessages(
+    @RequestParam Long courseId,
+    @RequestParam Set<Long> postingIds,
+    @RequestParam PostingType type,
+    @RequestParam(defaultValue = "0") int page,
+    @RequestParam(defaultValue = "20") int size) {
     // ... existing validation code ...

     Set<ForwardedMessage> forwardedMessages;
     if (type == PostingType.POST) {
-        forwardedMessages = forwardedMessageRepository.findAllByDestinationPostIds(postingIds);
+        forwardedMessages = forwardedMessageRepository.findAllByDestinationPostIds(postingIds, PageRequest.of(page, size));
     }
     else {
-        forwardedMessages = forwardedMessageRepository.findAllByDestinationAnswerPostIds(postingIds);
+        forwardedMessages = forwardedMessageRepository.findAllByDestinationAnswerPostIds(postingIds, PageRequest.of(page, size));
     }

     // ... rest of the code ...
 }
src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.ts (1)

329-402: Consider refactoring the forwarded messages handling logic.

The current implementation has high cyclomatic complexity with multiple nested conditions and API calls. Consider breaking it down into smaller, more manageable methods.

Here's a suggested refactoring approach:

-        if (postIdsWithForwardedMessages.length > 0) {
-            this.metisService.getForwardedMessagesByIds(postIdsWithForwardedMessages, PostingType.POST)?.subscribe((response) => {
+        private fetchForwardedMessages(postIds: number[]): void {
+            if (postIds.length === 0) {
+                this.groupPosts();
+                return;
+            }
+            
+            this.metisService.getForwardedMessagesByIds(postIds, PostingType.POST)?.subscribe({
+                next: (response) => this.handleForwardedMessagesResponse(response),
+                error: (error) => {
+                    console.error('Error fetching forwarded messages:', error);
+                    this.groupPosts();
+                }
+            });
+        }
+
+        private handleForwardedMessagesResponse(response: any): void {
+            if (!response?.body) {
+                this.groupPosts();
+                return;
+            }
+            
+            const forwardedMessagesGroups = response.body;
+            const map = new Map<number, ForwardedMessageDTO[]>(
+                forwardedMessagesGroups.map((group) => [group.id, group.messages])
+            );
+            
+            this.fetchSourceMessages(this.extractSourceIds(map));
+        }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79eccf9 and 0040569.

📒 Files selected for processing (12)
  • src/main/java/de/tum/cit/aet/artemis/communication/dto/ForwardedMessageDTO.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/communication/repository/ForwardedMessageRepository.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/communication/web/AnswerMessageResource.java (3 hunks)
  • src/main/java/de/tum/cit/aet/artemis/communication/web/ConversationMessageResource.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java (1 hunks)
  • src/main/webapp/app/entities/metis/forwarded-message.model.ts (1 hunks)
  • src/main/webapp/app/overview/course-conversations/course-conversations.component.ts (2 hunks)
  • src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.ts (5 hunks)
  • src/main/webapp/app/shared/metis/answer-post.service.ts (2 hunks)
  • src/main/webapp/app/shared/metis/forwarded-message.service.ts (1 hunks)
  • src/main/webapp/app/shared/metis/metis.service.ts (4 hunks)
  • src/main/webapp/app/shared/metis/post.service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/webapp/app/shared/metis/post.service.ts
  • src/main/java/de/tum/cit/aet/artemis/communication/repository/ForwardedMessageRepository.java
  • src/main/webapp/app/overview/course-conversations/course-conversations.component.ts
🧰 Additional context used
📓 Path-based instructions (2)
`src/main/java/**/*.java`: naming:CamelCase; principles:{sin...

src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

  • src/main/java/de/tum/cit/aet/artemis/communication/web/ConversationMessageResource.java
  • src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java
  • src/main/java/de/tum/cit/aet/artemis/communication/dto/ForwardedMessageDTO.java
  • src/main/java/de/tum/cit/aet/artemis/communication/web/AnswerMessageResource.java
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/shared/metis/answer-post.service.ts
  • src/main/webapp/app/entities/metis/forwarded-message.model.ts
  • src/main/webapp/app/shared/metis/forwarded-message.service.ts
  • src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.ts
  • src/main/webapp/app/shared/metis/metis.service.ts
🧠 Learnings (1)
src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java (1)
Learnt from: PaRangger
PR: ls1intum/Artemis#9794
File: src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java:79-79
Timestamp: 2024-12-10T12:02:00.447Z
Learning: In `ForwardedMessageResource.java`, when improving type safety for parameters representing posting types, prefer using the existing `PostingType` enum instead of defining new enums.
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: server-tests
  • GitHub Check: Call Build Workflow / Build and Push Docker Image
  • GitHub Check: Analyse
🔇 Additional comments (12)
src/main/webapp/app/shared/metis/metis.service.ts (5)

3-3: LGTM!

The imports are well-organized and properly scoped for the forwarded message functionality.

Also applies to: 8-8, 13-13, 36-38


48-48: LGTM!

The ForwardedMessageService is properly injected using Angular's inject function.


727-733: Return an empty Observable instead of undefined.

For consistency with RxJS patterns and to simplify error handling for consumers, return an empty Observable instead of undefined.

Apply this diff:

-getForwardedMessagesByIds(postingIds: number[], type: PostingType): Observable<HttpResponse<{ id: number; messages: ForwardedMessageDTO[] }[]>> | undefined {
+getForwardedMessagesByIds(postingIds: number[], type: PostingType): Observable<HttpResponse<{ id: number; messages: ForwardedMessageDTO[] }[]>> {
     if (postingIds && postingIds.length > 0) {
         return this.forwardedMessageService.getForwardedMessages(postingIds, type, this.courseId);
     } else {
-        return undefined;
+        return of(new HttpResponse<{ id: number; messages: ForwardedMessageDTO[] }[]>({ body: [] }));
     }
 }

741-744: Add explicit return type and make the method private.

The method lacks a return type annotation and should be private as suggested in past reviews.

Apply this diff:

-getSourcePostsByIds(postIds: number[]) {
-    if (postIds) return this.postService.getSourcePostsByIds(this.courseId, postIds);
-    else return;
+private getSourcePostsByIds(postIds: number[]): Observable<Post[]> {
+    return postIds ? this.postService.getSourcePostsByIds(this.courseId, postIds) : of([]);
}

752-755: Add explicit return type and make the method private.

Similar to getSourcePostsByIds, this method needs consistent return types and should be private.

Apply this diff:

-getSourceAnswerPostsByIds(answerPostIds: number[]) {
-    if (answerPostIds) return this.answerPostService.getSourceAnswerPostsByIds(this.courseId, answerPostIds);
-    else return;
+private getSourceAnswerPostsByIds(answerPostIds: number[]): Observable<AnswerPost[]> {
+    return answerPostIds ? this.answerPostService.getSourceAnswerPostsByIds(this.courseId, answerPostIds) : of([]);
}
src/main/java/de/tum/cit/aet/artemis/communication/dto/ForwardedMessageDTO.java (2)

15-16: LGTM! Well-structured DTO using Java record.

The use of Java record with @JsonInclude annotation is a good choice for a DTO. It provides immutability and efficient JSON serialization.


23-26: LGTM! Safe null handling in constructor.

The constructor properly handles null values using Optional, preventing potential NPEs.

src/main/webapp/app/entities/metis/forwarded-message.model.ts (2)

6-13: LGTM! Well-defined DTO interface.

The interface properly defines all required fields with optional types.


41-50: LGTM! Safe DTO conversion.

The toDTO method safely handles optional fields using the optional chaining operator.

src/main/webapp/app/shared/metis/forwarded-message.service.ts (1)

43-54: LGTM! Proper error handling for invalid input.

The method correctly handles empty IDs array and returns a typed error.

src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.ts (2)

45-48: LGTM! Clean implementation of new imports and output event.

The new imports and output event emitter are well-organized and follow Angular best practices.

Also applies to: 111-111


547-549: LGTM! Clean implementation of navigation method.

The navigation method is concise and follows Angular's event emission patterns.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/main/webapp/app/overview/course-conversations/dialogs/forward-message-dialog/forward-message-dialog.component.ts (3)

95-99: 🛠️ Refactor suggestion

Replace setTimeout with queueMicrotask.

Using setTimeout for view initialization is not recommended. This is a duplicate of a past review comment.

Apply this diff:

 ngAfterViewInit(): void {
-    setTimeout(() => {
+    queueMicrotask(() => {
         this.checkIfContentOverflows();
-    }, 0);
+    });
 }

211-218: ⚠️ Potential issue

Add validation before sending.

The send method should validate selections and content before closing the modal. This is a duplicate of a past review comment.

Apply this diff:

 send(): void {
+    if (!this.hasSelections()) {
+        return;
+    }
+    if (!this.newPost.content?.trim()) {
+        return;
+    }
     const selectedItems = {
         channels: this.selectedChannels,
         users: this.selectedUsers,
         messageContent: this.newPost.content,
     };
     this.activeModal.close(selectedItems);
 }

122-152: 🛠️ Refactor suggestion

Optimize search with debounce.

The search functionality should include debounce to prevent excessive API calls. This is a duplicate of a past review comment.

Apply this diff:

+import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
+import { Subject } from 'rxjs';

+private searchSubject = new Subject<string>();

+ngOnInit(): void {
+    this.setupSearch();
+    // ... rest of the code
+}

+private setupSearch(): void {
+    this.searchSubject.pipe(
+        debounceTime(300),
+        distinctUntilChanged(),
+    ).subscribe(term => {
+        if (term.length >= 3) {
+            this.performSearch(term);
+        }
+    });
+}

 filterOptions(): void {
     if (this.searchTerm) {
         const lowerCaseSearchTerm = this.searchTerm.toLowerCase();
-        if (lowerCaseSearchTerm.length >= 3) {
-            this.courseManagementService
-                .searchUsers(this.courseId()!, lowerCaseSearchTerm, ['students', 'tutors', 'instructors'])
-                .pipe(
-                    map((response) => response.body || []),
-                    map((users) => users.filter((user) => !this.selectedUsers.find((selectedUser) => selectedUser.id === user.id))),
-                    catchError(() => {
-                        return of([]);
-                    }),
-                )
-                .subscribe((users) => {
-                    this.filteredUsers = users;
-                    this.updateCombinedOptions();
-                    this.cdr.detectChanges();
-                });
-        }
+        this.searchSubject.next(lowerCaseSearchTerm);
     }
 }
🧹 Nitpick comments (7)
src/test/javascript/spec/component/overview/course-conversations/layout/conversation-messages/conversation-messages.component.spec.ts (5)

27-27: Simplify the import path for PostingType.

The current import path is unnecessarily verbose and fragile. Consider using a more direct import path.

-import { PostingType } from '../../../../../../../../main/webapp/app/entities/metis/posting.model';
+import { PostingType } from 'app/entities/metis/posting.model';

276-291: Improve type safety in mock data.

Instead of using unknown type assertion, consider creating a proper mock object that matches the ForwardedMessage interface structure.

-{ id: 101, sourceId: 10, sourceType: 'POST' } as unknown as ForwardedMessage,
+{ 
+  id: 101, 
+  sourceId: 10, 
+  sourceType: 'POST',
+  // Add other required properties from ForwardedMessage interface
+} as ForwardedMessage,

319-328: Enhance test specificity for better coverage.

While the test verifies undefined properties, it could be more comprehensive by checking additional post properties to ensure the entire state is correct.

 expect(component.posts[0].forwardedPosts).toBeUndefined();
 expect(component.posts[0].forwardedAnswerPosts).toBeUndefined();
+expect(component.posts[0].hasForwardedMessages).toBeFalse();
+expect(component.posts[0].id).toBe(1);

330-347: Document the error scenario being tested.

The test should include a comment explaining the specific error scenario being tested to improve maintainability.

+// Test scenario: When source posts/answers are not found, the component should
+// gracefully handle the missing data by setting empty arrays instead of undefined
 it('should handle forwarded messages with missing source gracefully', fakeAsync(() => {

368-408: Reduce type assertion duplication using helper functions.

Consider creating helper functions to generate mock data, reducing the need for repeated type assertions.

+const createMockForwardedMessage = (id: number, sourceId: number, sourceType: string): ForwardedMessage => ({
+  id,
+  sourceId,
+  sourceType,
+  // Add other required properties
+});

+const createMockPost = (id: number, content: string, conversation: Conversation): Post => ({
+  id,
+  content,
+  conversation,
+  // Add other required properties
+});

 const mockForwardedMessages: ForwardedMessage[] = [
-  { id: 101, sourceId: 10, sourceType: 'POST' } as unknown as ForwardedMessage,
-  { id: 102, sourceId: 11, sourceType: 'ANSWER' } as unknown as ForwardedMessage,
+  createMockForwardedMessage(101, 10, 'POST'),
+  createMockForwardedMessage(102, 11, 'ANSWER'),
 ];
src/main/webapp/app/overview/course-conversations/dialogs/forward-message-dialog/forward-message-dialog.component.ts (2)

34-40: Convert to standalone component.

As suggested in the past review comments, this component should be converted to standalone for better modularity and tree-shaking.

Apply this diff:

 @Component({
     selector: 'jhi-forward-message-dialog',
     templateUrl: './forward-message-dialog.component.html',
     styleUrls: ['./forward-message-dialog.component.scss'],
+    standalone: true,
     imports: [ArtemisTranslatePipe, ProfilePictureComponent, NgClass, PostingContentComponent, MarkdownEditorMonacoComponent, FormsModule, TranslateDirective],
     providers: [MetisService, LinkPreviewService, LinkifyService],
 })

42-60: Organize properties by type and consider using more signals.

The properties should be organized by type with empty lines between groups. Also, consider converting more properties to signals for better reactivity.

Apply this diff to organize properties:

-    channels = signal<ChannelDTO[] | []>([]);
-    users = signal<UserPublicInfoDTO[] | []>([]);
-    postToForward = signal<Post | undefined>(undefined);
-    courseId = signal<number | undefined>(undefined);
-    editorHeight = input<MarkdownEditorHeight>(MarkdownEditorHeight.INLINE);
-    filteredChannels: ChannelDTO[] = [];
-    filteredUsers: UserPublicInfoDTO[] = [];
-    selectedChannels: ChannelDTO[] = [];
-    selectedUsers: UserPublicInfoDTO[] = [];
-    combinedOptions: CombinedOption[] = [];
-    filteredOptions: CombinedOption[] = [];
-    defaultActions: TextEditorAction[];
-    searchTerm: string = '';
-    newPost = new Post();
-    isInputFocused = false;
-    showDropdown = false;
-    showFullForwardedMessage = false;
-    isContentLong = false;
+    // Signals
+    channels = signal<ChannelDTO[] | []>([]);
+    users = signal<UserPublicInfoDTO[] | []>([]);
+    postToForward = signal<Post | undefined>(undefined);
+    courseId = signal<number | undefined>(undefined);
+    
+    // Inputs
+    editorHeight = input<MarkdownEditorHeight>(MarkdownEditorHeight.INLINE);
+    
+    // Arrays
+    filteredChannels = signal<ChannelDTO[]>([]);
+    filteredUsers = signal<UserPublicInfoDTO[]>([]);
+    selectedChannels = signal<ChannelDTO[]>([]);
+    selectedUsers = signal<UserPublicInfoDTO[]>([]);
+    combinedOptions = signal<CombinedOption[]>([]);
+    filteredOptions = signal<CombinedOption[]>([]);
+    defaultActions: TextEditorAction[] = [];
+    
+    // Other properties
+    searchTerm = signal<string>('');
+    newPost = signal<Post>(new Post());
+    isInputFocused = signal<boolean>(false);
+    showDropdown = signal<boolean>(false);
+    showFullForwardedMessage = signal<boolean>(false);
+    isContentLong = signal<boolean>(false);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0040569 and 88491d6.

📒 Files selected for processing (2)
  • src/main/webapp/app/overview/course-conversations/dialogs/forward-message-dialog/forward-message-dialog.component.ts (1 hunks)
  • src/test/javascript/spec/component/overview/course-conversations/layout/conversation-messages/conversation-messages.component.spec.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`src/test/javascript/spec/**/*.ts`: jest: true; mock: NgMock...

src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

  • src/test/javascript/spec/component/overview/course-conversations/layout/conversation-messages/conversation-messages.component.spec.ts
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/overview/course-conversations/dialogs/forward-message-dialog/forward-message-dialog.component.ts
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Call Build Workflow / Build and Push Docker Image
  • GitHub Check: Call Build Workflow / Build .war artifact
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: server-style
  • GitHub Check: client-tests-selected
  • GitHub Check: server-tests
  • GitHub Check: client-tests
  • GitHub Check: Analyse
🔇 Additional comments (1)
src/test/javascript/spec/component/overview/course-conversations/layout/conversation-messages/conversation-messages.component.spec.ts (1)

349-366: LGTM! Well-structured test with proper spy verification.

The test effectively verifies the optimization where source posts are not fetched when there are no forwarded messages.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
src/main/java/de/tum/cit/aet/artemis/communication/web/AnswerMessageResource.java (1)

109-131: Improve log message specificity.

The log message could be more specific to match the entity type being handled.

Apply this diff to improve the log message:

-        log.debug("GET getSourceAnswerPostsByIds invoked for course {} with {} posts", courseId, answerPostIds.size());
+        log.debug("GET getSourceAnswerPostsByIds invoked for course {} with {} answer posts", courseId, answerPostIds.size());
src/main/java/de/tum/cit/aet/artemis/communication/web/ConversationMessageResource.java (1)

208-215: Enhance API documentation.

Add documentation for the 404 (Not Found) response status.

Apply this diff to improve the documentation:

     * @return ResponseEntity with status 200 (OK) containing the list of posts in the response body,
+    *         or with status 404 (Not Found) if no posts are found,
     *         or with status 400 (Bad Request) if the checks on user, course or post validity fail
src/main/webapp/app/shared/metis/forwarded-message.service.ts (1)

15-40: Consider adding retry logic for network failures.

While the error handling is good, consider adding retry logic for transient network failures using RxJS operators.

 return this.http
     .post<ForwardedMessageDTO>(`${this.resourceUrl}`, dto, {
         params,
         observe: 'response',
     })
     .pipe(
+        retry({
+            count: 3,
+            delay: (error, retryCount) => timer(retryCount * 1000)
+        }),
         catchError((error) => {
             return throwError(() => new Error('Failed to create forwarded message'));
         }),
     );

Don't forget to import:

import { timer, retry } from 'rxjs';
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88491d6 and 41e2973.

📒 Files selected for processing (4)
  • src/main/java/de/tum/cit/aet/artemis/communication/web/AnswerMessageResource.java (3 hunks)
  • src/main/java/de/tum/cit/aet/artemis/communication/web/ConversationMessageResource.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java (1 hunks)
  • src/main/webapp/app/shared/metis/forwarded-message.service.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`src/main/java/**/*.java`: naming:CamelCase; principles:{sin...

src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

  • src/main/java/de/tum/cit/aet/artemis/communication/web/ConversationMessageResource.java
  • src/main/java/de/tum/cit/aet/artemis/communication/web/AnswerMessageResource.java
  • src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/shared/metis/forwarded-message.service.ts
🧠 Learnings (1)
src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java (1)
Learnt from: PaRangger
PR: ls1intum/Artemis#9794
File: src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java:79-79
Timestamp: 2024-12-10T12:02:00.447Z
Learning: In `ForwardedMessageResource.java`, when improving type safety for parameters representing posting types, prefer using the existing `PostingType` enum instead of defining new enums.
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: Call Build Workflow / Build .war artifact
  • GitHub Check: Call Build Workflow / Build and Push Docker Image
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: client-tests-selected
  • GitHub Check: client-tests
  • GitHub Check: server-tests
  • GitHub Check: Analyse
🔇 Additional comments (3)
src/main/java/de/tum/cit/aet/artemis/communication/web/AnswerMessageResource.java (1)

42-50: LGTM! Dependencies are properly injected.

The constructor injection follows best practices with final fields and proper initialization.

src/main/webapp/app/shared/metis/forwarded-message.service.ts (1)

1-13: LGTM! Service setup and dependency injection look good.

The service is properly configured with root-level injection and uses the recommended dependency injection pattern with inject().

src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java (1)

1-55: LGTM! Class structure and dependency injection look good.

The class is properly structured with constructor-based dependency injection and appropriate logging setup.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java (3)

66-81: ⚠️ Potential issue

Add transaction boundary and security checks.

The endpoint needs transaction management and security validation for message source access.

 @PostMapping("forwarded-messages")
 @EnforceAtLeastStudentInCourse
+@Transactional
 public ResponseEntity<ForwardedMessageDTO> createForwardedMessage(@RequestParam Long courseId, @RequestBody ForwardedMessageDTO forwardedMessageDTO) throws URISyntaxException {
     log.debug("POST createForwardedMessage invoked with forwardedMessageDTO: {}", forwardedMessageDTO.toString());
     long start = System.nanoTime();

     if (forwardedMessageDTO.id() != null) {
         throw new BadRequestAlertException("A new forwarded message cannot already have an ID", ENTITY_NAME, "idExists");
     }

+    var course = courseRepository.findByIdElseThrow(courseId);
+    authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, null);
+
+    // Validate source message ownership
+    if (!authCheckService.isAllowedToAccessMessage(forwardedMessageDTO.sourceId(), course)) {
+        throw new BadRequestAlertException("Not allowed to forward this message", ENTITY_NAME, "accessDenied");
+    }

     ForwardedMessage forwardedMessage = forwardedMessageDTO.toEntity();
     ForwardedMessage savedMessage = forwardedMessageRepository.save(forwardedMessage);

     log.info("createForwardedMessage took {}", TimeLogUtil.formatDurationFrom(start));
     return ResponseEntity.created(new URI("/api/communication/forwarded-messages/" + savedMessage.getId())).body(new ForwardedMessageDTO(savedMessage));
 }

66-81: ⚠️ Potential issue

Add transaction boundary and security checks.

The method needs transaction boundary and security checks for message source access.

 @PostMapping("forwarded-messages")
 @EnforceAtLeastStudentInCourse
+@Transactional
 public ResponseEntity<ForwardedMessageDTO> createForwardedMessage(@RequestParam Long courseId, @RequestBody ForwardedMessageDTO forwardedMessageDTO) throws URISyntaxException {
     log.debug("POST createForwardedMessage invoked with forwardedMessageDTO: {}", forwardedMessageDTO.toString());
     long start = System.nanoTime();

     if (forwardedMessageDTO.id() != null) {
         throw new BadRequestAlertException("A new forwarded message cannot already have an ID", ENTITY_NAME, "idExists");
     }

+    var course = courseRepository.findByIdElseThrow(courseId);
+    if (!authCheckService.isAllowedToAccessMessage(forwardedMessageDTO.sourceId(), course)) {
+        throw new BadRequestAlertException("Not allowed to forward this message", ENTITY_NAME, "accessDenied");
+    }

     ForwardedMessage forwardedMessage = forwardedMessageDTO.toEntity();
     ForwardedMessage savedMessage = forwardedMessageRepository.save(forwardedMessage);

     log.info("createForwardedMessage took {}", TimeLogUtil.formatDurationFrom(start));
     return ResponseEntity.created(new URI("/api/communication/forwarded-messages/" + savedMessage.getId())).body(new ForwardedMessageDTO(savedMessage));
 }

93-118: ⚠️ Potential issue

Add security checks and transaction boundary.

The endpoint needs to validate destination message access and wrap the operation in a transaction.

Apply this diff to add security checks and transaction boundary:

     @GetMapping("forwarded-messages")
     @EnforceAtLeastStudentInCourse
+    @Transactional(readOnly = true)
     public ResponseEntity<List<ForwardedMessagesGroupDTO>> getForwardedMessages(@RequestParam Long courseId, @RequestParam Set<Long> postingIds, @RequestParam PostingType type) {
         log.debug("GET getForwardedMessages invoked with postingIds {} and type {}", postingIds, type);
         long start = System.nanoTime();

         if (type != PostingType.POST && type != PostingType.ANSWER) {
             throw new BadRequestAlertException("Invalid type provided. Must be 'post' or 'answer'.", ENTITY_NAME, "invalidType");
         }

+        var course = courseRepository.findByIdElseThrow(courseId);
+        // Validate access to all requested messages
+        for (Long postingId : postingIds) {
+            if (!authCheckService.isAllowedToAccessMessage(postingId, course)) {
+                throw new BadRequestAlertException("Not allowed to access message " + postingId, ENTITY_NAME, "accessDenied");
+            }
+        }

         Set<ForwardedMessage> forwardedMessages;
         if (type == PostingType.POST) {
             forwardedMessages = forwardedMessageRepository.findAllByDestinationPostIds(postingIds);
         }
         else { // type == PostingType.ANSWER
             forwardedMessages = forwardedMessageRepository.findAllByDestinationAnswerPostIds(postingIds);
         }

         List<ForwardedMessagesGroupDTO> result = forwardedMessages.stream()
                 .collect(Collectors.groupingBy(fm -> type == PostingType.POST ? fm.getDestinationPost().getId() : fm.getDestinationAnswerPost().getId(),
                         Collectors.mapping(ForwardedMessageDTO::new, Collectors.toSet())))
                 .entrySet().stream().map(entry -> new ForwardedMessagesGroupDTO(entry.getKey(), entry.getValue())).toList();

         log.info("getForwardedMessages took {}", TimeLogUtil.formatDurationFrom(start));
         return ResponseEntity.ok(result);
     }
🧹 Nitpick comments (7)
src/main/java/de/tum/cit/aet/artemis/communication/web/ConversationMessageResource.java (1)

218-238: Consider optimizing the implementation.

While the implementation is functionally correct with proper validation and error handling, there are opportunities for improvement:

  1. The course validation could be moved to the service layer to maintain consistency with other endpoints.
  2. Consider using Java records for the response to minimize data transfer.

Apply this diff to optimize the implementation:

     @GetMapping("communication/courses/{courseId}/messages-source-posts")
     @EnforceAtLeastStudentInCourse
     public ResponseEntity<List<Post>> getSourcePostsByIds(@PathVariable Long courseId, @RequestParam List<Long> postIds) {
         log.debug("GET getSourcePostsByIds invoked for course {} with {} posts", courseId, postIds.size());
         long start = System.nanoTime();

         if (postIds == null || postIds.isEmpty()) {
             throw new BadRequestAlertException("Post IDs cannot be null or empty", conversationMessagingService.getEntityName(), "invalidPostIds");
         }

-        List<Post> posts = conversationMessagingService.getMessageByIds(postIds);
-
-        if (posts.isEmpty()) {
-            return ResponseEntity.notFound().build();
-        }
-
-        if (posts.stream().anyMatch(post -> !post.getConversation().getCourse().getId().equals(courseId))) {
-            throw new BadRequestAlertException("Some posts do not belong to the specified course", conversationMessagingService.getEntityName(), "invalidCourse");
-        }
+        List<Post> posts = conversationMessagingService.getMessageByIdsForCourse(postIds, courseId);

         log.debug("getSourcePostsByIds took {}", TimeLogUtil.formatDurationFrom(start));
         return ResponseEntity.ok().body(posts);
     }

This change:

  1. Moves course validation to the service layer
  2. Simplifies the controller implementation
  3. Maintains separation of concerns
src/main/webapp/app/shared/metis/forwarded-message.service.ts (6)

36-39: Enhance error handling with specific error messages.

The current error handling returns a generic message. Consider capturing and handling specific error types (e.g., network errors, validation errors) to provide more informative feedback.

             .pipe(
                 catchError((error) => {
-                    return throwError(() => new Error('Failed to create forwarded message'));
+                    const errorMessage = error.status === 400 ? 'Invalid message data' :
+                        error.status === 403 ? 'Not authorized to create message' :
+                        error.status === 404 ? 'Course not found' :
+                        'Failed to create forwarded message';
+                    return throwError(() => new Error(errorMessage));
                 }),
             );

62-65: Enhance error handling with specific error messages.

Similar to the create method, consider providing more specific error messages based on the error type.

                 catchError((error) => {
-                    return throwError(() => new Error('Failed to retrieve forwarded messages'));
+                    const errorMessage = error.status === 400 ? 'Invalid request parameters' :
+                        error.status === 403 ? 'Not authorized to access messages' :
+                        error.status === 404 ? 'Course or messages not found' :
+                        'Failed to retrieve forwarded messages';
+                    return throwError(() => new Error(errorMessage));
                 }),

36-38: Enhance error handling with specific error messages.

The current error handling uses a generic message. Consider including the error details from the server response to provide more specific feedback.

             catchError((error) => {
-                return throwError(() => new Error('Failed to create forwarded message'));
+                const errorMessage = error.error?.message || 'Failed to create forwarded message';
+                return throwError(() => new Error(errorMessage));
             }),

62-64: Enhance error handling with specific error messages.

Similar to the previous comment, consider including the error details from the server response.

                catchError((error) => {
-                    return throwError(() => new Error('Failed to retrieve forwarded messages'));
+                    const errorMessage = error.error?.message || 'Failed to retrieve forwarded messages';
+                    return throwError(() => new Error(errorMessage));
                }),

36-38: Enhance error handling with specific error messages.

The error handling could be more specific to help with debugging and user feedback.

Apply this diff to improve error handling:

                 catchError((error) => {
-                    return throwError(() => new Error('Failed to create forwarded message'));
+                    const errorMessage = error.status === 403
+                        ? 'You do not have permission to create forwarded messages'
+                        : error.status === 400
+                            ? error.error?.message || 'Invalid forwarded message data'
+                            : 'Failed to create forwarded message';
+                    return throwError(() => new Error(errorMessage));
                 }),

62-64: Enhance error handling with specific error messages.

The error handling could be more specific to help with debugging and user feedback.

Apply this diff to improve error handling:

                 catchError((error) => {
-                    return throwError(() => new Error('Failed to retrieve forwarded messages'));
+                    const errorMessage = error.status === 403
+                        ? 'You do not have permission to view these forwarded messages'
+                        : error.status === 400
+                            ? error.error?.message || 'Invalid request parameters'
+                            : 'Failed to retrieve forwarded messages';
+                    return throwError(() => new Error(errorMessage));
                 }),
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41e2973 and f85999c.

📒 Files selected for processing (3)
  • src/main/java/de/tum/cit/aet/artemis/communication/web/ConversationMessageResource.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java (1 hunks)
  • src/main/webapp/app/shared/metis/forwarded-message.service.ts (1 hunks)
👮 Files not reviewed due to content moderation or server errors (2)
  • src/main/webapp/app/shared/metis/forwarded-message.service.ts
  • src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java
🧰 Additional context used
📓 Path-based instructions (2)
`src/main/java/**/*.java`: naming:CamelCase; principles:{sin...

src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

  • src/main/java/de/tum/cit/aet/artemis/communication/web/ConversationMessageResource.java
  • src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java
`src/main/webapp/**/*.ts`: angular_style:https://angular.io/...

src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

  • src/main/webapp/app/shared/metis/forwarded-message.service.ts
🧠 Learnings (1)
src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java (1)
Learnt from: PaRangger
PR: ls1intum/Artemis#9794
File: src/main/java/de/tum/cit/aet/artemis/communication/web/ForwardedMessageResource.java:79-79
Timestamp: 2024-12-10T12:02:00.447Z
Learning: In `ForwardedMessageResource.java`, when improving type safety for parameters representing posting types, prefer using the existing `PostingType` enum instead of defining new enums.
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: client-style
  • GitHub Check: server-tests
  • GitHub Check: server-style
  • GitHub Check: client-tests-selected
  • GitHub Check: client-tests
  • GitHub Check: Call Build Workflow / Build .war artifact
  • GitHub Check: Call Build Workflow / Build and Push Docker Image
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Analyse
🔇 Additional comments (3)
src/main/java/de/tum/cit/aet/artemis/communication/web/ConversationMessageResource.java (3)

41-41: LGTM!

The import for EnforceAtLeastStudentInCourse is correctly placed and used in the new endpoint.


208-215: LGTM!

The API documentation is complete and follows the class's documentation style.


216-217: LGTM!

The endpoint mapping and authorization are correctly implemented:

  • Path follows the REST API structure
  • @EnforceAtLeastStudentInCourse ensures proper authorization

@helios-aet helios-aet bot temporarily deployed to artemis-test1.artemis.cit.tum.de February 13, 2025 10:30 Inactive
@helios-aet helios-aet bot temporarily deployed to artemis-test3.artemis.cit.tum.de February 13, 2025 10:35 Inactive
@krusche krusche closed this Feb 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client Pull requests that update TypeScript code. (Added Automatically!) communication Pull requests that affect the corresponding module database Pull requests that update the database. (Added Automatically!). Require a CRITICAL deployment. feature server Pull requests that update Java code. (Added Automatically!) tests
Projects
Status: Ready For Review
Status: Done
Development

Successfully merging this pull request may close these issues.

Communication: Forward Messages Feature