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

Open
wants to merge 98 commits into
base: develop
Choose a base branch
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 93.33% ✅ ❌
post.model.ts 100%
posting.model.ts 100%
course-conversations.component.ts 84.74% ✅ ❌
forward-message-dialog.component.ts 90.29% ✅ ❌
conversation-messages.component.ts 79.84% ✅ ❌
answer-post.service.ts 100%
answer-post.component.ts 98.07% ✅ ❌
forwarded-message.service.ts 100%
forwarded-message.component.ts 94.11% ✅ ❌
metis.service.ts 83.39% ✅ ❌
post.service.ts 80% ✅ ❌
post.component.ts 89.43% ✅ ❌
answer-post-reactions-bar.component.ts 92.5% ✅ ❌
post-reactions-bar.component.ts 92.39% ✅ ❌
posting-reactions-bar.directive.ts 71.71% ✅ ❌
posting-thread.component.ts 92.3% ✅ ❌
posting.directive.ts 89.58% ✅ ❌

Screenshots

forwarded message view
image

forward message dialog
image

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added functionality to forward messages, including a new modal dialog for selecting channels and chats.
    • Introduced methods to retrieve messages by their IDs and manage forwarded messages.
    • Enhanced the Post and AnswerPost classes to track forwarded messages.
    • Added a new component for displaying forwarded messages.
    • Implemented a new method for batch retrieval of answer messages by their IDs.
    • Added a new endpoint for retrieving answer posts by their IDs.
    • Introduced a new DTO for grouping forwarded messages.
    • Added a new button for forwarding messages in various components.
    • Enhanced the ConversationMessagesComponent to handle forwarded messages and navigation.
    • Added new methods in MetisConversationService for creating group and direct conversations.
  • Bug Fixes

    • Improved error handling in message retrieval and forwarding functionalities.
  • Documentation

    • Updated UI elements and added tooltips for new features.
  • Style

    • Introduced new styles for the forward message dialog and related components.
  • Tests

    • Added tests for new functionalities related to message forwarding.
    • Enhanced test coverage for components managing forwarded messages.
  • Chores

    • Updated internationalization files to include translations for the forward message feature.

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

@JerroyTan JerroyTan left a comment

Choose a reason for hiding this comment

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

[Tested on TS2]
Functionalities work mostly as expected, except for two quirks while I was playing around.

  1. After forwarding messages, I logged in to another user and the forwarded content was not rendered at first. After refreshing, they got rendered like normal. This also happened after my 2. point.
    directly after logging in
    image
    after refreshing
    image

  2. When forwarding from a private channel to a public channel, the link for "view conversation" appears even if the user is not in the private channel, and clicking on it causes them to access a site they are not supposed to, and reloads them to the all public messages. Is this desired behaviour?
    image
    PS: after this happens, the forwarded content are again not rendered until you refresh.

  3. This is more of a sanity check, is it intended that messages from direct chats are allowed to be forwarded to public chats? I was thinking if this is suitable for the privacy of the users.

PaRangger
PaRangger previously approved these changes Dec 20, 2024
Copy link
Contributor

@PaRangger PaRangger left a comment

Choose a reason for hiding this comment

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

Tested on TS2. Everything works great now, thanks for implementing the requested changes! 😄

julian-wls
julian-wls previously approved these changes Dec 20, 2024
Copy link
Contributor

@julian-wls julian-wls left a comment

Choose a reason for hiding this comment

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

Tested on TS6. Works as expected. Re-approve.

Copy link
Member

@anian03 anian03 left a comment

Choose a reason for hiding this comment

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

Tested on TS6, I noticed a problem with the search bar: It's only possible to enter lowercase letters. So If I try to forward something to a direct chat with "Daniel" I can only find him by typing "aniel" (because "daniel" does not match "Daniel")

Daniel aniel

@asliayk asliayk dismissed stale reviews from julian-wls, PaRangger, and HawKhiem via ccfc1b7 December 25, 2024 09:30
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 (12)
src/main/webapp/app/shared/metis/metis.service.ts (1)

785-825: Potential memory leak in createForwardedMessages.

The forkJoin operation could lead to memory leaks if the component is destroyed before the operation completes. Consider using takeUntil or moving the subscription management to the component level.

Consider one of these approaches:

  1. Add takeUntil operator to handle component destruction:
private destroy$ = new Subject<void>();

// Add to the pipe chain
takeUntil(this.destroy$)

// Add to ngOnDestroy
this.destroy$.next();
this.destroy$.complete();
  1. Or move subscription management to the component level and use the async pipe in the template.
src/main/webapp/app/overview/course-conversations/dialogs/forward-message-dialog/forward-message-dialog.component.ts (10)

25-30: Check modular structure for the component.

The component selector and metadata are properly defined. If you expect higher reusability, consider marking it as a standalone component or referencing it in a dedicated shared module for better modular design.


31-47: Use consistent naming for signals and properties.

Your signals channels, users, and postToForward follow the new Angular reactivity model well. However, verify that all property names match the camelCase style recommended in the guidelines. For instance, courseId vs. _courseId in other files. Standardizing naming conventions across the entire codebase can help maintain clarity.


48-56: Dropdown display flag and default states.

The showDropdown field is a boolean that you toggle. You might consider resetting it to false on component initialization to ensure you don’t show the dropdown unexpectedly if some external condition sets it otherwise.


57-81: Leverage RxJS best practices and reduce side effects.

Inside ngOnInit, you pull data, build combined options, and filter them. If you foresee multiple side effects in the future, consider using a more reactive approach such as a single pipeline. This can help avoid duplication of filter logic and keep your code more organized.


83-87: Prefer Angular lifecycle hooks over setTimeout.

Using setTimeout(() => this.checkIfContentOverflows(), 0) ensures the DOM is stable, but you may also explore ngAfterViewChecked or ChangeDetectorRef.afterViewInit to avoid potential race conditions or performance overhead from repeated setTimeout usage in large or complex components.


105-108: Prefer using a single event handler for search terms.

filterItems is a thin wrapper around filterOptions(). Consider consolidating the logic if no additional transformations are performed. This keeps the component minimal and direct.


110-142: Avoid redundant filtering logic for channels and users.

filterOptions includes separate logic for channels and users, plus a search to the server for user data if the search term length >= 3. Consider centralizing this logic to a single pipeline or function to reduce duplication. Also, handle possible concurrency (multiple typed queries) by using switchMap or distinctUntilChanged in your streams, if relevant.


214-217: Implement keyboard accessibility.

onInputFocus sets showDropdown = true. Consider implementing keyboard navigation (arrow keys, tab/shift+tab) for selecting items in the dropdown to support accessibility best practices.


219-222: Consider a short delay or a click-away approach.

onInputBlur hides the dropdown immediately on blur. If your design calls for a short grace period, you could use a click-away approach or a slight delay to allow immediate re-click without closing.


224-228: Use a more direct approach to focusing.

focusInput calls this.renderer.selectRootElement(...). This can cause scrolling to the input if it’s not in view. If that’s intentional, it’s fine; otherwise, consider using @ViewChild to call nativeElement.focus() directly.

src/main/webapp/app/shared/metis/post/post.component.ts (1)

83-84: Consider improving type definitions and immutability.

The properties are well-structured, but could be enhanced:

-originalPostDetails: Post | AnswerPost | undefined = undefined;
+readonly originalPostDetails: Posting | undefined = undefined;

-isConsecutive = input<boolean>(false);
-forwardedPosts = input<Post[]>([]);
-forwardedAnswerPosts = input<AnswerPost[]>([]);
-course: Course;
+readonly isConsecutive = input<boolean>(false);
+readonly forwardedPosts = input<Post[]>([]);
+readonly forwardedAnswerPosts = input<AnswerPost[]>([]);
+readonly course: Course;

Also applies to: 97-99, 101-101

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1ec68a1 and af6ba8a.

⛔ Files ignored due to path filters (1)
  • src/main/resources/config/liquibase/master.xml is excluded by !**/*.xml
📒 Files selected for processing (10)
  • src/main/webapp/app/overview/course-conversations/course-conversations.component.html (1 hunks)
  • src/main/webapp/app/overview/course-conversations/course-conversations.component.ts (2 hunks)
  • src/main/webapp/app/overview/course-conversations/dialogs/forward-message-dialog/forward-message-dialog.component.ts (1 hunks)
  • src/main/webapp/app/shared/metis/answer-post/answer-post.component.html (2 hunks)
  • src/main/webapp/app/shared/metis/metis-conversation.service.ts (1 hunks)
  • src/main/webapp/app/shared/metis/metis.module.ts (3 hunks)
  • src/main/webapp/app/shared/metis/metis.service.ts (4 hunks)
  • src/main/webapp/app/shared/metis/post/post.component.html (3 hunks)
  • src/main/webapp/app/shared/metis/post/post.component.ts (8 hunks)
  • src/main/webapp/app/shared/metis/posting.directive.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/main/webapp/app/shared/metis/posting.directive.ts
  • src/main/webapp/app/shared/metis/metis.module.ts
  • src/main/webapp/app/shared/metis/answer-post/answer-post.component.html
  • src/main/webapp/app/shared/metis/post/post.component.html
  • src/main/webapp/app/overview/course-conversations/course-conversations.component.ts
🧰 Additional context used
📓 Path-based instructions (5)
src/main/webapp/app/shared/metis/metis-conversation.service.ts (1)

Pattern 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 (1)

Pattern 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/course-conversations.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/shared/metis/post/post.component.ts (1)

Pattern 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/metis.service.ts (1)

Pattern 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

🔇 Additional comments (23)
src/main/webapp/app/shared/metis/metis.service.ts (5)

3-3: LGTM! Imports are well-organized.

The new imports are correctly structured and necessary for implementing the forwarded messages functionality.

Also applies to: 13-13, 37-39


67-67: LGTM! Dependency injection is properly implemented.

The ForwardedMessageService is correctly injected following Angular's dependency injection pattern.


796-820: LGTM! Robust error handling and caching logic.

The implementation correctly:

  • Uses forkJoin for parallel execution
  • Updates the cache only when necessary
  • Handles errors appropriately
  • Maintains consistency in the observable streams

753-756: 🛠️ Refactor suggestion

Add explicit return type and improve return statement.

The method lacks a return type annotation and uses an implicit return.

-    getSourceAnswerPostsByIds(answerPostIds: number[]) {
-        if (answerPostIds) return this.answerPostService.getSourceAnswerPostsByIds(this.courseId, answerPostIds);
-        else return;
+    getSourceAnswerPostsByIds(answerPostIds: number[]): Observable<AnswerPost[]> | undefined {
+        return answerPostIds ? this.answerPostService.getSourceAnswerPostsByIds(this.courseId, answerPostIds) : undefined;
     }

Likely invalid or redundant comment.


728-734: 🛠️ Refactor suggestion

Add error handling for API failures.

The method should handle potential API errors to prevent unhandled exceptions.

     getForwardedMessagesByIds(ids: number[], type: 'post' | 'answer'): Observable<HttpResponse<{ id: number; messages: ForwardedMessage[] }[]>> | undefined {
         if (ids && ids.length > 0) {
-            return this.forwardedMessageService.getForwardedMessages(ids, type);
+            return this.forwardedMessageService.getForwardedMessages(ids, type).pipe(
+                catchError(error => {
+                    // Handle or transform the error as needed
+                    return throwError(() => error);
+                })
+            );
         } else {
             return undefined;
         }
     }

Likely invalid or redundant comment.

src/main/webapp/app/shared/metis/metis-conversation.service.ts (2)

242-244: Provide consistent naming conventions and align with existing create methods.

This new createGroupConversation method is clearly aligned with the existing pattern for creating different types of chats. Please ensure the naming convention is consistent across the entire codebase, and that all references to “group conversations” are updated accordingly in other Angular services or components if needed.


246-248: Validate single login parameter.

createDirectConversation currently accepts a single string parameter. Consider adding a null/undefined check or an empty string check, to avoid potential errors or unexpected results if no valid string is passed.

src/main/webapp/app/overview/course-conversations/dialogs/forward-message-dialog/forward-message-dialog.component.ts (13)

1-17: Confirm compliance with Angular style guidelines.

The import statements and usage of new Angular features like inject, signal, and viewChild are aligned with the latest Angular standards. Just ensure that references like input, signal, and viewChild are intended to be lowercase, as they deviate from the traditional uppercase @Input, @ViewChild usage. If you are leveraging new stable APIs, it’s fine; otherwise, consider verifying your Angular version and nomenclature to ensure compatibility.


18-24: Interface declaration looks concise.

The CombinedOption interface is straightforward and typed properly. This helps maintain clarity when filtering and rendering channel/user search results.


89-95: Validate content measurement approach.

checkIfContentOverflows() calculates scrollHeight vs clientHeight. Confirm that this logic accounts for any dynamic styling or if the content includes images that load asynchronously. You might re-check in ngAfterViewChecked or upon image load events if needed.


97-99: Toggle for message detail is well-structured.

toggleShowFullForwardedMessage() cleanly maintains expanded/collapsed state for message content. Just ensure that the HTML template accounts for state transitions to provide a good user experience (e.g., transitions or animations).


101-103: Editor binding is straightforward.

updateField updates this.newPost.content properly. Ensure that any input validation (minimum length, safe HTML content) is done if your scenario requires it.


144-159: Mapping logic for combined filters is consistent.

updateCombinedOptions effectively merges two arrays (channels, users) into a single combined array. Looks correct for your use case. Be sure to handle potential duplicates if the code’s functionality ever requires distinct checks for user or channel with the same ID or name.


161-183: Prevent adding duplicates by verifying item presence.

The logic inside selectOption is robust: it checks if an item is already in the selected list. This helps prevent duplication. Consider showing an alert or toast if the user tries to select a channel or user that is already chosen, so they know why nothing happens.


185-191: Confirm usage pattern for removing channels.

removeSelectedChannel updates the selected list and sets focus back to the search input. This is a good UX pattern. Just ensure that the parent component or service is informed if the removal also requires backend updates.


193-199: Check if removal from selected users requires further cleanup.

Removing users from selectedUsers is straightforward. If you rely on a backend or parent component, make sure you handle any side effects of user removal, such as unsubscribing or revoking user membership for the forward operation.


201-208: Validate message content before ending the flow.

You’re directly closing the modal with selectedItems data. If empty messages are not allowed, incorporate a content validation step here. Otherwise, the user might forward an empty or invalid message.


210-212: Check the UI for selection prompts.

hasSelections returns true if there are any selected channels or users. Ensure the template properly disables or hides the send button when no selections exist to improve user clarity.


230-235: Validate dropdown closing logic for all user events.

You’re checking if the click target is outside of searchInput. Make sure you also handle cases like pressing the Escape key or overlay clicks. Additionally, watch for race conditions if the user interacts extremely fast.


237-238: Leverage enumerations for editor height.

Referencing MarkdownEditorHeight is helpful for clarity. In alignment with Angular style guidelines, ensure the usage of enumerations or typed constants if you extend additional heights or rely on other enumerated values in the future.

src/main/webapp/app/shared/metis/post/post.component.ts (3)

17-17: LGTM! Import statements are properly organized.

The new imports are correctly added and follow Angular's style guide.

Also applies to: 24-24, 26-26, 36-36, 37-37


111-111: LGTM! Proper null checking implemented.

The course initialization correctly implements null checking using the nullish coalescing operator.


330-332: LGTM! Event emitter implementation is correct.

The onTriggerNavigateToPost method correctly implements the event emission.

@asliayk
Copy link
Contributor Author

asliayk commented Dec 25, 2024

Tested on TS6, I noticed a problem with the search bar: It's only possible to enter lowercase letters. So If I try to forward something to a direct chat with "Daniel" I can only find him by typing "aniel" (because "daniel" does not match "Daniel")

Daniel aniel

I used the existing logic for user selection. I face this issue while creating a direct conversation as well. So this issue is not related with this PR, but should be solved in another PR.

image
image

@asliayk
Copy link
Contributor Author

asliayk commented Dec 25, 2024

[Tested on TS2] Functionalities work mostly as expected, except for two quirks while I was playing around.

  1. After forwarding messages, I logged in to another user and the forwarded content was not rendered at first. After refreshing, they got rendered like normal. This also happened after my 2. point.
    directly after logging in
    image
    after refreshing
    image
  2. When forwarding from a private channel to a public channel, the link for "view conversation" appears even if the user is not in the private channel, and clicking on it causes them to access a site they are not supposed to, and reloads them to the all public messages. Is this desired behaviour?
    image
    PS: after this happens, the forwarded content are again not rendered until you refresh.
  3. This is more of a sanity check, is it intended that messages from direct chats are allowed to be forwarded to public chats? I was thinking if this is suitable for the privacy of the users.
  1. I tested it again and didn't encounter this issue. However, some content may load slowly when tested immediately after deployment; I've experienced this in other PRs as well.
  2. Since it doesn't crash or show any errors, I think displaying a warning that says "You are not a member of this conversation" is fine for now. In the future, there could be a preview of the channel for users who aren't members, along with a "Join" option at the bottom, similar to Slack.
  3. Yes, this is allowed in Slack as well. Artemis already has a confidentiality warning that says, "Keep your personal information safe by keeping it to yourself and not sharing it online" so I think it should be fine.

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 component:Communication database Pull requests that update the database. (Added Automatically!). Require a CRITICAL deployment. feature ready for review server Pull requests that update Java code. (Added Automatically!) tests
Projects
Status: Ready For Review
Status: In Progress
Development

Successfully merging this pull request may close these issues.

Communication: Forward Messages Feature