Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.epam.brn.model.projection

import java.time.LocalDateTime

interface UserStatisticsWithIdView {
val userId: Long
val firstStudy: LocalDateTime?
val lastStudy: LocalDateTime?
val spentTime: Long
val doneExercises: Int
}
26 changes: 26 additions & 0 deletions src/main/kotlin/com/epam/brn/repo/StudyHistoryRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.epam.brn.model.Exercise
import com.epam.brn.model.StudyHistory
import com.epam.brn.model.projection.ExerciseLastAttemptView
import com.epam.brn.model.projection.UserStatisticView
import com.epam.brn.model.projection.UserStatisticsWithIdView
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.CrudRepository
import org.springframework.data.repository.query.Param
Expand Down Expand Up @@ -167,4 +168,29 @@ interface StudyHistoryRepository : CrudRepository<StudyHistory, Long> {
"select count (s) > 0 from StudyHistory s where s.userAccount.id = :userId",
)
fun isUserHasStatistics(userId: Long): Boolean

@Query(
"SELECT s FROM StudyHistory s " +
"JOIN FETCH s.exercise e " +
"LEFT JOIN FETCH e.subGroup sg " +
"LEFT JOIN FETCH sg.series " +
"WHERE s.startTime >= :from " +
"AND s.startTime <= :to " +
"AND s.userAccount.id IN :userIds " +
"ORDER BY s.startTime",
)
fun getHistoriesByUserIds(
@Param("userIds") userIds: Collection<Long>,
@Param("from") from: LocalDateTime,
@Param("to") to: LocalDateTime,
): List<StudyHistory>

@Query(
"SELECT s.userAccount.id AS userId, MIN(s.startTime) AS firstStudy, MAX(s.startTime) AS lastStudy," +
" COALESCE(SUM(s.spentTimeInSeconds), 0) AS spentTime, COUNT(DISTINCT s.exercise.id) AS doneExercises" +
" FROM StudyHistory s WHERE s.userAccount.id IN :userIds GROUP BY s.userAccount.id",
)
fun getStatisticsByUserIds(
@Param("userIds") userIds: Collection<Long>,
): List<UserStatisticsWithIdView>
}
8 changes: 8 additions & 0 deletions src/main/kotlin/com/epam/brn/repo/UserAccountRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ interface UserAccountRepository : JpaRepository<UserAccount, Long> {
)
fun findUsersAccountsByRole(roleName: String): List<UserAccount>

@Query(
"""select u FROM UserAccount u join u.roleSet roles where roles.name = :roleName""",
)
fun findUsersAccountsByRole(
roleName: String,
pageable: Pageable,
): List<UserAccount>

@Transactional
@Modifying
@Query(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class UserAccountServiceImpl(
pageable: Pageable,
role: String,
): List<UserAccountDto> = userAccountRepository
.findUsersAccountsByRole(role)
.findUsersAccountsByRole(role, pageable)
.map { it.toDto() }

override fun updateAvatarForCurrentUser(avatarUrl: String): UserAccountDto {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.epam.brn.service.impl
import com.epam.brn.dto.AudioFileMetaData
import com.epam.brn.dto.response.UserWithAnalyticsResponse
import com.epam.brn.dto.statistics.DayStudyStatistics
import com.epam.brn.dto.statistics.UserExercisingPeriod
import com.epam.brn.enums.ExerciseType
import com.epam.brn.enums.Voice
import com.epam.brn.exception.EntityNotFoundException
Expand All @@ -17,7 +18,7 @@ import com.epam.brn.service.TimeService
import com.epam.brn.service.UserAccountService
import com.epam.brn.service.UserAnalyticsService
import com.epam.brn.service.WordsService
import com.epam.brn.service.statistics.UserPeriodStatisticsService
import com.epam.brn.service.statistics.progress.status.ProgressStatusManager
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import java.io.InputStream
Expand All @@ -33,20 +34,23 @@ class UserAnalyticsServiceImpl(
private val userAccountRepository: UserAccountRepository,
private val studyHistoryRepository: StudyHistoryRepository,
private val exerciseRepository: ExerciseRepository,
private val userDayStatisticsService: UserPeriodStatisticsService<DayStudyStatistics>,
private val timeService: TimeService,
private val textToSpeechService: TextToSpeechService,
private val userAccountService: UserAccountService,
private val exerciseService: ExerciseService,
private val wordsService: WordsService,
private val progressManager: ProgressStatusManager<List<StudyHistory>>,
) : UserAnalyticsService {
private val listTextExercises = listOf(ExerciseType.SENTENCE, ExerciseType.PHRASES)

override fun getUsersWithAnalytics(
pageable: Pageable,
role: String,
): List<UserWithAnalyticsResponse> {
val users = userAccountRepository.findUsersAccountsByRole(role).map { it.toAnalyticsDto() }
val users = userAccountRepository.findUsersAccountsByRole(role, pageable).map { it.toAnalyticsDto() }
if (users.isEmpty()) return emptyList()

val userIds = users.mapNotNull { it.id }

val now = timeService.now()
val firstWeekDay = WeekFields.of(Locale.getDefault()).dayOfWeek()
Expand All @@ -55,24 +59,52 @@ class UserAnalyticsServiceImpl(
val to = startDay.plusDays(7L).with(LocalTime.MAX)
val startOfCurrentMonth = now.withDayOfMonth(1).with(LocalTime.MIN)

val weekHistoriesByUserId =
studyHistoryRepository
.getHistoriesByUserIds(userIds, from, to)
.groupBy { it.userAccount.id }

val monthHistoriesByUserId =
studyHistoryRepository
.getHistoriesByUserIds(userIds, startOfCurrentMonth, now)
.groupBy { it.userAccount.id }

val statisticsByUserId =
studyHistoryRepository
.getStatisticsByUserIds(userIds)
.associateBy { it.userId }

users.onEach { user ->
user.lastWeek = userDayStatisticsService.getStatisticsForPeriod(from, to, user.id)
user.studyDaysInCurrentMonth =
countWorkDaysForMonth(
userDayStatisticsService.getStatisticsForPeriod(startOfCurrentMonth, now, user.id),
)

val userStatistic = studyHistoryRepository.getStatisticsByUserAccountId(user.id)
user.apply {
this.firstDone = userStatistic.firstStudy
this.lastDone = userStatistic.lastStudy
this.spentTime = userStatistic.spentTime.toDuration(DurationUnit.SECONDS)
this.doneExercises = userStatistic.doneExercises
val weekHistories = weekHistoriesByUserId[user.id] ?: emptyList()
user.lastWeek = computeDayStatistics(weekHistories)

val monthHistories = monthHistoriesByUserId[user.id] ?: emptyList()
user.studyDaysInCurrentMonth = countWorkDaysForMonth(computeDayStatistics(monthHistories))

val userStatistic = statisticsByUserId[user.id]
if (userStatistic != null) {
user.apply {
this.firstDone = userStatistic.firstStudy
this.lastDone = userStatistic.lastStudy
this.spentTime = userStatistic.spentTime.toDuration(DurationUnit.SECONDS)
this.doneExercises = userStatistic.doneExercises
}
}
}
return users
}

private fun computeDayStatistics(histories: List<StudyHistory>): List<DayStudyStatistics> {
val byDate = histories.groupBy { it.startTime.toLocalDate() }
return byDate.map { (_, dayHistories) ->
DayStudyStatistics(
exercisingTimeSeconds = dayHistories.sumOf { it.executionSeconds },
date = dayHistories.first().startTime,
progress = progressManager.getStatus(UserExercisingPeriod.DAY, dayHistories),
)
}
}

override fun prepareAudioStreamForUser(
exerciseId: Long,
audioFileMetaData: AudioFileMetaData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ internal class UserAccountServiceTest {
fun `should return all users`() {
// GIVEN
val usersList = listOf(userAccount, userAccount, userAccount)
every { userAccountRepository.findUsersAccountsByRole(BrnRole.USER) } returns usersList
every { userAccountRepository.findUsersAccountsByRole(BrnRole.USER, pageable) } returns usersList
// WHEN
val userAccountDtos = userAccountService.getUsers(pageable = pageable, BrnRole.USER)
// THEN
Expand Down
27 changes: 13 additions & 14 deletions src/test/kotlin/com/epam/brn/service/UserAnalyticsServiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import com.epam.brn.enums.ExerciseType
import com.epam.brn.enums.Voice
import com.epam.brn.model.StudyHistory
import com.epam.brn.model.UserAccount
import com.epam.brn.model.projection.UserStatisticView
import com.epam.brn.model.projection.UserStatisticsWithIdView
import com.epam.brn.repo.ExerciseRepository
import com.epam.brn.repo.StudyHistoryRepository
import com.epam.brn.repo.UserAccountRepository
import com.epam.brn.service.impl.UserAnalyticsServiceImpl
import com.epam.brn.service.statistics.UserPeriodStatisticsService
import com.epam.brn.service.statistics.progress.status.ProgressStatusManager
import com.epam.brn.exception.EntityNotFoundException
import io.kotest.matchers.shouldBe
import io.mockk.every
Expand Down Expand Up @@ -45,9 +45,6 @@ internal class UserAnalyticsServiceTest {
@MockK
lateinit var exerciseRepository: ExerciseRepository

@MockK
lateinit var userDayStatisticService: UserPeriodStatisticsService<DayStudyStatistics>

@MockK
lateinit var timeService: TimeService

Expand All @@ -70,24 +67,25 @@ internal class UserAnalyticsServiceTest {
lateinit var dayStudyStatistics: DayStudyStatistics

@MockK
lateinit var userStatisticView: UserStatisticView
lateinit var progressManager: ProgressStatusManager<List<StudyHistory>>

@MockK
lateinit var wordsService: WordsService

@Test
fun `should return all users with analytics`() {
val usersList = listOf(doctorAccount, doctorAccount)
val dayStatisticList = listOf(dayStudyStatistics, dayStudyStatistics)
val userStatisticView = mockk<UserStatisticsWithIdView>()
every { userStatisticView.userId } returns 1L
every { userStatisticView.firstStudy } returns LocalDateTime.now()
every { userStatisticView.lastStudy } returns LocalDateTime.now()
every { userStatisticView.spentTime } returns 10000L
every { userStatisticView.doneExercises } returns 1

every { userAccountRepository.findUsersAccountsByRole(BrnRole.ADMIN) } returns usersList
every { userDayStatisticService.getStatisticsForPeriod(any(), any(), any()) } returns dayStatisticList
every { userAccountRepository.findUsersAccountsByRole(BrnRole.ADMIN, pageable) } returns usersList
every { timeService.now() } returns LocalDateTime.now()
every { studyHistoryRepository.getStatisticsByUserAccountId(any()) } returns userStatisticView
every { studyHistoryRepository.getHistoriesByUserIds(any(), any(), any()) } returns emptyList()
every { studyHistoryRepository.getStatisticsByUserIds(any()) } returns listOf(userStatisticView)

val userAnalyticsDtos = userAnalyticsService.getUsersWithAnalytics(pageable, BrnRole.ADMIN)

Expand All @@ -97,16 +95,17 @@ internal class UserAnalyticsServiceTest {
@Test
fun `should not return user with analytics`() {
val usersList = listOf(doctorAccount)
val dayStatisticList = emptyList<DayStudyStatistics>()
val userStatisticView = mockk<UserStatisticsWithIdView>()
every { userStatisticView.userId } returns 1L
every { userStatisticView.firstStudy } returns LocalDateTime.now()
every { userStatisticView.lastStudy } returns LocalDateTime.now()
every { userStatisticView.spentTime } returns 10000L
every { userStatisticView.doneExercises } returns 1

every { userAccountRepository.findUsersAccountsByRole(BrnRole.ADMIN) } returns usersList
every { userDayStatisticService.getStatisticsForPeriod(any(), any(), any()) } returns dayStatisticList
every { userAccountRepository.findUsersAccountsByRole(BrnRole.ADMIN, pageable) } returns usersList
every { timeService.now() } returns LocalDateTime.now()
every { studyHistoryRepository.getStatisticsByUserAccountId(any()) } returns userStatisticView
every { studyHistoryRepository.getHistoriesByUserIds(any(), any(), any()) } returns emptyList()
every { studyHistoryRepository.getStatisticsByUserIds(any()) } returns emptyList()

val userAnalyticsDtos = userAnalyticsService.getUsersWithAnalytics(pageable, BrnRole.ADMIN)

Expand Down