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: Add shortcut to private messages on usernames #10007

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

PaRangger marked this conversation as resolved.
Show resolved Hide resolved
constructor(
protected postService: PostService,
protected answerPostService: AnswerPostService,
Expand Down
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,
},
});
});
}
}
}
PaRangger marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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,
},
});
});
}
}
}
PaRangger marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading