Skip to content

Commit 5fe987f

Browse files
Add unit tests for analytics controllers and implement analytics comparison and time bucketing logic
- Created `DashboardAnalyticsControllerTests` to validate dashboard analytics functionality. - Added `MapAnalyticsControllerTests` for map-related analytics endpoints. - Implemented `PlayerAnalyticsV2ControllerTests` to test player analytics features. - Developed `ServerAnalyticsControllerTests` for server analytics endpoints. - Introduced `AnalyticsComparison` class to handle comparison logic for analytics queries. - Added `AnalyticsTimeBucketing` class for consistent time bucketing across analytics controllers. - Ensured all tests cover various scenarios including valid and invalid inputs.
1 parent 17607f0 commit 5fe987f

64 files changed

Lines changed: 3257 additions & 687 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/XtremeIdiots.Portal.Repository.Abstractions.V1/Constants/V1/Analytics/AnalyticsQueryDefaults.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,11 @@ public static class AnalyticsQueryDefaults
1313
public const int FifteenMinuteMaxDays = 3;
1414
public const int OneHourMaxDays = 31;
1515
public const int OneDayMaxDays = 366;
16+
17+
/// <summary>
18+
/// Maximum total prior-period lookback (current window span x comparePeriods) permitted when the
19+
/// comparison windows are span-shifted (alignMode None). Week/Month alignment is naturally bounded.
20+
/// Guards against materialising very large row sets for the comparison query.
21+
/// </summary>
22+
public const int MaxComparisonLookbackDays = 731;
1623
}

src/XtremeIdiots.Portal.Repository.Abstractions.V1/Interfaces/V1/IDashboardAnalyticsApi.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace XtremeIdiots.Portal.Repository.Abstractions.Interfaces.V1;
77

88
public interface IDashboardAnalyticsApi
99
{
10-
Task<ApiResult<DashboardSummaryDto>> GetSummary(DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
11-
Task<ApiResult<DashboardTrendsDto>> GetTrends(DateTime fromUtc, DateTime toUtc, AnalyticsBucket bucket, CancellationToken cancellationToken = default);
12-
Task<ApiResult<DashboardCompositionDto>> GetComposition(DateTime fromUtc, DateTime toUtc, int top = AnalyticsQueryDefaults.DefaultTop, CancellationToken cancellationToken = default);
10+
Task<ApiResult<DashboardHomeDto>> GetHome(DateTime fromUtc, DateTime toUtc, AnalyticsBucket bucket, int top = AnalyticsQueryDefaults.DefaultTop, CancellationToken cancellationToken = default);
11+
12+
Task<ApiResult<DashboardServerDto>> GetServer(Guid gameServerId, DateTime fromUtc, DateTime toUtc, AnalyticsBucket bucket, CancellationToken cancellationToken = default);
1313
}

src/XtremeIdiots.Portal.Repository.Abstractions.V1/Interfaces/V1/IMapAnalyticsApi.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,17 @@ namespace XtremeIdiots.Portal.Repository.Abstractions.Interfaces.V1;
77

88
public interface IMapAnalyticsApi
99
{
10-
Task<ApiResult<MapOverviewDto>> GetOverview(Guid mapId, DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
11-
Task<ApiResult<MapTrendsDto>> GetTrends(Guid mapId, DateTime fromUtc, DateTime toUtc, AnalyticsBucket bucket, CancellationToken cancellationToken = default);
12-
Task<ApiResult<MapRankingsDto>> GetRankings(DateTime fromUtc, DateTime toUtc, int top = AnalyticsQueryDefaults.DefaultTop, CancellationToken cancellationToken = default);
10+
Task<ApiResult<MapsOverviewDto>> GetOverview(DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
11+
12+
Task<ApiResult<MapsHotspotsDto>> GetHotspots(DateTime fromUtc, DateTime toUtc, int top = AnalyticsQueryDefaults.DefaultTop, CancellationToken cancellationToken = default);
13+
14+
Task<ApiResult<MapsTopPlayedDto>> GetTopPlayed(DateTime fromUtc, DateTime toUtc, int top = AnalyticsQueryDefaults.DefaultTop, CancellationToken cancellationToken = default);
15+
16+
Task<ApiResult<MapsTopVotedDto>> GetTopVoted(DateTime fromUtc, DateTime toUtc, int top = AnalyticsQueryDefaults.DefaultTop, CancellationToken cancellationToken = default);
17+
18+
Task<ApiResult<MapsByGameDto>> GetByGame(DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
19+
20+
Task<ApiResult<MapsByServerDto>> GetByServer(Guid gameServerId, DateTime fromUtc, DateTime toUtc, int top = AnalyticsQueryDefaults.DefaultTop, CancellationToken cancellationToken = default);
21+
22+
Task<ApiResult<MapDetailDto>> GetMapDetail(Guid mapId, DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
1323
}

src/XtremeIdiots.Portal.Repository.Abstractions.V1/Interfaces/V1/IPlayerAnalyticsV2Api.cs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ namespace XtremeIdiots.Portal.Repository.Abstractions.Interfaces.V1;
77

88
public interface IPlayerAnalyticsV2Api
99
{
10-
Task<ApiResult<PlayerOverviewDto>> GetOverview(Guid playerId, DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
11-
Task<ApiResult<PlayerTrendsDto>> GetTrends(Guid playerId, DateTime fromUtc, DateTime toUtc, AnalyticsBucket bucket, CancellationToken cancellationToken = default);
12-
Task<ApiResult<PlayerTrendsDto>> GetTrends(
13-
Guid playerId,
10+
Task<ApiResult<PlayersOverviewDto>> GetOverview(DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
11+
12+
Task<ApiResult<PlayersTimeseriesDto>> GetTimeseries(DateTime fromUtc, DateTime toUtc, AnalyticsBucket bucket, CancellationToken cancellationToken = default);
13+
14+
Task<ApiResult<PlayersTimeseriesDto>> GetTimeseries(
1415
DateTime fromUtc,
1516
DateTime toUtc,
1617
AnalyticsBucket bucket,
@@ -19,11 +20,27 @@ Task<ApiResult<PlayerTrendsDto>> GetTrends(
1920
AnalyticsAlignMode alignMode = AnalyticsAlignMode.None,
2021
string timezone = "UTC",
2122
bool normalize = false,
22-
CancellationToken cancellationToken = default)
23-
{
24-
return GetTrends(playerId, fromUtc, toUtc, bucket, cancellationToken);
25-
}
23+
CancellationToken cancellationToken = default);
24+
25+
Task<ApiResult<PlayersTopDto>> GetTop(DateTime fromUtc, DateTime toUtc, int top = AnalyticsQueryDefaults.DefaultTop, CancellationToken cancellationToken = default);
26+
27+
Task<ApiResult<PlayersByGameDto>> GetByGame(DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
2628

27-
Task<ApiResult<PlayerRelatedActivityDto>> GetRelatedActivity(Guid playerId, DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
28-
Task<ApiResult<PlayerModerationSummaryDto>> GetModerationSummary(Guid playerId, DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
29+
Task<ApiResult<PlayersByServerDto>> GetByServer(DateTime fromUtc, DateTime toUtc, int top = AnalyticsQueryDefaults.DefaultTop, CancellationToken cancellationToken = default);
30+
31+
Task<ApiResult<PlayerDetailDto>> GetPlayerDetail(Guid playerId, DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
32+
33+
Task<ApiResult<PlayerTrendsDto>> GetPlayerTimeseries(Guid playerId, DateTime fromUtc, DateTime toUtc, AnalyticsBucket bucket, CancellationToken cancellationToken = default);
34+
35+
Task<ApiResult<PlayerTrendsDto>> GetPlayerTimeseries(
36+
Guid playerId,
37+
DateTime fromUtc,
38+
DateTime toUtc,
39+
AnalyticsBucket bucket,
40+
AnalyticsCompareMode compareMode,
41+
int comparePeriods = AnalyticsQueryDefaults.DefaultComparePeriods,
42+
AnalyticsAlignMode alignMode = AnalyticsAlignMode.None,
43+
string timezone = "UTC",
44+
bool normalize = false,
45+
CancellationToken cancellationToken = default);
2946
}

src/XtremeIdiots.Portal.Repository.Abstractions.V1/Interfaces/V1/IServerAnalyticsApi.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ namespace XtremeIdiots.Portal.Repository.Abstractions.Interfaces.V1;
88
public interface IServerAnalyticsApi
99
{
1010
Task<ApiResult<ServerOverviewDto>> GetOverview(Guid gameServerId, DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
11+
1112
Task<ApiResult<ServerTimeseriesDto>> GetTimeseries(Guid gameServerId, DateTime fromUtc, DateTime toUtc, AnalyticsBucket bucket, CancellationToken cancellationToken = default);
13+
1214
Task<ApiResult<ServerTimeseriesDto>> GetTimeseries(
1315
Guid gameServerId,
1416
DateTime fromUtc,
@@ -19,10 +21,13 @@ Task<ApiResult<ServerTimeseriesDto>> GetTimeseries(
1921
AnalyticsAlignMode alignMode = AnalyticsAlignMode.None,
2022
string timezone = "UTC",
2123
bool normalize = false,
22-
CancellationToken cancellationToken = default)
23-
{
24-
return GetTimeseries(gameServerId, fromUtc, toUtc, bucket, cancellationToken);
25-
}
24+
CancellationToken cancellationToken = default);
25+
26+
Task<ApiResult<ServerPlayersCurrentDto>> GetPlayersCurrent(Guid gameServerId, CancellationToken cancellationToken = default);
27+
28+
Task<ApiResult<ServerEventsSummaryDto>> GetEventsSummary(Guid gameServerId, DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
29+
30+
Task<ApiResult<ServerChatSummaryDto>> GetChatSummary(Guid gameServerId, DateTime fromUtc, DateTime toUtc, int top = AnalyticsQueryDefaults.DefaultTop, CancellationToken cancellationToken = default);
2631

27-
Task<ApiResult<ServerSummaryDto>> GetSummary(Guid gameServerId, DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
32+
Task<ApiResult<ServerMapRotationPerformanceDto>> GetMapRotationPerformance(Guid gameServerId, DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
2833
}

src/XtremeIdiots.Portal.Repository.Abstractions.V1/Models/V1/Analytics/AnalyticsSeriesDto.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ public record AnalyticsSeriesDto : IDto
1010
[JsonProperty]
1111
public string Label { get; internal set; } = string.Empty;
1212

13+
/// <summary>
14+
/// Distinguishes the current-window series ("current") from prior-period overlay series ("comparison").
15+
/// </summary>
16+
[JsonProperty]
17+
public string Role { get; internal set; } = "current";
18+
19+
/// <summary>
20+
/// Human-readable label for the comparison period this series represents (e.g. "Previous week").
21+
/// Empty for current-window series.
22+
/// </summary>
23+
[JsonProperty]
24+
public string PeriodLabel { get; internal set; } = string.Empty;
25+
1326
[JsonProperty]
1427
public List<AnalyticsSeriesValueDto> Values { get; internal set; } = [];
1528

src/XtremeIdiots.Portal.Repository.Abstractions.V1/Models/V1/Analytics/Dashboard/DashboardTrendsDto.cs renamed to src/XtremeIdiots.Portal.Repository.Abstractions.V1/Models/V1/Analytics/Dashboard/DashboardHomeDto.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@
44

55
namespace XtremeIdiots.Portal.Repository.Abstractions.Models.V1.Analytics.Dashboard;
66

7-
public record DashboardTrendsDto : IDto
7+
public record DashboardHomeDto : IDto
88
{
99
[JsonProperty]
1010
public AnalyticsTimeWindowDto Window { get; internal set; } = new();
1111

12+
[JsonProperty]
13+
public DashboardSummaryDto Summary { get; internal set; } = new();
14+
1215
[JsonProperty]
1316
public AnalyticsBucket Bucket { get; internal set; }
1417

1518
[JsonProperty]
16-
public List<AnalyticsTimeseriesPointDto> Points { get; internal set; } = [];
19+
public List<AnalyticsTimeseriesPointDto> TrendPoints { get; internal set; } = [];
20+
21+
[JsonProperty]
22+
public DashboardCompositionDto Composition { get; internal set; } = new();
1723

1824
[JsonIgnore]
1925
public Dictionary<string, string> TelemetryProperties => [];
20-
}
26+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Newtonsoft.Json;
2+
3+
using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1;
4+
using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1.Analytics;
5+
6+
namespace XtremeIdiots.Portal.Repository.Abstractions.Models.V1.Analytics.Dashboard;
7+
8+
public record DashboardServerDto : IDto
9+
{
10+
[JsonProperty]
11+
public AnalyticsTimeWindowDto Window { get; internal set; } = new();
12+
13+
[JsonProperty]
14+
public Guid GameServerId { get; internal set; }
15+
16+
[JsonProperty]
17+
public string Title { get; internal set; } = string.Empty;
18+
19+
[JsonProperty]
20+
public GameType GameType { get; internal set; }
21+
22+
[JsonProperty]
23+
public bool Online { get; internal set; }
24+
25+
[JsonProperty]
26+
public int CurrentPlayers { get; internal set; }
27+
28+
[JsonProperty]
29+
public int MaxPlayers { get; internal set; }
30+
31+
[JsonProperty]
32+
public string? MapName { get; internal set; }
33+
34+
[JsonProperty]
35+
public double AvgPlayers { get; internal set; }
36+
37+
[JsonProperty]
38+
public int PeakPlayers { get; internal set; }
39+
40+
[JsonProperty]
41+
public int EventsCount { get; internal set; }
42+
43+
[JsonProperty]
44+
public int ChatCount { get; internal set; }
45+
46+
[JsonProperty]
47+
public int UniquePlayers { get; internal set; }
48+
49+
[JsonProperty]
50+
public AnalyticsBucket Bucket { get; internal set; }
51+
52+
[JsonProperty]
53+
public List<AnalyticsTimeseriesPointDto> TrendPoints { get; internal set; } = [];
54+
55+
[JsonIgnore]
56+
public Dictionary<string, string> TelemetryProperties => [];
57+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Newtonsoft.Json;
2+
3+
using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1;
4+
5+
namespace XtremeIdiots.Portal.Repository.Abstractions.Models.V1.Analytics.Maps;
6+
7+
public record MapDetailDto : IDto
8+
{
9+
[JsonProperty]
10+
public AnalyticsTimeWindowDto Window { get; internal set; } = new();
11+
12+
[JsonProperty]
13+
public Guid MapId { get; internal set; }
14+
15+
[JsonProperty]
16+
public string MapName { get; internal set; } = string.Empty;
17+
18+
[JsonProperty]
19+
public GameType GameType { get; internal set; }
20+
21+
[JsonProperty]
22+
public int VotesCount { get; internal set; }
23+
24+
[JsonProperty]
25+
public int PlaysCount { get; internal set; }
26+
27+
[JsonProperty]
28+
public double AveragePosition { get; internal set; }
29+
30+
[JsonProperty]
31+
public double AvgPlayers { get; internal set; }
32+
33+
[JsonProperty]
34+
public int PeakPlayers { get; internal set; }
35+
36+
[JsonIgnore]
37+
public Dictionary<string, string> TelemetryProperties => [];
38+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Newtonsoft.Json;
2+
3+
using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1;
4+
5+
namespace XtremeIdiots.Portal.Repository.Abstractions.Models.V1.Analytics.Maps;
6+
7+
public record MapHotspotItemDto : IDto
8+
{
9+
[JsonProperty]
10+
public string MapName { get; internal set; } = string.Empty;
11+
12+
[JsonProperty]
13+
public GameType GameType { get; internal set; }
14+
15+
[JsonProperty]
16+
public double AvgPlayers { get; internal set; }
17+
18+
[JsonProperty]
19+
public int PeakPlayers { get; internal set; }
20+
21+
[JsonProperty]
22+
public int SampleCount { get; internal set; }
23+
24+
[JsonIgnore]
25+
public Dictionary<string, string> TelemetryProperties => [];
26+
}

0 commit comments

Comments
 (0)