Skip to content

Commit

Permalink
v3.27.5 (#589)
Browse files Browse the repository at this point in the history
* Fix an issue that caused the game name to be null in ticket markdown export

* Fixed an issue where challenges from games with no end date wouldn't appear in the ticket challenge picker

* Add cancellation tokens to report endpoints, fix denorm bug for new scoring teams.

* Cleanup

* Minor cleanup

* Fix search bugs with Game Center Teams
  • Loading branch information
sei-bstein authored Jan 10, 2025
1 parent 5ea3b94 commit 5ac505b
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ public async Task<GameCenterTeamsResults> Handle(GetGameCenterTeamsQuery request
{
var nowish = _nowService.Get();

// a little blarghy because we're counting on the Role, not the whole captain resolution thing
// normalize parameters
if (request.Args.SearchTerm.IsNotEmpty())
{
request.Args.SearchTerm = request.Args.SearchTerm.ToLower();
}

var query = _store
.WithNoTracking<Data.Player>()
.Where(p => p.Mode == PlayerMode.Competition)
Expand Down Expand Up @@ -99,15 +104,25 @@ public async Task<GameCenterTeamsResults> Handle(GetGameCenterTeamsQuery request
{
if (request.Args.HasPendingNames.Value)
{
query = query.Where(_playerService.GetWhereHasPendingNamePredicate());
query = query.Where(_playerService.GetHasPendingNamePredicate());
}
else
{
query = query.Where(_playerService.GetWhereDoesntHavePendingNamePredicate());
query = query.Where(_playerService.GetDoesntHavePendingNamePredicate());
}
}

var matchingTeams = await query
var matchingTeamIds = await query
.Select(p => p.TeamId)
.Distinct()
.ToArrayAsync(cancellationToken);

var captains = await _teamService.ResolveCaptains(matchingTeamIds, cancellationToken);

// now load the actual data of all team members for the matching teamIds
var matchingTeams = await _store
.WithNoTracking<Data.Player>()
.Where(p => matchingTeamIds.Contains(p.TeamId))
.Select(p => new
{
p.Id,
Expand Down Expand Up @@ -139,15 +154,7 @@ public async Task<GameCenterTeamsResults> Handle(GetGameCenterTeamsQuery request
p.WhenCreated
})
.GroupBy(p => p.TeamId)
.ToDictionaryAsync(gr => gr.Key, gr => gr.ToArray(), cancellationToken); ;

var matchingTeamIds = matchingTeams.Keys.ToArray();
var captains = matchingTeams.ToDictionary(kv => kv.Key, kv =>
{
var captain = kv.Value.SingleOrDefault(p => p.Role == PlayerRole.Manager);

return captain ?? kv.Value.FirstOrDefault();
});
.ToDictionaryAsync(gr => gr.Key, gr => gr.ToArray(), cancellationToken);

// we'll need this data no matter what, and if we get it here, we can
// use it to do sorting stuff
Expand Down Expand Up @@ -202,15 +209,15 @@ public async Task<GameCenterTeamsResults> Handle(GetGameCenterTeamsQuery request
}

// compute global stats as efficiently as we can before we load data for the paged results
var activeTeams = captains.Values
.Where(t => t.IsActive)
.Select(t => t.TeamId)
var activeTeamIds = matchingTeams
.Where(t => t.Value.Any(p => p.IsActive))
.Select(t => t.Key)
.ToArray();

var allPlayerStatuses = await _store
.WithNoTracking<Data.Player>()
.Where(p => matchingTeamIds.Contains(p.TeamId))
.Select(p => new { p.UserId, IsActive = activeTeams.Contains(p.TeamId) })
.Select(p => new { p.UserId, IsActive = activeTeamIds.Contains(p.TeamId) })
.Distinct()
.ToArrayAsync(cancellationToken);
var activePlayerCount = allPlayerStatuses.Where(p => p.IsActive).Count();
Expand Down Expand Up @@ -265,7 +272,7 @@ public async Task<GameCenterTeamsResults> Handle(GetGameCenterTeamsQuery request
var pendingNameCount = await _store
.WithNoTracking<Data.Player>()
.Where(p => p.GameId == request.GameId)
.Where(_playerService.GetWhereHasPendingNamePredicate())
.Where(_playerService.GetHasPendingNamePredicate())
.CountAsync(cancellationToken);

return new GameCenterTeamsResults
Expand All @@ -277,15 +284,16 @@ public async Task<GameCenterTeamsResults> Handle(GetGameCenterTeamsQuery request
Items = pagedTeamIds.Select(tId =>
{
var players = matchingTeams[tId];
var captain = captains[tId];
captains.TryGetValue(tId, out var captainPlayer);
var captain = players.Where(p => p.Id == captainPlayer?.Id).SingleOrDefault();
var solves = teamSolves[tId];

return new GameCenterTeamsResultsTeam
{
Id = tId,
Name = captain.Name,
IsExtended = captain.SessionEnd is not null && captain.SessionBegin is not null && (captain.SessionEnd.Value - captain.SessionBegin.Value).TotalMinutes > gameSessionMinutes,
Advancement = captain.Advancement is null ? null : new GameCenterTeamsAdvancement
Name = captain?.Name ?? "Unknown",
IsExtended = captain is not null && captain.SessionEnd is not null && captain.SessionBegin is not null && (captain.SessionEnd - captain.SessionBegin)?.TotalMinutes > gameSessionMinutes,
Advancement = captain?.Advancement is null ? null : new GameCenterTeamsAdvancement
{
FromGame = captain.Advancement.FromGame,
FromTeam = captain.Advancement.FromTeam,
Expand Down Expand Up @@ -328,11 +336,11 @@ public async Task<GameCenterTeamsResults> Handle(GetGameCenterTeamsQuery request
Score = scores.TryGetValue(tId, out var score) ? score : Score.Default,
Session = new GameCenterTeamsSession
{
Start = captains[tId].SessionBegin.HasValue ? captains[tId].SessionBegin.Value.ToUnixTimeMilliseconds() : default(long?),
End = captains[tId].SessionEnd.HasValue ? captains[tId].SessionEnd.Value.ToUnixTimeMilliseconds() : default(long?),
TimeCumulativeMs = captains[tId].TimeCumulative > 0 ? captains[tId].TimeCumulative : default(long?),
TimeRemainingMs = captains[tId].TimeRemaining,
TimeSinceStartMs = captains[tId].TimeSinceStart
Start = captain?.SessionBegin is not null ? captain?.SessionBegin.Value.ToUnixTimeMilliseconds() : default(long?),
End = captain?.SessionEnd is not null ? captain?.SessionEnd.Value.ToUnixTimeMilliseconds() : default(long?),
TimeCumulativeMs = captain?.TimeCumulative > 0 ? captain?.TimeCumulative : default(long?),
TimeRemainingMs = captain?.TimeRemaining,
TimeSinceStartMs = captain?.TimeSinceStart
},
TicketCount = ticketCounts.TryGetValue(tId, out int value) ? value : 0
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,13 @@ public async Task<ChallengeOverview[]> ListByUser(string uid)
var practiceChallengesCutoff = _now.Get().AddDays(-7);
q = q.Include(c => c.Player).Include(c => c.Game);
// band-aid for #296
q = q.Where(c => c.Game.GameEnd > recent || (c.PlayerMode == PlayerMode.Practice && c.StartTime >= practiceChallengesCutoff));
q = q.Where
(
c =>
c.EndTime > recent ||
c.Game.GameEnd > recent ||
(c.PlayerMode == PlayerMode.Practice && c.StartTime >= practiceChallengesCutoff)
);
q = q.OrderByDescending(p => p.StartTime);

return await Mapper.ProjectTo<ChallengeOverview>(q).ToArrayAsync();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;

namespace Gameboard.Api.Features.Games;

public sealed class ExportGameResult
{
public required string ExportBatchId { get; set; }
public required string GameId { get; set; }
public required GameImportExport GameData { get; set; }
}

public sealed class GameImportExport
{
public required string ExportedGameId { get; set; }
public required string Name { get; set; }
public required string Competition { get; set; }
public required string Season { get; set; }
public required string Track { get; set; }
public required string Division { get; set; }
public required string Logo { get; set; }
public required string Sponsor { get; set; }
public required string Background { get; set; }
public required string TestCode { get; set; }
public required DateTimeOffset? GameStart { get; set; }
public required DateTimeOffset? GameEnd { get; set; }
public required string GameMarkdown { get; set; }
public required string FeedbackConfig { get; set; }
public required string RegistrationMarkdown { get; set; }
public required DateTimeOffset? RegistrationOpen { get; set; }
public required DateTimeOffset? RegistrationClose { get; set; }
public required GameRegistrationType RegistrationType { get; set; }
public required int MinTeamSize { get; set; } = 1;
public required int MaxTeamSize { get; set; } = 1;
public required int? MaxAttempts { get; set; } = 0;
public required bool RequireSponsoredTeam { get; set; }
public required int SessionMinutes { get; set; } = 60;
public required int? SessionLimit { get; set; } = 0;
public required int? SessionAvailabilityWarningThreshold { get; set; }
public required int GamespaceLimitPerSession { get; set; } = 1;
public required bool IsPublished { get; set; }
public required bool AllowLateStart { get; set; }
public required bool AllowPreview { get; set; }
public required bool AllowPublicScoreboardAccess { get; set; }
public required bool AllowReset { get; set; }
public required string CardText1 { get; set; }
public required string CardText2 { get; set; }
public required string CardText3 { get; set; }
public required bool IsFeatured { get; set; }

// mode stuff
public string ExternalHostId { get; set; }
public GameImportExportExternalHost ExternalHost { get; set; }
public string Mode { get; set; }
public PlayerMode PlayerMode { get; set; }
public bool RequireSynchronizedStart { get; set; } = false;
public bool ShowOnHomePageInPracticeMode { get; set; } = false;

// feedback
// public string ChallengesFeedbackTemplateId { get; set; }
// public FeedbackTemplate ChallengesFeedbackTemplate { get; set; }
// public string FeedbackTemplateId { get; set; }
// public FeedbackTemplate FeedbackTemplate { get; set; }

// public string CertificateTemplateId { get; set; }
// public CertificateTemplate CertificateTemplate { get; set; }
// public string PracticeCertificateTemplateId { get; set; }
// public CertificateTemplate PracticeCertificateTemplate { get; set; }
}

public sealed class GameImportExportExternalHost
{
public required string ExportedId { get; set; }
}

public sealed class GameImportExportFeedbackTemplate
{
public required string ExportedFeedbackTemplateId { get; set; }
}
16 changes: 13 additions & 3 deletions src/Gameboard.Api/Features/Player/Services/PlayerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,22 @@ await _store
);
}

public Expression<Func<Data.Player, bool>> GetWhereHasPendingNamePredicate()
public Func<Data.Player, bool> GetHasActiveSessionFunc(DateTimeOffset now)
{
return p => p.SessionBegin != DateTimeOffset.MinValue && p.SessionBegin < now && p.SessionEnd > now;
}

public Expression<Func<Data.Player, bool>> GetHasActiveSessionPredicate(DateTimeOffset now)
{
return p => p.SessionBegin != DateTimeOffset.MinValue && p.SessionBegin < now && p.SessionEnd > now;
}

public Expression<Func<Data.Player, bool>> GetHasPendingNamePredicate()
{
return p => p.Name != p.ApprovedName && p.Name != null && p.Name != string.Empty && (p.NameStatus == null || p.NameStatus == string.Empty || p.NameStatus == AppConstants.NameStatusPending);
}

public Expression<Func<Data.Player, bool>> GetWhereDoesntHavePendingNamePredicate()
public Expression<Func<Data.Player, bool>> GetDoesntHavePendingNamePredicate()
{
return p => !(p.Name != p.ApprovedName && p.Name != null && p.Name != string.Empty && (p.NameStatus == null || p.NameStatus == string.Empty || p.NameStatus == AppConstants.NameStatusPending));
}
Expand Down Expand Up @@ -322,7 +332,7 @@ public async Task<Player[]> List(PlayerDataFilter model, bool sudo = false)

if (model.WantsPending)
{
var pendingNamePredicate = GetWhereHasPendingNamePredicate();
var pendingNamePredicate = GetHasPendingNamePredicate();
q = q.Where(pendingNamePredicate);
}

Expand Down
5 changes: 1 addition & 4 deletions src/Gameboard.Api/Features/Practice/PracticeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ namespace Gameboard.Api.Features.Practice;
[ApiController]
[Authorize]
[Route("/api/practice")]
public class PracticeController(
IActingUserService actingUserService,
IMediator mediator
) : ControllerBase
public class PracticeController(IActingUserService actingUserService, IMediator mediator) : ControllerBase
{
private readonly IActingUserService _actingUserService = actingUserService;
private readonly IMediator _mediator = mediator;
Expand Down
Loading

0 comments on commit 5ac505b

Please sign in to comment.