mod leaderboard history

pull/1431/head
Thibault Duplessis 2016-01-11 16:37:09 +07:00
parent 97cdb41e27
commit bb19f26f14
7 changed files with 121 additions and 26 deletions

View File

@ -131,9 +131,10 @@ object Mod extends LilaController {
def gamify = Secure(_.SeeReport) { implicit ctx =>
me =>
Env.mod.gamify.leaderboards map { leaderboards =>
Ok(html.mod.gamify.index(leaderboards))
}
Env.mod.gamify.leaderboards zip
Env.mod.gamify.history(orCompute = true) map {
case (leaderboards, history) => Ok(html.mod.gamify.index(leaderboards, history))
}
}
def gamifyPeriod(periodStr: String) = Secure(_.SeeReport) { implicit ctx =>
me =>

View File

@ -1,9 +1,19 @@
@(leaderboards: lila.mod.Gamify.Leaderboards)(implicit ctx: Context)
@(leaderboards: lila.mod.Gamify.Leaderboards, history: List[lila.mod.Gamify.HistoryMonth])(implicit ctx: Context)
@import lila.mod.Gamify.Period
@title = @{ "Moderator hall of fame" }
@yearHeader(year: Int) = @{
Html(s"""<tr class="year">
<th>$year</th>
<th>Champions of the past</th>
<th>Score</th>
<th>Actions taken</th>
<th>Reports closed</th>
</tr>""")
}
@mod.layout(
title = title,
active = "gamify",
@ -23,7 +33,25 @@ moreCss = cssTag("mod-gamify.css")) {
</div>
</div>
<div class="history">
TODO: History of former champions for past months.
<table class="slist">
<tbody>
@history.headOption.filterNot(_.date.getMonthOfYear == 12).map { h =>
@yearHeader(h.date.getYear)
}
@history.map { h =>
@if(h.date.getMonthOfYear == 12) {
@yearHeader(h.date.getYear)
}
<tr>
<th>@h.date.monthOfYear.getAsText</th>
<th>@userIdLink(h.champion.modId.some, withOnline = false)</th>
<td class="score">@h.champion.score.localize</td>
<td>@h.champion.action.localize</td>
<td>@h.champion.report.localize</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}

View File

@ -1,11 +1,11 @@
@(perfType: lila.rating.PerfType, users: List[lila.user.User.LightPerf])(implicit ctx: Context)
@user.layout(trans.onlinePlayers.str()) {
@title = @{ "s{perfType.name} top 200" }
e
@user.layout(title) {
<div class="content_box small_box">
<h1>
<a href="@routes.User.list" data-icon="I" class="text">
@perfType.name top 200
</a>
<a href="@routes.User.list" data-icon="I" class="text">@title</a>
</h1>
<br />
<br />

View File

@ -287,6 +287,7 @@ mod {
modlog = modlog
player_assessment = player_assessment
boosting = boosting
gaming_history = mod_gaming_history
}
boosting.nb_games_to_mark = 5
boosting.ratio_games_to_mark = 0.01

View File

@ -21,6 +21,7 @@ final class Env(
val CollectionPlayerAssessment = config getString "collection.player_assessment"
val CollectionBoosting = config getString "collection.boosting"
val CollectionModlog = config getString "collection.modlog"
val CollectionGamingHistory = config getString "collection.gaming_history"
val ActorName = config getString "actor.name"
val NbGamesToMark = config getInt "boosting.nb_games_to_mark"
val RatioGamesToMark = config getDouble "boosting.ratio_games_to_mark"
@ -56,7 +57,8 @@ final class Env(
lazy val gamify = new Gamify(
logColl = logColl,
reportColl = reportColl)
reportColl = reportColl,
historyColl = db(CollectionGamingHistory))
private val neuralApi = new NeuralApi(
endpoint = NeuralApiEndpoint,

View File

@ -11,22 +11,60 @@ import lila.memo.AsyncCache
final class Gamify(
logColl: Coll,
reportColl: Coll) {
reportColl: Coll,
historyColl: Coll) {
import Gamify._
def history(orCompute: Boolean = true): Fu[List[HistoryMonth]] = {
val until = DateTime.now minusMonths 1 withDayOfMonth 1
val lastId = HistoryMonth.makeId(until.getYear, until.getMonthOfYear)
historyColl.find(BSONDocument()).sort(BSONDocument(
"year" -> -1,
"month" -> -1
)).cursor[HistoryMonth]().collect[List]().flatMap { months =>
months.headOption match {
case Some(m) if m._id == lastId || !orCompute => fuccess(months)
case Some(m) => buildHistoryAfter(m.year, m.month, until) >> history(false)
case _ => buildHistoryAfter(2012, 6, until) >> history(false)
}
}
}
private implicit val modMixedBSONHandler = Macros.handler[ModMixed]
private implicit val historyMonthBSONHandler = Macros.handler[HistoryMonth]
private def buildHistoryAfter(afterYear: Int, afterMonth: Int, until: DateTime): Funit =
(afterYear to until.getYear).flatMap { year =>
((year == afterYear).fold(afterMonth + 1, 1) to
(year == until.getYear).fold(until.getMonthOfYear, 12)).map { month =>
mixedLeaderboard(
after = new DateTime(year, month, 1, 0, 0).pp("compute mod history"),
before = new DateTime(year, month, 1, 0, 0).plusMonths(1).some
).map {
_.headOption.map { champ =>
HistoryMonth(HistoryMonth.makeId(year, month), year, month, champ)
}
}
}.toList
}.toList.sequenceFu.map(_.flatten).flatMap {
_.map { month =>
historyColl.update(BSONDocument("_id" -> month._id), month, upsert = true)
}.sequenceFu
}.void
def leaderboards = leaderboardsCache(true)
private val leaderboardsCache = AsyncCache.single[Leaderboards](
f = mixedLeaderboard(_ minusDays 1) zip
mixedLeaderboard(_ minusWeeks 1) zip
mixedLeaderboard(_ minusMonths 1) map {
f = mixedLeaderboard(DateTime.now minusDays 1, none) zip
mixedLeaderboard(DateTime.now minusWeeks 1, none) zip
mixedLeaderboard(DateTime.now minusMonths 1, none) map {
case ((daily, weekly), monthly) => Leaderboards(daily, weekly, monthly)
},
timeToLive = 10 seconds)
private def mixedLeaderboard(since: DateTime => DateTime): Fu[List[ModMixed]] =
actionLeaderboard(since) zip reportLeaderboard(since) map {
private def mixedLeaderboard(after: DateTime, before: Option[DateTime]): Fu[List[ModMixed]] =
actionLeaderboard(after, before) zip reportLeaderboard(after, before) map {
case (actions, reports) => actions.map(_.modId) intersect reports.map(_.modId) map { modId =>
ModMixed(modId,
action = actions.find(_.modId == modId) ?? (_.count),
@ -34,31 +72,31 @@ final class Gamify(
} sortBy (-_.score)
}
private def after(date: DateTime) = BSONDocument("$gte" -> date)
private def dateRange(from: DateTime, toOption: Option[DateTime]) =
BSONDocument("$gte" -> from) ++ toOption.?? { to => BSONDocument("$lt" -> to) }
private val notLichess = BSONDocument("$ne" -> "lichess")
private def actionLeaderboard(since: DateTime => DateTime): Fu[List[ModCount]] =
private def actionLeaderboard(after: DateTime, before: Option[DateTime]): Fu[List[ModCount]] =
logColl.aggregate(Match(BSONDocument(
"date" -> after(since(DateTime.now)),
"date" -> dateRange(after, before),
"mod" -> notLichess
)), List(
GroupField("mod")("nb" -> SumValue(1)),
Sort(Descending("nb"))
)).map {
Sort(Descending("nb")))).map {
_.documents.flatMap { obj =>
obj.getAs[String]("_id") |@| obj.getAs[Int]("nb") apply ModCount.apply
}
}
private def reportLeaderboard(since: DateTime => DateTime): Fu[List[ModCount]] =
private def reportLeaderboard(after: DateTime, before: Option[DateTime]): Fu[List[ModCount]] =
reportColl.aggregate(
Match(BSONDocument(
"createdAt" -> after(since(DateTime.now)),
"createdAt" -> dateRange(after, before),
"processedBy" -> notLichess
)), List(
GroupField("processedBy")("nb" -> SumValue(1)),
Sort(Descending("nb"))
)).map {
Sort(Descending("nb")))).map {
_.documents.flatMap { obj =>
obj.getAs[String]("_id") |@| obj.getAs[Int]("nb") apply ModCount.apply
}
@ -67,6 +105,13 @@ final class Gamify(
object Gamify {
case class HistoryMonth(_id: String, year: Int, month: Int, champion: ModMixed) {
def date = new DateTime(year, month, 1, 0, 0)
}
object HistoryMonth {
def makeId(year: Int, month: Int) = s"$year/$month"
}
sealed trait Period {
def name = toString.toLowerCase
}

View File

@ -79,6 +79,24 @@
font-size: 1.5em;
}
#mod-gamify .history {
padding: 20px;
margin-top: 20px;
font-size: 1.2em;
}
#mod-gamify .history table tr.year th {
font-family: 'PT Serif';
padding: 20px 0;
}
#mod-gamify .history table tr.year th:first-child {
padding-left: 25px;
font-size: 1.8em;
}
#mod-gamify .history table tr.year th:nth-child(n+3),
#mod-gamify .history table tr td {
text-align: right;
padding-right: 25px;
}
#mod-gamify .history table a,
#mod-gamify .history table .score {
font-size: 1.2em;
}