show team tournaments on /tournament

db.tournament2.dropIndex('startsAt_1')
db.tournament2.dropIndex('createdAt_1')
pull/6748/head
Thibault Duplessis 2020-06-01 11:45:34 -06:00
parent be19303557
commit 1aec43b930
11 changed files with 109 additions and 135 deletions

View File

@ -20,7 +20,7 @@ final class KeyPages(env: Env)(implicit ec: scala.concurrent.ExecutionContext) {
env
.preloader(
posts = env.forum.recent(ctx.me, env.team.cached.teamIdsList).nevermind,
tours = env.tournament.cached.promotable.getUnit.nevermind,
tours = env.tournament.cached.onHomepage.getUnit.nevermind,
events = env.event.api.promoteTo(ctx.req).nevermind,
simuls = env.simul.allCreatedFeaturable.get {}.nevermind
)

View File

@ -32,7 +32,7 @@ final class Tournament(
.buildAsyncFuture { _ =>
for {
visible <- api.fetchVisibleTournaments
scheduled <- repo.scheduledDedup
scheduled <- repo.allScheduledDedup
} yield (visible, scheduled)
}
}
@ -44,7 +44,9 @@ final class Tournament(
(visible, scheduled) <- upcomingCache.getUnit
finished <- api.notableFinished
winners <- env.tournament.winners.all
scheduleJson <- env.tournament apiJsonView visible
teamIds <- ctx.userId.??(env.team.cached.teamIdsList)
teamVisible <- repo.visibleForTeams(teamIds, 5 * 60)
scheduleJson <- env.tournament.apiJsonView(visible add teamVisible)
} yield NoCache {
pageHit
Ok(html.tournament.home(scheduled, finished, winners, scheduleJson))
@ -383,7 +385,7 @@ final class Tournament(
negotiate(
html = notFound,
api = _ =>
env.tournament.cached.promotable.getUnit.nevermind map {
env.tournament.cached.onHomepage.getUnit.nevermind map {
lila.tournament.Spotlight.select(_, ctx.me, 4)
} flatMap env.tournament.apiJsonView.featured map { Ok(_) }
)

View File

@ -0,0 +1,26 @@
const colls = ['tournament2'];
colls.forEach(function(coll) {
indexes = db[coll].getIndexes();
if(indexes && indexes.length>0) {
print("//Indexes for " + coll + ":");
}
indexes.forEach(function(index) {
var options = {};
if(index.unique) {
options.unique = index.unique;
}
if(index.sparse) {
options.sparse = index.sparse;
}
if(index.partialFilterExpression) {
options.partialFilterExpression = index.partialFilterExpression;
}
// options.background = true;
options = JSON.stringify(options);
var key = JSON.stringify(index.key);
if(key !== '{"_id":1}') {
print('db.' + coll + '.createIndex(' + key + ', ' + options + ');');
}
});
});

View File

@ -13,19 +13,15 @@ final class ApiJsonView(lightUserApi: LightUserApi)(implicit ec: scala.concurren
def apply(tournaments: VisibleTournaments)(implicit lang: Lang): Fu[JsObject] =
for {
created <- tournaments.created.collect(visibleJson).sequenceFu
started <- tournaments.started.collect(visibleJson).sequenceFu
finished <- tournaments.finished.collect(visibleJson).sequenceFu
created <- tournaments.created.map(fullJson).sequenceFu
started <- tournaments.started.map(fullJson).sequenceFu
finished <- tournaments.finished.map(fullJson).sequenceFu
} yield Json.obj(
"created" -> created,
"started" -> started,
"finished" -> finished
)
private def visibleJson(implicit lang: Lang): PartialFunction[Tournament, Fu[JsObject]] = {
case tour if tour.teamBattle.fold(true)(_.hasEnoughTeams) => fullJson(tour)
}
def featured(tournaments: List[Tournament])(implicit lang: Lang): Fu[JsObject] =
tournaments.map(fullJson).sequenceFu map { objs =>
Json.obj("featured" -> objs)
@ -64,17 +60,12 @@ final class ApiJsonView(lightUserApi: LightUserApi)(implicit ec: scala.concurren
.add("private", tour.isPrivate)
.add("position", tour.position.some.filterNot(_.initial) map positionJson)
.add("schedule", tour.schedule map scheduleJson)
.add("battle", tour.teamBattle.map(_ => Json.obj()))
def fullJson(tour: Tournament)(implicit lang: Lang): Fu[JsObject] =
for {
owner <- tour.nonLichessCreatedBy ?? lightUserApi.async
winner <- tour.winnerId ?? lightUserApi.async
} yield baseJson(tour) ++ Json
.obj(
"winner" -> winner.map(userJson)
)
.add("major", owner.exists(_.title.isDefined))
} yield baseJson(tour) ++ Json.obj("winner" -> winner.map(userJson))
private def userJson(u: lila.common.LightUser) =
Json.obj(

View File

@ -29,9 +29,9 @@ final private[tournament] class Cached(
expireAfter = Syncache.ExpireAfterAccess(20 minutes)
)
val promotable = cacheApi.unit[List[Tournament]] {
val onHomepage = cacheApi.unit[List[Tournament]] {
_.refreshAfterWrite(2 seconds)
.buildAsyncFuture(_ => tournamentRepo.promotable)
.buildAsyncFuture(_ => tournamentRepo.onHomepage)
}
def ranking(tour: Tournament): Fu[Ranking] =

View File

@ -603,35 +603,22 @@ final class TournamentApi(
def notableFinished = cached.notableFinishedCache.get {}
private def scheduledCreatedAndStarted =
tournamentRepo.scheduledCreated(6 * 60) zip tournamentRepo.scheduledStarted
// when loading /tournament
def fetchVisibleTournaments: Fu[VisibleTournaments] =
tournamentRepo.publicCreatedSorted(6 * 60).flatMap(filterMajor) zip
tournamentRepo.publicStarted.flatMap(filterMajor) zip
notableFinished map {
scheduledCreatedAndStarted zip notableFinished map {
case ((created, started), finished) =>
VisibleTournaments(created, started, finished)
}
// when updating /tournament
def fetchUpdateTournaments: Fu[VisibleTournaments] =
tournamentRepo.scheduledCreatedSorted(6 * 60) zip
tournamentRepo.scheduledStarted map {
case (created, started) =>
VisibleTournaments(created, started, Nil)
scheduledCreatedAndStarted dmap {
case (created, started) => VisibleTournaments(created, started, Nil)
}
private def filterMajor(tours: List[Tournament]): Fu[List[Tournament]] =
tours
.map { t =>
(fuccess(t.isScheduled) >>| lightUserApi
.async(t.createdBy)
.dmap(_.exists(_.title.isDefined))) dmap {
_ option t
}
}
.sequenceFu
.dmap(_.flatten)
def playerInfo(tour: Tournament, userId: User.ID): Fu[Option[PlayerInfoExt]] =
userRepo named userId flatMap {
_ ?? { user =>
@ -646,13 +633,11 @@ final class TournamentApi(
}
def allCurrentLeadersInStandard: Fu[Map[Tournament, TournamentTop]] =
tournamentRepo.standardPublicStartedFromSecondary.flatMap { tours =>
tours
.map { tour =>
tournamentTop(tour.id) map (tour -> _)
}
.sequenceFu
.map(_.toMap)
tournamentRepo.standardPublicStartedFromSecondary.flatMap {
_.map { tour =>
tournamentTop(tour.id) dmap (tour -> _)
}.sequenceFu
.dmap(_.toMap)
}
def calendar: Fu[List[Tournament]] = {
@ -719,7 +704,7 @@ final class TournamentApi(
"sendToFlag"
)
}
tournamentRepo.promotable foreach { tours =>
cached.onHomepage.get {} foreach { tours =>
renderer.actor ? Tournament.TournamentTable(tours) map {
case view: String => Bus.publish(ReloadTournaments(view), "lobbySocket")
}

View File

@ -17,13 +17,15 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(implicit
ec: scala.concurrent.ExecutionContext
) {
private val enterableSelect = $doc("status" $lt Status.Finished.id)
private val createdSelect = $doc("status" -> Status.Created.id)
private val startedSelect = $doc("status" -> Status.Started.id)
private[tournament] val finishedSelect = $doc("status" -> Status.Finished.id)
private val unfinishedSelect = $doc("status" $ne Status.Finished.id)
private[tournament] val scheduledSelect = $doc("schedule" $exists true)
private def sinceSelect(date: DateTime) = $doc("startsAt" $gt date)
private val enterableSelect = $doc("status" $lt Status.Finished.id)
private val createdSelect = $doc("status" -> Status.Created.id)
private val startedSelect = $doc("status" -> Status.Started.id)
private[tournament] val finishedSelect = $doc("status" -> Status.Finished.id)
private val unfinishedSelect = $doc("status" $ne Status.Finished.id)
private[tournament] val scheduledSelect = $doc("schedule" $exists true)
private def forTeamSelect(id: TeamID) = $doc("forTeams" -> id)
private def forTeamsSelect(ids: Seq[TeamID]) = $doc("forTeams" $in ids)
private def sinceSelect(date: DateTime) = $doc("startsAt" $gt date)
private def variantSelect(variant: Variant) =
if (variant.standard) $doc("variant" $exists false)
else $doc("variant" -> variant.id)
@ -72,12 +74,6 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(implicit
def startedIds: Fu[List[Tournament.ID]] =
coll.primitive[Tournament.ID](startedSelect, sort = $doc("createdAt" -> -1), "_id")
def publicStarted: Fu[List[Tournament]] =
coll.ext
.find(startedSelect ++ $doc("password" $exists false))
.sort($doc("createdAt" -> -1))
.list[Tournament]()
def standardPublicStartedFromSecondary: Fu[List[Tournament]] =
coll.ext
.find(
@ -111,13 +107,13 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(implicit
private[tournament] def upcomingByTeam(teamId: TeamID, nb: Int) =
(nb > 0) ?? coll.ext
.find($doc("forTeams" -> teamId) ++ enterableSelect)
.find(forTeamSelect(teamId) ++ enterableSelect)
.sort($sort asc "startsAt")
.list[Tournament](nb)
private[tournament] def finishedByTeam(teamId: TeamID, nb: Int) =
(nb > 0) ?? coll.ext
.find($doc("forTeams" -> teamId) ++ finishedSelect)
.find(forTeamSelect(teamId) ++ finishedSelect)
.sort($sort desc "startsAt")
.list[Tournament](nb)
@ -131,7 +127,7 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(implicit
coll
.aggregateList(Int.MaxValue, readPreference = ReadPreference.secondaryPreferred) { implicit framework =>
import framework._
Match(enterableSelect ++ nonEmptySelect ++ teamId.?? { t => $doc("forTeams" -> t) }) -> List(
Match(enterableSelect ++ nonEmptySelect ++ teamId.??(forTeamSelect)) -> List(
PipelineOperator(
$doc(
"$lookup" -> $doc(
@ -183,24 +179,26 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(implicit
createdSelect ++
$doc("startsAt" $lt (DateTime.now plusMinutes aheadMinutes))
def publicCreatedSorted(aheadMinutes: Int): Fu[List[Tournament]] =
coll.ext
.find(startingSoonSelect(aheadMinutes) ++ $doc("password" $exists false))
.sort($doc("startsAt" -> 1))
.list[Tournament](none)
def scheduledCreatedSorted(aheadMinutes: Int): Fu[List[Tournament]] =
def scheduledCreated(aheadMinutes: Int): Fu[List[Tournament]] =
coll.ext
.find(startingSoonSelect(aheadMinutes) ++ scheduledSelect)
.sort($doc("startsAt" -> 1))
.list[Tournament](none)
.list[Tournament]()
def scheduledStarted: Fu[List[Tournament]] =
coll.ext
.find(startedSelect ++ scheduledSelect)
.sort($doc("startsAt" -> -1))
.list[Tournament]()
def visibleForTeams(teamIds: Seq[TeamID], aheadMinutes: Int) =
coll.ext
.find(startingSoonSelect(aheadMinutes) ++ forTeamsSelect(teamIds))
.list[Tournament](none, ReadPreference.secondaryPreferred) zip
coll.ext
.find(startedSelect ++ forTeamsSelect(teamIds))
.list[Tournament](none, ReadPreference.secondaryPreferred) dmap {
case (created, started) => created ::: started
}
private[tournament] def shouldStartCursor =
coll.ext
.find($doc("startsAt" $lt DateTime.now) ++ createdSelect)
@ -209,24 +207,13 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(implicit
private def scheduledStillWorthEntering: Fu[List[Tournament]] =
coll.ext
.find(
startedSelect ++ scheduledSelect
)
.sort($doc("startsAt" -> 1))
.list[Tournament]() map {
.find(startedSelect ++ scheduledSelect)
.list[Tournament]() dmap {
_.filter(_.isStillWorthEntering)
}
private def scheduledCreatedSorted(aheadMinutes: Int): Fu[List[Tournament]] =
coll.ext
.find(
startingSoonSelect(aheadMinutes) ++ scheduledSelect
)
.sort($doc("startsAt" -> 1))
.list[Tournament]()
private def isPromotable(tour: Tournament): Boolean =
tour.schedule ?? { schedule =>
private def canShowOnHomepage(tour: Tournament): Boolean =
tour.schedule exists { schedule =>
tour.startsAt isBefore DateTime.now.plusMinutes {
import Schedule.Freq._
val base = schedule.freq match {
@ -241,12 +228,13 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(implicit
}
}
private[tournament] def promotable: Fu[List[Tournament]] =
scheduledStillWorthEntering zip scheduledCreatedSorted(crud.CrudForm.maxHomepageHours * 60) map {
private[tournament] def onHomepage: Fu[List[Tournament]] =
scheduledStillWorthEntering zip scheduledCreated(crud.CrudForm.maxHomepageHours * 60) map {
case (started, created) =>
(started ::: created)
.sortBy(_.startsAt.getSeconds)
.foldLeft(List.empty[Tournament]) {
case (acc, tour) if !isPromotable(tour) => acc
case (acc, tour) if !canShowOnHomepage(tour) => acc
case (acc, tour) if acc.exists(_ similarTo tour) => acc
case (acc, tour) => tour :: acc
}
@ -266,26 +254,22 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(implicit
.sort($doc("startsAt" -> 1))
.list[Tournament]()
def scheduledCreated: Fu[List[Tournament]] =
def allScheduledDedup: Fu[List[Tournament]] =
coll.ext
.find(createdSelect ++ scheduledSelect)
.sort($doc("startsAt" -> 1))
.list[Tournament]()
def scheduledDedup: Fu[List[Tournament]] =
scheduledCreated map {
import Schedule.Freq
.list[Tournament]() map {
_.flatMap { tour =>
tour.schedule map (tour -> _)
}.foldLeft(List[Tournament]() -> none[Freq]) {
}.foldLeft(List.empty[Tournament] -> none[Schedule.Freq]) {
case ((tours, skip), (_, sched)) if skip.contains(sched.freq) => (tours, skip)
case ((tours, skip), (tour, sched)) =>
(
tour :: tours,
sched.freq match {
case Freq.Daily => Freq.Eastern.some
case Freq.Eastern => Freq.Daily.some
case _ => skip
case Schedule.Freq.Daily => Schedule.Freq.Eastern.some
case Schedule.Freq.Eastern => Schedule.Freq.Daily.some
case _ => skip
}
)
}

View File

@ -40,6 +40,12 @@ case class VisibleTournaments(
def unfinished = created ::: started
def all = started ::: created ::: finished
def add(tours: List[Tournament]) =
copy(
created = tours.filter(_.isCreated) ++ created,
started = tours.filter(_.isStarted) ++ started
)
}
case class PlayerInfoExt(

View File

@ -9,7 +9,6 @@ $c-tour-weekend: $c-brag;
$c-tour-marathon: #66558C;
$c-tour-unique: $c-brag;
$c-tour-max-rating: #8572ff;
$c-tour-battle: hsl(49, 74%, 37%);
.tour-chart {
min-height: 400px;
@ -35,10 +34,7 @@ $c-tour-battle: hsl(49, 74%, 37%);
}
.tournamentline {
position: relative;
height: 2.5em;
&.large {
height: 4em;
}
height: 4em;
}
.timeheader {
position: absolute;
@ -119,9 +115,6 @@ $c-tour-battle: hsl(49, 74%, 37%);
&-max-rating {
background-color: $c-tour-max-rating;
}
&-battle {
background-color: $c-tour-battle;
}
&-short.tsht-thematic {
letter-spacing: -1px;
}
@ -129,9 +122,9 @@ $c-tour-battle: hsl(49, 74%, 37%);
margin-right: 4px;
}
.icon {
font-size: 1.3em;
font-size: 2.4em;
line-height: 1;
margin: -4px 2px -1px 4px;
margin: 0 .2em 0 .2em;
&::before {
vertical-align: middle;
}
@ -140,9 +133,9 @@ $c-tour-battle: hsl(49, 74%, 37%);
@extend %nowrap-ellipsis;
}
.name {
display: inline-block;
display: flex;
max-width: none;
vertical-align: top;
max-width: 66%;
}
.body {
flex: 1 0;
@ -158,16 +151,4 @@ $c-tour-battle: hsl(49, 74%, 37%);
.infos .nb-players {
flex: 0 0 auto;
}
.large & .icon {
font-size: 2.4em;
margin: 0 .2em 0 .2em;
}
.large & .name {
display: block;
max-width: none;
}
.large & .name {
display: flex;
}
}

View File

@ -27,8 +27,16 @@ export function app(element: HTMLElement, env: any) {
return {
update: d => {
env.data = d;
env.data = {
created: update(env.data.created, d.created),
started: update(env.data.started, d.started),
finished: update(env.data.finished, d.finished)
},
redraw();
}
};
};
function update(prevs, news) {
return news.concat(prevs.filter(p => !p.schedule));
}

View File

@ -105,9 +105,7 @@ function tournamentClass(tour) {
'tsht-finished': finished,
'tsht-joinable': !finished,
'tsht-user-created': userCreated,
'tsht-major': tour.major,
'tsht-thematic': !!tour.position,
'tsht-battle': !!tour.battle,
'tsht-short': tour.minutes <= 30,
'tsht-max-rating': !userCreated && tour.hasMaxRating
};
@ -200,8 +198,6 @@ export default function(ctrl) {
const data = ctrl.data();
const systemTours: any[] = [],
majorTours: any[] = [],
teamBattles: any[] = [],
userTours: any[] = [];
data.finished
@ -210,16 +206,12 @@ export default function(ctrl) {
.filter(t => t.finishesAt > startTime)
.forEach(t => {
if (isSystemTournament(t)) systemTours.push(t);
else if (t.major) majorTours.push(t);
else if (t.battle) teamBattles.push(t);
else userTours.push(t);
});
// group system tournaments into dedicated lanes for PerfType
const tourLanes = splitOverlaping(
group(systemTours, laneGrouper)
.concat([majorTours])
.concat([teamBattles])
.concat([userTours])
).filter(lane => lane.length > 0);
@ -246,8 +238,7 @@ export default function(ctrl) {
}, [
renderTimeline(),
...tourLanes.map(lane => {
const large = lane.find(t => isSystemTournament(t) || t.major || t.battle);
return h('div.tournamentline' + (large ? '.large' : ''), lane.map(tour =>
return h('div.tournamentline', lane.map(tour =>
renderTournament(ctrl, tour)))
})
])