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: Fix visibility of the edit message option for non-authors #9830

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 @@ -32,7 +32,8 @@
[isThreadSidebar]="isThreadSidebar"
(openPostingCreateEditModal)="createAnswerPostModal.open()"
(reactionsUpdated)="onReactionsUpdated($event)"
(mayEditOrDeleteOutput)="onMayEditOrDelete($event)"
(mayDeleteOutput)="onMayDelete($event)"
(mayEditOutput)="onMayEdit($event)"
(isDeleteEvent)="onDeleteEvent(true)"
/>
</div>
Expand Down Expand Up @@ -64,11 +65,13 @@
<fa-icon [icon]="faSmile" class="item-icon"></fa-icon>
<span jhiTranslate="artemisApp.metis.post.addReaction"></span>
</button>
@if (mayEditOrDelete) {
@if (mayEdit) {
<button class="dropdown-item d-flex" (click)="editPosting()">
<fa-icon [icon]="faPencilAlt" class="item-icon"></fa-icon>
<span jhiTranslate="artemisApp.metis.post.editMessage"></span>
</button>
}
@if (mayDelete) {
<button class="dropdown-item d-flex" (click)="deletePost()">
<fa-icon [icon]="faTrash" class="item-icon"></fa-icon>
<span jhiTranslate="artemisApp.metis.post.deleteMessage"></span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export class AnswerPostComponent extends PostingDirective<AnswerPost> {
readonly faTrash = faTrash;
readonly faThumbtack = faThumbtack;
static activeDropdownPost: AnswerPostComponent | null = null;
mayEditOrDelete: boolean = false;
mayEdit: boolean = false;
mayDelete: boolean = false;
@ViewChild(AnswerPostReactionsBarComponent) private reactionsBarComponent!: AnswerPostReactionsBarComponent;

constructor(
Expand Down Expand Up @@ -95,8 +96,12 @@ export class AnswerPostComponent extends PostingDirective<AnswerPost> {
}
}

onMayEditOrDelete(value: boolean) {
this.mayEditOrDelete = value;
onMayDelete(value: boolean) {
this.mayDelete = value;
}

onMayEdit(value: boolean) {
this.mayEdit = value;
}

onRightClick(event: MouseEvent) {
Expand Down
7 changes: 5 additions & 2 deletions src/main/webapp/app/shared/metis/post/post.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
(openPostingCreateEditModal)="openCreateAnswerPostModal()"
(openThread)="openThread.emit()"
(isModalOpen)="displayInlineInput = true"
(mayEditOrDeleteOutput)="onMayEditOrDelete($event)"
(mayEditOutput)="onMayEdit($event)"
(mayDeleteOutput)="onMayDelete($event)"
(canPinOutput)="onCanPin($event)"
(isDeleteEvent)="onDeleteEvent(true)"
/>
Expand Down Expand Up @@ -148,11 +149,13 @@
<span [jhiTranslate]="checkIfPinned() === DisplayPriority.PINNED ? 'artemisApp.metis.post.unpinMessage' : 'artemisApp.metis.post.pinMessage'"></span>
</button>
}
@if (mayEditOrDelete) {
@if (mayEdit) {
<button class="dropdown-item d-flex editIcon" (click)="editPosting()">
<fa-icon [icon]="faPencilAlt" class="item-icon"></fa-icon>
<span jhiTranslate="artemisApp.metis.post.editMessage"></span>
</button>
}
@if (mayDelete) {
<button class="dropdown-item d-flex deleteIcon" (click)="deletePost()">
<fa-icon [icon]="faTrash" class="item-icon"></fa-icon>
<span jhiTranslate="artemisApp.metis.post.deleteMessage"></span>
Expand Down
11 changes: 8 additions & 3 deletions src/main/webapp/app/shared/metis/post/post.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
contextInformation: ContextInformation;
readonly PageType = PageType;
readonly DisplayPriority = DisplayPriority;
mayEditOrDelete: boolean = false;
mayEdit: boolean = false;
mayDelete: boolean = false;
canPin: boolean = false;

// Icons
Expand Down Expand Up @@ -112,8 +113,12 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
return this.posting.displayPriority === DisplayPriority.PINNED;
}

onMayEditOrDelete(value: boolean) {
this.mayEditOrDelete = value;
onMayEdit(value: boolean) {
this.mayEdit = value;
}

onMayDelete(value: boolean) {
this.mayDelete = value;
}

onCanPin(value: boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,12 @@
}
</button>
}
@if (mayEditOrDelete) {
@if (mayEdit) {
<button class="reaction-button clickable px-2 fs-small edit" (click)="editPosting()" [ngbTooltip]="'artemisApp.metis.editPosting' | artemisTranslate">
<fa-icon [icon]="faPencilAlt" cdkOverlayOrigin></fa-icon>
</button>

}
@if (mayDelete) {
<button class="reaction-button clickable px-2 fs-small delete">
<jhi-confirm-icon
iconSize="sm"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, output } from '@angular/core';
import { Reaction } from 'app/entities/metis/reaction.model';
import { PostingsReactionsBarDirective } from 'app/shared/metis/posting-reactions-bar/posting-reactions-bar.directive';
import { AnswerPost } from 'app/entities/metis/answer-post.model';
Expand All @@ -22,8 +22,10 @@ export class AnswerPostReactionsBarComponent extends PostingsReactionsBarDirecti
@Output() openPostingCreateEditModal = new EventEmitter<void>();
isAuthorOfOriginalPost: boolean;
isAnswerOfAnnouncement: boolean;
@Output() mayEditOrDeleteOutput = new EventEmitter<boolean>();
mayEditOrDelete: boolean;
mayDeleteOutput = output<boolean>();
mayDelete: boolean;
mayEditOutput = output<boolean>();
mayEdit: boolean;
readonly faPencilAlt = faPencilAlt;
@Input() isEmojiCount: boolean = false;
@Output() postingUpdated = new EventEmitter<void>();
Expand All @@ -34,12 +36,14 @@ export class AnswerPostReactionsBarComponent extends PostingsReactionsBarDirecti

ngOnInit() {
super.ngOnInit();
this.setMayEditOrDelete();
this.setMayEdit();
this.setMayDelete();
}

ngOnChanges() {
super.ngOnChanges();
this.setMayEditOrDelete();
this.setMayEdit();
this.setMayDelete();
}

isAnyReactionCountAboveZero(): boolean {
Expand All @@ -64,16 +68,21 @@ export class AnswerPostReactionsBarComponent extends PostingsReactionsBarDirecti
return reaction;
}

setMayEditOrDelete(): void {
setMayDelete(): void {
// determines if the current user is the author of the original post, that the answer belongs to
this.isAuthorOfOriginalPost = this.metisService.metisUserIsAuthorOfPosting(this.posting.post!);
this.isAnswerOfAnnouncement = getAsChannelDTO(this.posting.post?.conversation)?.isAnnouncementChannel ?? false;
const isCourseWideChannel = getAsChannelDTO(this.posting.post?.conversation)?.isCourseWide ?? false;
const isAtLeastInstructorInCourse = this.metisService.metisUserIsAtLeastInstructorInCourse();
const mayEditOrDeleteOtherUsersAnswer =
(isCourseWideChannel && isAtLeastInstructorInCourse) || (getAsChannelDTO(this.metisService.getCurrentConversation())?.hasChannelModerationRights ?? false);
this.mayEditOrDelete = !this.isReadOnlyMode && (this.isAuthorOfPosting || mayEditOrDeleteOtherUsersAnswer);
this.mayEditOrDeleteOutput.emit(this.mayEditOrDelete);
this.mayDelete = !this.isReadOnlyMode && (this.isAuthorOfPosting || mayEditOrDeleteOtherUsersAnswer);
this.mayDeleteOutput.emit(this.mayDelete);
}

setMayEdit() {
this.mayEdit = this.isAuthorOfPosting;
this.mayEditOutput.emit(this.mayEdit);
}

editPosting() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
</ng-container>
}

@if (!isEmojiCount && mayEditOrDelete) {
@if (!isEmojiCount && mayEdit) {
<button
class="reaction-button clickable px-2 fs-small edit"
[disabled]="readOnlyMode"
Expand All @@ -119,7 +119,7 @@
</button>
}
<jhi-post-create-edit-modal #createEditModal [posting]="posting" [isCommunicationPage]="isCommunicationPage" (isModalOpen)="isModalOpen.emit()" />
@if (!isEmojiCount && mayEditOrDelete) {
@if (!isEmojiCount && mayDelete) {
<button class="reaction-button clickable fs-small">
<jhi-confirm-icon
iconSize="sm"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, ViewChild, output } from '@angular/core';
import { Reaction } from 'app/entities/metis/reaction.model';
import { Post } from 'app/entities/metis/post.model';
import { PostingsReactionsBarDirective } from 'app/shared/metis/posting-reactions-bar/posting-reactions-bar.directive';
Expand Down Expand Up @@ -44,9 +44,11 @@ export class PostReactionsBarComponent extends PostingsReactionsBarDirective<Pos
@Output() openThread = new EventEmitter<void>();
@Input() previewMode: boolean;
isAtLeastInstructorInCourse: boolean;
@Output() mayEditOrDeleteOutput = new EventEmitter<boolean>();
mayDeleteOutput = output<boolean>();
mayEditOutput = output<boolean>();
@Output() canPinOutput = new EventEmitter<boolean>();
mayEditOrDelete: boolean;
mayEdit: boolean;
mayDelete: boolean;
@ViewChild(PostCreateEditModalComponent) postCreateEditModal?: PostCreateEditModalComponent;
@Input() isEmojiCount = false;
@Input() hoverBar: boolean = true;
Expand All @@ -71,8 +73,9 @@ export class PostReactionsBarComponent extends PostingsReactionsBarDirective<Pos

const currentConversation = this.metisService.getCurrentConversation();
this.setCanPin(currentConversation);
this.setMayDelete();
this.setMayEdit();
this.resetTooltipsAndPriority();
this.setMayEditOrDelete();
}

ngOnDestroy() {
Expand Down Expand Up @@ -106,7 +109,8 @@ export class PostReactionsBarComponent extends PostingsReactionsBarDirective<Pos
ngOnChanges() {
super.ngOnChanges();
this.resetTooltipsAndPriority();
this.setMayEditOrDelete();
this.setMayDelete();
this.setMayEdit();
}

/**
Expand Down Expand Up @@ -185,12 +189,17 @@ export class PostReactionsBarComponent extends PostingsReactionsBarDirective<Pos
}
}

setMayEditOrDelete(): void {
setMayDelete(): void {
this.isAtLeastInstructorInCourse = this.metisService.metisUserIsAtLeastInstructorInCourse();
const isCourseWideChannel = getAsChannelDTO(this.posting.conversation)?.isCourseWide ?? false;
const mayEditOrDeleteOtherUsersAnswer =
const mayDeleteOtherUsersAnswer =
(isCourseWideChannel && this.isAtLeastInstructorInCourse) || (getAsChannelDTO(this.metisService.getCurrentConversation())?.hasChannelModerationRights ?? false);
this.mayEditOrDelete = !this.readOnlyMode && !this.previewMode && (this.isAuthorOfPosting || mayEditOrDeleteOtherUsersAnswer);
this.mayEditOrDeleteOutput.emit(this.mayEditOrDelete);
this.mayDelete = !this.readOnlyMode && !this.previewMode && (this.isAuthorOfPosting || mayDeleteOtherUsersAnswer);
this.mayDeleteOutput.emit(this.mayDelete);
}

setMayEdit(): void {
this.mayEdit = this.isAuthorOfPosting;
this.mayEditOutput.emit(this.mayEdit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,36 @@ describe('AnswerPostReactionsBarComponent', () => {
expect(getDeleteButton()).not.toBeNull();
});

it('should display edit and delete options to instructor if posting is in course-wide channel from a student', () => {
it('should display the delete option to instructor if posting is in course-wide channel from a student', () => {
metisServiceUserIsAtLeastInstructorMock.mockReturnValue(true);
metisServiceUserPostingAuthorMock.mockReturnValue(false);
component.posting = { ...metisResolvingAnswerPostUser1, post: { ...metisPostInChannel } };
component.posting.authorRole = UserRole.USER;
component.ngOnInit();
fixture.detectChanges();
expect(getEditButton()).not.toBeNull();
expect(getDeleteButton()).not.toBeNull();
});

it('should not display the edit option to user (even instructor) if s/he is not the author of posting', () => {
metisServiceUserIsAtLeastInstructorMock.mockReturnValue(true);
metisServiceUserPostingAuthorMock.mockReturnValue(false);

component.ngOnInit();
fixture.detectChanges();

expect(getEditButton()).toBeNull();
});

it('should display the edit option to user if s/he is the author of posting', () => {
metisServiceUserIsAtLeastInstructorMock.mockReturnValue(false);
metisServiceUserPostingAuthorMock.mockReturnValue(true);

component.ngOnInit();
fixture.detectChanges();

expect(getEditButton()).not.toBeNull();
});

it('should not display edit and delete options to tutor if posting is in course-wide channel from a student', () => {
metisServiceUserIsAtLeastInstructorMock.mockReturnValue(false);
metisServiceUserIsAtLeastTutorMock.mockReturnValue(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ describe('PostReactionsBarComponent', () => {
expect(getEditButton()).not.toBeNull();
});

it('should display edit and delete options to user with channel moderation rights when not the author', () => {
it('should display the delete option to user with channel moderation rights when not the author', () => {
component.readOnlyMode = false;
component.previewMode = false;
component.isEmojiCount = false;
Expand All @@ -166,10 +166,49 @@ describe('PostReactionsBarComponent', () => {
component.ngOnInit();
fixture.detectChanges();

expect(getEditButton()).not.toBeNull();
expect(getDeleteButton()).not.toBeNull();
});

it('should not display the edit option to user (even instructor) if s/he is not the author of posting', () => {
component.readOnlyMode = false;
component.previewMode = false;
component.isEmojiCount = false;

const channelConversation = {
type: ConversationType.CHANNEL,
hasChannelModerationRights: true,
} as ChannelDTO;

jest.spyOn(metisService, 'metisUserIsAuthorOfPosting').mockReturnValue(false);
jest.spyOn(metisService, 'metisUserIsAtLeastInstructorInCourse').mockReturnValue(true);
jest.spyOn(metisService, 'getCurrentConversation').mockReturnValue(channelConversation);

component.ngOnInit();
fixture.detectChanges();

expect(getEditButton()).toBeNull();
});

it('should display the edit option to user if s/he is the author of posting', () => {
component.readOnlyMode = false;
component.previewMode = false;
component.isEmojiCount = false;

const channelConversation = {
type: ConversationType.CHANNEL,
hasChannelModerationRights: true,
} as ChannelDTO;

jest.spyOn(metisService, 'metisUserIsAuthorOfPosting').mockReturnValue(true);
jest.spyOn(metisService, 'metisUserIsAtLeastInstructorInCourse').mockReturnValue(false);
jest.spyOn(metisService, 'getCurrentConversation').mockReturnValue(channelConversation);

component.ngOnInit();
fixture.detectChanges();

expect(getEditButton()).not.toBeNull();
});

it('should not display edit and delete options when user is not the author and lacks permissions', () => {
component.readOnlyMode = false;
component.previewMode = false;
Expand Down Expand Up @@ -198,30 +237,31 @@ describe('PostReactionsBarComponent', () => {

it('should not display edit and delete options to tutor if posting is announcement', () => {
metisServiceUserIsAtLeastInstructorStub.mockReturnValue(false);
metisServiceUserIsAuthorOfPostingStub.mockReturnValue(false);
component.posting = metisAnnouncement;
component.ngOnInit();
fixture.detectChanges();
expect(getEditButton()).toBeNull();
expect(getDeleteButton()).toBeNull();
});

it('should display edit and delete options to instructor if posting is announcement', () => {
it('should display edit and delete options to instructor if his posting is announcement', () => {
metisServiceUserIsAtLeastInstructorStub.mockReturnValue(true);
metisServiceUserIsAuthorOfPostingStub.mockReturnValue(true);
component.posting = metisAnnouncement;
component.ngOnInit();
fixture.detectChanges();
expect(getEditButton()).not.toBeNull();
expect(getDeleteButton()).not.toBeNull();
});

it('should display edit and delete options to instructor if posting is in course-wide channel from a student', () => {
it('should display the delete option to instructor if posting is in course-wide channel from a student', () => {
metisServiceUserIsAtLeastInstructorStub.mockReturnValue(true);
metisServiceUserIsAuthorOfPostingStub.mockReturnValue(false);
component.posting = { ...metisPostInChannel };
component.posting.authorRole = UserRole.USER;
component.ngOnInit();
fixture.detectChanges();
expect(getEditButton()).not.toBeNull();
expect(getDeleteButton()).not.toBeNull();
});

Expand Down
Loading