feat: 재수집 실패 건 수 표시
This commit is contained in:
부모
43c28eeccd
커밋
eb8ed22139
3
.gitignore
vendored
3
.gitignore
vendored
@ -34,6 +34,9 @@ dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
.mvn/wrapper/maven-wrapper.properties
|
||||
mvnw
|
||||
mvnw.cmd
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
|
||||
@ -287,6 +287,7 @@ export interface RecollectionSearchResponse {
|
||||
number: number;
|
||||
size: number;
|
||||
totalPages: number;
|
||||
failedRecordCounts: Record<number, number>;
|
||||
}
|
||||
|
||||
export interface RecollectionStatsResponse {
|
||||
|
||||
@ -94,6 +94,9 @@ export default function Recollects() {
|
||||
const [totalCount, setTotalCount] = useState(0);
|
||||
const [useSearch, setUseSearch] = useState(false);
|
||||
|
||||
// 실패건 수 (jobExecutionId → count)
|
||||
const [failedRecordCounts, setFailedRecordCounts] = useState<Record<number, number>>({});
|
||||
|
||||
// 실패 로그 모달
|
||||
const [failLogTarget, setFailLogTarget] = useState<RecollectionHistoryDto | null>(null);
|
||||
|
||||
@ -256,6 +259,7 @@ export default function Recollects() {
|
||||
setHistories(data.content);
|
||||
setTotalPages(data.totalPages);
|
||||
setTotalCount(data.totalElements);
|
||||
setFailedRecordCounts(data.failedRecordCounts ?? {});
|
||||
if (!useSearch) setPage(data.number);
|
||||
} catch {
|
||||
setHistories([]);
|
||||
@ -684,6 +688,7 @@ export default function Recollects() {
|
||||
<th className="px-4 py-3 font-medium">재수집 시작일시</th>
|
||||
<th className="px-4 py-3 font-medium">재수집 종료일시</th>
|
||||
<th className="px-4 py-3 font-medium">소요시간</th>
|
||||
<th className="px-4 py-3 font-medium text-center">실패건</th>
|
||||
<th className="px-4 py-3 font-medium text-right">액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -727,6 +732,23 @@ export default function Recollects() {
|
||||
<td className="px-4 py-4 text-wing-muted whitespace-nowrap">
|
||||
{formatDuration(hist.durationMs)}
|
||||
</td>
|
||||
<td className="px-4 py-4 text-center">
|
||||
{(() => {
|
||||
const count = hist.jobExecutionId
|
||||
? (failedRecordCounts[hist.jobExecutionId] ?? 0)
|
||||
: 0;
|
||||
if (hist.executionStatus === 'STARTED') {
|
||||
return <span className="text-xs text-wing-muted">-</span>;
|
||||
}
|
||||
return count > 0 ? (
|
||||
<span className="inline-flex items-center px-2 py-0.5 text-xs font-semibold rounded-full bg-red-100 text-red-700">
|
||||
{count}건
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs text-wing-muted">0</span>
|
||||
);
|
||||
})()}
|
||||
</td>
|
||||
<td className="px-4 py-4 text-right">
|
||||
<button
|
||||
onClick={() => navigate(`/recollects/${hist.historyId}`)}
|
||||
|
||||
@ -28,6 +28,7 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@ -497,12 +498,21 @@ public class BatchController {
|
||||
Page<BatchRecollectionHistory> histories = recollectionHistoryService
|
||||
.getHistories(apiKey, jobName, status, from, to, PageRequest.of(page, size));
|
||||
|
||||
// 목록의 jobExecutionId들로 실패건수 한번에 조회
|
||||
List<Long> jobExecutionIds = histories.getContent().stream()
|
||||
.map(BatchRecollectionHistory::getJobExecutionId)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
Map<Long, Long> failedRecordCounts = recollectionHistoryService
|
||||
.getFailedRecordCounts(jobExecutionIds);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("content", histories.getContent());
|
||||
response.put("totalElements", histories.getTotalElements());
|
||||
response.put("totalPages", histories.getTotalPages());
|
||||
response.put("number", histories.getNumber());
|
||||
response.put("size", histories.getSize());
|
||||
response.put("failedRecordCounts", failedRecordCounts);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
|
||||
@ -33,6 +33,14 @@ public interface BatchFailedRecordRepository extends JpaRepository<BatchFailedRe
|
||||
*/
|
||||
long countByJobExecutionId(Long jobExecutionId);
|
||||
|
||||
/**
|
||||
* 여러 jobExecutionId에 대해 FAILED 상태 건수를 한번에 조회 (N+1 방지)
|
||||
*/
|
||||
@Query("SELECT r.jobExecutionId, COUNT(r) FROM BatchFailedRecord r " +
|
||||
"WHERE r.jobExecutionId IN :jobExecutionIds AND r.status = 'FAILED' " +
|
||||
"GROUP BY r.jobExecutionId")
|
||||
List<Object[]> countFailedByJobExecutionIds(@Param("jobExecutionIds") List<Long> jobExecutionIds);
|
||||
|
||||
/**
|
||||
* 특정 Step 실행의 실패 레코드를 RESOLVED로 벌크 업데이트
|
||||
*/
|
||||
|
||||
@ -330,6 +330,21 @@ public class RecollectionHistoryService {
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 재수집 이력 목록의 jobExecutionId별 FAILED 상태 실패건수 조회
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public Map<Long, Long> getFailedRecordCounts(List<Long> jobExecutionIds) {
|
||||
if (jobExecutionIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return failedRecordRepository.countFailedByJobExecutionIds(jobExecutionIds).stream()
|
||||
.collect(Collectors.toMap(
|
||||
row -> ((Number) row[0]).longValue(),
|
||||
row -> ((Number) row[1]).longValue()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 대시보드용 최근 10건
|
||||
*/
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user