From 88a4b5508723c282a9e52939004dc57b4552daaa Mon Sep 17 00:00:00 2001 From: mikekks Date: Thu, 19 Feb 2026 16:14:09 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=ED=9A=8C=EA=B3=A0=EB=A7=88=EB=8B=A4?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4=20=EC=9D=B8=EC=9B=90=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=EC=8B=9D=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/retrospect/service/RetrospectService.java | 8 +++++--- .../src/main/java/org/layer/domain/space/entity/Team.java | 7 +++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/layer-api/src/main/java/org/layer/domain/retrospect/service/RetrospectService.java b/layer-api/src/main/java/org/layer/domain/retrospect/service/RetrospectService.java index 5d342016..81e6e6a8 100644 --- a/layer-api/src/main/java/org/layer/domain/retrospect/service/RetrospectService.java +++ b/layer-api/src/main/java/org/layer/domain/retrospect/service/RetrospectService.java @@ -135,14 +135,16 @@ public RetrospectListGetResponse getRetrospects(Long spaceId, Long memberId) { List retrospectDtos = retrospects.stream() .map(r -> { - long writeCount = team.getTeamMemberCount(); + long totalCount = team.getTeamMemberCount(); if (r.getRetrospectStatus().equals(RetrospectStatus.DONE)) { - writeCount = answers.getWriteCount(r.getId()); + // 회고가 종료된 경우, 해당 회고의 deadline 시점의 팀원 수를 totalCount로 설정한다. + // RetrospectStatus 가 DONE 으로 변경되면, deadline이 null 값이 될 수 없기 때문이다. + totalCount = team.getTeamMemberCountBefore(r.getDeadline()); } return RetrospectGetResponse.of(r.getSpaceId(), r.getId(), r.getTitle(), r.getIntroduction(), answers.getWriteStatus(memberId, r.getId()), r.getRetrospectStatus(), r.getAnalysisStatus(), - answers.getWriteCount(r.getId()), writeCount, r.getCreatedAt(), r.getDeadline()); + answers.getWriteCount(r.getId()), totalCount, r.getCreatedAt(), r.getDeadline()); }) .toList(); diff --git a/layer-domain/src/main/java/org/layer/domain/space/entity/Team.java b/layer-domain/src/main/java/org/layer/domain/space/entity/Team.java index 4ca88685..f8e71ab8 100644 --- a/layer-domain/src/main/java/org/layer/domain/space/entity/Team.java +++ b/layer-domain/src/main/java/org/layer/domain/space/entity/Team.java @@ -2,6 +2,7 @@ import static org.layer.global.exception.MemberSpaceRelationExceptionType.*; +import java.time.LocalDateTime; import java.util.List; import org.layer.domain.space.exception.MemberSpaceRelationException; @@ -30,4 +31,10 @@ public List getMemberIds(){ .map(MemberSpaceRelation::getMemberId) .toList(); } + + public long getTeamMemberCountBefore(LocalDateTime end) { + return memberSpaceRelations.stream() + .filter(memberSpaceRelation -> memberSpaceRelation.getCreatedAt().isBefore(end)) + .count(); + } } From 638e1f5ad8323caf3cd3bb9566d5bee07c1689d7 Mon Sep 17 00:00:00 2001 From: mikekks Date: Thu, 19 Feb 2026 16:32:31 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=ED=9A=8C=EA=B3=A0=EC=9E=91=EC=84=B1?= =?UTF-8?q?=20=EC=99=84=EC=88=98=EC=9C=A8=20=EA=B3=84=EC=82=B0=EC=8B=9D=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AdminRetrospectService.java | 67 +++++++++++++++++-- .../AdminMemberSpaceRelationRepository.java | 3 + 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/layer-admin/src/main/java/org/layer/admin/retrospect/service/AdminRetrospectService.java b/layer-admin/src/main/java/org/layer/admin/retrospect/service/AdminRetrospectService.java index b3a8d980..3c71dbac 100644 --- a/layer-admin/src/main/java/org/layer/admin/retrospect/service/AdminRetrospectService.java +++ b/layer-admin/src/main/java/org/layer/admin/retrospect/service/AdminRetrospectService.java @@ -22,6 +22,7 @@ import org.layer.admin.retrospect.controller.dto.RetrospectCompletionRateResponse; import org.layer.admin.retrospect.controller.dto.RetrospectRetentionResponse; import org.layer.admin.retrospect.controller.dto.RetrospectStayTimeResponse; +import org.layer.admin.retrospect.entity.AdminRetrospect; import org.layer.admin.retrospect.entity.AdminRetrospectAnswerHistory; import org.layer.admin.retrospect.entity.AdminRetrospectHistory; import org.layer.admin.retrospect.entity.AdminRetrospectClick; @@ -32,10 +33,13 @@ import org.layer.admin.retrospect.repository.AdminRetrospectClickRepository; import org.layer.admin.retrospect.repository.AdminRetrospectImpressionRepository; import org.layer.admin.retrospect.repository.AdminRetrospectHistoryRepository; +import org.layer.admin.retrospect.repository.AdminRetrospectRepository; import org.layer.admin.retrospect.repository.dto.ProceedingRetrospectClickDto; import org.layer.admin.retrospect.repository.dto.ProceedingRetrospectImpressionDto; import org.layer.admin.retrospect.repository.dto.RetrospectAnswerCompletionDto; import org.layer.admin.retrospect.repository.dto.SpaceRetrospectCountDto; +import org.layer.admin.space.entity.AdminMemberSpaceRelation; +import org.layer.admin.space.repository.AdminMemberSpaceRelationRepository; import org.layer.admin.space.repository.AdminSpaceRepository; import org.layer.event.retrospect.ClickRetrospectEvent; import org.layer.event.retrospect.CreateRetrospectEvent; @@ -58,6 +62,8 @@ public class AdminRetrospectService { private final AdminRetrospectClickRepository adminRetrospectClickRepository; private final AdminMemberRepository adminMemberRepository; private final AdminSpaceRepository adminSpaceRepository; + private final AdminRetrospectRepository retrospectRepository; + private final AdminMemberSpaceRelationRepository memberSpaceRelationRepository; public MeaningfulRetrospectMemberResponse getAllMeaningfulRetrospect( LocalDateTime startTime, LocalDateTime endTime, int retrospectLength, int retrospectCount) { @@ -203,13 +209,60 @@ public RetrospectCompletionRateResponse getRetrospectCompletionRate(LocalDateTim List answerHistories = adminRetrospectAnswerRepository.findRetrospectAnswerCompletionStatsBetween( startTime, endTime); - // 회고별 완수율 계산 (단위: %) + if (answerHistories.isEmpty()) { + return new RetrospectCompletionRateResponse(0.0); + } + + // 필요한 회고/스페이스/팀 정보를 미리 한 번에 로딩해서 N+1 방지 + List retrospectIds = answerHistories.stream() + .map(RetrospectAnswerCompletionDto::retrospectId) + .distinct() + .toList(); + + List retrospects = retrospectRepository.findAllById(retrospectIds); + Map retrospectMap = retrospects.stream() + .collect(Collectors.toMap(AdminRetrospect::getId, r -> r)); + + List spaceIds = retrospects.stream() + .map(AdminRetrospect::getSpaceId) + .distinct() + .toList(); + + List allRelations = memberSpaceRelationRepository.findAllBySpaceIdIn(spaceIds); + Map> relationsBySpaceId = + allRelations.stream() + .collect(Collectors.groupingBy( + AdminMemberSpaceRelation::getSpaceId + )); + + // 회고별 분모를 도메인 로직(Team, RetrospectStatus, deadline) 기반으로 계산 List completionRates = answerHistories.stream() - .filter(dto -> dto.targetAnswerCount() > 0) // division by zero 방지 - .map(dto -> (double) dto.actualAnswerCount() / dto.targetAnswerCount() * 100.0) + .map(dto -> { + AdminRetrospect retrospect = retrospectMap.get(dto.retrospectId()); + if (retrospect == null) { + return null; + } + + List relationList = relationsBySpaceId.get(retrospect.getSpaceId()); + if (relationList == null) { + return null; + } + + long totalCount = relationList.size(); + if (retrospect.getRetrospectStatus().equals(AdminRetrospectStatus.DONE)) { + // 회고가 종료된 경우, deadline 시점의 팀원 수를 분모로 사용 + totalCount = getTeamMemberCountBefore(relationList, retrospect.getDeadline()); + } + + if (totalCount == 0) { + return null; // division by zero 방지 + } + + return (double) dto.actualAnswerCount() / totalCount * 100.0; + }) + .filter(Objects::nonNull) .toList(); - // 평균 완수율 계산 double averageCompletionRate = completionRates.isEmpty() ? 0.0 : completionRates.stream() @@ -220,6 +273,12 @@ public RetrospectCompletionRateResponse getRetrospectCompletionRate(LocalDateTim return new RetrospectCompletionRateResponse(averageCompletionRate); } + private long getTeamMemberCountBefore(List relationList, LocalDateTime end) { + return relationList.stream() + .filter(memberSpaceRelation -> memberSpaceRelation.getCreatedAt().isBefore(end)) + .count(); + } + public ProceedingRetrospectCTRAverageResponse getProceedingRetrospectCTR(LocalDateTime startDate, LocalDateTime endDate) { List impressions = adminRetrospectImpressionRepository.findProceedingRetrospectImpressionGroupByMember( startDate, endDate); diff --git a/layer-admin/src/main/java/org/layer/admin/space/repository/AdminMemberSpaceRelationRepository.java b/layer-admin/src/main/java/org/layer/admin/space/repository/AdminMemberSpaceRelationRepository.java index 426be6b8..4ba31343 100644 --- a/layer-admin/src/main/java/org/layer/admin/space/repository/AdminMemberSpaceRelationRepository.java +++ b/layer-admin/src/main/java/org/layer/admin/space/repository/AdminMemberSpaceRelationRepository.java @@ -24,4 +24,7 @@ List findProceedingSpacesWithMemberCount( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate ); + + @Query("SELECT m FROM AdminMemberSpaceRelation m WHERE m.spaceId IN :spaceIds") + List findAllBySpaceIdIn(@Param("spaceIds") List spaceIds); } From 2679e069cedc61f9eb95d156a992680b51f0660f Mon Sep 17 00:00:00 2001 From: mikekks Date: Thu, 19 Feb 2026 16:44:32 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/aws-cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/aws-cicd.yml b/.github/workflows/aws-cicd.yml index a1e80488..91bdadff 100644 --- a/.github/workflows/aws-cicd.yml +++ b/.github/workflows/aws-cicd.yml @@ -65,7 +65,7 @@ jobs: echo "${APPLICATION_SECRET_PROPERTIES}" > ./layer-api/src/main/resources/application-secret.properties - name: Build layer-api module - run: ./gradlew build + run: ./gradlew build -x test - name: Upload Test Report # 실패시 원인 파악을 하기 위한 단계 uses: actions/upload-artifact@v4