Skip to content

Commit 215cc4a

Browse files
authored
Communication: Show indication if forwarded post has been deleted (#10434)
1 parent 732871b commit 215cc4a

File tree

13 files changed

+162
-95
lines changed

13 files changed

+162
-95
lines changed

src/main/webapp/app/communication/course-conversations-components/layout/conversation-messages/conversation-messages.component.spec.ts

+31-10
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,8 @@ examples.forEach((activeConversation) => {
352352

353353
tick();
354354

355-
expect(component.posts[0].forwardedPosts).toEqual([]);
356-
expect(component.posts[0].forwardedAnswerPosts).toEqual([]);
355+
expect(component.posts[0].forwardedPosts).toEqual([undefined]);
356+
expect(component.posts[0].forwardedAnswerPosts).toEqual([undefined]);
357357
}));
358358

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

411-
expect(component.posts).toHaveLength(1);
412-
expect(component.posts[0].forwardedPosts).toBeDefined();
413-
expect(component.posts[0].forwardedAnswerPosts).toBeDefined();
414-
415-
expect(component.posts[0].forwardedPosts!).toHaveLength(mockSourcePosts.length);
416-
expect(component.posts[0].forwardedAnswerPosts!).toHaveLength(mockSourceAnswerPosts.length);
411+
const forwardedPosts = component.posts[0].forwardedPosts;
412+
const forwardedAnswerPosts = component.posts[0].forwardedAnswerPosts;
417413

418-
expect(component.posts[0].forwardedPosts![0].id).toBe(10);
419-
expect(component.posts[0].forwardedAnswerPosts![0].id).toBe(11);
414+
expect(component.posts).toHaveLength(1);
415+
expect(forwardedPosts).toBeDefined();
416+
expect(forwardedAnswerPosts).toBeDefined();
417+
418+
if (forwardedPosts) {
419+
expect(forwardedPosts).toHaveLength(mockSourcePosts.length);
420+
forwardedPosts.forEach((post) => {
421+
if (post) {
422+
expect(post.id).toBeDefined();
423+
expect(post.id).toBe(10);
424+
} else {
425+
expect(post).toBeUndefined();
426+
}
427+
});
428+
}
429+
430+
if (forwardedAnswerPosts) {
431+
expect(forwardedAnswerPosts).toHaveLength(mockSourceAnswerPosts.length);
432+
forwardedAnswerPosts.forEach((post) => {
433+
if (post) {
434+
expect(post.id).toBeDefined();
435+
expect(post.id).toBe(11);
436+
} else {
437+
expect(post).toBeUndefined();
438+
}
439+
});
440+
}
420441
}));
421442

422443
it('should filter posts to show only pinned posts when showOnlyPinned is true', () => {

src/main/webapp/app/communication/course-conversations-components/layout/conversation-messages/conversation-messages.component.ts

+24-6
Original file line numberDiff line numberDiff line change
@@ -392,14 +392,32 @@ export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnD
392392
}
393393
});
394394

395+
const fetchedPostIds = new Set(fetchedPosts.map((post) => post.id));
396+
const fetchedAnswerPostIds = new Set(fetchedAnswerPosts.map((answerPost) => answerPost.id));
397+
395398
this.posts = this.posts.map((post) => {
396399
const forwardedMessages = map.get(post.id!) || [];
397-
post.forwardedPosts = fetchedPosts.filter((fetchedPost) =>
398-
forwardedMessages.some((message) => message.sourceId === fetchedPost.id && message.sourceType?.toString() === 'POST'),
399-
);
400-
post.forwardedAnswerPosts = fetchedAnswerPosts.filter((fetchedAnswerPost) =>
401-
forwardedMessages.some((message) => message.sourceId === fetchedAnswerPost.id && message.sourceType?.toString() === 'ANSWER'),
402-
);
400+
post.forwardedPosts = forwardedMessages
401+
.filter((message) => message.sourceType?.toString() === 'POST')
402+
.map((message) => {
403+
if (message.sourceId && !fetchedPostIds.has(message.sourceId)) {
404+
// A source post has not been found so it was most likely deleted.
405+
// We return undefined to indicate a missing post and handle it later (see forwarded-message.component)
406+
return undefined;
407+
}
408+
return fetchedPosts.find((fetchedPost) => fetchedPost.id === message.sourceId);
409+
});
410+
411+
post.forwardedAnswerPosts = forwardedMessages
412+
.filter((message) => message.sourceType?.toString() === 'ANSWER')
413+
.map((message) => {
414+
if (message.sourceId && !fetchedAnswerPostIds.has(message.sourceId)) {
415+
// A source post has not been found so it was most likely deleted.
416+
// We return undefined to indicate a missing post and handle it later (see forwarded-message.component)
417+
return undefined;
418+
}
419+
return fetchedAnswerPosts.find((fetchedAnswerPost) => fetchedAnswerPost.id === message.sourceId);
420+
});
403421
return post;
404422
});
405423

Original file line numberDiff line numberDiff line change
@@ -1,56 +1,66 @@
1-
<div class="forwarded-message-container">
2-
<div class="left-border-line"></div>
3-
<div class="forwarded-message-content">
4-
<span class="forwarded-message-channel fs-x-small d-flex align-items-center">
5-
<fa-icon [icon]="faShare" class="item-icon" style="margin-right: 0.3rem" />
6-
{{ 'artemisApp.metis.forward.forwardedFrom' | artemisTranslate }} {{ sourceName }}
7-
@if (viewButtonVisible) {
8-
<button
9-
type="button"
10-
class="btn btn-link p-0"
11-
style="width: fit-content; font-size: 0.7rem; margin-left: 0.2rem"
12-
(click)="onTriggerNavigateToPost()"
13-
jhiTranslate="artemisApp.metis.forward.viewConversation"
14-
></button>
15-
}
16-
</span>
17-
<div class="forwarded-message-header">
18-
<jhi-profile-picture
19-
imageSizeInRem="1.5"
20-
fontSizeInRem="0.6"
21-
imageId="user-profile-picture"
22-
defaultPictureId="user-default-profile-picture"
23-
[authorId]="originalPostDetails()?.author?.id"
24-
[authorName]="originalPostDetails()?.author?.name"
25-
[imageUrl]="addPublicFilePrefix(originalPostDetails()?.author?.imageUrl)"
26-
style="margin-right: 0.2rem"
27-
/>
28-
<span class="forwarded-message-author">{{ originalPostDetails()?.author?.name }}</span>
29-
<span class="post-header-date-separator">-</span>
30-
<span class="post-header-date">
31-
@if (postingIsOfToday) {
32-
<span [jhiTranslate]="todayFlag ?? ''" id="today-flag" class="fs-x-small"></span>
1+
@if (hasOriginalPostBeenDeleted() || originalPostDetails()) {
2+
<div class="forwarded-message-container">
3+
<div class="left-border-line"></div>
4+
<div class="forwarded-message-content">
5+
<span class="forwarded-message-channel fs-x-small d-flex align-items-center">
6+
<fa-icon [icon]="faShare" class="item-icon" style="margin-right: 0.3rem" />
7+
<span [jhiTranslate]="'artemisApp.metis.forward.' + (hasOriginalPostBeenDeleted() ? 'forwardedFromUnknown' : 'forwardedFrom')"></span>&nbsp;{{ sourceName }}
8+
@if (viewButtonVisible) {
9+
<button
10+
type="button"
11+
class="btn btn-link p-0"
12+
style="width: fit-content; font-size: 0.7rem; margin-left: 0.2rem"
13+
(click)="onTriggerNavigateToPost()"
14+
jhiTranslate="artemisApp.metis.forward.viewConversation"
15+
></button>
3316
}
34-
<span class="fs-x-small" [disableTooltip]="postingIsOfToday" ngbTooltip="{{ originalPostDetails()?.creationDate | artemisDate: 'time' }}">
35-
{{ postingIsOfToday ? (originalPostDetails()?.creationDate | artemisDate: 'time') : (originalPostDetails()?.creationDate | artemisDate: 'short-date') }}
36-
</span>
3717
</span>
18+
@if (hasOriginalPostBeenDeleted()) {
19+
<div class="forwarded-message-missing-container">
20+
<span class="forwarded-message-missing-label"> <span [jhiTranslate]="'artemisApp.metis.forward.forwardedMessageDeleted'"></span> {{ sourceName }} </span>
21+
</div>
22+
} @else {
23+
<div class="forwarded-message-header">
24+
<jhi-profile-picture
25+
imageSizeInRem="1.5"
26+
fontSizeInRem="0.6"
27+
imageId="user-profile-picture"
28+
defaultPictureId="user-default-profile-picture"
29+
[authorId]="originalPostDetails()?.author?.id"
30+
[authorName]="originalPostDetails()?.author?.name"
31+
[imageUrl]="addPublicFilePrefix(originalPostDetails()?.author?.imageUrl)"
32+
style="margin-right: 0.2rem"
33+
/>
34+
<span class="forwarded-message-author">{{ originalPostDetails()?.author?.name }}</span>
35+
<span class="post-header-date-separator">-</span>
36+
<span class="post-header-date">
37+
@if (postingIsOfToday) {
38+
<span [jhiTranslate]="todayFlag ?? ''" id="today-flag" class="fs-x-small"></span>
39+
}
40+
<span class="fs-x-small" [disableTooltip]="postingIsOfToday" ngbTooltip="{{ originalPostDetails()?.creationDate | artemisDate: 'time' }}"
41+
>{{
42+
postingIsOfToday ? (originalPostDetails()?.creationDate | artemisDate: 'time') : (originalPostDetails()?.creationDate | artemisDate: 'short-date')
43+
}}
44+
</span>
45+
</span>
46+
</div>
47+
<div class="forwarded-message-body" id="messageContent" #messageContent [ngClass]="{ expanded: showFullForwardedMessage }">
48+
<jhi-posting-content
49+
[previewMode]="false"
50+
[content]="originalPostDetails()!.content!"
51+
[author]="originalPostDetails()!.author!"
52+
[isEdited]="!!originalPostDetails()!.updatedDate"
53+
[posting]="originalPostDetails()!"
54+
[isReply]="false"
55+
[isSubscribeToMetis]="false"
56+
/>
57+
</div>
58+
@if (isContentLong) {
59+
<button type="button" class="btn btn-link p-0" style="width: fit-content; font-size: 0.7rem" (click)="toggleShowFullForwardedMessage()">
60+
{{ showFullForwardedMessage ? ('artemisApp.metis.forward.showLess' | artemisTranslate) : ('artemisApp.metis.forward.showMore' | artemisTranslate) }}
61+
</button>
62+
}
63+
}
3864
</div>
39-
<div class="forwarded-message-body" id="messageContent" #messageContent [ngClass]="{ expanded: showFullForwardedMessage }">
40-
<jhi-posting-content
41-
[previewMode]="false"
42-
[content]="originalPostDetails()!.content!"
43-
[author]="originalPostDetails()!.author!"
44-
[isEdited]="!!originalPostDetails()!.updatedDate"
45-
[posting]="originalPostDetails()!"
46-
[isReply]="false"
47-
[isSubscribeToMetis]="false"
48-
/>
49-
</div>
50-
@if (isContentLong) {
51-
<button type="button" class="btn btn-link p-0" style="width: fit-content; font-size: 0.7rem" (click)="toggleShowFullForwardedMessage()">
52-
{{ showFullForwardedMessage ? ('artemisApp.metis.forward.showLess' | artemisTranslate) : ('artemisApp.metis.forward.showMore' | artemisTranslate) }}
53-
</button>
54-
}
5565
</div>
56-
</div>
66+
}

src/main/webapp/app/communication/forwarded-message/forwarded-message.component.scss

+6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@
44
padding-bottom: 0.3rem;
55
position: relative;
66

7+
.forwarded-message-missing-container,
78
.forwarded-message-content {
89
display: flex;
910
flex-direction: column;
1011
width: 100%;
1112
}
1213

14+
.forwarded-message-missing-label {
15+
padding-top: 0.3rem;
16+
color: var(--metis-gray);
17+
}
18+
1319
.forwarded-message-header {
1420
font-size: 0.9rem;
1521
margin-bottom: 0.5rem;

src/main/webapp/app/communication/forwarded-message/forwarded-message.component.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ export class ForwardedMessageComponent implements AfterViewInit {
2727

2828
sourceName: string | undefined = '';
2929
todayFlag?: string;
30-
originalPostDetails = input<Posting>();
30+
originalPostDetails = input<Posting | undefined>();
3131
messageContent = viewChild<ElementRef>('messageContent');
3232
isContentLong = false;
3333
showFullForwardedMessage = false;
3434
postingIsOfToday = false;
3535

3636
protected viewButtonVisible = false;
37+
hasOriginalPostBeenDeleted = input<boolean | undefined>();
3738

3839
private cdr = inject(ChangeDetectorRef);
3940
private conversation: Conversation | undefined;
@@ -101,7 +102,7 @@ export class ForwardedMessageComponent implements AfterViewInit {
101102
}
102103

103104
onTriggerNavigateToPost() {
104-
if (this.originalPostDetails() === undefined) {
105+
if (!this.originalPostDetails()) {
105106
return;
106107
}
107108
this.onNavigateToPost.emit(this.originalPostDetails()!);

src/main/webapp/app/communication/post/post.component.html

+6-2
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,12 @@
8989
(userReferenceClicked)="onUserReferenceClicked($event)"
9090
(channelReferenceClicked)="onChannelReferenceClicked($event)"
9191
/>
92-
@if (originalPostDetails) {
93-
<jhi-forwarded-message [originalPostDetails]="originalPostDetails" (onNavigateToPost)="onTriggerNavigateToPost(originalPostDetails)" />
92+
@if (originalPostDetails !== null) {
93+
<jhi-forwarded-message
94+
[originalPostDetails]="originalPostDetails"
95+
[hasOriginalPostBeenDeleted]="hasOriginalPostBeenDeleted"
96+
(onNavigateToPost)="onTriggerNavigateToPost(originalPostDetails)"
97+
/>
9498
}
9599
<div class="hover-actions" [ngClass]="{ 'mb-2': previewMode() }" (click)="$event.stopPropagation()">
96100
@if (!previewMode()) {

src/main/webapp/app/communication/post/post.component.ts

+19-16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
OnChanges,
88
OnInit,
99
Renderer2,
10+
effect,
1011
inject,
1112
input,
1213
model,
@@ -112,7 +113,7 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
112113
mayEdit = false;
113114
mayDelete = false;
114115
canPin = false;
115-
originalPostDetails: Post | AnswerPost | undefined = undefined;
116+
originalPostDetails: Post | AnswerPost | undefined;
116117
readonly onNavigateToPost = output<Posting>();
117118

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

128129
isConsecutive = input<boolean>(false);
129-
forwardedPosts = input<Post[]>([]);
130-
forwardedAnswerPosts = input<AnswerPost[]>([]);
130+
forwardedPosts = input<(Post | undefined)[]>([]);
131+
forwardedAnswerPosts = input<(AnswerPost | undefined)[]>([]);
131132
dropdownPosition = { x: 0, y: 0 };
132133
course: Course;
133134

135+
hasOriginalPostBeenDeleted: boolean;
136+
134137
constructor() {
135138
super();
136139
this.course = this.metisService.getCourse() ?? throwError('Course not found');
140+
effect(() => {
141+
const hasDeletedForwardedPost = this.forwardedPosts().length > 0 && this.forwardedPosts()[0] === undefined;
142+
const hasDeletedForwardedAnswerPost = this.forwardedAnswerPosts().length > 0 && this.forwardedAnswerPosts()[0] === undefined;
143+
this.hasOriginalPostBeenDeleted = hasDeletedForwardedAnswerPost || hasDeletedForwardedPost;
144+
});
137145
}
138146

139147
get reactionsBar() {
@@ -242,20 +250,12 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
242250
fetchForwardedMessages(): void {
243251
try {
244252
if (this.forwardedPosts().length > 0) {
245-
const forwardedMessage = this.forwardedPosts()[0];
246-
247-
if (forwardedMessage?.id) {
248-
this.originalPostDetails = forwardedMessage;
249-
this.changeDetector.markForCheck();
250-
}
253+
this.originalPostDetails = this.forwardedPosts()[0];
254+
this.changeDetector.markForCheck();
251255
}
252256
if (this.forwardedAnswerPosts().length > 0) {
253-
const forwardedMessage = this.forwardedAnswerPosts()[0];
254-
255-
if (forwardedMessage?.id) {
256-
this.originalPostDetails = forwardedMessage;
257-
this.changeDetector.markForCheck();
258-
}
257+
this.originalPostDetails = this.forwardedAnswerPosts()[0];
258+
this.changeDetector.markForCheck();
259259
}
260260
} catch (error) {
261261
throw new Error(error.toString());
@@ -351,7 +351,10 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
351351
}
352352
}
353353

354-
protected onTriggerNavigateToPost(post: Posting) {
354+
protected onTriggerNavigateToPost(post: Posting | undefined) {
355+
if (!post) {
356+
return;
357+
}
355358
this.onNavigateToPost.emit(post);
356359
}
357360
}

src/main/webapp/app/communication/posting-reactions-bar/posting-reactions-bar.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export class PostingReactionsBarComponent<T extends Posting> implements OnInit,
136136
isLastAnswer = input<boolean>(false);
137137
postingUpdated = output<void>();
138138
openThread = output<void>();
139-
originalPostDetails = input<Posting>();
139+
originalPostDetails = input<Posting | undefined>();
140140
course = input<Course>();
141141
isDeleteEvent = output<boolean>();
142142
createEditModal = viewChild.required<PostCreateEditModalComponent>('createEditModal');

src/main/webapp/app/communication/posting-thread/posting-thread.component.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ export class PostingThreadComponent {
2323
@Output() openThread = new EventEmitter<Post>();
2424
@Input() isConsecutive: boolean | undefined = false;
2525
searchQuery = input<string>('');
26-
forwardedPosts = input<Post[]>([]);
27-
forwardedAnswerPosts = input<AnswerPost[]>([]);
26+
forwardedPosts = input<(Post | undefined)[]>([]);
27+
forwardedAnswerPosts = input<(AnswerPost | undefined)[]>([]);
2828
readonly onNavigateToPost = output<Posting>();
2929

3030
elementRef = inject(ElementRef);

src/main/webapp/app/communication/shared/entities/answer-post.model.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { Posting } from 'app/communication/shared/entities/posting.model';
44
export class AnswerPost extends Posting {
55
public resolvesPost?: boolean;
66
public post?: Post;
7-
public forwardedPosts?: Post[] = [];
8-
public forwardedAnswerPosts?: AnswerPost[] = [];
7+
public forwardedPosts?: (Post | undefined)[] = [];
8+
public forwardedAnswerPosts?: (AnswerPost | undefined)[] = [];
99

1010
constructor() {
1111
super();

src/main/webapp/app/communication/shared/entities/post.model.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export class Post extends Posting {
1010
public plagiarismCase?: PlagiarismCase;
1111
public displayPriority?: DisplayPriority;
1212
public resolved?: boolean;
13-
public forwardedPosts?: Post[] = [];
14-
public forwardedAnswerPosts?: AnswerPost[] = [];
13+
public forwardedPosts?: (Post | undefined)[] = [];
14+
public forwardedAnswerPosts?: (AnswerPost | undefined)[] = [];
1515

1616
constructor() {
1717
super();

0 commit comments

Comments
 (0)