-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 스터디 통계 조회 API 추가 #809
Changes from 9 commits
fba70ef
b15488b
adba316
b20bfab
76d6554
b5fc4f6
7a0f8d6
7a930db
9c9321e
21909c7
cf591e2
c16cc3d
f613571
9b03ac7
5d665a0
b18bea5
f9051e4
dc9c54f
1fb9722
8e79492
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,25 @@ | ||
package com.gdschongik.gdsc.domain.study.application; | ||
|
||
import static com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionStatus.SUCCESS; | ||
import static com.gdschongik.gdsc.global.exception.ErrorCode.*; | ||
|
||
import com.gdschongik.gdsc.domain.member.domain.Member; | ||
import com.gdschongik.gdsc.domain.study.dao.AssignmentHistoryRepository; | ||
import com.gdschongik.gdsc.domain.study.dao.AttendanceRepository; | ||
import com.gdschongik.gdsc.domain.study.dao.StudyDetailRepository; | ||
import com.gdschongik.gdsc.domain.study.dao.StudyHistoryRepository; | ||
import com.gdschongik.gdsc.domain.study.dao.StudyRepository; | ||
import com.gdschongik.gdsc.domain.study.domain.Study; | ||
import com.gdschongik.gdsc.domain.study.domain.StudyDetail; | ||
import com.gdschongik.gdsc.domain.study.domain.StudyDetailValidator; | ||
import com.gdschongik.gdsc.domain.study.domain.StudyHistory; | ||
import com.gdschongik.gdsc.domain.study.domain.StudyValidator; | ||
import com.gdschongik.gdsc.domain.study.dto.request.AssignmentCreateUpdateRequest; | ||
import com.gdschongik.gdsc.domain.study.dto.response.AssignmentResponse; | ||
import com.gdschongik.gdsc.domain.study.dto.response.StudyCurriculumResponse; | ||
import com.gdschongik.gdsc.domain.study.dto.response.StudyMentorAttendanceResponse; | ||
import com.gdschongik.gdsc.domain.study.dto.response.StudyStatisticsResponse; | ||
import com.gdschongik.gdsc.domain.study.dto.response.StudyWeekStatisticsResponse; | ||
import com.gdschongik.gdsc.global.exception.CustomException; | ||
import com.gdschongik.gdsc.global.util.MemberUtil; | ||
import java.time.LocalDate; | ||
|
@@ -27,6 +37,11 @@ public class MentorStudyDetailService { | |
private final MemberUtil memberUtil; | ||
private final StudyDetailRepository studyDetailRepository; | ||
private final StudyDetailValidator studyDetailValidator; | ||
private final StudyHistoryRepository studyHistoryRepository; | ||
private final AttendanceRepository attendanceRepository; | ||
private final AssignmentHistoryRepository assignmentHistoryRepository; | ||
private final StudyValidator studyValidator; | ||
private final StudyRepository studyRepository; | ||
|
||
@Transactional(readOnly = true) | ||
public List<AssignmentResponse> getWeeklyAssignments(Long studyId) { | ||
|
@@ -108,4 +123,89 @@ public List<StudyMentorAttendanceResponse> getAttendanceNumbers(Long studyId) { | |
.limit(2) | ||
.toList(); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public StudyStatisticsResponse getStudyStatistics(Long studyId) { | ||
Member currentMember = memberUtil.getCurrentMember(); | ||
Study study = studyRepository.findById(studyId).orElseThrow(() -> new CustomException(STUDY_NOT_FOUND)); | ||
studyValidator.validateStudyMentor(currentMember, study); | ||
|
||
List<StudyHistory> studyHistories = studyHistoryRepository.findAllByStudyId(studyId); | ||
long totalStudentCount = studyHistories.size(); | ||
long studyCompleteStudentCount = | ||
studyHistories.stream().filter(StudyHistory::isComplete).count(); | ||
|
||
List<StudyDetail> studyDetails = studyDetailRepository.findAllByStudyIdOrderByWeekAsc(studyId); | ||
long openedWeekCount = studyDetails.stream() | ||
.filter(studyDetail -> studyDetail.getCurriculum().isOpen()) | ||
.count(); | ||
kckc0608 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
List<StudyWeekStatisticsResponse> studyWeekStatisticsResponses = | ||
calculateStudyWeekStatistics(studyDetails, totalStudentCount); | ||
|
||
long averageAttendanceRate = calculateAverageWeekAttendanceRate(studyWeekStatisticsResponses, openedWeekCount); | ||
long averageAssignmentSubmitRate = | ||
calculateAverageWeekAssignmentSubmitRate(studyWeekStatisticsResponses, openedWeekCount); | ||
|
||
return StudyStatisticsResponse.of( | ||
totalStudentCount, | ||
studyCompleteStudentCount, | ||
averageAttendanceRate, | ||
averageAssignmentSubmitRate, | ||
studyWeekStatisticsResponses); | ||
} | ||
|
||
private List<StudyWeekStatisticsResponse> calculateStudyWeekStatistics( | ||
List<StudyDetail> studyDetails, Long totalStudentCount) { | ||
|
||
return studyDetails.stream() | ||
.map((studyDetail -> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. map 내부 람다식이 복잡한데, 별도 메서드로 추출하면 좋을듯 합니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 우선 DTO 정적 팩토리 메서드 종류를 나눠서 케이스마다 반환하도록 했는데, 지금 생각하기에는 내부 로직에서 메서드로 더 분리할 만한 부분은 평균 출석률 계산, 평균 과제 제출률 계산 로직 정도가 있을 것 같습니다. 개인적으로는 각 계산 로직의 길이가 2줄로 길지 않고, 변수명을 통해 코드의 의도를 이해할 수 있을 것 같아서 메서드로 분리하지 않고 놔두었는데, 의도하신 리뷰에 맞는지 확인 부탁드립니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아 람다식을 extract해서, 스트림 내부를 method reference로 처리하면 더 깔끔할 것 같다는 이야기었어요. 제가 설명을 제대로 못했네요 ++ 후속 리뷰 필요한 경우 resolve 처리하지 마시고 열어두시면 제가 리뷰 확인할 때 더 편할 것 같아요~ 이번엔 제가 unresolve 처리해두었습니다 |
||
if (!studyDetail.getCurriculum().isOpen()) { | ||
return StudyWeekStatisticsResponse.createCanceledWeekStatistics(studyDetail.getWeek()); | ||
} | ||
|
||
if (totalStudentCount == 0) { | ||
return StudyWeekStatisticsResponse.createOpenedWeekStatistics(studyDetail.getWeek(), 0L, 0L); | ||
} | ||
|
||
long attendanceCount = attendanceRepository.countByStudyDetailId(studyDetail.getId()); | ||
long assignmentCount = assignmentHistoryRepository.countByStudyDetailIdAndSubmissionStatusEquals( | ||
studyDetail.getId(), SUCCESS); | ||
|
||
return StudyWeekStatisticsResponse.createOpenedWeekStatistics( | ||
studyDetail.getWeek(), | ||
Math.round(attendanceCount / (double) totalStudentCount * 100), | ||
Math.round(assignmentCount / (double) totalStudentCount * 100)); | ||
})) | ||
.toList(); | ||
} | ||
|
||
private long calculateAverageWeekAttendanceRate( | ||
List<StudyWeekStatisticsResponse> studyWeekStatisticsResponses, long openedWeekCount) { | ||
|
||
if (openedWeekCount == 0) { | ||
return 0; | ||
} | ||
|
||
long attendanceRateSum = studyWeekStatisticsResponses.stream() | ||
.mapToLong(weekStatistics -> weekStatistics.isCanceledWeek() ? 0 : weekStatistics.attendanceRate()) | ||
.sum(); | ||
|
||
return Math.round(attendanceRateSum / (double) openedWeekCount); | ||
} | ||
|
||
private long calculateAverageWeekAssignmentSubmitRate( | ||
List<StudyWeekStatisticsResponse> studyWeekStatisticsResponses, long openedWeekCount) { | ||
|
||
if (openedWeekCount == 0) { | ||
return 0; | ||
} | ||
|
||
long assignmentSubmitRateSum = studyWeekStatisticsResponses.stream() | ||
.mapToLong( | ||
weekStatistics -> weekStatistics.isCanceledWeek() ? 0 : weekStatistics.assignmentSubmitRate()) | ||
.sum(); | ||
|
||
return Math.round(assignmentSubmitRateSum / (double) openedWeekCount); | ||
} | ||
kckc0608 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -82,4 +82,8 @@ public void complete() { | |||||
public boolean isWithinApplicationAndCourse() { | ||||||
return study.isWithinApplicationAndCourse(); | ||||||
} | ||||||
|
||||||
public boolean isComplete() { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
return studyHistoryStatus == COMPLETED; | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.gdschongik.gdsc.domain.study.dto.response; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import java.util.List; | ||
|
||
public record StudyStatisticsResponse( | ||
@Schema(description = "스터디 전체 수강생 수") Long totalStudentCount, | ||
@Schema(description = "스터디 수료 수강생 수") Long completeStudentCount, | ||
@Schema(description = "평균 출석률") Long averageAttendanceRate, | ||
@Schema(description = "평균 과제 제출률") Long averageAssignmentSubmitRate, | ||
@Schema(description = "스터디 수료율") Long studyCompleteRate, | ||
@Schema(description = "주차별 출석률 및 과제 제출률") List<StudyWeekStatisticsResponse> studyWeekStatisticsResponses) { | ||
|
||
public static StudyStatisticsResponse of( | ||
Long totalStudentCount, | ||
Long completeStudentCount, | ||
Long averageAttendanceRate, | ||
Long averageAssignmentSubmitRate, | ||
List<StudyWeekStatisticsResponse> studyWeekStatisticsResponses) { | ||
return new StudyStatisticsResponse( | ||
totalStudentCount, | ||
completeStudentCount, | ||
averageAttendanceRate, | ||
averageAssignmentSubmitRate, | ||
Math.round(completeStudentCount / (double) totalStudentCount * 100), | ||
studyWeekStatisticsResponses); | ||
} | ||
kckc0608 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.gdschongik.gdsc.domain.study.dto.response; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
public record StudyWeekStatisticsResponse( | ||
@Schema(description = "스터디 주차") Long studyWeek, | ||
kckc0608 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@Schema(description = "현재 주차 출석률") Long attendanceRate, | ||
@Schema(description = "현재 주차 과제 제출률") Long assignmentSubmitRate, | ||
@Schema(description = "현재 주차 휴강 여부") boolean isCanceledWeek) { | ||
kckc0608 marked this conversation as resolved.
Show resolved
Hide resolved
kckc0608 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public static StudyWeekStatisticsResponse createOpenedWeekStatistics( | ||
kckc0608 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Long studyWeek, Long attendanceRate, Long assignmentSubmitRate) { | ||
return new StudyWeekStatisticsResponse(studyWeek, attendanceRate, assignmentSubmitRate, false); | ||
} | ||
|
||
public static StudyWeekStatisticsResponse createCanceledWeekStatistics(Long studyWeek) { | ||
return new StudyWeekStatisticsResponse(studyWeek, 0L, 0L, true); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이거 아직 반영 안 된 것 같은데 resolve되어 있어서 unresolve 해둘게요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 코드 상에 반영해두고 resolve 한 뒤 커밋을 안 했었네요. 앞으론 커밋하고 resolve 할게요! 감사합니당