remove report score threshold and unprocessed counts

use highest report score instead
report-no-threshold
Thibault Duplessis 2020-10-12 16:12:21 +02:00
parent 0d796c60f4
commit e29907ad3c
19 changed files with 158 additions and 116 deletions

View File

@ -32,10 +32,10 @@ final class Appeal(env: Env, reportC: => Report) extends LilaController(env) {
def queue =
Secure(_.Appeals) { implicit ctx => me =>
env.appeal.api.queue zip env.report.api.inquiries.allBySuspect zip reportC.getCounts flatMap {
case ((appeals, inquiries), counts ~ streamers ~ nbAppeals) =>
env.appeal.api.queue zip env.report.api.inquiries.allBySuspect zip reportC.getScores flatMap {
case ((appeals, inquiries), scores ~ streamers ~ nbAppeals) =>
(env.user.lightUserApi preloadMany appeals.map(_.id)) inject
Ok(html.appeal.queue(appeals, inquiries, counts, streamers, nbAppeals))
Ok(html.appeal.queue(appeals, inquiries, scores, streamers, nbAppeals))
}
}

View File

@ -12,7 +12,7 @@ final class Dev(env: Env) extends LilaController(env) {
env.security.spamKeywordsSetting,
env.irwin.irwinThresholdsSetting,
env.explorer.indexFlowSetting,
env.report.scoreThresholdSetting,
env.report.scoreThresholdsSetting,
env.report.slackScoreThresholdSetting,
env.streamer.homepageMaxSetting,
env.streamer.alwaysFeaturedSetting,

View File

@ -30,14 +30,14 @@ final class Report(
renderList(room)
}
protected[controllers] def getCounts =
api.countOpenByRooms zip env.streamer.api.approval.countRequests zip env.appeal.api.countUnread
protected[controllers] def getScores =
api.maxScores zip env.streamer.api.approval.countRequests zip env.appeal.api.countUnread
private def renderList(room: String)(implicit ctx: Context) =
api.openAndRecentWithFilter(12, Room(room)) zip
getCounts flatMap { case (reports, counts ~ streamers ~ appeals) =>
getScores flatMap { case (reports, scores ~ streamers ~ appeals) =>
(env.user.lightUserApi preloadMany reports.flatMap(_.report.userIds)) inject
Ok(html.report.list(reports, room, counts, streamers, appeals))
Ok(html.report.list(reports, room, scores, streamers, appeals))
}
def inquiry(id: String) =

View File

@ -46,7 +46,8 @@ object Environment
def isChatPanicEnabled = env.chat.panic.enabled
def blockingReportMaxScore: Int = env.report.api.maxScore.awaitOrElse(30.millis, "nbReports", 0)
def blockingReportMaxScore: Int =
env.report.api.maxScores.dmap(_.highest).awaitOrElse(50.millis, "nbReports", 0)
val spinner: Frag = raw(
"""<div class="spinner"><svg viewBox="0 0 40 40"><circle cx=20 cy=20 r=18 fill="none"></circle></svg></div>"""

View File

@ -80,11 +80,11 @@ object appeal {
def queue(
appeals: List[Appeal],
inquiries: Map[User.ID, Inquiry],
counts: lila.report.Room.Counts,
scores: lila.report.Room.Scores,
streamers: Int,
nbAppeals: Int
)(implicit ctx: Context) =
views.html.report.list.layout("appeal", counts, streamers, nbAppeals)(
views.html.report.list.layout("appeal", scores, streamers, nbAppeals)(
table(cls := "slist slist-pad see appeal-queue")(
thead(
tr(

View File

@ -12,11 +12,11 @@ object list {
def apply(
reports: List[lila.report.Report.WithSuspect],
filter: String,
counts: lila.report.Room.Counts,
scores: lila.report.Room.Scores,
streamers: Int,
appeals: Int
)(implicit ctx: Context) =
layout(filter, counts, streamers, appeals)(
layout(filter, scores, streamers, appeals)(
table(cls := "slist slist-pad see")(
thead(
tr(
@ -88,7 +88,9 @@ object list {
)
)
def layout(filter: String, counts: lila.report.Room.Counts, streamers: Int, appeals: Int)(
private val scoreTag = tag("score")
def layout(filter: String, scores: lila.report.Room.Scores, streamers: Int, appeals: Int)(
body: Frag
)(implicit ctx: Context) =
views.html.base.layout(
@ -103,22 +105,21 @@ object list {
span(cls := "tabs")(
a(
href := routes.Report.listWithFilter("all"),
cls := List("new" -> (counts.sum > 0), "active" -> (filter == "all"))
cls := List("active" -> (filter == "all"))
)(
countTag(counts.sum > 0 option counts.sum),
"All"
"All",
scoreTag(scores.highest)
),
lila.report.Room.all.map { room =>
a(
href := routes.Report.listWithFilter(room.key),
cls := List(
"new" -> counts.value.contains(room),
"active" -> (filter == room.key),
s"room-${room.key}" -> true
)
)(
countTag(counts.get(room)),
room.name
room.name,
scoreTag(scores get room)
)
},
(appeals > 0 && isGranted(_.Appeals)) option a(

View File

@ -6,6 +6,11 @@ import chess.Centis
trait Iso[A, B] {
val from: A => B
val to: B => A
def map[BB](mapFrom: B => BB, mapTo: BB => B) = new Iso[A, BB] {
val from = a => mapFrom(Iso.this.from(a))
val to = bb => Iso.this.to(mapTo(bb))
}
}
object Iso {
@ -29,12 +34,17 @@ object Iso {
def strings(sep: String): StringIso[Strings] =
Iso[String, Strings](
str => Strings(str.split(sep).iterator.map(_.trim).to(List)),
str => Strings(str.split(sep).iterator.map(_.trim).toList),
strs => strs.value mkString sep
)
def userIds(sep: String): StringIso[UserIds] =
Iso[String, UserIds](
str => UserIds(str.split(sep).iterator.map(_.trim.toLowerCase).to(List)),
str => UserIds(str.split(sep).iterator.map(_.trim.toLowerCase).toList),
strs => strs.value mkString sep
)
def ints(sep: String): StringIso[Ints] =
Iso[String, Ints](
str => Ints(str.split(sep).iterator.map(_.trim).flatMap(_.toIntOption).toList),
strs => strs.value mkString sep
)

View File

@ -121,6 +121,7 @@ object Domain {
case class Strings(value: List[String]) extends AnyVal
case class UserIds(value: List[String]) extends AnyVal
case class Ints(value: List[Int]) extends AnyVal
case class Every(value: FiniteDuration) extends AnyVal
case class AtMost(value: FiniteDuration) extends AnyVal

View File

@ -239,7 +239,7 @@ object mon {
}
object mod {
object report {
val unprocessed = gauge("mod.report.unprocessed").withoutTags()
val highest = gauge("mod.report.highest").withoutTags()
val close = counter("mod.report.close").withoutTags()
def create(reason: String) = counter("mod.report.create").withTag("reason", reason)
}

View File

@ -46,7 +46,7 @@ package clas {
package report {
case class Cheater(userId: String, text: String)
case class Shutup(userId: String, text: String, major: Boolean)
case class Shutup(userId: String, text: String)
case class Booster(winnerId: String, loserId: String)
case class AutoFlag(suspectId: String, resource: String, text: String)
case class CheatReportCreated(userId: String)

View File

@ -3,26 +3,23 @@ package lila.irwin
import lila.memo.SettingStore.{ Formable, StringReader }
import play.api.data.Form
import play.api.data.Forms.{ single, text }
import lila.common.Ints
case class IrwinThresholds(report: Int, mark: Int)
private object IrwinThresholds {
val defaultThresholds = IrwinThresholds(88, 95)
val thresholdsIso = lila.common.Iso[String, IrwinThresholds](
str =>
private val defaultThresholds = IrwinThresholds(88, 95)
val thresholdsIso = lila.common.Iso
.ints(",")
.map[IrwinThresholds](
{
str.split(',').map(_.trim) match {
case Array(rs, ms) =>
for {
report <- rs.toIntOption
mark <- ms.toIntOption
} yield IrwinThresholds(report, mark)
case _ => none
}
} | defaultThresholds,
t => s"${t.report}, ${t.mark}"
)
case Ints(List(r, m)) => IrwinThresholds(r, m)
case _ => defaultThresholds
},
t => Ints(List(t.report, t.mark))
)
implicit val thresholdsBsonHandler = lila.db.dsl.isoHandler(thresholdsIso)
implicit val thresholdsStringReader = StringReader.fromIso(thresholdsIso)

View File

@ -79,6 +79,11 @@ object SettingStore {
implicit val userIdsBsonHandler = lila.db.dsl.isoHandler(userIdsIso)
implicit val userIdsReader = StringReader.fromIso(userIdsIso)
}
object Ints {
val intsIso = lila.common.Iso.ints(",")
implicit val intsBsonHandler = lila.db.dsl.isoHandler(intsIso)
implicit val intsReader = StringReader.fromIso(intsIso)
}
object Regex {
val regexIso = lila.common.Iso.string[Regex](_.r, _.toString)
implicit val regexBsonHandler = lila.db.dsl.isoHandler(regexIso)

View File

@ -15,8 +15,6 @@ private class ReportConfig(
@ConfigName("actor.name") val actorName: String
)
private case class Thresholds(score: () => Int, slack: () => Int)
@Module
final class Env(
appConfig: Configuration,
@ -43,20 +41,12 @@ final class Env(
private lazy val reportColl = db(config.reportColl)
lazy val scoreThresholdSetting = settingStore[Int](
"reportScoreThreshold",
default = config.scoreThreshold,
text = "Report score threshold. Reports with lower scores are concealed to moderators".some
)
lazy val scoreThresholdsSetting = ReportThresholds makeScoreSetting settingStore
lazy val slackScoreThresholdSetting = settingStore[Int](
"slackScoreThreshold",
default = 80,
text = "Slack score threshold. Comm reports with higher scores are notified in slack".some
)
lazy val slackScoreThresholdSetting = ReportThresholds makeSlackSetting settingStore
private val thresholds = Thresholds(
score = scoreThresholdSetting.get _,
score = scoreThresholdsSetting.get _,
slack = slackScoreThresholdSetting.get _
)
@ -74,8 +64,8 @@ final class Env(
def receive = {
case lila.hub.actorApi.report.Cheater(userId, text) =>
api.autoCheatReport(userId, text).unit
case lila.hub.actorApi.report.Shutup(userId, text, major) =>
api.autoInsultReport(userId, text, major).unit
case lila.hub.actorApi.report.Shutup(userId, text) =>
api.autoInsultReport(userId, text).unit
case lila.hub.actorApi.report.Booster(winnerId, loserId) =>
api.autoBoostReport(winnerId, loserId).unit
}

View File

@ -120,7 +120,7 @@ object Report {
def urgency: Int =
report.score.value.toInt +
(isOnline ?? 1000) +
(report.closed ?? Int.MinValue)
(report.closed ?? -999999)
}
case class ByAndAbout(by: List[Report], about: List[Report]) {

View File

@ -1,14 +1,14 @@
package lila.report
import com.softwaremill.macwire._
import org.joda.time.DateTime
import reactivemongo.api.ReadPreference
import scala.concurrent.duration._
import com.softwaremill.macwire._
import lila.common.Bus
import lila.db.dsl._
import lila.memo.CacheApi._
import lila.user.{ User, UserRepo }
import org.joda.time.DateTime
import reactivemongo.api.ReadPreference
final class ReportApi(
val coll: Coll,
@ -276,7 +276,7 @@ final class ReportApi(
)
.void
def autoInsultReport(userId: String, text: String, major: Boolean): Funit =
def autoInsultReport(userId: String, text: String): Funit =
getSuspect(userId) zip getLichessReporter flatMap {
case (Some(suspect), reporter) =>
create(
@ -286,7 +286,7 @@ final class ReportApi(
reason = Reason.Comm,
text = text
),
score => if (major) Report.Score(score.value atLeast thresholds.score()) else score
score => score
)
case _ => funit
}
@ -299,39 +299,46 @@ final class ReportApi(
)
.void
private val closedSelect: Bdoc = $doc("open" -> false)
private def scoreThresholdSelect = $doc("score" $gte thresholds.score())
private val sortLastAtomAt = $doc("atoms.0.at" -> -1)
private val closedSelect: Bdoc = $doc("open" -> false)
private val sortLastAtomAt = $doc("atoms.0.at" -> -1)
private def roomSelect(room: Option[Room]): Bdoc =
room.fold($doc("room" $ne Room.Xfiles.key)) { r =>
room.fold($doc("room" $in Room.allButXfiles)) { r =>
$doc("room" -> r)
}
private def selectOpenInRoom(room: Option[Room]) =
$doc("open" -> true) ++ roomSelect(room) ++ scoreThresholdSelect
$doc("open" -> true) ++ roomSelect(room)
private def selectOpenAvailableInRoom(room: Option[Room]) =
selectOpenInRoom(room) ++ $doc("inquiry" $exists false)
private val maxScoreCache = cacheApi.unit[Int] {
private val maxScoreCache = cacheApi.unit[Room.Scores] {
_.refreshAfterWrite(5 minutes)
.buildAsyncFuture { _ =>
coll // hits the best_open partial index
.primitiveOne[Int](
$doc(
"open" -> true,
"room" $in List(Room.Cheat, Room.Print, Room.Comm, Room.Other).flatMap(RoomBSONHandler.writeOpt)
),
$sort desc "score",
"score"
)
.dmap(~_)
.addEffect(lila.mon.mod.report.unprocessed.update(_).unit)
Room.all
.map { room =>
coll // hits the best_open partial index
.primitiveOne[Float](
selectOpenAvailableInRoom(room.some),
$sort desc "score",
"score"
)
.dmap(room -> _)
}
.sequenceFu
.dmap { scores =>
Room.Scores(scores.map { case (room, s) =>
room -> s.??(_.toInt)
}.toMap)
}
.addEffect { scores =>
lila.mon.mod.report.highest.update(scores.highest).unit
}
}
}
def maxScore = maxScoreCache.getUnit
def maxScores = maxScoreCache.getUnit
def recent(
suspect: Suspect,
@ -407,7 +414,7 @@ final class ReportApi(
findBest(1, selectOpenAvailableInRoom(room.some)).map(_.headOption)
private def addSuspectsAndNotes(reports: List[Report]): Fu[List[Report.WithSuspect]] =
userRepo byIdsSecondary (reports.map(_.user).distinct) map { users =>
userRepo byIdsSecondary reports.map(_.user).distinct map { users =>
reports
.flatMap { r =>
users.find(_.id == r.user) map { u =>
@ -462,22 +469,6 @@ final class ReportApi(
.void
}
def countOpenByRooms: Fu[Room.Counts] =
coll
.aggregateList(maxDocs = 100) { framework =>
import framework._
Match(selectOpenAvailableInRoom(none)) -> List(
GroupField("room")("nb" -> SumAll)
)
}
.map { docs =>
Room.Counts(docs.flatMap { doc =>
doc.string("_id") flatMap Room.apply flatMap { room =>
doc.int("nb") map { room -> _ }
}
}.toMap)
}
private def findRecent(nb: Int, selector: Bdoc): Fu[List[Report]] =
(nb > 0) ?? coll.find(selector).sort(sortLastAtomAt).cursor[Report]().list(nb)

View File

@ -0,0 +1,45 @@
package lila.report
import play.api.data.Form
import play.api.data.Forms.{ single, text }
import lila.common.Ints
import lila.memo.SettingStore.{ Formable, StringReader }
case class ScoreThresholds(mid: Int, high: Int)
private case class Thresholds(score: () => ScoreThresholds, slack: () => Int)
private object ReportThresholds {
private val defaultScoreThresholds = ScoreThresholds(40, 50)
val thresholdsIso = lila.common.Iso
.ints(",")
.map[ScoreThresholds](
{
case Ints(List(m, h)) => ScoreThresholds(m, h)
case _ => defaultScoreThresholds
},
t => Ints(List(t.mid, t.high))
)
implicit val scoreThresholdsBsonHandler = lila.db.dsl.isoHandler(thresholdsIso)
implicit val scoreThresholdsStringReader = StringReader.fromIso(thresholdsIso)
implicit val scoreThresholdsFormable =
new Formable[ScoreThresholds](t => Form(single("v" -> text)) fill thresholdsIso.to(t))
def makeScoreSetting(store: lila.memo.SettingStore.Builder) =
store[ScoreThresholds](
"reportScoreThresholds",
default = defaultScoreThresholds,
text = "Report score mid and high thresholds, separated with a comma.".some
)
def makeSlackSetting(store: lila.memo.SettingStore.Builder) =
store[Int](
"slackScoreThreshold",
default = 80,
text = "Slack score threshold. Comm reports with higher scores are notified in slack".some
)
}

View File

@ -22,6 +22,8 @@ object Room {
(v.key, v)
} toMap
val allButXfiles: List[Room] = all.filter(Xfiles !=)
implicit val roomIso = lila.common.Iso[String, Room](k => byKey.getOrElse(k, Other), _.key)
def apply(key: String): Option[Room] = byKey get key
@ -43,8 +45,8 @@ object Room {
case Xfiles => Set.empty
}
case class Counts(value: Map[Room, Int]) {
def get = value.get _
lazy val sum = value.view.filterKeys(Xfiles !=).values.sum
case class Scores(value: Map[Room, Int]) {
def get = value.get _
def highest = ~value.values.maxOption
}
}

View File

@ -46,8 +46,7 @@ final class ShutupApi(
text: String,
textType: TextType,
source: Option[PublicSource] = None,
toUserId: Option[User.ID] = None,
major: Boolean = false
toUserId: Option[User.ID] = None
): Funit =
userRepo isTroll userId flatMap {
case true => funit
@ -79,29 +78,28 @@ final class ShutupApi(
)
.flatMap {
case None => fufail(s"can't find user record for $userId")
case Some(userRecord) => legiferate(userRecord, major)
case Some(userRecord) => legiferate(userRecord)
}
.recover(lila.db.ignoreDuplicateKey)
}
}
private def legiferate(userRecord: UserRecord, major: Boolean): Funit = {
major || userRecord.reports.exists(_.unacceptable)
} ?? {
reporter ! lila.hub.actorApi.report.Shutup(userRecord.userId, reportText(userRecord), major)
coll.update
.one(
$id(userRecord.userId),
$unset(
TextType.PublicForumMessage.key,
TextType.TeamForumMessage.key,
TextType.PrivateMessage.key,
TextType.PrivateChat.key,
TextType.PublicChat.key
private def legiferate(userRecord: UserRecord): Funit =
userRecord.reports.exists(_.unacceptable) ?? {
reporter ! lila.hub.actorApi.report.Shutup(userRecord.userId, reportText(userRecord))
coll.update
.one(
$id(userRecord.userId),
$unset(
TextType.PublicForumMessage.key,
TextType.TeamForumMessage.key,
TextType.PrivateMessage.key,
TextType.PrivateChat.key,
TextType.PublicChat.key
)
)
)
.void
}
.void
}
private def reportText(userRecord: UserRecord) =
userRecord.reports

View File

@ -58,6 +58,7 @@ $c-report-high-over: white;
font-size: 1.2em;
padding: .3em .5em;
border-radius: .3em;
margin-right: .5em;
}
.score.green {