diff --git a/src/Gameboard.Api/Common/CommonRegexes.cs b/src/Gameboard.Api/Common/CommonRegexes.cs new file mode 100644 index 00000000..128c6d33 --- /dev/null +++ b/src/Gameboard.Api/Common/CommonRegexes.cs @@ -0,0 +1,11 @@ +using System.Text.RegularExpressions; + +namespace Gameboard.Api.Common; + +public static partial class CommonRegexes +{ + public static readonly Regex WhitespaceGreedy = _WhitespaceGreedy(); + + [GeneratedRegex(@"\s+", RegexOptions.Multiline)] + private static partial Regex _WhitespaceGreedy(); +} diff --git a/src/Gameboard.Api/Features/Challenge/Services/ChallengeService.cs b/src/Gameboard.Api/Features/Challenge/Services/ChallengeService.cs index 68cff25e..079a73ce 100644 --- a/src/Gameboard.Api/Features/Challenge/Services/ChallengeService.cs +++ b/src/Gameboard.Api/Features/Challenge/Services/ChallengeService.cs @@ -72,6 +72,19 @@ public async Task GetOrCreate(NewChallenge model, string actorId, str return await Create(model, actorId, graderUrl, CancellationToken.None); } + public IEnumerable GetTags(Data.ChallengeSpec spec) + { + if (spec.Tags.IsEmpty()) + return []; + + return CommonRegexes + .WhitespaceGreedy + .Split(spec.Tags) + .Select(m => m.Trim().ToLower()) + .Where(m => m.IsNotEmpty()) + .ToArray(); + } + public async Task Create(NewChallenge model, string actorId, string graderUrl, CancellationToken cancellationToken) { var now = _now.Get(); diff --git a/src/Gameboard.Api/Features/Practice/PracticeService.cs b/src/Gameboard.Api/Features/Practice/PracticeService.cs index f049bb40..f57f725f 100644 --- a/src/Gameboard.Api/Features/Practice/PracticeService.cs +++ b/src/Gameboard.Api/Features/Practice/PracticeService.cs @@ -56,8 +56,9 @@ public IEnumerable UnescapeSuggestedSearches(string input) if (input.IsEmpty()) return []; - return Regex - .Split(input, @"/s+", RegexOptions.Multiline) + return CommonRegexes + .WhitespaceGreedy + .Split(input) .Select(m => m.Trim().ToLower()) .Where(m => m.IsNotEmpty()) .ToArray(); diff --git a/src/Gameboard.Api/Features/Reports/Queries/EnrollmentReport/EnrollmentReportService.cs b/src/Gameboard.Api/Features/Reports/Queries/EnrollmentReport/EnrollmentReportService.cs index 4ac1587a..58fb5b22 100644 --- a/src/Gameboard.Api/Features/Reports/Queries/EnrollmentReport/EnrollmentReportService.cs +++ b/src/Gameboard.Api/Features/Reports/Queries/EnrollmentReport/EnrollmentReportService.cs @@ -166,7 +166,7 @@ public async Task> GetRawResults(Enro PlayTime = new EnrollmentReportPlayTimeViewModel { Start = p.SessionBegin.HasValue() ? p.SessionBegin : null, - DurationMs = p.Time > 0 ? p.Time : null, + DurationMs = p.SessionBegin.HasValue() ? p.Time : null, End = (p.SessionBegin.HasValue() && p.Time > 0) ? p.SessionBegin.AddMilliseconds(p.Time) : null }, Challenges = challenges, diff --git a/src/Gameboard.Api/Features/Reports/Queries/PracticeMode/PracticeModeReportService.cs b/src/Gameboard.Api/Features/Reports/Queries/PracticeMode/PracticeModeReportService.cs index 971f7029..002168ca 100644 --- a/src/Gameboard.Api/Features/Reports/Queries/PracticeMode/PracticeModeReportService.cs +++ b/src/Gameboard.Api/Features/Reports/Queries/PracticeMode/PracticeModeReportService.cs @@ -7,8 +7,8 @@ using Gameboard.Api.Data; using Gameboard.Api.Features.Games; using Gameboard.Api.Features.Practice; +using Gameboard.Api.Services; using Microsoft.EntityFrameworkCore; -using ServiceStack; namespace Gameboard.Api.Features.Reports; @@ -22,14 +22,18 @@ public interface IPracticeModeReportService Task GetPlayerModePerformanceSummary(string userId, bool isPractice, CancellationToken cancellationToken); } -internal partial class PracticeModeReportService : IPracticeModeReportService +internal class PracticeModeReportService +( + ChallengeService challengeService, + IPracticeService practiceService, + IReportsService reportsService, + IStore store +) : IPracticeModeReportService { - private readonly IPracticeService _practiceService; - private readonly IReportsService _reportsService; - private readonly IStore _store; - - public PracticeModeReportService(IPracticeService practiceService, IReportsService reportsService, IStore store) - => (_practiceService, _reportsService, _store) = (practiceService, reportsService, store); + private readonly ChallengeService _challengeService = challengeService; + private readonly IPracticeService _practiceService = practiceService; + private readonly IReportsService _reportsService = reportsService; + private readonly IStore _store = store; private sealed class PracticeModeReportUngroupedResults { @@ -203,6 +207,7 @@ public async Task GetResultsByChallenge(PracticeModeR { var attempts = g.ToList(); var spec = ungroupedResults.Specs[g.Key]; + var specTags = _challengeService.GetTags(spec); var sponsorIdsPlayed = attempts.Select(a => a.Player.Sponsor.Id).Distinct(); var sponsorsPlayed = ungroupedResults @@ -219,16 +224,6 @@ public async Task GetResultsByChallenge(PracticeModeR Performance = BuildChallengePerformance(attempts, s) }); - var tags = Array.Empty(); - if (spec.Tags.IsNotEmpty()) - { - var specTags = Regex - .Split(spec.Tags, @"\s+", RegexOptions.Multiline) - .Select(m => m.Trim().ToLower()) - .ToArray(); - tags = visibleTags.Intersect(specTags).ToArray(); - } - return new PracticeModeByChallengeReportRecord { Id = spec.Id, @@ -245,7 +240,7 @@ public async Task GetResultsByChallenge(PracticeModeR MaxPossibleScore = spec.Points, AvgScore = attempts.Select(a => a.Score).Average(), Description = spec.Description, - Tags = tags, + Tags = specTags.Intersect(visibleTags).ToArray(), Text = spec.Text, SponsorsPlayed = sponsorsPlayed, OverallPerformance = performanceOverall, diff --git a/src/Gameboard.Api/Features/Reports/ReportService.cs b/src/Gameboard.Api/Features/Reports/ReportService.cs index 83069666..faaf9727 100644 --- a/src/Gameboard.Api/Features/Reports/ReportService.cs +++ b/src/Gameboard.Api/Features/Reports/ReportService.cs @@ -74,9 +74,9 @@ internal async Task GetSponsorStats() internal Task GetGameSponsorsStats(string gameId) { - List gameSponsorStats = new List(); + var gameSponsorStats = new List(); - if (string.IsNullOrWhiteSpace(gameId)) + if (gameId.IsEmpty()) throw new ArgumentNullException("Invalid game id"); var game = _store