add total ranked client number for stats pages
This commit is contained in:
parent
0446fe1ec5
commit
5433d7d1d2
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
|
1638
Data/Migrations/MySql/20220609135128_AddIndexToEFRankingHistoryCreatedDatetime.Designer.cs
generated
Normal file
1638
Data/Migrations/MySql/20220609135128_AddIndexToEFRankingHistoryCreatedDatetime.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
1695
Data/Migrations/Postgresql/20220609135210_AddIndexToEFRankingHistoryCreatedDatetime.Designer.cs
generated
Normal file
1695
Data/Migrations/Postgresql/20220609135210_AddIndexToEFRankingHistoryCreatedDatetime.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
1636
Data/Migrations/Sqlite/20220609022511_AddIndexToEFRankingHistoryCreatedDatetime.Designer.cs
generated
Normal file
1636
Data/Migrations/Sqlite/20220609022511_AddIndexToEFRankingHistoryCreatedDatetime.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
gameEvent.Owner.Broadcast(topStats);
|
await gameEvent.Owner.BroadcastAsync(topStats);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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; }
|
||||||
|
@ -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
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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>
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
— <span class="text-primary">@ViewBag.TotalRankedClients.ToString("#,##0")</span> Ranked Players
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div id="topPlayersContainer">
|
<div id="topPlayersContainer">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user