feat(dashboard): add contributors to TranscriptionQueueItemDTO with 5-cap and hasMore flag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,19 @@
|
|||||||
package org.raddatz.familienarchiv.dto;
|
package org.raddatz.familienarchiv.dto;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.raddatz.familienarchiv.dashboard.ActivityActorDTO;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
|
||||||
* A single row in one of the three Mission Control Strip queues.
|
|
||||||
* Annotation/block counts drive the per-document mini progress bar
|
|
||||||
* in the Transkription column and the percentage label in Lesefertig.
|
|
||||||
*/
|
|
||||||
public record TranscriptionQueueItemDTO(
|
public record TranscriptionQueueItemDTO(
|
||||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) UUID id,
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) UUID id,
|
||||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) String title,
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) String title,
|
||||||
LocalDate documentDate,
|
LocalDate documentDate,
|
||||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) int annotationCount,
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) int annotationCount,
|
||||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) int textedBlockCount,
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) int textedBlockCount,
|
||||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) int reviewedBlockCount
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) int reviewedBlockCount,
|
||||||
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) List<ActivityActorDTO> contributors,
|
||||||
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) boolean hasMoreContributors
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.raddatz.familienarchiv.service;
|
package org.raddatz.familienarchiv.service;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.raddatz.familienarchiv.dashboard.ActivityActorDTO;
|
||||||
|
import org.raddatz.familienarchiv.dashboard.AuditLogQueryService;
|
||||||
import org.raddatz.familienarchiv.dto.TranscriptionQueueItemDTO;
|
import org.raddatz.familienarchiv.dto.TranscriptionQueueItemDTO;
|
||||||
import org.raddatz.familienarchiv.dto.TranscriptionWeeklyStatsDTO;
|
import org.raddatz.familienarchiv.dto.TranscriptionWeeklyStatsDTO;
|
||||||
import org.raddatz.familienarchiv.repository.DocumentRepository;
|
import org.raddatz.familienarchiv.repository.DocumentRepository;
|
||||||
@@ -8,38 +10,29 @@ import org.raddatz.familienarchiv.repository.TranscriptionQueueProjection;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
|
||||||
* Serves the three Mission Control Strip queues (Segmentierung / Transkription / Lesefertig)
|
|
||||||
* and the weekly activity pulse used by the column headers.
|
|
||||||
*/
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class TranscriptionQueueService {
|
public class TranscriptionQueueService {
|
||||||
|
|
||||||
private static final int DEFAULT_QUEUE_SIZE = 5;
|
private static final int DEFAULT_QUEUE_SIZE = 5;
|
||||||
|
private static final int MAX_CONTRIBUTORS = 5;
|
||||||
|
|
||||||
private final DocumentRepository documentRepository;
|
private final DocumentRepository documentRepository;
|
||||||
|
private final AuditLogQueryService auditLogQueryService;
|
||||||
|
|
||||||
public List<TranscriptionQueueItemDTO> getSegmentationQueue() {
|
public List<TranscriptionQueueItemDTO> getSegmentationQueue() {
|
||||||
return documentRepository.findSegmentationQueue(DEFAULT_QUEUE_SIZE)
|
return enrichWithContributors(documentRepository.findSegmentationQueue(DEFAULT_QUEUE_SIZE));
|
||||||
.stream()
|
|
||||||
.map(this::toDTO)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TranscriptionQueueItemDTO> getTranscriptionQueue() {
|
public List<TranscriptionQueueItemDTO> getTranscriptionQueue() {
|
||||||
return documentRepository.findTranscriptionQueue(DEFAULT_QUEUE_SIZE)
|
return enrichWithContributors(documentRepository.findTranscriptionQueue(DEFAULT_QUEUE_SIZE));
|
||||||
.stream()
|
|
||||||
.map(this::toDTO)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TranscriptionQueueItemDTO> getReadyToReadQueue() {
|
public List<TranscriptionQueueItemDTO> getReadyToReadQueue() {
|
||||||
return documentRepository.findReadyToReadQueue(DEFAULT_QUEUE_SIZE)
|
return enrichWithContributors(documentRepository.findReadyToReadQueue(DEFAULT_QUEUE_SIZE));
|
||||||
.stream()
|
|
||||||
.map(this::toDTO)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TranscriptionWeeklyStatsDTO getWeeklyStats() {
|
public TranscriptionWeeklyStatsDTO getWeeklyStats() {
|
||||||
@@ -50,14 +43,26 @@ public class TranscriptionQueueService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TranscriptionQueueItemDTO toDTO(TranscriptionQueueProjection p) {
|
private List<TranscriptionQueueItemDTO> enrichWithContributors(List<TranscriptionQueueProjection> projections) {
|
||||||
|
List<UUID> ids = projections.stream().map(TranscriptionQueueProjection::getId).toList();
|
||||||
|
Map<UUID, List<ActivityActorDTO>> contributorMap = auditLogQueryService.findContributorsPerDocument(ids);
|
||||||
|
return projections.stream()
|
||||||
|
.map(p -> toDTO(p, contributorMap.getOrDefault(p.getId(), List.of())))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TranscriptionQueueItemDTO toDTO(TranscriptionQueueProjection p, List<ActivityActorDTO> allContributors) {
|
||||||
|
boolean hasMore = allContributors.size() > MAX_CONTRIBUTORS;
|
||||||
|
List<ActivityActorDTO> capped = hasMore ? allContributors.subList(0, MAX_CONTRIBUTORS) : allContributors;
|
||||||
return new TranscriptionQueueItemDTO(
|
return new TranscriptionQueueItemDTO(
|
||||||
p.getId(),
|
p.getId(),
|
||||||
p.getTitle(),
|
p.getTitle(),
|
||||||
p.getDocumentDate(),
|
p.getDocumentDate(),
|
||||||
p.getAnnotationCount(),
|
p.getAnnotationCount(),
|
||||||
p.getTextedBlockCount(),
|
p.getTextedBlockCount(),
|
||||||
p.getReviewedBlockCount()
|
p.getReviewedBlockCount(),
|
||||||
|
capped,
|
||||||
|
hasMore
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import org.springframework.security.test.context.support.WithMockUser;
|
|||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import org.raddatz.familienarchiv.dashboard.ActivityActorDTO;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -41,7 +43,9 @@ class TranscriptionQueueControllerTest {
|
|||||||
UUID.fromString("00000000-0000-0000-0000-000000000001"),
|
UUID.fromString("00000000-0000-0000-0000-000000000001"),
|
||||||
"Testbrief",
|
"Testbrief",
|
||||||
LocalDate.of(1920, 6, 15),
|
LocalDate.of(1920, 6, 15),
|
||||||
3, 1, 0
|
3, 1, 0,
|
||||||
|
List.of(new ActivityActorDTO("TR", "#a6dad8", "Test Raddatz")),
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
private static final TranscriptionWeeklyStatsDTO STATS = new TranscriptionWeeklyStatsDTO(2L, 5L);
|
private static final TranscriptionWeeklyStatsDTO STATS = new TranscriptionWeeklyStatsDTO(2L, 5L);
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package org.raddatz.familienarchiv.service;
|
package org.raddatz.familienarchiv.service;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.raddatz.familienarchiv.dashboard.ActivityActorDTO;
|
||||||
|
import org.raddatz.familienarchiv.dashboard.AuditLogQueryService;
|
||||||
import org.raddatz.familienarchiv.dto.TranscriptionQueueItemDTO;
|
import org.raddatz.familienarchiv.dto.TranscriptionQueueItemDTO;
|
||||||
import org.raddatz.familienarchiv.dto.TranscriptionWeeklyStatsDTO;
|
import org.raddatz.familienarchiv.dto.TranscriptionWeeklyStatsDTO;
|
||||||
import org.raddatz.familienarchiv.repository.DocumentRepository;
|
import org.raddatz.familienarchiv.repository.DocumentRepository;
|
||||||
@@ -13,17 +16,25 @@ import org.raddatz.familienarchiv.repository.TranscriptionWeeklyStatsProjection;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class TranscriptionQueueServiceTest {
|
class TranscriptionQueueServiceTest {
|
||||||
|
|
||||||
@Mock DocumentRepository documentRepository;
|
@Mock DocumentRepository documentRepository;
|
||||||
|
@Mock AuditLogQueryService auditLogQueryService;
|
||||||
@InjectMocks TranscriptionQueueService service;
|
@InjectMocks TranscriptionQueueService service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void stubContributors() {
|
||||||
|
lenient().when(auditLogQueryService.findContributorsPerDocument(any())).thenReturn(Map.of());
|
||||||
|
}
|
||||||
|
|
||||||
// ─── getSegmentationQueue ─────────────────────────────────────────────────
|
// ─── getSegmentationQueue ─────────────────────────────────────────────────
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -108,6 +119,47 @@ class TranscriptionQueueServiceTest {
|
|||||||
assertThat(result.transcriptionCount()).isEqualTo(0L);
|
assertThat(result.transcriptionCount()).isEqualTo(0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── contributors ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getSegmentationQueue_includesContributors_whenAuditDataPresent() {
|
||||||
|
UUID docId = UUID.randomUUID();
|
||||||
|
TranscriptionQueueProjection proj = mockQueueProjection(docId, "Brief", null, 0, 0, 0);
|
||||||
|
when(documentRepository.findSegmentationQueue(5)).thenReturn(List.of(proj));
|
||||||
|
|
||||||
|
ActivityActorDTO actor = new ActivityActorDTO("MR", "#a6dad8", "Max Raddatz");
|
||||||
|
when(auditLogQueryService.findContributorsPerDocument(List.of(docId)))
|
||||||
|
.thenReturn(Map.of(docId, List.of(actor)));
|
||||||
|
|
||||||
|
List<TranscriptionQueueItemDTO> result = service.getSegmentationQueue();
|
||||||
|
|
||||||
|
assertThat(result.get(0).contributors()).containsExactly(actor);
|
||||||
|
assertThat(result.get(0).hasMoreContributors()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getSegmentationQueue_capsContributorsAtFive_andSetsHasMoreFlag() {
|
||||||
|
UUID docId = UUID.randomUUID();
|
||||||
|
TranscriptionQueueProjection proj = mockQueueProjection(docId, "Brief", null, 0, 0, 0);
|
||||||
|
when(documentRepository.findSegmentationQueue(5)).thenReturn(List.of(proj));
|
||||||
|
|
||||||
|
List<ActivityActorDTO> sixActors = List.of(
|
||||||
|
new ActivityActorDTO("A1", "#111", "Alice One"),
|
||||||
|
new ActivityActorDTO("A2", "#222", "Alice Two"),
|
||||||
|
new ActivityActorDTO("A3", "#333", "Alice Three"),
|
||||||
|
new ActivityActorDTO("A4", "#444", "Alice Four"),
|
||||||
|
new ActivityActorDTO("A5", "#555", "Alice Five"),
|
||||||
|
new ActivityActorDTO("A6", "#666", "Alice Six")
|
||||||
|
);
|
||||||
|
when(auditLogQueryService.findContributorsPerDocument(List.of(docId)))
|
||||||
|
.thenReturn(Map.of(docId, sixActors));
|
||||||
|
|
||||||
|
List<TranscriptionQueueItemDTO> result = service.getSegmentationQueue();
|
||||||
|
|
||||||
|
assertThat(result.get(0).contributors()).hasSize(5);
|
||||||
|
assertThat(result.get(0).hasMoreContributors()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
// ─── helpers ─────────────────────────────────────────────────────────────
|
// ─── helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private TranscriptionQueueProjection mockQueueProjection(
|
private TranscriptionQueueProjection mockQueueProjection(
|
||||||
|
|||||||
Reference in New Issue
Block a user