From 6161a821e2448243e337d040c2c9e2dcefe14e3d Mon Sep 17 00:00:00 2001 From: misaugstad Date: Fri, 5 Oct 2018 16:26:08 -0700 Subject: [PATCH] drastically improved admin page load time --- app/models/daos/UserDAOImpl.scala | 66 +++++++++++++++++++++- app/models/user/WebpageActivityTable.scala | 46 --------------- app/views/admin/index.scala.html | 20 +++---- 3 files changed, 75 insertions(+), 57 deletions(-) diff --git a/app/models/daos/UserDAOImpl.scala b/app/models/daos/UserDAOImpl.scala index 26aaf6cff2..893859d428 100644 --- a/app/models/daos/UserDAOImpl.scala +++ b/app/models/daos/UserDAOImpl.scala @@ -1,12 +1,15 @@ package models.daos +import java.sql.Timestamp import java.util.UUID import com.mohiva.play.silhouette.api.LoginInfo import models.daos.UserDAOImpl._ import models.daos.slick.DBTableDefinitions.{DBUser, UserTable} -import models.user.{RoleTable, User, UserRoleTable} +import models.user.{RoleTable, User, UserRoleTable, WebpageActivityTable} import models.audit._ +import models.label.LabelTable +import models.mission.MissionTable import play.api.Play.current import scala.collection.mutable @@ -14,6 +17,10 @@ import scala.concurrent.Future import scala.slick.driver.PostgresDriver.simple._ import scala.slick.jdbc.{StaticQuery => Q} +case class UserStatsForAdminPage(userId: String, username: String, email: String, role: String, + signUpTime: Option[Timestamp], lastSignInTime: Option[Timestamp], signInCount: Int, + completedMissions: Int, completedAudits: Int, labels: Int) + class UserDAOImpl extends UserDAO { @@ -210,4 +217,61 @@ object UserDAOImpl { countUsersContributedYesterday("Turker") + countResearchersContributedYesterday } + + /** + * Gets metadata for each user that we use on the admin page. + * + * @return + */ + def getUserStatsForAdminPage: List[UserStatsForAdminPage] = db.withSession { implicit session => + + // We run 6 queries for different bits of metadata that we need. We run each query and convert them to Scala maps + // with the user_id as the key. We then query for all the users in the `user` table and for each user, we lookup + // the user's metadata in each of the maps from those 6 queries. This simulates a left join across the six sub- + // queries. We are using Scala Map objects instead of Slick b/c Slick doesn't create very efficient queries for this + // use-case (at least in the old version of Slick that we are using right now). + + // Map(user_id: String -> role: String) + val roles = + userRoleTable.innerJoin(roleTable).on(_.roleId === _.roleId).map(x => (x._1.userId, x._2.role)).list.toMap + + // Map(user_id: String -> signup_time: Option[Timestamp]) + val signUpTimes = + WebpageActivityTable.activities.filter(_.activity inSet List("AnonAutoSignUp", "SignUp")) + .groupBy(_.userId).map{ case (_userId, group) => (_userId, group.map(_.timestamp).max) }.list.toMap + + // Map(user_id: String -> (most_recent_sign_in_time: Option[Timestamp], sign_in_count: Int)) + val signInTimesAndCounts = + WebpageActivityTable.activities.filter(_.activity inSet List("AnonAutoSignUp", "SignIn")) + .groupBy(_.userId).map{ case (_userId, group) => (_userId, group.map(_.timestamp).min, group.length) } + .list.map{ case (_userId, _time, _count) => (_userId, (_time, _count)) }.toMap + + // Map(user_id: String -> mission_count: Int) + val missionCounts = + MissionTable.missionsWithoutDeleted.innerJoin(MissionTable.missionUsers).on(_.missionId === _.missionId) + .groupBy(x => (x._1.missionId, x._2.userId)).map(_._1) + .groupBy(_._2).map{ case (_userId, group) => (_userId, group.length) }.list.toMap + + // Map(user_id: String -> audit_count: Int) + val auditCounts = + AuditTaskTable.completedTasks.groupBy(_.userId).map { case (_uId, group) => (_uId, group.length) }.list.toMap + + // Map(user_id: String -> label_count: Int) + val labelCounts = + AuditTaskTable.auditTasks.innerJoin(LabelTable.labelsWithoutDeleted).on(_.auditTaskId === _.auditTaskId) + .groupBy(_._1.userId).map { case (_userId, group) => (_userId, group.length) }.list.toMap + + // Now left join them all together and put into UserStatsForAdminPage objects. + userTable.list.map{ u => + UserStatsForAdminPage( + u.userId, u.username, u.email, + roles.getOrElse(u.userId, ""), + signUpTimes.get(u.userId).flatten, + signInTimesAndCounts.get(u.userId).flatMap(_._1), signInTimesAndCounts.get(u.userId).map(_._2).getOrElse(0), + missionCounts.getOrElse(u.userId, 0), + auditCounts.getOrElse(u.userId, 0), + labelCounts.getOrElse(u.userId, 0) + ) + } + } } diff --git a/app/models/user/WebpageActivityTable.scala b/app/models/user/WebpageActivityTable.scala index 51f7b0c7e7..2d7ead6459 100644 --- a/app/models/user/WebpageActivityTable.scala +++ b/app/models/user/WebpageActivityTable.scala @@ -35,52 +35,6 @@ object WebpageActivityTable { } } - /** - * Returns the last log in timestamp - * - * @param userId User id - * @return - */ - def selectLastSignInTimestamp(userId: UUID): Option[java.sql.Timestamp] = db.withTransaction { implicit session => - val signInString: String = if (UserRoleTable.getRole(userId) == "Anonymous") "AnonAutoSignUp" else "SignIn" - val signInActivities: List[WebpageActivity] = - activities.filter(_.userId === userId.toString).filter(_.activity === signInString).sortBy(_.timestamp.desc).list - - if (signInActivities.nonEmpty) { - Some(signInActivities.head.timestamp) - } else { - None - } - } - - /** - * Returns the signup timestamp - * - * @param userId User id - * @return - */ - def selectSignUpTimestamp(userId: UUID): Option[java.sql.Timestamp] = db.withTransaction { implicit session => - val signUpString: String = if (UserRoleTable.getRole(userId) == "Anonymous") "AnonAutoSignUp" else "SignUp" - val signUpActivities: List[WebpageActivity] = - activities.filter(_.userId === userId.toString).filter(_.activity === signUpString).sortBy(_.timestamp.desc).list - - if (signUpActivities.nonEmpty) { - Some(signUpActivities.head.timestamp) - } else { - None - } - } - - /** - * Returns the signin count - * @param userId User id - * @return - */ - def selectSignInCount(userId: UUID): Option[Integer] = db.withTransaction { implicit session => - val signInActivities: List[WebpageActivity] = activities.filter(_.userId === userId.toString).filter(_.activity === "SignIn").list - Some(signInActivities.length) - } - /** * Returns a list of signin counts, each element being a count of logins for a user * diff --git a/app/views/admin/index.scala.html b/app/views/admin/index.scala.html index 70ba29dbdc..145a41669a 100644 --- a/app/views/admin/index.scala.html +++ b/app/views/admin/index.scala.html @@ -552,16 +552,16 @@

Users

- @UserDAOImpl.all.map { u => + @UserDAOImpl.getUserStatsForAdminPage.map { u => @u.username @u.userId @u.email - @if(UserRoleTable.getRole(UUID.fromString(u.userId)) != "Owner"){ + @if(u.role != "Owner"){ } else { - @UserRoleTable.getRole(UUID.fromString(u.userId)) + @u.role } - @WebpageActivityTable.selectSignUpTimestamp(UUID.fromString(u.userId)) - @WebpageActivityTable.selectLastSignInTimestamp(UUID.fromString(u.userId)) - @WebpageActivityTable.selectSignInCount(UUID.fromString(u.userId)) - @MissionTable.countCompletedMissionsByUserId(UUID.fromString(u.userId)) - @AuditTaskTable.countCompletedAuditsByUserId(UUID.fromString(u.userId)) - @LabelTable.countLabelsByUserId(UUID.fromString(u.userId)) + @u.signUpTime + @u.lastSignInTime + @u.signInCount + @u.completedMissions + @u.completedAudits + @u.labels }