From c70a4a5534f4b10b5fa6a3e195a78d7b0a2a6d2e Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sat, 29 Jun 2024 15:49:47 +0200 Subject: [PATCH 1/4] denormalized, translated broadcast dates - requires migration --- bin/mongodb/relay-dates-migrate.js | 16 ++++ modules/relay/src/main/BSONHandlers.scala | 1 + modules/relay/src/main/JsonView.scala | 4 + modules/relay/src/main/RelayApi.scala | 34 ++++++-- modules/relay/src/main/RelayTour.scala | 9 +- modules/relay/src/main/RelayTourForm.scala | 1 - modules/relay/src/main/RelayTourRepo.scala | 9 +- modules/relay/src/main/ui/FormUi.scala | 21 ++--- ui/@types/lichess/index.d.ts | 1 + ui/analyse/src/study/relay/interfaces.ts | 4 +- ui/analyse/src/study/relay/relayTourView.ts | 95 ++++++++++++--------- ui/chart/src/chart.ratingHistory.ts | 13 ++- ui/chart/src/chart.relayStats.ts | 19 ++--- ui/site/src/site.ts | 3 +- ui/site/src/timeago.ts | 25 +++--- 15 files changed, 156 insertions(+), 99 deletions(-) create mode 100644 bin/mongodb/relay-dates-migrate.js diff --git a/bin/mongodb/relay-dates-migrate.js b/bin/mongodb/relay-dates-migrate.js new file mode 100644 index 000000000000..150dcf8438f9 --- /dev/null +++ b/bin/mongodb/relay-dates-migrate.js @@ -0,0 +1,16 @@ +// db.relay.aggregate([{$match:{tourId:'KNfeoWE'}},{$project:{name:1,at:{$ifNull:['$startsAt','$startedAt']}}},{$sort:{at:1}},{$group:{_id:null,at:{$push:'$at'}}},{$project:{start:{$first:'$at'},end:{$last:'$at'}}}]) + +const fetchDates = (tourId) => db.relay.aggregate([ + { $match: { tourId } }, + { $project: { name: 1, at: { $ifNull: ['$startsAt', '$startedAt'] } } }, + { $sort: { at: 1 } }, { $group: { _id: null, at: { $push: '$at' } } }, + { $project: { start: { $first: '$at' }, end: { $last: '$at' } } } +]).next(); + +db.relay_tour.find({ dates: { $exists: 0 } }).sort({ $natural: -1 }).forEach(tour => { + const dates = fetchDates(tour._id); + if (dates) { + console.log(tour._id + ' ' + tour.createdAt); + db.relay_tour.updateOne({ _id: tour._id }, { $set: { dates } }); + } +}); diff --git a/modules/relay/src/main/BSONHandlers.scala b/modules/relay/src/main/BSONHandlers.scala index 2bf807c953b2..551ed360a03d 100644 --- a/modules/relay/src/main/BSONHandlers.scala +++ b/modules/relay/src/main/BSONHandlers.scala @@ -42,6 +42,7 @@ object BSONHandlers: given BSONDocumentHandler[RelayTour.Spotlight] = Macros.handler given BSONDocumentHandler[RelayTour.Info] = Macros.handler + given BSONDocumentHandler[RelayTour.Dates] = Macros.handler given tourHandler: BSONDocumentHandler[RelayTour] = Macros.handler given BSONDocumentHandler[RelayTour.IdName] = Macros.handler diff --git a/modules/relay/src/main/JsonView.scala b/modules/relay/src/main/JsonView.scala index deda83dcc625..a47a2268d186 100644 --- a/modules/relay/src/main/JsonView.scala +++ b/modules/relay/src/main/JsonView.scala @@ -26,6 +26,9 @@ final class JsonView( given OWrites[RelayTour.Info] = Json.writes + given Writes[RelayTour.Dates] = Writes: ds => + JsArray(List(ds.start.some, ds.end).flatten.map(Json.toJson)) + given OWrites[RelayTour] = OWrites: t => Json .obj( @@ -37,6 +40,7 @@ final class JsonView( "url" -> s"$baseUrl${t.path}" ) .add("tier" -> t.tier) + .add("dates" -> t.dates) .add("image" -> t.image.map(id => RelayTour.thumbnail(picfitUrl, id, _.Size.Large))) given OWrites[RelayTour.IdName] = Json.writes diff --git a/modules/relay/src/main/RelayApi.scala b/modules/relay/src/main/RelayApi.scala index fac12205671d..6969dd9821ff 100644 --- a/modules/relay/src/main/RelayApi.scala +++ b/modules/relay/src/main/RelayApi.scala @@ -90,14 +90,33 @@ final class RelayApi( def withRounds(tour: RelayTour) = roundRepo.byTourOrdered(tour.id).dmap(tour.withRounds) - def denormalizeTourActive(tourId: RelayTourId): Funit = + def denormalizeTour(tourId: RelayTourId): Funit = 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) + dates <- computeDates(tourId) + _ <- tourRepo.denormalize(tourId, active, live, dates) yield () + private def computeDates(tourId: RelayTourId): Fu[Option[RelayTour.Dates]] = + roundRepo.coll + .aggregateOne(): framework => + import framework.* + Match($doc("tourId" -> tourId)) -> List( + Project($doc("at" -> $doc("ifNull" -> $arr("$startsAt", "$startedAt")))), + Sort(Ascending("at")), + Group(BSONNull)("at" -> PushField("at")), + Project($doc("start" -> $doc("$first" -> "$at"), "end" -> $doc("$first" -> "$at"))) + ) + .map: + _.flatMap: doc => + for + start <- doc.getAsOpt[Instant]("start") + end <- doc.getAsOpt[Instant]("end") + endMaybe = Option.when(end != start)(end) + yield RelayTour.Dates(start, endMaybe) + object countOwnedByUser: private val cache = cacheApi[UserId, Int](16_384, "relay.nb.owned"): _.expireAfterWrite(5.minutes).buildAsyncFuture(tourRepo.countByOwner(_, false)) @@ -239,9 +258,10 @@ final class RelayApi( ) ) .orFail(s"Can't create study for relay $relay") - _ <- roundRepo.coll.insert.one(relay) - _ <- tourRepo.setActive(tour.id, true, relay.hasStarted) - _ <- studyApi.addTopics(relay.studyId, List(StudyTopic.broadcast.value)) + _ <- roundRepo.coll.insert.one(relay) + dates <- computeDates(tour.id) + _ <- tourRepo.denormalize(tour.id, true, relay.hasStarted, dates) + _ <- studyApi.addTopics(relay.studyId, List(StudyTopic.broadcast.value)) yield relay.withTour(tour).withStudy(study.study) def requestPlay(id: RelayRoundId, v: Boolean): Funit = @@ -269,7 +289,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.stateHash != from.stateHash).so(denormalizeTourActive(round.tourId)) + _ <- (round.stateHash != from.stateHash).so(denormalizeTour(round.tourId)) yield round.sync.log.events.lastOption .ifTrue(round.sync.log != from.sync.log) @@ -296,7 +316,7 @@ final class RelayApi( byIdWithTour(roundId).flatMapz: rt => for _ <- roundRepo.coll.delete.one($id(rt.round.id)) - _ <- denormalizeTourActive(rt.tour.id) + _ <- denormalizeTour(rt.tour.id) yield rt.tour.some def deleteTourIfOwner(tour: RelayTour)(using me: Me): Fu[Boolean] = diff --git a/modules/relay/src/main/RelayTour.scala b/modules/relay/src/main/RelayTour.scala index 32301956c3d3..4d1c431e00b5 100644 --- a/modules/relay/src/main/RelayTour.scala +++ b/modules/relay/src/main/RelayTour.scala @@ -24,6 +24,7 @@ case class RelayTour( players: Option[RelayPlayersTextarea] = None, teams: Option[RelayTeamsTextarea] = None, image: Option[ImageId] = None, + dates: Option[RelayTour.Dates] = None, // denormalized from round dates pinnedStreamer: Option[UserStr] = None, pinnedStreamerImage: Option[ImageId] = None ): @@ -76,13 +77,15 @@ object RelayTour: type Selector = RelayTour.Tier.type => RelayTour.Tier case class Info( - dates: Option[String], format: Option[String], tc: Option[String], players: Option[String] ): - def nonEmpty = List(dates, format, tc, players).exists(_.nonEmpty) - override def toString = List(format, tc, players).flatten.mkString(" | ") + val all = List(format, tc, players).flatten + export all.nonEmpty + override def toString = all.flatten.mkString(" | ") + + case class Dates(start: Instant, end: Option[Instant]) case class Spotlight(enabled: Boolean, language: Language, title: Option[String]): def isEmpty = !enabled && specialLanguage.isEmpty && title.isEmpty diff --git a/modules/relay/src/main/RelayTourForm.scala b/modules/relay/src/main/RelayTourForm.scala index b3453c40d677..7a792c8fdba2 100644 --- a/modules/relay/src/main/RelayTourForm.scala +++ b/modules/relay/src/main/RelayTourForm.scala @@ -18,7 +18,6 @@ final class RelayTourForm(langList: lila.core.i18n.LangList): )(unapply) val infoMapping = mapping( - "dates" -> optional(cleanText(maxLength = 80)), "format" -> optional(cleanText(maxLength = 80)), "tc" -> optional(cleanText(maxLength = 80)), "players" -> optional(cleanText(maxLength = 120)) diff --git a/modules/relay/src/main/RelayTourRepo.scala b/modules/relay/src/main/RelayTourRepo.scala index fed24b208718..6c285ea803f7 100644 --- a/modules/relay/src/main/RelayTourRepo.scala +++ b/modules/relay/src/main/RelayTourRepo.scala @@ -11,8 +11,13 @@ 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, live: Boolean): Funit = - coll.update.one($id(tourId), $set("active" -> active, "live" -> live)).void + def denormalize( + tourId: RelayTourId, + active: Boolean, + live: Boolean, + dates: Option[RelayTour.Dates] + ): Funit = + coll.update.one($id(tourId), $set("active" -> active, "live" -> live, "dates" -> dates)).void def lookup(local: String) = $lookup.simple(coll, "tour", local, "_id") diff --git a/modules/relay/src/main/ui/FormUi.scala b/modules/relay/src/main/ui/FormUi.scala index a615acd753b4..1ac55c2f7831 100644 --- a/modules/relay/src/main/ui/FormUi.scala +++ b/modules/relay/src/main/ui/FormUi.scala @@ -382,33 +382,24 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): form3.group(form("name"), trb.tournamentName())(form3.input(_)(autofocus)), form3.fieldset("Optional details", toggle = tg.exists(_.tour.info.nonEmpty).some)( form3.split( - form3.group( - form("info.dates"), - "Event dates", - help = frag("""Start and end dates, eg "June 17th - 25th" or "June 17th"""").some, - half = true - )(form3.input(_)), form3.group( form("info.format"), "Tournament format", help = frag("""e.g. "8-player round-robin" or "5-round Swiss"""").some, half = true - )(form3.input(_)) - ), - form3.split( + )(form3.input(_)), form3.group( form("info.tc"), "Time control", help = frag(""""Classical" or "Rapid"""").some, half = true - )(form3.input(_)), - form3.group( - form("info.players"), - "Top players", - help = frag("Mention up to 4 of the best players participating").some, - half = true )(form3.input(_)) ), + form3.group( + form("info.players"), + "Top players", + help = frag("Mention up to 4 of the best players participating").some + )(form3.input(_)), form3.group( form("markdown"), trb.fullDescription(), diff --git a/ui/@types/lichess/index.d.ts b/ui/@types/lichess/index.d.ts index d9c566a98c71..05b009c9823b 100644 --- a/ui/@types/lichess/index.d.ts +++ b/ui/@types/lichess/index.d.ts @@ -71,6 +71,7 @@ interface Site { }; timeago(date: number | Date): string; dateFormat: () => (date: Date) => string; + displayLocale: string; contentLoaded(parent?: HTMLElement): void; blindMode: boolean; makeChat(data: any): any; diff --git a/ui/analyse/src/study/relay/interfaces.ts b/ui/analyse/src/study/relay/interfaces.ts index e9f643f01f91..5cd11bcddb21 100644 --- a/ui/analyse/src/study/relay/interfaces.ts +++ b/ui/analyse/src/study/relay/interfaces.ts @@ -30,12 +30,13 @@ export interface RelayRound { } export interface RelayTourInfo { - dates?: string; format?: string; tc?: string; players?: string; } +export type RelayTourDates = [number] | [number, number]; + export interface RelayTour { id: string; name: string; @@ -48,6 +49,7 @@ export interface RelayTour { teamTable?: boolean; leaderboard?: boolean; tier?: number; + dates?: RelayTourDates; } export interface RelaySync { diff --git a/ui/analyse/src/study/relay/relayTourView.ts b/ui/analyse/src/study/relay/relayTourView.ts index 0f10b4b282a3..42ad3d08d29f 100644 --- a/ui/analyse/src/study/relay/relayTourView.ts +++ b/ui/analyse/src/study/relay/relayTourView.ts @@ -4,9 +4,9 @@ import * as licon from 'common/licon'; import { bind, dataIcon, onInsert, looseH as h } from 'common/snabbdom'; import { VNode } from 'snabbdom'; import { innerHTML } from 'common/richText'; -import { RelayGroup, RelayRound, RelayTourInfo } from './interfaces'; +import { RelayGroup, RelayRound, RelayTourDates, RelayTourInfo } from './interfaces'; import { view as multiBoardView } from '../multiBoard'; -import { defined } from 'common'; +import { defined, memoize } from 'common'; import StudyCtrl from '../studyCtrl'; import { toggle } from 'common/controls'; import * as xhr from 'common/xhr'; @@ -103,11 +103,11 @@ const leaderboard = (ctx: RelayViewContext) => [ ctx.relay.leaderboard && leaderboardView(ctx.relay.leaderboard), ]; -const showInfo = (i: RelayTourInfo) => +const showInfo = (i: RelayTourInfo, dates?: RelayTourDates) => h( 'div.relay-tour__info', [ - ['dates', i.dates, 'objects.calendar'], + ['dates', dates && showDates(dates), 'objects.calendar'], ['format', i.format, 'objects.crown'], ['tc', i.tc, 'objects.mantelpiece-clock'], ['players', i.players, 'activity.sparkles'], @@ -119,41 +119,60 @@ const showInfo = (i: RelayTourInfo) => ), ); -const overview = (ctx: RelayViewContext) => [ - ...header(ctx), - showInfo(ctx.relay.data.tour.info), - ctx.relay.data.tour.markup - ? h('div.relay-tour__markup', { - hook: innerHTML(ctx.relay.data.tour.markup, () => ctx.relay.data.tour.markup!), - }) - : undefined, - h('div.relay-tour__share', [ - h('h2.text', { attrs: dataIcon(licon.Heart) }, 'Sharing is caring'), - ...[ - [ctx.relay.data.tour.name, ctx.relay.tourPath()], - [ctx.study.data.name, ctx.relay.roundPath()], - [ - `${ctx.study.data.name} PGN`, - `${ctx.relay.roundPath()}.pgn`, - h('div.form-help', [ - 'A public, real-time PGN source for this round. We also offer a ', - h( - 'a', - { attrs: { href: 'https://lichess.org/api#tag/Broadcasts/operation/broadcastStreamRoundPgn' } }, - 'streaming API', - ), - ' for faster and more efficient synchronisation.', +const dateFormat = memoize(() => + window.Intl && Intl.DateTimeFormat + ? new Intl.DateTimeFormat(site.displayLocale, { + month: 'short', + day: '2-digit', + } as any).format + : (d: Date) => d.toLocaleDateString(), +); + +const showDates = (dates: RelayTourDates) => { + const rendered = dates.map(date => dateFormat()(new Date(date))); + if (rendered[1]) return `${rendered[0]} - ${rendered[1]}`; + return rendered[0]; +}; + +const overview = (ctx: RelayViewContext) => { + const tour = ctx.relay.data.tour; + console.log(tour.dates); + return [ + ...header(ctx), + showInfo(tour.info, tour.dates), + tour.markup + ? h('div.relay-tour__markup', { + hook: innerHTML(tour.markup, () => tour.markup!), + }) + : undefined, + h('div.relay-tour__share', [ + h('h2.text', { attrs: dataIcon(licon.Heart) }, 'Sharing is caring'), + ...[ + [tour.name, ctx.relay.tourPath()], + [ctx.study.data.name, ctx.relay.roundPath()], + [ + `${ctx.study.data.name} PGN`, + `${ctx.relay.roundPath()}.pgn`, + h('div.form-help', [ + 'A public, real-time PGN source for this round. We also offer a ', + h( + 'a', + { attrs: { href: 'https://lichess.org/api#tag/Broadcasts/operation/broadcastStreamRoundPgn' } }, + 'streaming API', + ), + ' for faster and more efficient synchronisation.', + ]), + ], + ].map(([i18n, path, help]: [string, string, VNode]) => + h('div.form-group', [ + h('label.form-label', ctx.ctrl.trans.noarg(i18n)), + copyMeInput(`${baseUrl()}${path}`), + help, ]), - ], - ].map(([i18n, path, help]: [string, string, VNode]) => - h('div.form-group', [ - h('label.form-label', ctx.ctrl.trans.noarg(i18n)), - copyMeInput(`${baseUrl()}${path}`), - help, - ]), - ), - ]), -]; + ), + ]), + ]; +}; const groupSelect = (relay: RelayCtrl, group: RelayGroup) => { const toggle = relay.groupSelectShow; diff --git a/ui/chart/src/chart.ratingHistory.ts b/ui/chart/src/chart.ratingHistory.ts index 14046476a1c5..e15c9e0b3e6a 100644 --- a/ui/chart/src/chart.ratingHistory.ts +++ b/ui/chart/src/chart.ratingHistory.ts @@ -71,14 +71,11 @@ const oneDay = 24 * 60 * 60 * 1000; const dateFormat = memoize(() => window.Intl && Intl.DateTimeFormat - ? new Intl.DateTimeFormat( - document.documentElement.lang.startsWith('ar-') ? 'ar-ly' : document.documentElement.lang, - { - month: 'short', - day: '2-digit', - year: 'numeric', - }, - ).format + ? new Intl.DateTimeFormat(site.displayLocale, { + month: 'short', + day: '2-digit', + year: 'numeric', + }).format : (d: Date) => d.toLocaleDateString(), ); diff --git a/ui/chart/src/chart.relayStats.ts b/ui/chart/src/chart.relayStats.ts index faef705d02be..114808da0209 100644 --- a/ui/chart/src/chart.relayStats.ts +++ b/ui/chart/src/chart.relayStats.ts @@ -25,17 +25,14 @@ interface RelayChart extends chart.Chart { const dateFormat = memoize(() => window.Intl && Intl.DateTimeFormat - ? new Intl.DateTimeFormat( - document.documentElement.lang.startsWith('ar-') ? 'ar-ly' : document.documentElement.lang, - { - year: 'numeric', - month: 'short', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - hour12: false, - }, - ).format + ? new Intl.DateTimeFormat(site.displayLocale, { + year: 'numeric', + month: 'short', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false, + }).format : (d: Date) => d.toLocaleDateString(), ); diff --git a/ui/site/src/site.ts b/ui/site/src/site.ts index 468ec90925d0..f1ad16b4d26c 100644 --- a/ui/site/src/site.ts +++ b/ui/site/src/site.ts @@ -19,7 +19,7 @@ import sound from './sound'; import { mic } from './mic'; import * as miniBoard from 'common/miniBoard'; import * as miniGame from './miniGame'; -import { format as timeago, formatter as dateFormat } from './timeago'; +import { format as timeago, formatter as dateFormat, displayLocale } from './timeago'; import watchers from './watchers'; import { Chessground } from 'chessground'; import { domDialog, ready, snabDialog } from './dialog'; @@ -55,6 +55,7 @@ s.miniBoard = miniBoard; s.miniGame = miniGame; s.timeago = timeago; s.dateFormat = dateFormat; +s.displayLocale = displayLocale; s.contentLoaded = (parent?: HTMLElement) => pubsub.emit('content-loaded', parent); s.blindMode = document.body.classList.contains('blind-mode'); s.makeChat = data => site.asset.loadEsm('chat', { init: { el: document.querySelector('.mchat')!, ...data } }); diff --git a/ui/site/src/timeago.ts b/ui/site/src/timeago.ts index 8eb5f9b9cdbb..ba6e80b29b91 100644 --- a/ui/site/src/timeago.ts +++ b/ui/site/src/timeago.ts @@ -39,20 +39,21 @@ const formatRemaining = (seconds: number): string => ? siteTrans.pluralSame('nbMinutesRemaining', Math.floor(seconds / 60)) : siteTrans.pluralSame('nbHoursRemaining', Math.floor(seconds / 3600)); +// for many users, using the islamic calendar is not practical on the internet +// due to international context, so we make sure it's displayed using the gregorian calendar +export const displayLocale = document.documentElement.lang.startsWith('ar-') + ? 'ar-ly' + : document.documentElement.lang; + export const formatter = memoize(() => window.Intl - ? // for many users, using the islamic calendar is not practical on the internet - // due to international context, so we make sure it's displayed using the gregorian calendar - new Intl.DateTimeFormat( - document.documentElement.lang.startsWith('ar-') ? 'ar-ly' : document.documentElement.lang, - { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - }, - ).format + ? new Intl.DateTimeFormat(displayLocale, { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + }).format : (d: Date) => d.toLocaleString(), ); From 44bcc3da752a83033ae90f9409cbc386feb593c2 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sat, 29 Jun 2024 16:18:02 +0200 Subject: [PATCH 2/4] set profile real name max length to 100 --- modules/user/src/main/UserForm.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/user/src/main/UserForm.scala b/modules/user/src/main/UserForm.scala index 621b7a426507..9ae6a0864a3f 100644 --- a/modules/user/src/main/UserForm.scala +++ b/modules/user/src/main/UserForm.scala @@ -42,7 +42,7 @@ final class UserForm: "flag" -> optional(text.verifying(Flags.codeSet contains _)), "location" -> optional(cleanNoSymbolsAndNonEmptyText(maxLength = 80)), "bio" -> optional(cleanNoSymbolsAndNonEmptyText(maxLength = 400)), - "realName" -> nameField, + "realName" -> optional(cleanNoSymbolsText(minLength = 1, maxLength = 100)), "fideRating" -> optional(number(min = 1400, max = 3000)), "uscfRating" -> optional(number(min = 100, max = 3000)), "ecfRating" -> optional(number(min = 0, max = 3000)), @@ -57,8 +57,6 @@ final class UserForm: def flair(using Me) = Form[Option[Flair]]: single(FlairApi.formPair()) - private def nameField = optional(cleanNoSymbolsText(minLength = 1, maxLength = 20)) - object UserForm: val note = Form: From dba76506844f22950821351b1522058998ef0fa3 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sat, 29 Jun 2024 16:23:12 +0200 Subject: [PATCH 3/4] fix broadcast false error reporting --- modules/relay/src/main/RelayTour.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/relay/src/main/RelayTour.scala b/modules/relay/src/main/RelayTour.scala index 4d1c431e00b5..5938982951f5 100644 --- a/modules/relay/src/main/RelayTour.scala +++ b/modules/relay/src/main/RelayTour.scala @@ -104,7 +104,8 @@ object RelayTour: ~round.sync.log.lastErrors.some .filter(_.nonEmpty) .orElse: - (round.hasStarted && !round.sync.ongoing).option(List("Not syncing!")) + (round.hasStarted && round.sync.upstream.isDefined && !round.sync.ongoing) + .option(List("Not syncing!")) case class WithLastRound(tour: RelayTour, round: RelayRound, group: Option[RelayGroup.Name]) extends RelayRound.AndTourAndGroup: From d2a903c6bd9f0227404007e7ac06a6d9ea0a4451 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sat, 29 Jun 2024 16:34:33 +0200 Subject: [PATCH 4/4] add player replacements to pushed broadcast rounds --- modules/relay/src/main/RelayPush.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/relay/src/main/RelayPush.scala b/modules/relay/src/main/RelayPush.scala index ff226320c6cc..bdcdb12df6f7 100644 --- a/modules/relay/src/main/RelayPush.scala +++ b/modules/relay/src/main/RelayPush.scala @@ -48,7 +48,8 @@ final class RelayPush( private def push(rt: RelayRound.WithTour, rawGames: Vector[RelayGame], andSyncTargets: Boolean) = workQueue(rt.round.id): for - games <- fidePlayers.enrichGames(rt.tour)(rawGames) + withPlayers <- fuccess(rt.tour.players.fold(rawGames)(_.update(rawGames))) + games <- fidePlayers.enrichGames(rt.tour)(withPlayers) event <- sync .updateStudyChapters(rt, rt.tour.players.fold(games)(_.update(games))) .map: res =>