Skip to content

Commit

Permalink
Merge pull request #15532 from lichess-org/broadcast-top-ongoing
Browse files Browse the repository at this point in the history
Broadcast top ongoing
  • Loading branch information
ornicar authored Jun 16, 2024
2 parents 2afdddc + b02802b commit 185c2f0
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 42 deletions.
2 changes: 1 addition & 1 deletion modules/memo/src/main/SettingStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ object SettingStore:
given StringReader[Strings] = StringReader.fromIso(using stringsIso)
object UserIds:
val userIdsIso = Strings.stringsIso.map[lila.core.data.UserIds](
strs => lila.core.data.UserIds(UserId.from(strs.value)),
strs => lila.core.data.UserIds(UserStr.from(strs.value).map(_.id)),
uids => lila.core.data.Strings(UserId.raw(uids.value))
)
given BSONHandler[UserIds] = lila.db.dsl.isoHandler(using userIdsIso)
Expand Down
19 changes: 10 additions & 9 deletions modules/relay/src/main/RelayApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,12 @@ final class RelayApi(
def withRounds(tour: RelayTour) = roundRepo.byTourOrdered(tour).dmap(tour.withRounds)

def denormalizeTourActive(tourId: RelayTourId): Funit =
roundRepo.coll.exists(RelayRoundRepo.selectors.tour(tourId) ++ $doc("finished" -> false)).flatMap {
tourRepo.setActive(tourId, _)
}
val unfinished = RelayRoundRepo.selectors.tour(tourId) ++ $doc("finished" -> false)
for
active <- roundRepo.coll.exists(unfinished)
live <- active.so(roundRepo.coll.exists(unfinished ++ $doc("startedAt".$exists(true))))
_ <- tourRepo.setActive(tourId, active, live)
yield ()

object countOwnedByUser:
private val cache = cacheApi[UserId, Int](16_384, "relay.nb.owned"):
Expand Down Expand Up @@ -191,10 +194,9 @@ final class RelayApi(
def create(data: RelayRoundForm.Data, tour: RelayTour)(using me: Me): Fu[RelayRound.WithTourAndStudy] =
roundRepo
.lastByTour(tour)
.flatMapz { last =>
.flatMapz: last =>
studyRepo.byId(last.studyId)
}
.flatMap { lastStudy =>
.flatMap: lastStudy =>
import lila.study.{ StudyMember, StudyMembers }
val relay = data.make(me, tour)
for
Expand Down Expand Up @@ -223,10 +225,9 @@ final class RelayApi(
)
.orFail(s"Can't create study for relay $relay")
_ <- roundRepo.coll.insert.one(relay)
_ <- tourRepo.setActive(tour.id, true)
_ <- tourRepo.setActive(tour.id, true, relay.hasStarted)
_ <- studyApi.addTopics(relay.studyId, List(StudyTopic.broadcast.value))
yield relay.withTour(tour).withStudy(study.study)
}

def requestPlay(id: RelayRoundId, v: Boolean): Funit =
WithRelay(id): relay =>
Expand All @@ -251,7 +252,7 @@ final class RelayApi(
_ <- roundRepo.coll.update.one($id(round.id), round).void
_ <- (round.sync.playing != from.sync.playing)
.so(sendToContributors(round.id, "relaySync", jsonView.sync(round)))
_ <- (round.finished != from.finished).so(denormalizeTourActive(round.tourId))
_ <- (round.stateHash != from.stateHash).so(denormalizeTourActive(round.tourId))
yield
round.sync.log.events.lastOption
.ifTrue(round.sync.log != from.sync.log)
Expand Down
115 changes: 99 additions & 16 deletions modules/relay/src/main/RelayListing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,92 @@ final class RelayListing(
for
upcoming <- upcoming.get({})
max = 100
tourIds <- colls.tour.distinctEasy[RelayTourId, List](
"_id",
RelayTourRepo.selectors.officialActive ++ $doc("_id".$nin(upcoming.map(_.tour.id)))
)
groupToursDocs <- colls.group.aggregateList(Int.MaxValue): framework =>
import framework.*
Match($doc("tours".$in(tourIds))) -> List(
PipelineOperator(
$lookup.pipelineFull(
from = colls.tour.name,
as = "tours",
let = $doc("tourIds" -> "$tours"),
pipe = List(
$doc("$match" -> $doc("$expr" -> $doc("$in" -> $arr("$_id", "$$tourIds")))),
$doc("$addFields" -> $doc("__order" -> $doc("$indexOfArray" -> $arr("$$tourIds", "$_id")))),
$doc("$sort" -> $doc("tier" -> -1, "__order" -> 1)),
$doc("$project" -> $doc("live" -> true))
)
)
),
Project(
$doc(
"tours" -> $doc(
"$ifNull" -> $arr(
$doc(
"$first" -> $doc(
"$filter" -> $doc(
"input" -> "$tours",
"as" -> "tour",
"cond" -> "$$tour.live",
"limit" -> 1
)
)
),
$doc("$first" -> "$tours")
)
)
)
),
Project($doc("_id" -> true, "tour" -> "$tours._id"))
)
groupTourPairs = for
doc <- groupToursDocs
groupId <- doc.getAsOpt[RelayGroup.Id]("_id")
tour <- doc.getAsOpt[RelayTourId]("tour")
yield s"$groupId$tour"
docs <- colls.tour
.aggregateList(max): framework =>
import framework.*
Match(
RelayTourRepo.selectors.officialActive ++ $doc("_id".$nin(upcoming.map(_.tour.id)))
) -> List(
Match($inIds(tourIds)) -> List(
Sort(Descending("tier")),
PipelineOperator(group.lookup(colls.group)),
Match(group.filter),
Project(
$doc("subscribers" -> false, "notified" -> false, "teams" -> false, "players" -> false)
),
PipelineOperator(
$lookup.pipelineFull(
from = colls.group.name,
as = "group",
let = $doc("tourId" -> "$_id"),
pipe = List(
$doc("$match" -> $doc("$expr" -> $doc("$in" -> $arr("$$tourId", "$tours")))),
$doc(
"$project" -> $doc(
"_id" -> true,
"name" -> true
)
)
)
)
),
AddFields($doc("group" -> $doc("$first" -> "$group"))),
AddFields(
$doc(
"isGroupTour" ->
$doc(
"$let" -> $doc(
"vars" -> $doc(
"allPairs" -> groupTourPairs,
"pair" -> $doc("$concat" -> $arr("$group._id", "$_id"))
),
"in" -> $doc("$in" -> $arr("$$pair", "$$allPairs"))
)
)
)
),
Match($doc($or("group".$exists(false), "isGroupTour".$eq(true)))),
PipelineOperator(roundLookup),
UnwindField("round"),
Limit(max)
Expand All @@ -52,11 +129,11 @@ final class RelayListing(
doc <- docs
tour <- doc.asOpt[RelayTour]
round <- doc.getAsOpt[RelayRound]("round")
group = RelayListing.group.readFrom(doc)
group = RelayListing.group.readFromOne(doc)
yield (tour, round, group)
sorted = tours.sortBy: (tour, round, _) =>
(
!round.startedAt.isDefined, // ongoing tournaments first
!round.hasStarted, // ongoing tournaments first
0 - ~tour.tier, // then by tier
0 - ~round.crowd, // then by viewers
round.startsAt.fold(Long.MaxValue)(_.toMillis) // then by next round date
Expand All @@ -82,8 +159,8 @@ final class RelayListing(
import framework.*
Match(RelayTourRepo.selectors.officialActive) -> List(
Sort(Descending("tier")),
PipelineOperator(group.lookup(colls.group)),
Match(group.filter),
PipelineOperator(group.firstLookup(colls.group)),
Match(group.firstFilter),
PipelineOperator:
$lookup.pipeline(
from = colls.round,
Expand Down Expand Up @@ -143,27 +220,33 @@ final class RelayListing(
private object RelayListing:

object group:

// look at the groups where the tour appears.
// only keep the tour if there is no group,
// or if the tour is the first in the group.
def lookup(groupColl: Coll) = $lookup.pipelineFull(
def firstLookup(groupColl: Coll) = $lookup.pipelineFull(
from = groupColl.name,
as = "group",
let = $doc("id" -> "$_id"),
let = $doc("tourId" -> "$_id"),
pipe = List(
$doc("$match" -> $doc("$expr" -> $doc("$in" -> $arr("$$id", "$tours")))),
$doc("$match" -> $doc("$expr" -> $doc("$in" -> $arr("$$tourId", "$tours")))),
$doc:
"$project" -> $doc(
"_id" -> false,
"name" -> true,
"first" -> $doc("$eq" -> $arr("$$id", $doc("$first" -> "$tours")))
"_id" -> false,
"name" -> true,
"isFirst" -> $doc("$eq" -> $arr("$$tourId", $doc("$first" -> "$tours")))
)
)
)
val filter = $doc("group.0.first".$ne(false))
val firstFilter = $doc("group.0.isFirst".$ne(false))

def readFrom(doc: Bdoc): Option[RelayGroup.Name] = for
garr <- doc.getAsOpt[Barr]("group")
gdoc <- garr.getAsOpt[Bdoc](0)
name <- gdoc.getAsOpt[RelayGroup.Name]("name")
yield name

def readFromOne(doc: Bdoc): Option[RelayGroup.Name] = for
gdoc <- doc.getAsOpt[Bdoc]("group")
name <- gdoc.getAsOpt[RelayGroup.Name]("name")
yield name
4 changes: 2 additions & 2 deletions modules/relay/src/main/RelayPager.scala
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ final class RelayPager(
) =
onlyKeepGroupFirst.so(
List(
framework.PipelineOperator(RelayListing.group.lookup(colls.group)),
framework.Match(RelayListing.group.filter)
framework.PipelineOperator(RelayListing.group.firstLookup(colls.group)),
framework.Match(RelayListing.group.firstFilter)
)
) ::: List(
framework.PipelineOperator(
Expand Down
2 changes: 2 additions & 0 deletions modules/relay/src/main/RelayRound.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ case class RelayRound(
case Some(at) => at.isBefore(nowInstant.minusHours(3))
case None => createdAt.isBefore(nowInstant.minusDays(1))

def stateHash = (hasStarted, finished)

def withSync(f: RelayRound.Sync => RelayRound.Sync) = copy(sync = f(sync))

def withTour(tour: RelayTour) = RelayRound.WithTour(this, tour)
Expand Down
1 change: 1 addition & 0 deletions modules/relay/src/main/RelayTour.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ case class RelayTour(
createdAt: Instant,
tier: Option[RelayTour.Tier], // if present, it's an official broadcast
active: Boolean, // a round is scheduled or ongoing
live: Option[Boolean], // a round is live, i.e. started and not finished
syncedAt: Option[Instant], // last time a round was synced
spotlight: Option[RelayTour.Spotlight] = None,
autoLeaderboard: Boolean = true,
Expand Down
1 change: 1 addition & 0 deletions modules/relay/src/main/RelayTourForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ object RelayTourForm:
ownerId = me,
tier = tier.ifTrue(Granter(_.Relay)),
active = false,
live = none,
createdAt = nowInstant,
syncedAt = none,
autoLeaderboard = autoLeaderboard,
Expand Down
4 changes: 2 additions & 2 deletions modules/relay/src/main/RelayTourRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ final private class RelayTourRepo(val coll: Coll)(using Executor):
def setSyncedNow(tour: RelayTour): Funit =
coll.updateField($id(tour.id), "syncedAt", nowInstant).void

def setActive(tourId: RelayTourId, active: Boolean): Funit =
coll.updateField($id(tourId), "active", active).void
def setActive(tourId: RelayTourId, active: Boolean, live: Boolean): Funit =
coll.update.one($id(tourId), $set("active" -> active, "live" -> live)).void

def lookup(local: String) = $lookup.simple(coll, "tour", local, "_id")

Expand Down
20 changes: 10 additions & 10 deletions modules/relay/src/main/ui/RelayTourUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi):
val selected = active.filter(_.tour.tierIs(selector))
selected.nonEmpty.option(st.section(cls := s"relay-cards relay-cards--tier-$tier"):
selected.map:
card.render(_, ongoing = _.display.hasStarted)
card.render(_, live = _.display.hasStarted)
)
Page(trc.liveBroadcasts.txt())
.css("bits.relay.index")
Expand All @@ -40,7 +40,7 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi):
h2(cls := "relay-index__section")("Upcoming broadcasts"),
st.section(cls := "relay-cards relay-cards--upcoming"):
upcoming.map:
card.render(_, ongoing = _ => false)
card.render(_, live = _ => false)
)
),
h2(cls := "relay-index__section")("Past broadcasts"),
Expand Down Expand Up @@ -146,24 +146,24 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi):
)

private object card:
private def link(t: RelayTour, url: String, ongoing: Boolean) = a(
private def link(t: RelayTour, url: String, live: Boolean) = a(
href := url,
cls := List(
"relay-card" -> true,
"relay-card--active" -> t.active,
"relay-card--ongoing" -> ongoing
"relay-card" -> true,
"relay-card--active" -> t.active,
"relay-card--live" -> live
)
)
private def image(t: RelayTour) = t.image.fold(ui.thumbnail.fallback(cls := "relay-card__image")): id =>
img(cls := "relay-card__image", src := ui.thumbnail.url(id, _.Size.Small))

def render[A <: RelayRound.AndTourAndGroup](tr: A, ongoing: A => Boolean)(using Context) =
link(tr.tour, tr.path, ongoing(tr))(
def render[A <: RelayRound.AndTourAndGroup](tr: A, live: A => Boolean)(using Context) =
link(tr.tour, tr.path, live(tr))(
image(tr.tour),
span(cls := "relay-card__body")(
span(cls := "relay-card__info")(
tr.tour.active.option(span(cls := "relay-card__round")(tr.display.name)),
if ongoing(tr)
if live(tr)
then
span(cls := "relay-card__live")(
"LIVE",
Expand Down Expand Up @@ -204,7 +204,7 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi):
def renderPager(pager: Paginator[RelayTour | WithLastRound])(next: Int => Call)(using Context): Tag =
st.section(cls := "infinite-scroll relay-cards")(
pager.currentPageResults.map:
case w: WithLastRound => card.render(w, ongoing = _ => false)(cls := "paginated")
case w: WithLastRound => card.render(w, live = _ => false)(cls := "paginated")
case t: RelayTour => card.empty(t)(cls := "paginated")
,
pagerNext(pager, next(_).url)
Expand Down
4 changes: 2 additions & 2 deletions ui/bits/css/relay/_card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
0 0 20px $c-link;
}

&--ongoing {
&--live {
outline: 3px solid $m-bad--alpha-50;
&:hover {
box-shadow:
Expand All @@ -42,7 +42,7 @@
width: 100%;
height: auto;
opacity: 0.7;
.relay-card--ongoing & {
.relay-card--live & {
opacity: 0.9;
}
@include transition(opacity);
Expand Down

0 comments on commit 185c2f0

Please sign in to comment.