Skip to content
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

380 improve admin page load time #1325

Merged
merged 3 commits into from
Nov 20, 2018
Merged
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
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