Skip to content

Commit

Permalink
Merge pull request #1325 from ProjectSidewalk/380-improve-admin-page-…
Browse files Browse the repository at this point in the history
…speed-1

380 improve admin page load time
  • Loading branch information
misaugstad authored Nov 20, 2018
2 parents 7267ae7 + ccf02bd commit 53d7510
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 57 deletions.
65 changes: 64 additions & 1 deletion app/models/daos/UserDAOImpl.scala
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
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
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 {


Expand Down Expand Up @@ -210,4 +217,60 @@ 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.missions.filter(_.completed)
.groupBy(_.userId).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)
)
}
}
}
46 changes: 0 additions & 46 deletions app/models/user/WebpageActivityTable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
20 changes: 10 additions & 10 deletions app/views/admin/index.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -552,16 +552,16 @@ <h1>Users</h1>
</tr>
</thead>
<tbody>
@UserDAOImpl.all.map { u =>
@UserDAOImpl.getUserStatsForAdminPage.map { u =>
<tr>
<td><a href='@routes.AdminController.userProfile(u.username)'>@u.username</a></td>
<td>@u.userId</td>
<td>@u.email</td>
<td>
@if(UserRoleTable.getRole(UUID.fromString(u.userId)) != "Owner"){
@if(u.role != "Owner"){
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="userRoleDropdown@u.userId" data-toggle="dropdown" style="cursor: default">
@UserRoleTable.getRole(UUID.fromString(u.userId))
@u.role
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="userRoleDropdown@u.userId">
Expand All @@ -573,14 +573,14 @@ <h1>Users</h1>
</ul>
</div>
} else {
@UserRoleTable.getRole(UUID.fromString(u.userId))
@u.role
}</td>
<td>@WebpageActivityTable.selectSignUpTimestamp(UUID.fromString(u.userId))</td>
<td>@WebpageActivityTable.selectLastSignInTimestamp(UUID.fromString(u.userId))</td>
<td>@WebpageActivityTable.selectSignInCount(UUID.fromString(u.userId))</td>
<td>@MissionTable.countCompletedMissionsByUserId(UUID.fromString(u.userId), includeOnboarding = false)</td>
<td>@AuditTaskTable.countCompletedAuditsByUserId(UUID.fromString(u.userId))</td>
<td>@LabelTable.countLabelsByUserId(UUID.fromString(u.userId))</td>
<td>@u.signUpTime</td>
<td>@u.lastSignInTime</td>
<td>@u.signInCount</td>
<td>@u.completedMissions</td>
<td>@u.completedAudits</td>
<td>@u.labels</td>
</tr>
}
</tbody>
Expand Down

0 comments on commit 53d7510

Please sign in to comment.