Skip to content

Communication: Show indication if forwarded post has been deleted #10434

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

Merged
merged 19 commits into from
Apr 30, 2025
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 @@ -352,8 +352,8 @@ examples.forEach((activeConversation) => {

tick();

expect(component.posts[0].forwardedPosts).toEqual([]);
expect(component.posts[0].forwardedAnswerPosts).toEqual([]);
expect(component.posts[0].forwardedPosts).toEqual([undefined]);
expect(component.posts[0].forwardedAnswerPosts).toEqual([undefined]);
}));

it('should not fetch source posts or answers for empty forwarded messages', fakeAsync(() => {
Expand Down Expand Up @@ -408,15 +408,36 @@ examples.forEach((activeConversation) => {
expect(getSourcePostsSpy).toHaveBeenCalled();
expect(getSourceAnswersSpy).toHaveBeenCalled();

expect(component.posts).toHaveLength(1);
expect(component.posts[0].forwardedPosts).toBeDefined();
expect(component.posts[0].forwardedAnswerPosts).toBeDefined();

expect(component.posts[0].forwardedPosts!).toHaveLength(mockSourcePosts.length);
expect(component.posts[0].forwardedAnswerPosts!).toHaveLength(mockSourceAnswerPosts.length);
const forwardedPosts = component.posts[0].forwardedPosts;
const forwardedAnswerPosts = component.posts[0].forwardedAnswerPosts;

expect(component.posts[0].forwardedPosts![0].id).toBe(10);
expect(component.posts[0].forwardedAnswerPosts![0].id).toBe(11);
expect(component.posts).toHaveLength(1);
expect(forwardedPosts).toBeDefined();
expect(forwardedAnswerPosts).toBeDefined();

if (forwardedPosts) {
expect(forwardedPosts).toHaveLength(mockSourcePosts.length);
forwardedPosts.forEach((post) => {
if (post) {
expect(post.id).toBeDefined();
expect(post.id).toBe(10);
} else {
expect(post).toBeUndefined();
}
});
}

if (forwardedAnswerPosts) {
expect(forwardedAnswerPosts).toHaveLength(mockSourceAnswerPosts.length);
forwardedAnswerPosts.forEach((post) => {
if (post) {
expect(post.id).toBeDefined();
expect(post.id).toBe(11);
} else {
expect(post).toBeUndefined();
}
});
}
}));

it('should filter posts to show only pinned posts when showOnlyPinned is true', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,14 +395,32 @@ export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnD
}
});

const fetchedPostIds = new Set(fetchedPosts.map((post) => post.id));
const fetchedAnswerPostIds = new Set(fetchedAnswerPosts.map((answerPost) => answerPost.id));

this.posts = this.posts.map((post) => {
const forwardedMessages = map.get(post.id!) || [];
post.forwardedPosts = fetchedPosts.filter((fetchedPost) =>
forwardedMessages.some((message) => message.sourceId === fetchedPost.id && message.sourceType?.toString() === 'POST'),
);
post.forwardedAnswerPosts = fetchedAnswerPosts.filter((fetchedAnswerPost) =>
forwardedMessages.some((message) => message.sourceId === fetchedAnswerPost.id && message.sourceType?.toString() === 'ANSWER'),
);
post.forwardedPosts = forwardedMessages
.filter((message) => message.sourceType?.toString() === 'POST')
.map((message) => {
if (message.sourceId && !fetchedPostIds.has(message.sourceId)) {
// A source post has not been found so it was most likely deleted.
// We return undefined to indicate a missing post and handle it later (see forwarded-message.component)
return undefined;
}
return fetchedPosts.find((fetchedPost) => fetchedPost.id === message.sourceId);
});

post.forwardedAnswerPosts = forwardedMessages
.filter((message) => message.sourceType?.toString() === 'ANSWER')
.map((message) => {
if (message.sourceId && !fetchedAnswerPostIds.has(message.sourceId)) {
// A source post has not been found so it was most likely deleted.
// We return undefined to indicate a missing post and handle it later (see forwarded-message.component)
return undefined;
}
return fetchedAnswerPosts.find((fetchedAnswerPost) => fetchedAnswerPost.id === message.sourceId);
});
return post;
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,66 @@
<div class="forwarded-message-container">
<div class="left-border-line"></div>
<div class="forwarded-message-content">
<span class="forwarded-message-channel fs-x-small d-flex align-items-center">
<fa-icon [icon]="faShare" class="item-icon" style="margin-right: 0.3rem" />
{{ 'artemisApp.metis.forward.forwardedFrom' | artemisTranslate }} {{ sourceName }}
@if (viewButtonVisible) {
<button
type="button"
class="btn btn-link p-0"
style="width: fit-content; font-size: 0.7rem; margin-left: 0.2rem"
(click)="onTriggerNavigateToPost()"
jhiTranslate="artemisApp.metis.forward.viewConversation"
></button>
}
</span>
<div class="forwarded-message-header">
<jhi-profile-picture
imageSizeInRem="1.5"
fontSizeInRem="0.6"
imageId="user-profile-picture"
defaultPictureId="user-default-profile-picture"
[authorId]="originalPostDetails()?.author?.id"
[authorName]="originalPostDetails()?.author?.name"
[imageUrl]="addPublicFilePrefix(originalPostDetails()?.author?.imageUrl)"
style="margin-right: 0.2rem"
/>
<span class="forwarded-message-author">{{ originalPostDetails()?.author?.name }}</span>
<span class="post-header-date-separator">-</span>
<span class="post-header-date">
@if (postingIsOfToday) {
<span [jhiTranslate]="todayFlag ?? ''" id="today-flag" class="fs-x-small"></span>
@if (hasOriginalPostBeenDeleted() || originalPostDetails()) {
<div class="forwarded-message-container">
<div class="left-border-line"></div>
<div class="forwarded-message-content">
<span class="forwarded-message-channel fs-x-small d-flex align-items-center">
<fa-icon [icon]="faShare" class="item-icon" style="margin-right: 0.3rem" />
<span [jhiTranslate]="'artemisApp.metis.forward.' + (hasOriginalPostBeenDeleted() ? 'forwardedFromUnknown' : 'forwardedFrom')"></span>&nbsp;{{ sourceName }}
@if (viewButtonVisible) {
<button
type="button"
class="btn btn-link p-0"
style="width: fit-content; font-size: 0.7rem; margin-left: 0.2rem"
(click)="onTriggerNavigateToPost()"
jhiTranslate="artemisApp.metis.forward.viewConversation"
></button>
}
<span class="fs-x-small" [disableTooltip]="postingIsOfToday" ngbTooltip="{{ originalPostDetails()?.creationDate | artemisDate: 'time' }}">
{{ postingIsOfToday ? (originalPostDetails()?.creationDate | artemisDate: 'time') : (originalPostDetails()?.creationDate | artemisDate: 'short-date') }}
</span>
</span>
@if (hasOriginalPostBeenDeleted()) {
<div class="forwarded-message-missing-container">
<span class="forwarded-message-missing-label"> <span [jhiTranslate]="'artemisApp.metis.forward.forwardedMessageDeleted'"></span> {{ sourceName }} </span>
</div>
} @else {
<div class="forwarded-message-header">
<jhi-profile-picture
imageSizeInRem="1.5"
fontSizeInRem="0.6"
imageId="user-profile-picture"
defaultPictureId="user-default-profile-picture"
[authorId]="originalPostDetails()?.author?.id"
[authorName]="originalPostDetails()?.author?.name"
[imageUrl]="addPublicFilePrefix(originalPostDetails()?.author?.imageUrl)"
style="margin-right: 0.2rem"
/>
<span class="forwarded-message-author">{{ originalPostDetails()?.author?.name }}</span>
<span class="post-header-date-separator">-</span>
<span class="post-header-date">
@if (postingIsOfToday) {
<span [jhiTranslate]="todayFlag ?? ''" id="today-flag" class="fs-x-small"></span>
}
<span class="fs-x-small" [disableTooltip]="postingIsOfToday" ngbTooltip="{{ originalPostDetails()?.creationDate | artemisDate: 'time' }}"
>{{
postingIsOfToday ? (originalPostDetails()?.creationDate | artemisDate: 'time') : (originalPostDetails()?.creationDate | artemisDate: 'short-date')
}}
</span>
</span>
</div>
<div class="forwarded-message-body" id="messageContent" #messageContent [ngClass]="{ expanded: showFullForwardedMessage }">
<jhi-posting-content
[previewMode]="false"
[content]="originalPostDetails()!.content!"
[author]="originalPostDetails()!.author!"
[isEdited]="!!originalPostDetails()!.updatedDate"
[posting]="originalPostDetails()!"
[isReply]="false"
[isSubscribeToMetis]="false"
/>
</div>
@if (isContentLong) {
<button type="button" class="btn btn-link p-0" style="width: fit-content; font-size: 0.7rem" (click)="toggleShowFullForwardedMessage()">
{{ showFullForwardedMessage ? ('artemisApp.metis.forward.showLess' | artemisTranslate) : ('artemisApp.metis.forward.showMore' | artemisTranslate) }}
</button>
}
}
</div>
<div class="forwarded-message-body" id="messageContent" #messageContent [ngClass]="{ expanded: showFullForwardedMessage }">
<jhi-posting-content
[previewMode]="false"
[content]="originalPostDetails()!.content!"
[author]="originalPostDetails()!.author!"
[isEdited]="!!originalPostDetails()!.updatedDate"
[posting]="originalPostDetails()!"
[isReply]="false"
[isSubscribeToMetis]="false"
/>
</div>
@if (isContentLong) {
<button type="button" class="btn btn-link p-0" style="width: fit-content; font-size: 0.7rem" (click)="toggleShowFullForwardedMessage()">
{{ showFullForwardedMessage ? ('artemisApp.metis.forward.showLess' | artemisTranslate) : ('artemisApp.metis.forward.showMore' | artemisTranslate) }}
</button>
}
</div>
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
padding-bottom: 0.3rem;
position: relative;

.forwarded-message-missing-container,
.forwarded-message-content {
display: flex;
flex-direction: column;
width: 100%;
}

.forwarded-message-missing-label {
padding-top: 0.3rem;
color: var(--metis-gray);
}

.forwarded-message-header {
font-size: 0.9rem;
margin-bottom: 0.5rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ export class ForwardedMessageComponent implements AfterViewInit {

sourceName: string | undefined = '';
todayFlag?: string;
originalPostDetails = input<Posting>();
originalPostDetails = input<Posting | undefined>();
messageContent = viewChild<ElementRef>('messageContent');
isContentLong = false;
showFullForwardedMessage = false;
postingIsOfToday = false;

protected viewButtonVisible = false;
hasOriginalPostBeenDeleted = input<boolean | undefined>();

private cdr = inject(ChangeDetectorRef);
private conversation: Conversation | undefined;
Expand Down Expand Up @@ -101,7 +102,7 @@ export class ForwardedMessageComponent implements AfterViewInit {
}

onTriggerNavigateToPost() {
if (this.originalPostDetails() === undefined) {
if (!this.originalPostDetails()) {
return;
}
this.onNavigateToPost.emit(this.originalPostDetails()!);
Expand Down
8 changes: 6 additions & 2 deletions src/main/webapp/app/communication/post/post.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,12 @@
(userReferenceClicked)="onUserReferenceClicked($event)"
(channelReferenceClicked)="onChannelReferenceClicked($event)"
/>
@if (originalPostDetails) {
<jhi-forwarded-message [originalPostDetails]="originalPostDetails" (onNavigateToPost)="onTriggerNavigateToPost(originalPostDetails)" />
@if (originalPostDetails !== null) {
<jhi-forwarded-message
[originalPostDetails]="originalPostDetails"
[hasOriginalPostBeenDeleted]="hasOriginalPostBeenDeleted"
(onNavigateToPost)="onTriggerNavigateToPost(originalPostDetails)"
/>
}
<div class="hover-actions" [ngClass]="{ 'mb-2': previewMode() }" (click)="$event.stopPropagation()">
@if (!previewMode()) {
Expand Down
35 changes: 19 additions & 16 deletions src/main/webapp/app/communication/post/post.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
OnChanges,
OnInit,
Renderer2,
effect,
inject,
input,
model,
Expand Down Expand Up @@ -112,7 +113,7 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
mayEdit = false;
mayDelete = false;
canPin = false;
originalPostDetails: Post | AnswerPost | undefined = undefined;
originalPostDetails: Post | AnswerPost | undefined;
readonly onNavigateToPost = output<Posting>();

// Icons
Expand All @@ -126,14 +127,21 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
readonly faShare = faShare;

isConsecutive = input<boolean>(false);
forwardedPosts = input<Post[]>([]);
forwardedAnswerPosts = input<AnswerPost[]>([]);
forwardedPosts = input<(Post | undefined)[]>([]);
forwardedAnswerPosts = input<(AnswerPost | undefined)[]>([]);
dropdownPosition = { x: 0, y: 0 };
course: Course;

hasOriginalPostBeenDeleted: boolean;

constructor() {
super();
this.course = this.metisService.getCourse() ?? throwError('Course not found');
effect(() => {
const hasDeletedForwardedPost = this.forwardedPosts().length > 0 && this.forwardedPosts()[0] === undefined;
const hasDeletedForwardedAnswerPost = this.forwardedAnswerPosts().length > 0 && this.forwardedAnswerPosts()[0] === undefined;
this.hasOriginalPostBeenDeleted = hasDeletedForwardedAnswerPost || hasDeletedForwardedPost;
});
}

get reactionsBar() {
Expand Down Expand Up @@ -242,20 +250,12 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
fetchForwardedMessages(): void {
try {
if (this.forwardedPosts().length > 0) {
const forwardedMessage = this.forwardedPosts()[0];

if (forwardedMessage?.id) {
this.originalPostDetails = forwardedMessage;
this.changeDetector.markForCheck();
}
this.originalPostDetails = this.forwardedPosts()[0];
this.changeDetector.markForCheck();
}
if (this.forwardedAnswerPosts().length > 0) {
const forwardedMessage = this.forwardedAnswerPosts()[0];

if (forwardedMessage?.id) {
this.originalPostDetails = forwardedMessage;
this.changeDetector.markForCheck();
}
this.originalPostDetails = this.forwardedAnswerPosts()[0];
this.changeDetector.markForCheck();
}
} catch (error) {
throw new Error(error.toString());
Expand Down Expand Up @@ -351,7 +351,10 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
}
}

protected onTriggerNavigateToPost(post: Posting) {
protected onTriggerNavigateToPost(post: Posting | undefined) {
if (!post) {
return;
}
this.onNavigateToPost.emit(post);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export class PostingReactionsBarComponent<T extends Posting> implements OnInit,
isLastAnswer = input<boolean>(false);
postingUpdated = output<void>();
openThread = output<void>();
originalPostDetails = input<Posting>();
originalPostDetails = input<Posting | undefined>();
course = input<Course>();
isDeleteEvent = output<boolean>();
createEditModal = viewChild.required<PostCreateEditModalComponent>('createEditModal');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export class PostingThreadComponent {
@Output() openThread = new EventEmitter<Post>();
@Input() isConsecutive: boolean | undefined = false;
searchQuery = input<string>('');
forwardedPosts = input<Post[]>([]);
forwardedAnswerPosts = input<AnswerPost[]>([]);
forwardedPosts = input<(Post | undefined)[]>([]);
forwardedAnswerPosts = input<(AnswerPost | undefined)[]>([]);
readonly onNavigateToPost = output<Posting>();

elementRef = inject(ElementRef);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Posting } from 'app/communication/shared/entities/posting.model';
export class AnswerPost extends Posting {
public resolvesPost?: boolean;
public post?: Post;
public forwardedPosts?: Post[] = [];
public forwardedAnswerPosts?: AnswerPost[] = [];
public forwardedPosts?: (Post | undefined)[] = [];
public forwardedAnswerPosts?: (AnswerPost | undefined)[] = [];

constructor() {
super();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export class Post extends Posting {
public plagiarismCase?: PlagiarismCase;
public displayPriority?: DisplayPriority;
public resolved?: boolean;
public forwardedPosts?: Post[] = [];
public forwardedAnswerPosts?: AnswerPost[] = [];
public forwardedPosts?: (Post | undefined)[] = [];
public forwardedAnswerPosts?: (AnswerPost | undefined)[] = [];

constructor() {
super();
Expand Down
Loading
Loading