add total ranked client number for stats pages

This commit is contained in:
RaidMax 2022-06-09 09:56:41 -05:00
parent 0446fe1ec5
commit 5433d7d1d2
29 changed files with 5205 additions and 58 deletions

View File

@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Abstractions; using Data.Abstractions;
using Data.Models.Client; using Data.Models.Client;
using Data.Models.Client.Stats;
using Data.Models.Server; using Data.Models.Server;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -22,18 +23,20 @@ namespace IW4MAdmin.Application.Misc
private readonly IDataValueCache<EFServerSnapshot, (int?, DateTime?)> _snapshotCache; private readonly IDataValueCache<EFServerSnapshot, (int?, DateTime?)> _snapshotCache;
private readonly IDataValueCache<EFClient, (int, int)> _serverStatsCache; private readonly IDataValueCache<EFClient, (int, int)> _serverStatsCache;
private readonly IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> _clientHistoryCache; private readonly IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> _clientHistoryCache;
private readonly IDataValueCache<EFClientRankingHistory, int> _rankedClientsCache;
private readonly TimeSpan? _cacheTimeSpan = private readonly TimeSpan? _cacheTimeSpan =
Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?) TimeSpan.FromMinutes(10); Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?) TimeSpan.FromMinutes(10);
public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, (int?, DateTime?)> snapshotCache, public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, (int?, DateTime?)> snapshotCache,
IDataValueCache<EFClient, (int, int)> serverStatsCache, IDataValueCache<EFClient, (int, int)> serverStatsCache,
IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> clientHistoryCache) IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> clientHistoryCache, IDataValueCache<EFClientRankingHistory, int> rankedClientsCache)
{ {
_logger = logger; _logger = logger;
_snapshotCache = snapshotCache; _snapshotCache = snapshotCache;
_serverStatsCache = serverStatsCache; _serverStatsCache = serverStatsCache;
_clientHistoryCache = clientHistoryCache; _clientHistoryCache = clientHistoryCache;
_rankedClientsCache = rankedClientsCache;
} }
public async Task<(int?, DateTime?)> public async Task<(int?, DateTime?)>
@ -160,5 +163,30 @@ namespace IW4MAdmin.Application.Misc
return Enumerable.Empty<ClientHistoryInfo>(); return Enumerable.Empty<ClientHistoryInfo>();
} }
} }
public async Task<int> RankedClientsCountAsync(long? serverId = null, CancellationToken token = default)
{
_rankedClientsCache.SetCacheItem(async (set, cancellationToken) =>
{
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
return await set
.Where(rating => rating.Newest)
.Where(rating => rating.ServerId == serverId)
.Where(rating => rating.CreatedDateTime >= fifteenDaysAgo)
.Where(rating => rating.Client.Level != EFClient.Permission.Banned)
.Where(rating => rating.Ranking != null)
.CountAsync(cancellationToken);
}, nameof(_rankedClientsCache), serverId is null ? null: new[] { (object)serverId }, _cacheTimeSpan, true);
try
{
return await _rankedClientsCache.GetCacheItem(nameof(_rankedClientsCache), serverId, token);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not retrieve data for {Name}", nameof(RankedClientsCountAsync));
return 0;
}
}
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -9,6 +10,11 @@ namespace Data.Abstractions
{ {
void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> itemGetter, string keyName, void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> itemGetter, string keyName,
TimeSpan? expirationTime = null, bool autoRefresh = false); TimeSpan? expirationTime = null, bool autoRefresh = false);
void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> itemGetter, string keyName,
IEnumerable<object> ids = null, TimeSpan? expirationTime = null, bool autoRefresh = false);
Task<TReturnType> GetCacheItem(string keyName, CancellationToken token = default); Task<TReturnType> GetCacheItem(string keyName, CancellationToken token = default);
Task<TReturnType> GetCacheItem(string keyName, object id = null, CancellationToken token = default);
} }
} }

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Abstractions; using Data.Abstractions;
@ -15,8 +17,7 @@ namespace Data.Helpers
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IDatabaseContextFactory _contextFactory; private readonly IDatabaseContextFactory _contextFactory;
private readonly ConcurrentDictionary<string, CacheState<TReturnType>> _cacheStates = private readonly ConcurrentDictionary<string, Dictionary<object, CacheState<TReturnType>>> _cacheStates = new();
new ConcurrentDictionary<string, CacheState<TReturnType>>();
private bool _autoRefresh; private bool _autoRefresh;
private const int DefaultExpireMinutes = 15; private const int DefaultExpireMinutes = 15;
@ -51,10 +52,24 @@ namespace Data.Helpers
public void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> getter, string key, public void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> getter, string key,
TimeSpan? expirationTime = null, bool autoRefresh = false) TimeSpan? expirationTime = null, bool autoRefresh = false)
{ {
if (_cacheStates.ContainsKey(key)) SetCacheItem(getter, key, null, expirationTime, autoRefresh);
}
public void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> getter, string key,
IEnumerable<object> ids = null, TimeSpan? expirationTime = null, bool autoRefresh = false)
{ {
_logger.LogDebug("Cache key {Key} is already added", key); ids ??= new[] { new object() };
return;
if (!_cacheStates.ContainsKey(key))
{
_cacheStates.TryAdd(key, new Dictionary<object, CacheState<TReturnType>>());
}
foreach (var id in ids)
{
if (_cacheStates[key].ContainsKey(id))
{
continue;
} }
var state = new CacheState<TReturnType> var state = new CacheState<TReturnType>
@ -64,9 +79,10 @@ namespace Data.Helpers
ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes) ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes)
}; };
_cacheStates[key].Add(id, state);
_autoRefresh = autoRefresh; _autoRefresh = autoRefresh;
_cacheStates.TryAdd(key, state);
if (!_autoRefresh || expirationTime == TimeSpan.MaxValue) if (!_autoRefresh || expirationTime == TimeSpan.MaxValue)
{ {
@ -77,15 +93,20 @@ namespace Data.Helpers
_timer.Elapsed += async (sender, args) => await RunCacheUpdate(state, CancellationToken.None); _timer.Elapsed += async (sender, args) => await RunCacheUpdate(state, CancellationToken.None);
_timer.Start(); _timer.Start();
} }
}
public async Task<TReturnType> GetCacheItem(string keyName, CancellationToken cancellationToken = default) public async Task<TReturnType> GetCacheItem(string keyName, CancellationToken cancellationToken = default) =>
await GetCacheItem(keyName, null, cancellationToken);
public async Task<TReturnType> GetCacheItem(string keyName, object id = null,
CancellationToken cancellationToken = default)
{ {
if (!_cacheStates.ContainsKey(keyName)) if (!_cacheStates.ContainsKey(keyName))
{ {
throw new ArgumentException("No cache found for key {key}", keyName); throw new ArgumentException("No cache found for key {key}", keyName);
} }
var state = _cacheStates[keyName]; var state = id is null ? _cacheStates[keyName].Values.First() : _cacheStates[keyName][id];
// when auto refresh is off we want to check the expiration and value // when auto refresh is off we want to check the expiration and value
// when auto refresh is on, we want to only check the value, because it'll be refreshed automatically // when auto refresh is on, we want to only check the value, because it'll be refreshed automatically

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddIndexToEFRankingHistoryCreatedDatetime : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_EFClientRankingHistory_CreatedDateTime",
table: "EFClientRankingHistory",
column: "CreatedDateTime");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFClientRankingHistory_CreatedDateTime",
table: "EFClientRankingHistory");
}
}
}

View File

@ -456,6 +456,8 @@ namespace Data.Migrations.MySql
b.HasIndex("ClientId"); b.HasIndex("ClientId");
b.HasIndex("CreatedDateTime");
b.HasIndex("Ranking"); b.HasIndex("Ranking");
b.HasIndex("ServerId"); b.HasIndex("ServerId");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddIndexToEFRankingHistoryCreatedDatetime : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_EFClientRankingHistory_CreatedDateTime",
table: "EFClientRankingHistory",
column: "CreatedDateTime");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFClientRankingHistory_CreatedDateTime",
table: "EFClientRankingHistory");
}
}
}

View File

@ -475,6 +475,8 @@ namespace Data.Migrations.Postgresql
b.HasIndex("ClientId"); b.HasIndex("ClientId");
b.HasIndex("CreatedDateTime");
b.HasIndex("Ranking"); b.HasIndex("Ranking");
b.HasIndex("ServerId"); b.HasIndex("ServerId");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Sqlite
{
public partial class AddIndexToEFRankingHistoryCreatedDatetime : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_EFClientRankingHistory_CreatedDateTime",
table: "EFClientRankingHistory",
column: "CreatedDateTime");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFClientRankingHistory_CreatedDateTime",
table: "EFClientRankingHistory");
}
}
}

View File

@ -454,6 +454,8 @@ namespace Data.Migrations.Sqlite
b.HasIndex("ClientId"); b.HasIndex("ClientId");
b.HasIndex("CreatedDateTime");
b.HasIndex("Ranking"); b.HasIndex("Ranking");
b.HasIndex("ServerId"); b.HasIndex("ServerId");

View File

@ -86,6 +86,7 @@ namespace Data.Models.Configuration
entity.HasIndex(ranking => ranking.Ranking); entity.HasIndex(ranking => ranking.Ranking);
entity.HasIndex(ranking => ranking.ZScore); entity.HasIndex(ranking => ranking.ZScore);
entity.HasIndex(ranking => ranking.UpdatedDateTime); entity.HasIndex(ranking => ranking.UpdatedDateTime);
entity.HasIndex(ranking => ranking.CreatedDateTime);
}); });
} }
} }

View File

@ -10,7 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" /> <PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.3.23.1" PrivateAssets="All" /> <PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -16,7 +16,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.3.23.1" PrivateAssets="All" /> <PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -19,7 +19,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.3.23.1" PrivateAssets="All" /> <PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -16,7 +16,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.3.23.1" PrivateAssets="All" /> <PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Abstractions; using Data.Abstractions;
using Data.Models.Client; using Data.Models.Client;
@ -88,8 +89,8 @@ namespace Stats.Client
return zScore ?? 0; return zScore ?? 0;
}, MaxZScoreCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromMinutes(30)); }, MaxZScoreCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromMinutes(30));
await _distributionCache.GetCacheItem(DistributionCacheKey); await _distributionCache.GetCacheItem(DistributionCacheKey, new CancellationToken());
await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey); await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey, new CancellationToken());
/*foreach (var serverId in _serverIds) /*foreach (var serverId in _serverIds)
{ {
@ -132,7 +133,7 @@ namespace Stats.Client
public async Task<double> GetZScoreForServer(long serverId, double value) public async Task<double> GetZScoreForServer(long serverId, double value)
{ {
var serverParams = await _distributionCache.GetCacheItem(DistributionCacheKey); var serverParams = await _distributionCache.GetCacheItem(DistributionCacheKey, new CancellationToken());
if (!serverParams.ContainsKey(serverId)) if (!serverParams.ContainsKey(serverId))
{ {
return 0.0; return 0.0;
@ -150,7 +151,7 @@ namespace Stats.Client
public async Task<double?> GetRatingForZScore(double? value) public async Task<double?> GetRatingForZScore(double? value)
{ {
var maxZScore = await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey); var maxZScore = await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey, new CancellationToken());
return maxZScore == 0 ? null : value.GetRatingForZScore(maxZScore); return maxZScore == 0 ? null : value.GetRatingForZScore(maxZScore);
} }
} }

View File

@ -79,7 +79,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
} }
else else
{ {
gameEvent.Owner.Broadcast(topStats); await gameEvent.Owner.BroadcastAsync(topStats);
} }
} }
} }

View File

@ -14,6 +14,7 @@ namespace Stats.Dtos
public EFClient.Permission Level { get; set; } public EFClient.Permission Level { get; set; }
public double? Performance { get; set; } public double? Performance { get; set; }
public int? Ranking { get; set; } public int? Ranking { get; set; }
public int TotalRankedClients { get; set; }
public double? ZScore { get; set; } public double? ZScore { get; set; }
public double? Rating { get; set; } public double? Rating { get; set; }
public List<ServerInfo> Servers { get; set; } public List<ServerInfo> Servers { get; set; }

View File

@ -42,10 +42,11 @@ namespace IW4MAdmin.Plugins.Stats
private readonly ILogger<Plugin> _logger; private readonly ILogger<Plugin> _logger;
private readonly List<IClientStatisticCalculator> _statCalculators; private readonly List<IClientStatisticCalculator> _statCalculators;
private readonly IServerDistributionCalculator _serverDistributionCalculator; private readonly IServerDistributionCalculator _serverDistributionCalculator;
private readonly IServerDataViewer _serverDataViewer;
public Plugin(ILogger<Plugin> logger, IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory databaseContextFactory, public Plugin(ILogger<Plugin> logger, IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory databaseContextFactory,
ITranslationLookup translationLookup, IMetaServiceV2 metaService, IResourceQueryHelper<ChatSearchQuery, MessageResponse> chatQueryHelper, ILogger<StatManager> managerLogger, ITranslationLookup translationLookup, IMetaServiceV2 metaService, IResourceQueryHelper<ChatSearchQuery, MessageResponse> chatQueryHelper, ILogger<StatManager> managerLogger,
IEnumerable<IClientStatisticCalculator> statCalculators, IServerDistributionCalculator serverDistributionCalculator) IEnumerable<IClientStatisticCalculator> statCalculators, IServerDistributionCalculator serverDistributionCalculator, IServerDataViewer serverDataViewer)
{ {
Config = configurationHandlerFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings"); Config = configurationHandlerFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
_databaseContextFactory = databaseContextFactory; _databaseContextFactory = databaseContextFactory;
@ -56,6 +57,7 @@ namespace IW4MAdmin.Plugins.Stats
_logger = logger; _logger = logger;
_statCalculators = statCalculators.ToList(); _statCalculators = statCalculators.ToList();
_serverDistributionCalculator = serverDistributionCalculator; _serverDistributionCalculator = serverDistributionCalculator;
_serverDataViewer = serverDataViewer;
} }
public async Task OnEventAsync(GameEvent gameEvent, Server server) public async Task OnEventAsync(GameEvent gameEvent, Server server)
@ -201,13 +203,17 @@ namespace IW4MAdmin.Plugins.Stats
var performancePlayTime = validPerformanceValues.Sum(s => s.TimePlayed); var performancePlayTime = validPerformanceValues.Sum(s => s.TimePlayed);
var performance = Math.Round(validPerformanceValues.Sum(c => c.Performance * c.TimePlayed / performancePlayTime), 2); var performance = Math.Round(validPerformanceValues.Sum(c => c.Performance * c.TimePlayed / performancePlayTime), 2);
var spm = Math.Round(clientStats.Sum(c => c.SPM) / clientStats.Count(c => c.SPM > 0), 1); var spm = Math.Round(clientStats.Sum(c => c.SPM) / clientStats.Count(c => c.SPM > 0), 1);
var overallRanking = await Manager.GetClientOverallRanking(request.ClientId);
return new List<InformationResponse> return new List<InformationResponse>
{ {
new InformationResponse new InformationResponse
{ {
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"], Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
Value = "#" + (await Manager.GetClientOverallRanking(request.ClientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)), Value = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING_FORMAT"].FormatExt((overallRanking == 0 ? "--" :
overallRanking.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))),
(await _serverDataViewer.RankedClientsCountAsync(token: token)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))
),
Column = 0, Column = 0,
Order = 0, Order = 0,
Type = MetaType.Information Type = MetaType.Information

View File

@ -17,7 +17,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.3.23.1" PrivateAssets="All" /> <PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -20,7 +20,7 @@
</Target> </Target>
<ItemGroup> <ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.3.23.1" PrivateAssets="All" /> <PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -37,5 +37,13 @@ namespace SharedLibraryCore.Interfaces
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<ClientHistoryInfo>> ClientHistoryAsync(TimeSpan? overPeriod = null, Task<IEnumerable<ClientHistoryInfo>> ClientHistoryAsync(TimeSpan? overPeriod = null,
CancellationToken token = default); CancellationToken token = default);
/// <summary>
/// Retrieves the number of ranked clients for given server id
/// </summary>
/// <param name="serverId">ServerId to query on</param>
/// <param name="token">CancellationToken</param>
/// <returns></returns>
Task<int> RankedClientsCountAsync(long? serverId = null, CancellationToken token = default);
} }
} }

View File

@ -4,7 +4,7 @@
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId> <PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2022.3.23.1</Version> <Version>2022.6.9.1</Version>
<Authors>RaidMax</Authors> <Authors>RaidMax</Authors>
<Company>Forever None</Company> <Company>Forever None</Company>
<Configurations>Debug;Release;Prerelease</Configurations> <Configurations>Debug;Release;Prerelease</Configurations>
@ -19,7 +19,7 @@
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>Shared Library for IW4MAdmin</Description> <Description>Shared Library for IW4MAdmin</Description>
<PackageVersion>2022.3.23.1</PackageVersion> <PackageVersion>2022.6.9.1</PackageVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn> <NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup> </PropertyGroup>

View File

@ -1,5 +1,7 @@
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using IW4MAdmin.Plugins.Stats.Helpers;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
@ -13,26 +15,38 @@ namespace WebfrontCore.Controllers
{ {
private IResourceQueryHelper<StatsInfoRequest, AdvancedStatsInfo> _queryHelper; private IResourceQueryHelper<StatsInfoRequest, AdvancedStatsInfo> _queryHelper;
private readonly DefaultSettings _defaultConfig; private readonly DefaultSettings _defaultConfig;
private readonly IServerDataViewer _serverDataViewer;
public ClientStatisticsController(IManager manager, public ClientStatisticsController(IManager manager,
IResourceQueryHelper<StatsInfoRequest, AdvancedStatsInfo> queryHelper, IResourceQueryHelper<StatsInfoRequest, AdvancedStatsInfo> queryHelper,
DefaultSettings defaultConfig) : base(manager) DefaultSettings defaultConfig, IServerDataViewer serverDataViewer) : base(manager)
{ {
_queryHelper = queryHelper; _queryHelper = queryHelper;
_defaultConfig = defaultConfig; _defaultConfig = defaultConfig;
_serverDataViewer = serverDataViewer;
} }
[HttpGet("{id:int}/advanced")] [HttpGet("{id:int}/advanced")]
public async Task<IActionResult> Advanced(int id, [FromQuery] string serverId) public async Task<IActionResult> Advanced(int id, [FromQuery] string serverId, CancellationToken token = default)
{ {
ViewBag.Config = _defaultConfig.GameStrings; ViewBag.Config = _defaultConfig.GameStrings;
var hitInfo = await _queryHelper.QueryResource(new StatsInfoRequest var hitInfo = (await _queryHelper.QueryResource(new StatsInfoRequest
{ {
ClientId = id, ClientId = id,
ServerEndpoint = serverId ServerEndpoint = serverId
}); })).Results.First();
return View("~/Views/Client/Statistics/Advanced.cshtml", hitInfo.Results.First()); var server = Manager.GetServers().FirstOrDefault(server => server.ToString() == serverId);
long? matchedServerId = null;
if (server != null)
{
matchedServerId = StatManager.GetIdForServer(server);
}
hitInfo.TotalRankedClients = await _serverDataViewer.RankedClientsCountAsync(matchedServerId, token);
return View("~/Views/Client/Statistics/Advanced.cshtml", hitInfo);
} }
} }
} }

View File

@ -11,6 +11,7 @@ using Stats.Dtos;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -28,10 +29,11 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
private readonly ITranslationLookup _translationLookup; private readonly ITranslationLookup _translationLookup;
private readonly IDatabaseContextFactory _contextFactory; private readonly IDatabaseContextFactory _contextFactory;
private readonly StatsConfiguration _config; private readonly StatsConfiguration _config;
private readonly IServerDataViewer _serverDataViewer;
public StatsController(ILogger<StatsController> logger, IManager manager, IResourceQueryHelper<ChatSearchQuery, public StatsController(ILogger<StatsController> logger, IManager manager, IResourceQueryHelper<ChatSearchQuery,
MessageResponse> resourceQueryHelper, ITranslationLookup translationLookup, MessageResponse> resourceQueryHelper, ITranslationLookup translationLookup,
IDatabaseContextFactory contextFactory, StatsConfiguration config) : base(manager) IDatabaseContextFactory contextFactory, StatsConfiguration config, IServerDataViewer serverDataViewer) : base(manager)
{ {
_logger = logger; _logger = logger;
_manager = manager; _manager = manager;
@ -39,16 +41,27 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
_translationLookup = translationLookup; _translationLookup = translationLookup;
_contextFactory = contextFactory; _contextFactory = contextFactory;
_config = config; _config = config;
_serverDataViewer = serverDataViewer;
} }
[HttpGet] [HttpGet]
public IActionResult TopPlayers(string serverId = null) public async Task<IActionResult> TopPlayers(string serverId = null, CancellationToken token = default)
{ {
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"]; ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"];
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"]; ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"];
ViewBag.Localization = _translationLookup; ViewBag.Localization = _translationLookup;
ViewBag.SelectedServerId = serverId; ViewBag.SelectedServerId = serverId;
var server = _manager.GetServers().FirstOrDefault(server => server.ToString() == serverId);
long? matchedServerId = null;
if (server != null)
{
matchedServerId = StatManager.GetIdForServer(server);
}
ViewBag.TotalRankedClients = await _serverDataViewer.RankedClientsCountAsync(matchedServerId, token);
return View("~/Views/Client/Statistics/Index.cshtml", _manager.GetServers() return View("~/Views/Client/Statistics/Index.cshtml", _manager.GetServers()
.Select(server => new ServerInfo .Select(server => new ServerInfo
{ {

View File

@ -256,7 +256,7 @@
{ {
if (Model.Ranking > 0) if (Model.Ranking > 0)
{ {
<div class="h5 mb-0">@Html.Raw((ViewBag.Localization["WEBFRONT_ADV_STATS_RANKED"] as string).FormatExt(Model.Ranking))</div> <div class="h5 mb-0">@Html.Raw((ViewBag.Localization["WEBFRONT_ADV_STATS_RANKED_V2"] as string).FormatExt(Model.Ranking?.ToString("#,##0"), Model.TotalRankedClients.ToString("#,##0"))))</div>
} }
else else

View File

@ -6,6 +6,7 @@
<h2 class="content-title mb-0">Top Players</h2> <h2 class="content-title mb-0">Top Players</h2>
<span class="text-muted"> <span class="text-muted">
<color-code value="@(Model.FirstOrDefault(m => m.Endpoint == ViewBag.SelectedServerId)?.Name ?? ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"])"></color-code> <color-code value="@(Model.FirstOrDefault(m => m.Endpoint == ViewBag.SelectedServerId)?.Name ?? ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"])"></color-code>
&mdash; <span class="text-primary">@ViewBag.TotalRankedClients.ToString("#,##0")</span> Ranked Players
</span> </span>
<div id="topPlayersContainer"> <div id="topPlayersContainer">