Skip to content

Commit

Permalink
Communication: Add shortcut to private messages on usernames (#10007)
Browse files Browse the repository at this point in the history
  • Loading branch information
PaRangger authored Dec 20, 2024
1 parent 0d0a9e9 commit 8442b3d
Show file tree
Hide file tree
Showing 18 changed files with 509 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import de.tum.cit.aet.artemis.communication.service.conversation.OneToOneChatService;
import de.tum.cit.aet.artemis.communication.service.conversation.auth.OneToOneChatAuthorizationService;
import de.tum.cit.aet.artemis.communication.service.notifications.SingleUserNotificationService;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException;
import de.tum.cit.aet.artemis.core.repository.CourseRepository;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
Expand Down Expand Up @@ -66,16 +68,17 @@ public OneToOneChatResource(SingleUserNotificationService singleUserNotification
*
* @param courseId the id of the course
* @param otherChatParticipantLogins logins of other participants (must be 1 for one to one chat) excluding the requesting user
* @return ResponseEntity with status 201 (Created) and with body containing the created one to one chat
*
* @return ResponseEntity according to createOneToOneChat function
*/
@PostMapping("{courseId}/one-to-one-chats")
@EnforceAtLeastStudent
public ResponseEntity<OneToOneChatDTO> startOneToOneChat(@PathVariable Long courseId, @RequestBody List<String> otherChatParticipantLogins) throws URISyntaxException {
var requestingUser = userRepository.getUserWithGroupsAndAuthorities();
log.debug("REST request to create one to one chat in course {} between : {} and : {}", courseId, requestingUser.getLogin(), otherChatParticipantLogins);
var course = courseRepository.findByIdElseThrow(courseId);
checkMessagingEnabledElseThrow(course);
oneToOneChatAuthorizationService.isAllowedToCreateOneToOneChat(course, requestingUser);

validateInputElseThrow(requestingUser, course);

var loginsToSearchFor = new HashSet<>(otherChatParticipantLogins);
loginsToSearchFor.add(requestingUser.getLogin());
Expand All @@ -88,8 +91,53 @@ public ResponseEntity<OneToOneChatDTO> startOneToOneChat(@PathVariable Long cour
var userA = chatMembers.getFirst();
var userB = chatMembers.get(1);

var oneToOneChat = oneToOneChatService.startOneToOneChat(course, userA, userB);
var userToBeNotified = userA.getLogin().equals(requestingUser.getLogin()) ? userB : userA;
return createOneToOneChat(requestingUser, userToBeNotified, course);
}

/**
* POST /api/courses/:courseId/one-to-one-chats/:userId: Starts a new one to one chat in a course
*
* @param courseId the id of the course
* @param userId the id of the participating user
*
* @return ResponseEntity according to createOneToOneChat function
*/
@PostMapping("{courseId}/one-to-one-chats/{userId}")
@EnforceAtLeastStudent
public ResponseEntity<OneToOneChatDTO> startOneToOneChat(@PathVariable Long courseId, @PathVariable Long userId) throws URISyntaxException {
var requestingUser = userRepository.getUserWithGroupsAndAuthorities();
var otherUser = userRepository.findByIdElseThrow(userId);
log.debug("REST request to create one to one chat by id in course {} between : {} and : {}", courseId, requestingUser.getLogin(), otherUser.getLogin());
var course = courseRepository.findByIdElseThrow(courseId);

validateInputElseThrow(requestingUser, course);

return createOneToOneChat(requestingUser, otherUser, course);
}

/**
* Function to validate incoming request data
*
* @param requestingUser user that wants to create the one to one chat
* @param course course to create the one to one chat
*/
private void validateInputElseThrow(User requestingUser, Course course) {
checkMessagingEnabledElseThrow(course);
oneToOneChatAuthorizationService.isAllowedToCreateOneToOneChat(course, requestingUser);
}

/**
* Function to create a one to one chat and return the corresponding response to the client
*
* @param requestingUser user that wants to create the one to one chat
* @param userToBeNotified user that is invited into the one to one chat
* @param course course to create the one to one chat
*
* @return ResponseEntity with status 201 (Created) and with body containing the created one to one chat
*/
private ResponseEntity<OneToOneChatDTO> createOneToOneChat(User requestingUser, User userToBeNotified, Course course) throws URISyntaxException {
var oneToOneChat = oneToOneChatService.startOneToOneChat(course, requestingUser, userToBeNotified);
singleUserNotificationService.notifyClientAboutConversationCreationOrDeletion(oneToOneChat, userToBeNotified, requestingUser,
NotificationType.CONVERSATION_CREATE_ONE_TO_ONE_CHAT);
// also send notification to the author in order for the author to subscribe to the new chat (this notification won't be saved and shown to author)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
[isCommunicationPage]="isCommunicationPage"
[lastReadDate]="lastReadDate"
[isDeleted]="isDeleted"
(onUserNameClicked)="onUserNameClicked()"
/>
</div>
}
Expand All @@ -39,7 +40,7 @@
[isDeleted]="isDeleted"
[deleteTimerInSeconds]="deleteTimerInSeconds"
(onUndoDeleteEvent)="onDeleteEvent(false)"
(userReferenceClicked)="userReferenceClicked.emit($event)"
(userReferenceClicked)="onUserReferenceClicked($event)"
(channelReferenceClicked)="channelReferenceClicked.emit($event)"
/>
<div class="post-content-padding hover-actions">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ export class OneToOneChatService {
.post<OneToOneChatDTO>(`${this.resourceUrl}${courseId}/one-to-one-chats`, [loginOfChatPartner], { observe: 'response' })
.pipe(map(this.conversationService.convertDateFromServer));
}

createWithId(courseId: number, userIdOfChatPartner: number) {
return this.http
.post<OneToOneChatDTO>(`${this.resourceUrl}${courseId}/one-to-one-chats/${userIdOfChatPartner}`, null, { observe: 'response' })
.pipe(map(this.conversationService.convertDateFromServer));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ export class MetisConversationService implements OnDestroy {

public createOneToOneChat = (loginOfChatPartner: string): Observable<HttpResponse<OneToOneChatDTO>> =>
this.onConversationCreation(this.oneToOneChatService.create(this._courseId, loginOfChatPartner));
public createOneToOneChatWithId = (userId: number): Observable<HttpResponse<OneToOneChatDTO>> =>
this.onConversationCreation(this.oneToOneChatService.createWithId(this._courseId, userId));
public createChannel = (channel: ChannelDTO) => this.onConversationCreation(this.channelService.create(this._courseId, channel));
public createGroupChat = (loginsOfChatPartners: string[]) => this.onConversationCreation(this.groupChatService.create(this._courseId, loginsOfChatPartners));
private onConversationCreation = (creation$: Observable<HttpResponse<ConversationDTO>>): Observable<never> => {
Expand Down
3 changes: 2 additions & 1 deletion src/main/webapp/app/shared/metis/metis.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export class MetisService implements OnDestroy {
private currentConversation?: ConversationDTO = undefined;
private user: User;
private pageType: PageType;
private course: Course;
private courseId: number;
private cachedPosts: Post[] = [];
private cachedTotalNumberOfPosts: number;
Expand All @@ -53,6 +52,8 @@ export class MetisService implements OnDestroy {
private courseWideTopicSubscription: Subscription;
private savedPostService: SavedPostService = inject(SavedPostService);

course: Course;

constructor(
protected postService: PostService,
protected answerPostService: AnswerPostService,
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/app/shared/metis/post/post.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
[isDeleted]="isDeleted"
[isCommunicationPage]="isCommunicationPage"
(isModalOpen)="displayInlineInput = true"
(onUserNameClicked)="onUserNameClicked()"
[lastReadDate]="lastReadDate"
/>
</div>
Expand Down
30 changes: 1 addition & 29 deletions src/main/webapp/app/shared/metis/post/post.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ import { ContextInformation, DisplayPriority, PageType, RouteComponents } from '
import { faBookmark, faBullhorn, faCheckSquare, faComments, faPencilAlt, faSmile, faThumbtack, faTrash } from '@fortawesome/free-solid-svg-icons';
import dayjs from 'dayjs/esm';
import { PostFooterComponent } from 'app/shared/metis/posting-footer/post-footer/post-footer.component';
import { OneToOneChatService } from 'app/shared/metis/conversations/one-to-one-chat.service';
import { isCommunicationEnabled, isMessagingEnabled } from 'app/entities/course.model';
import { Router } from '@angular/router';
import { MetisConversationService } from 'app/shared/metis/metis-conversation.service';
import { isCommunicationEnabled } from 'app/entities/course.model';
import { getAsChannelDTO } from 'app/entities/metis/conversation/channel.model';
import { AnswerPost } from 'app/entities/metis/answer-post.model';
import { AnswerPostCreateEditModalComponent } from 'app/shared/metis/posting-create-edit-modal/answer-post-create-edit-modal/answer-post-create-edit-modal.component';
Expand Down Expand Up @@ -98,9 +95,6 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
constructor(
public metisService: MetisService,
public changeDetector: ChangeDetectorRef,
private oneToOneChatService: OneToOneChatService,
private metisConversationService: MetisConversationService,
private router: Router,
public renderer: Renderer2,
@Inject(DOCUMENT) private document: Document,
) {
Expand Down Expand Up @@ -255,28 +249,6 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
);
}

/**
* Create a or navigate to one-to-one chat with the referenced user
*
* @param referencedUserLogin login of the referenced user
*/
onUserReferenceClicked(referencedUserLogin: string) {
const course = this.metisService.getCourse();
if (isMessagingEnabled(course)) {
if (this.isCommunicationPage) {
this.metisConversationService.createOneToOneChat(referencedUserLogin).subscribe();
} else {
this.oneToOneChatService.create(course.id!, referencedUserLogin).subscribe((res) => {
this.router.navigate(['courses', course.id, 'communication'], {
queryParams: {
conversationId: res.body!.id,
},
});
});
}
}
}

/**
* Navigate to the referenced channel
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
>
</jhi-profile-picture>
<span class="fs-small d-inline-flex flex-column align-items-start">
<span class="fw-semibold">{{ posting.author?.name }}</span>
<span class="fw-semibold" [ngClass]="{ clickable: !isAuthorOfPosting && posting.authorRole }" (click)="userNameClicked()">{{ posting.author?.name }}</span>
<span class="fs-x-small mt-1 text-body-secondary">
<span class="me-1 fs-xx-small" [ngClass]="'post-authority-icon-' + userAuthority" id="role-badge">
<fa-icon [icon]="userAuthorityIcon" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
>
</jhi-profile-picture>
<span class="fs-small d-inline-flex flex-column align-items-start">
<span class="fw-semibold">{{ posting.author?.name }}</span>
<span class="fw-semibold" [ngClass]="{ clickable: !isAuthorOfPosting && posting.authorRole }" (click)="userNameClicked()">{{ posting.author?.name }}</span>
<span class="fs-x-small mt-1 text-body-secondary">
<span class="me-1 fs-x-small" [ngClass]="'post-authority-icon-' + userAuthority" id="role-badge">
<fa-icon [icon]="userAuthorityIcon" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Posting } from 'app/entities/metis/posting.model';
import { Directive, EventEmitter, Input, OnInit, Output, inject, input } from '@angular/core';
import { Directive, EventEmitter, Input, OnInit, Output, inject, input, output } from '@angular/core';
import dayjs from 'dayjs/esm';
import { MetisService } from 'app/shared/metis/metis.service';
import { UserRole } from 'app/shared/metis/metis.util';
Expand All @@ -19,6 +19,8 @@ export abstract class PostingHeaderDirective<T extends Posting> implements OnIni

isDeleted = input<boolean>(false);

readonly onUserNameClicked = output<void>();

isAtLeastTutorInCourse: boolean;
isAuthorOfPosting: boolean;
postingIsOfToday: boolean;
Expand Down Expand Up @@ -99,4 +101,12 @@ export abstract class PostingHeaderDirective<T extends Posting> implements OnIni
this.userAuthorityTooltip = 'artemisApp.metis.userAuthorityTooltips.deleted';
}
}

protected userNameClicked() {
if (this.isAuthorOfPosting || !this.posting.authorRole) {
return;
}

this.onUserNameClicked.emit();
}
}
55 changes: 55 additions & 0 deletions src/main/webapp/app/shared/metis/posting.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { MetisService } from 'app/shared/metis/metis.service';
import { DisplayPriority } from 'app/shared/metis/metis.util';
import { faBookmark } from '@fortawesome/free-solid-svg-icons';
import { faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons';
import { isMessagingEnabled } from 'app/entities/course.model';
import { OneToOneChatService } from 'app/shared/metis/conversations/one-to-one-chat.service';
import { MetisConversationService } from 'app/shared/metis/metis-conversation.service';
import { Router } from '@angular/router';

@Directive()
export abstract class PostingDirective<T extends Posting> implements OnInit, OnDestroy {
Expand All @@ -28,8 +32,11 @@ export abstract class PostingDirective<T extends Posting> implements OnInit, OnD

content?: string;

protected oneToOneChatService = inject(OneToOneChatService);
protected metisConversationService = inject(MetisConversationService);
protected metisService = inject(MetisService);
protected changeDetector = inject(ChangeDetectorRef);
protected router = inject(Router);

// Icons
farBookmark = farBookmark;
Expand Down Expand Up @@ -131,4 +138,52 @@ export abstract class PostingDirective<T extends Posting> implements OnInit, OnD
this.posting.isSaved = true;
}
}

/**
* Create a or navigate to one-to-one chat with the referenced user
*
* @param referencedUserLogin login of the referenced user
*/
onUserReferenceClicked(referencedUserLogin: string) {
const course = this.metisService.course;
if (isMessagingEnabled(course)) {
if (this.isCommunicationPage) {
this.metisConversationService.createOneToOneChat(referencedUserLogin).subscribe();
} else {
this.oneToOneChatService.create(course.id!, referencedUserLogin).subscribe((res) => {
this.router.navigate(['courses', course.id, 'communication'], {
queryParams: {
conversationId: res.body!.id,
},
});
});
}
}
}

/**
* Create a or navigate to one-to-one chat with the referenced user
*/
onUserNameClicked() {
if (!this.posting.author?.id) {
return;
}

const referencedUserId = this.posting.author?.id;

const course = this.metisService.course;
if (isMessagingEnabled(course)) {
if (this.isCommunicationPage) {
this.metisConversationService.createOneToOneChatWithId(referencedUserId).subscribe();
} else {
this.oneToOneChatService.createWithId(course.id!, referencedUserId).subscribe((res) => {
this.router.navigate(['courses', course.id, 'communication'], {
queryParams: {
conversationId: res.body!.id,
},
});
});
}
}
}
}
Loading

0 comments on commit 8442b3d

Please sign in to comment.