feat(dashboard): add commentPreview to ActivityFeedItemDTO; wire via findDataByIds()
ActivityFeedItemDTO gains a nullable commentPreview field (plain-text, 120 chars max). DashboardService.getActivity() now calls findDataByIds() once instead of findAnnotationIdsByIds(), halving DB round-trips for the Chronik page load. Empty-string previews are normalised to null so the frontend can use ?? cleanly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,5 +29,11 @@ public record ActivityFeedItemDTO(
|
|||||||
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
|
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
|
||||||
description = "Annotation associated with the comment; populated only for COMMENT_ADDED and MENTION_CREATED kinds."
|
description = "Annotation associated with the comment; populated only for COMMENT_ADDED and MENTION_CREATED kinds."
|
||||||
)
|
)
|
||||||
UUID annotationId
|
UUID annotationId,
|
||||||
|
@Nullable
|
||||||
|
@Schema(
|
||||||
|
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
|
||||||
|
description = "Plain-text preview of the comment body (HTML stripped server-side, truncated to 120 chars); null for non-comment feed items or deleted comments."
|
||||||
|
)
|
||||||
|
String commentPreview
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.raddatz.familienarchiv.document.Document;
|
|||||||
import org.raddatz.familienarchiv.person.Person;
|
import org.raddatz.familienarchiv.person.Person;
|
||||||
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlock;
|
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlock;
|
||||||
import org.raddatz.familienarchiv.document.comment.CommentService;
|
import org.raddatz.familienarchiv.document.comment.CommentService;
|
||||||
|
import org.raddatz.familienarchiv.document.comment.CommentService.CommentData;
|
||||||
import org.raddatz.familienarchiv.document.DocumentService;
|
import org.raddatz.familienarchiv.document.DocumentService;
|
||||||
import org.raddatz.familienarchiv.document.transcription.TranscriptionService;
|
import org.raddatz.familienarchiv.document.transcription.TranscriptionService;
|
||||||
import org.raddatz.familienarchiv.user.UserService;
|
import org.raddatz.familienarchiv.user.UserService;
|
||||||
@@ -133,9 +134,9 @@ public class DashboardService {
|
|||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.distinct()
|
.distinct()
|
||||||
.toList();
|
.toList();
|
||||||
Map<UUID, UUID> annotationByComment = commentIds.isEmpty()
|
Map<UUID, CommentData> commentDataByComment = commentIds.isEmpty()
|
||||||
? Map.of()
|
? Map.of()
|
||||||
: commentService.findAnnotationIdsByIds(commentIds);
|
: commentService.findDataByIds(commentIds);
|
||||||
|
|
||||||
return rows.stream().map(row -> {
|
return rows.stream().map(row -> {
|
||||||
ActivityActorDTO actor = row.getActorId() != null
|
ActivityActorDTO actor = row.getActorId() != null
|
||||||
@@ -146,7 +147,10 @@ public class DashboardService {
|
|||||||
? row.getHappenedAtUntil().atOffset(ZoneOffset.UTC)
|
? row.getHappenedAtUntil().atOffset(ZoneOffset.UTC)
|
||||||
: null;
|
: null;
|
||||||
UUID commentId = row.getCommentId();
|
UUID commentId = row.getCommentId();
|
||||||
UUID annotationId = commentId != null ? annotationByComment.get(commentId) : null;
|
CommentData commentData = commentId != null ? commentDataByComment.get(commentId) : null;
|
||||||
|
UUID annotationId = commentData != null ? commentData.annotationId() : null;
|
||||||
|
String commentPreview = commentData != null && !commentData.preview().isEmpty()
|
||||||
|
? commentData.preview() : null;
|
||||||
return new ActivityFeedItemDTO(
|
return new ActivityFeedItemDTO(
|
||||||
org.raddatz.familienarchiv.audit.AuditKind.valueOf(row.getKind()),
|
org.raddatz.familienarchiv.audit.AuditKind.valueOf(row.getKind()),
|
||||||
actor,
|
actor,
|
||||||
@@ -158,7 +162,8 @@ public class DashboardService {
|
|||||||
row.getCount(),
|
row.getCount(),
|
||||||
happenedAtUntil,
|
happenedAtUntil,
|
||||||
commentId,
|
commentId,
|
||||||
annotationId
|
annotationId,
|
||||||
|
commentPreview
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.raddatz.familienarchiv.user.AppUser;
|
|||||||
import org.raddatz.familienarchiv.document.Document;
|
import org.raddatz.familienarchiv.document.Document;
|
||||||
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlock;
|
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlock;
|
||||||
import org.raddatz.familienarchiv.document.comment.CommentService;
|
import org.raddatz.familienarchiv.document.comment.CommentService;
|
||||||
|
import org.raddatz.familienarchiv.document.comment.CommentService.CommentData;
|
||||||
import org.raddatz.familienarchiv.document.DocumentService;
|
import org.raddatz.familienarchiv.document.DocumentService;
|
||||||
import org.raddatz.familienarchiv.document.transcription.TranscriptionService;
|
import org.raddatz.familienarchiv.document.transcription.TranscriptionService;
|
||||||
import org.raddatz.familienarchiv.user.UserService;
|
import org.raddatz.familienarchiv.user.UserService;
|
||||||
@@ -142,7 +143,8 @@ class DashboardServiceTest {
|
|||||||
when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
|
when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
|
||||||
Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
|
Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
|
||||||
));
|
));
|
||||||
when(commentService.findAnnotationIdsByIds(List.of(commentId))).thenReturn(Map.of());
|
when(commentService.findDataByIds(List.of(commentId)))
|
||||||
|
.thenReturn(Map.of(commentId, new CommentData(null, "preview text")));
|
||||||
|
|
||||||
List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);
|
List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);
|
||||||
|
|
||||||
@@ -162,8 +164,8 @@ class DashboardServiceTest {
|
|||||||
when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
|
when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
|
||||||
Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
|
Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
|
||||||
));
|
));
|
||||||
when(commentService.findAnnotationIdsByIds(List.of(commentId)))
|
when(commentService.findDataByIds(List.of(commentId)))
|
||||||
.thenReturn(Map.of(commentId, annotationId));
|
.thenReturn(Map.of(commentId, new CommentData(annotationId, "preview text")));
|
||||||
|
|
||||||
List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);
|
List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);
|
||||||
|
|
||||||
@@ -187,7 +189,62 @@ class DashboardServiceTest {
|
|||||||
assertThat(items).hasSize(1);
|
assertThat(items).hasSize(1);
|
||||||
assertThat(items.get(0).commentId()).isNull();
|
assertThat(items.get(0).commentId()).isNull();
|
||||||
assertThat(items.get(0).annotationId()).isNull();
|
assertThat(items.get(0).annotationId()).isNull();
|
||||||
verify(commentService, never()).findAnnotationIdsByIds(anyList());
|
verify(commentService, never()).findDataByIds(anyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── getActivity commentPreview ───────────────────────────────────────────
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getActivity_populates_commentPreview_for_COMMENT_ADDED_rows() {
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
UUID docId = UUID.randomUUID();
|
||||||
|
UUID commentId = UUID.randomUUID();
|
||||||
|
|
||||||
|
ActivityFeedRow row = mockFeedRow(docId, "COMMENT_ADDED", commentId);
|
||||||
|
when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row));
|
||||||
|
when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
|
||||||
|
Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
|
||||||
|
));
|
||||||
|
when(commentService.findDataByIds(List.of(commentId)))
|
||||||
|
.thenReturn(Map.of(commentId, new CommentData(null, "Hello family!")));
|
||||||
|
|
||||||
|
List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);
|
||||||
|
|
||||||
|
assertThat(items.get(0).commentPreview()).isEqualTo("Hello family!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getActivity_leaves_commentPreview_null_for_TEXT_SAVED_rows() {
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
UUID docId = UUID.randomUUID();
|
||||||
|
|
||||||
|
ActivityFeedRow row = mockFeedRow(docId, "TEXT_SAVED", null);
|
||||||
|
when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row));
|
||||||
|
when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
|
||||||
|
Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
|
||||||
|
));
|
||||||
|
|
||||||
|
List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);
|
||||||
|
|
||||||
|
assertThat(items.get(0).commentPreview()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getActivity_leaves_commentPreview_null_when_comment_is_deleted() {
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
UUID docId = UUID.randomUUID();
|
||||||
|
UUID deletedCommentId = UUID.randomUUID();
|
||||||
|
|
||||||
|
ActivityFeedRow row = mockFeedRow(docId, "COMMENT_ADDED", deletedCommentId);
|
||||||
|
when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row));
|
||||||
|
when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
|
||||||
|
Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
|
||||||
|
));
|
||||||
|
when(commentService.findDataByIds(List.of(deletedCommentId))).thenReturn(Map.of());
|
||||||
|
|
||||||
|
List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);
|
||||||
|
|
||||||
|
assertThat(items.get(0).commentPreview()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── getPulse — always uses full ROLLUP_ELIGIBLE set ─────────────────────
|
// ─── getPulse — always uses full ROLLUP_ELIGIBLE set ─────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user