-
Notifications
You must be signed in to change notification settings - Fork 30
2609 [BE] new user analytics functionality using database table #2687
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
base: master
Are you sure you want to change the base?
Changes from all commits
c8d7813
4ce9107
257ce50
f3db773
922c53a
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 |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.epam.brn.config | ||
|
|
||
| import org.springframework.beans.factory.annotation.Value | ||
| import org.springframework.context.annotation.Configuration | ||
|
|
||
| @Configuration | ||
| class UserDetailControllerConfig( | ||
| @Value("\${brn.user.analytics.use.new.version}") | ||
| val isUseNewAnalyticsService: Boolean, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package com.epam.brn.job | ||
|
|
||
| import org.apache.logging.log4j.kotlin.logger | ||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty | ||
| import org.springframework.jdbc.core.JdbcTemplate | ||
| import org.springframework.scheduling.annotation.Scheduled | ||
| import org.springframework.stereotype.Component | ||
| import org.springframework.transaction.annotation.Transactional | ||
|
|
||
| @Component | ||
| @ConditionalOnProperty(name = ["brn.user.analytics.job.enabled"], havingValue = "true") | ||
| class UserAnalyticsJob( | ||
| private val jdbcTemplate: JdbcTemplate, | ||
| ) { | ||
| private val log = logger() | ||
|
|
||
| @Scheduled(cron = "@midnight") | ||
| @Transactional | ||
| fun fillUserAnalytics() { | ||
| try { | ||
| log.info("start filling study analytics table...") | ||
| val rowsCount = jdbcTemplate.update(FILL_USER_ANALYTICS_SQL) | ||
| log.info("filling study analytics table was finished successfully. total $rowsCount rows inserted") | ||
| } catch (e: Exception) { | ||
| log.error("Some error occurred on fill statistics tables: ${e.message}", e) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private const val FILL_USER_ANALYTICS_SQL: String = """ | ||
| TRUNCATE TABLE user_analytics CASCADE; | ||
| INSERT INTO user_analytics (user_id, first_done, last_done, spent_time, | ||
| done_exercises, study_days, role_name) | ||
| SELECT s.user_id, | ||
| min(start_time), | ||
| max(start_time), | ||
| coalesce(sum(spent_time_in_seconds), 0), | ||
| count(distinct exercise_id), | ||
| (SELECT distinct count(distinct date_trunc('day', s1.start_time)) | ||
| FROM study_history s1 | ||
| WHERE s1.user_id = s.user_id | ||
| AND s1.start_time between date_trunc('month', current_date) | ||
| AND current_date), | ||
| r.name | ||
| FROM study_history s, | ||
| user_roles ur, | ||
| role r | ||
| WHERE s.user_id = ur.user_id | ||
| AND ur.role_id = r.id | ||
| GROUP BY s.user_id, r.name; | ||
| """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package com.epam.brn.model | ||
|
|
||
| import java.time.LocalDateTime | ||
| import javax.persistence.Column | ||
| import javax.persistence.GeneratedValue | ||
| import javax.persistence.GenerationType | ||
| import javax.persistence.Index | ||
| import javax.persistence.Entity | ||
| import javax.persistence.Table | ||
| import javax.persistence.Id | ||
|
|
||
| @Entity | ||
| @Table(indexes = [Index(name = "user_analytics_ix_role_name", columnList = "role_name")]) | ||
| class UserAnalytics( | ||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| val id: Long? = null, | ||
| val userId: Long, | ||
| val firstDone: LocalDateTime?, | ||
| val lastDone: LocalDateTime?, | ||
| val spentTime: Long?, | ||
| val doneExercises: Int?, | ||
| val studyDays: Int?, | ||
| @Column(name = "role_name") | ||
| val roleName: String, | ||
| ) { | ||
| override fun toString(): String = | ||
| "UserAnalytics(id=$id, userId=$userId, firstDone=$firstDone, lastDone=$lastDone, spentTime=$spentTime, doneExercises=$doneExercises, studyDays=$studyDays, roleName='$roleName')" | ||
|
|
||
| override fun equals(other: Any?): Boolean { | ||
| if (this === other) return true | ||
| if (javaClass != other?.javaClass) return false | ||
|
|
||
| other as UserAnalytics | ||
|
|
||
| if (id != other.id) return false | ||
| if (userId != other.userId) return false | ||
|
|
||
| return true | ||
| } | ||
|
|
||
| override fun hashCode(): Int { | ||
| var result = id?.hashCode() ?: 0 | ||
| result = 31 * result + userId.hashCode() | ||
| return result | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.epam.brn.model.projection | ||
|
|
||
| import com.epam.brn.enums.BrnGender | ||
| import java.time.LocalDateTime | ||
|
|
||
| /** | ||
| * StudyAnalyticsView. | ||
| * | ||
| * @author Andrey Samoylov | ||
| */ | ||
| interface UsersWithAnalyticsView { | ||
| val id: Long | ||
| val userId: String | ||
| val fullName: String | ||
| val email: String | ||
| val bornYear: Int? | ||
| val gender: BrnGender | ||
| var active: Boolean | ||
| val firstDone: LocalDateTime | ||
| val lastDone: LocalDateTime | ||
| var lastVisit: LocalDateTime | ||
| val doneExercises: Int | ||
| val spentTime: Long | ||
| val studyDays: Int | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package com.epam.brn.repo | ||
|
|
||
| import com.epam.brn.model.UserAnalytics | ||
| import com.epam.brn.model.projection.UsersWithAnalyticsView | ||
| import org.springframework.data.domain.Pageable | ||
| import org.springframework.data.jpa.repository.JpaRepository | ||
| import org.springframework.data.jpa.repository.Query | ||
|
|
||
| interface UserAnalyticsRepository : JpaRepository<UserAnalytics, Long> { | ||
| @Query( | ||
| """ | ||
| select u.id as id, | ||
| u.userId as userId, | ||
| u.fullName as fullName, | ||
| u.email as email, | ||
| u.bornYear as bornYear, | ||
| u.gender as gender, | ||
| u.active as active, | ||
| u.lastVisit as lastVisit, | ||
| a.firstDone as firstDone, | ||
| a.lastDone as lastDone, | ||
| a.doneExercises as doneExercises, | ||
| a.spentTime as spentTime, | ||
| a.studyDays as studyDays | ||
| from UserAnalytics a | ||
| join UserAccount u on a.userId = u.id | ||
| where a.roleName=:roleName | ||
| """, | ||
| ) | ||
| fun getUserAnalytics( | ||
| pageable: Pageable, | ||
| roleName: String, | ||
| ): List<UsersWithAnalyticsView> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.epam.brn.service | ||
|
|
||
| import com.epam.brn.dto.response.UserWithAnalyticsResponse | ||
| import org.springframework.data.domain.Pageable | ||
|
|
||
| interface UserAnalyticsServiceV1 { | ||
|
Contributor
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. maybe it is reasonable to design one interface for both UserAnalyticsServices?
Collaborator
Author
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. UserAnalyticsServiceV1 - temporary solution, switched using brn.user.analytics.use.new.version toggle in application.properties |
||
| fun getUsersWithAnalytics( | ||
| pageable: Pageable, | ||
| role: String, | ||
| ): List<UserWithAnalyticsResponse> | ||
| } | ||
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.
we have 'dto' package for such dto meanings
lets be in one style and rename it UsersWithAnalyticsDto and move it there. ?
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.
no. UsersWithAnalyticsView - is a projection and located in projections package