diff --git a/Data/Models/Client/Stats/EFClientRankingHistory.cs b/Data/Models/Client/Stats/EFClientRankingHistory.cs index 4bff2626..6d067900 100644 --- a/Data/Models/Client/Stats/EFClientRankingHistory.cs +++ b/Data/Models/Client/Stats/EFClientRankingHistory.cs @@ -7,8 +7,6 @@ namespace Data.Models.Client.Stats { public class EFClientRankingHistory: AuditFields { - public const int MaxRankingCount = 1728; - [Key] public long ClientRankingHistoryId { get; set; } diff --git a/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs b/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs index 452727d1..a1cd006e 100644 --- a/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs +++ b/Plugins/Stats/Helpers/AdvancedClientStatsResourceQueryHelper.cs @@ -78,7 +78,7 @@ namespace Stats.Helpers .Where(r => r.ClientId == clientInfo.ClientId) .Where(r => r.ServerId == serverId) .Where(r => r.Ranking != null) - .OrderByDescending(r => r.UpdatedDateTime) + .OrderByDescending(r => r.CreatedDateTime) .Take(250) .ToListAsync(); diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index e2d8cb2f..68732d11 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -138,6 +138,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers .CountAsync(); } + public class RankingSnapshot + { + public int ClientId { get; set; } + public string Name { get; set; } + public DateTime LastConnection { get; set; } + public double? PerformanceMetric { get; set; } + public double? ZScore { get; set; } + public int? Ranking { get; set; } + public DateTime CreatedDateTime { get; set; } + } + public async Task> GetNewTopStats(int start, int count, long? serverId = null) { await using var context = _contextFactory.CreateContext(false); @@ -150,24 +161,29 @@ namespace IW4MAdmin.Plugins.Stats.Helpers .Take(count) .ToListAsync(); - var rankings = await context.Set() - .Where(ranking => clientIdsList.Contains(ranking.ClientId)) - .Where(ranking => ranking.ServerId == serverId) - .Select(ranking => new - { - ranking.ClientId, - ranking.Client.CurrentAlias.Name, - ranking.Client.LastConnection, - ranking.PerformanceMetric, - ranking.ZScore, - ranking.Ranking, - ranking.CreatedDateTime - }) - .ToListAsync(); - - var rankingsDict = rankings.GroupBy(rank => rank.ClientId) - .ToDictionary(rank => rank.Key, rank => rank.OrderBy(r => r.CreatedDateTime).ToList()); + var rankingsDict = new Dictionary>(); + foreach (var clientId in clientIdsList) + { + var eachRank = await context.Set() + .Where(ranking => ranking.ClientId == clientId) + .Where(ranking => ranking.ServerId == serverId) + .OrderByDescending(ranking => ranking.CreatedDateTime) + .Select(ranking => new RankingSnapshot + { + ClientId = ranking.ClientId, + Name = ranking.Client.CurrentAlias.Name, + LastConnection = ranking.Client.LastConnection, + PerformanceMetric = ranking.PerformanceMetric, + ZScore = ranking.ZScore, + Ranking = ranking.Ranking, + CreatedDateTime = ranking.CreatedDateTime + }) + .Take(60) + .ToListAsync(); + rankingsDict.Add(clientId, eachRank); + } + var statsInfo = await context.Set() .Where(stat => clientIdsList.Contains(stat.ClientId)) .Where(stat => stat.TimePlayed > 0) @@ -185,9 +201,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers UpdatedAt = s.Max(c => c.UpdatedAt) }) .ToListAsync(); - + var finished = statsInfo - .OrderByDescending(stat => rankingsDict[stat.ClientId].Last().PerformanceMetric) + .OrderByDescending(stat => rankingsDict[stat.ClientId].First().PerformanceMetric) .Select((s, index) => new TopStatsInfo { ClientId = s.ClientId, @@ -195,24 +211,23 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Deaths = s.Deaths, Kills = s.Kills, KDR = Math.Round(s.KDR, 2), - LastSeen = (DateTime.UtcNow - (s.UpdatedAt ?? rankingsDict[s.ClientId].Last().LastConnection)) + LastSeen = (DateTime.UtcNow - (s.UpdatedAt ?? rankingsDict[s.ClientId].First().LastConnection)) .HumanizeForCurrentCulture(1, TimeUnit.Week, TimeUnit.Second, ",", false), - LastSeenValue = DateTime.UtcNow - (s.UpdatedAt ?? rankingsDict[s.ClientId].Last().LastConnection), + LastSeenValue = DateTime.UtcNow - (s.UpdatedAt ?? rankingsDict[s.ClientId].First().LastConnection), Name = rankingsDict[s.ClientId].First().Name, - Performance = Math.Round(rankingsDict[s.ClientId].Last().PerformanceMetric ?? 0, 2), - RatingChange = (rankingsDict[s.ClientId].First().Ranking - - rankingsDict[s.ClientId].Last().Ranking) ?? 0, + Performance = Math.Round(rankingsDict[s.ClientId].First().PerformanceMetric ?? 0, 2), + RatingChange = (rankingsDict[s.ClientId].Last().Ranking - + rankingsDict[s.ClientId].First().Ranking) ?? 0, PerformanceHistory = rankingsDict[s.ClientId].Select(ranking => new PerformanceHistory { Performance = ranking.PerformanceMetric ?? 0, OccurredAt = ranking.CreatedDateTime }) .ToList(), TimePlayed = Math.Round(s.TotalTimePlayed / 3600.0, 1).ToString("#,##0"), TimePlayedValue = TimeSpan.FromSeconds(s.TotalTimePlayed), Ranking = index + start + 1, - ZScore = rankingsDict[s.ClientId].Last().ZScore, + ZScore = rankingsDict[s.ClientId].First().ZScore, ServerId = serverId }) .OrderBy(r => r.Ranking) - .Take(60) .ToList(); return finished; @@ -1322,14 +1337,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers context.Update(mostRecent); } - if (totalRankingEntries > EFClientRankingHistory.MaxRankingCount) + const int maxRankingCount = 1728; // 60 / 2.5 * 24 * 3 ( 3 days at sample every 2.5 minutes) + + if (totalRankingEntries > maxRankingCount) { var lastRating = await context.Set() .Where(r => r.ClientId == clientId) .Where(r => r.ServerId == serverId) .OrderBy(r => r.CreatedDateTime) .FirstOrDefaultAsync(); - context.Remove(lastRating); + + if (lastRating is not null) + { + context.Remove(lastRating); + } } } diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index 60a6f4cf..41310434 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -192,17 +192,7 @@ namespace SharedLibraryCore.Services .Where(FilterById); return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync(); } - - public async Task> GetActivePenaltiesByClientId(int clientId) - { - await using var context = _contextFactory.CreateContext(false); - return await context.PenaltyIdentifiers - .Where(identifier => identifier.Penalty.Offender.ClientId == clientId) - .Select(identifier => identifier.Penalty) - .Where(Filter) - .ToListAsync(); - } - + public async Task> ActivePenaltiesByRecentIdentifiers(int linkId) { await using var context = _contextFactory.CreateContext(false); diff --git a/WebfrontCore/Views/Client/Statistics/Advanced.cshtml b/WebfrontCore/Views/Client/Statistics/Advanced.cshtml index 03efa889..261bbadb 100644 --- a/WebfrontCore/Views/Client/Statistics/Advanced.cshtml +++ b/WebfrontCore/Views/Client/Statistics/Advanced.cshtml @@ -124,11 +124,12 @@ var spm = Model.ServerId != null ? serverLegacyStat?.SPM.ToNumericalString() : Model.LegacyStats.WeightValueByPlaytime(nameof(EFClientStatistics.SPM), 0).ToNumericalString(); var performanceHistory = Model.Ratings + .OrderBy(rating => rating.CreatedDateTime) .Select(rating => new PerformanceHistory { Performance = rating.PerformanceMetric, OccurredAt = rating.CreatedDateTime }); - if (performance != null) + if (performance != null && performance != Model.Ratings.FirstOrDefault().PerformanceMetric) { - performanceHistory = performanceHistory.Append(new PerformanceHistory { Performance = performance.Value, OccurredAt = DateTime.UtcNow }); + performanceHistory = performanceHistory.Append(new PerformanceHistory { Performance = performance.Value, OccurredAt = Model.Ratings.FirstOrDefault()?.CreatedDateTime ?? DateTime.UtcNow }); } var score = allPerServer.Any() @@ -285,7 +286,7 @@ @if (performanceHistory.Count() > 5) { -
+
} diff --git a/WebfrontCore/Views/Client/Statistics/Components/TopPlayers/_List.cshtml b/WebfrontCore/Views/Client/Statistics/Components/TopPlayers/_List.cshtml index e428ebe1..f4bbb0ac 100644 --- a/WebfrontCore/Views/Client/Statistics/Components/TopPlayers/_List.cshtml +++ b/WebfrontCore/Views/Client/Statistics/Components/TopPlayers/_List.cshtml @@ -1,5 +1,4 @@ @using IW4MAdmin.Plugins.Stats -@using System.Text.Json.Serialization @using System.Text.Json @model List @{ @@ -86,7 +85,7 @@
- +
@stat.Performance diff --git a/WebfrontCore/wwwroot/js/advanced_stats.js b/WebfrontCore/wwwroot/js/advanced_stats.js index 9358bd65..999c581d 100644 --- a/WebfrontCore/wwwroot/js/advanced_stats.js +++ b/WebfrontCore/wwwroot/js/advanced_stats.js @@ -26,7 +26,7 @@ function setupPerformanceGraph() { } const chart = $('#client_performance_history'); const container = $('#client_performance_history_container'); - chart.attr('height', summary.height()); + chart.attr('height', summary.height() * 1.5); chart.attr('width', container.width()); renderPerformanceChart(); } @@ -394,7 +394,7 @@ function renderPerformanceChart() { position: 'right', ticks: { precision: 0, - stepSize: 3, + stepSize: max - min / 2, callback: function (value, index, values) { if (index === values.length - 1) { return min; diff --git a/WebfrontCore/wwwroot/js/stats.js b/WebfrontCore/wwwroot/js/stats.js index eba781a4..42b30e66 100644 --- a/WebfrontCore/wwwroot/js/stats.js +++ b/WebfrontCore/wwwroot/js/stats.js @@ -88,7 +88,7 @@ function getStatsChart(id) { position: 'right', ticks: { precision: 0, - stepSize: 3, + stepSize: max - min / 2, callback: function (value, index, values) { if (index === values.length - 1) { return min;