Skip to content

Commit

Permalink
shits gonna work, trust me
Browse files Browse the repository at this point in the history
  • Loading branch information
Simyon264 committed Jun 15, 2024
1 parent 72e3d91 commit 45975ae
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 0 deletions.
37 changes: 37 additions & 0 deletions ReplayBrowser/Data/AnalyticsData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace ReplayBrowser.Data;

/// <summary>
/// Contains data for the analytics page.
/// </summary>
public class AnalyticsData
{
public required List<Analytics> Analytics { get; set; }
}

/// <summary>
/// Contains the chart.js data for the analytics page.
/// </summary>
public class Analytics
{
public required string Name { get; set; }
public required string Description { get; set; }

/// <summary>
/// The type of chart to display.
/// </summary>
public required string Type { get; set; }

/// <summary>
/// The data for the chart.
/// </summary>
public required List<ChartData> Data { get; set; }
}

/// <summary>
/// Contains the data for a chart.js chart.
/// </summary>
public class ChartData
{
public required string Label { get; set; }
public required double Data { get; set; }
}
1 change: 1 addition & 0 deletions ReplayBrowser/Pages/Shared/MainLayout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

<br/>
<a class="btn btn-primary" onclick="window.location.href = '/leaderboard'" style="margin-right: 10px; margin-top: 10px">Leaderboard</a>
<a class="btn btn-primary" onclick="window.location.href = '/stats'" style="margin-right: 10px; margin-top: 10px">Stats</a>
<a class="btn btn-primary" onclick="window.location.href = '/'" style="margin-right: 10px; margin-top: 10px">Main page</a>
<a class="btn btn-primary" onclick="window.location.href = '/downloads'" style="margin-right: 10px; margin-top: 10px">Current downloads</a>
<a class="btn btn-primary" onclick="window.location.href = '/changelog'" style="margin-right: 10px; margin-top: 10px">Changelog</a>
Expand Down
84 changes: 84 additions & 0 deletions ReplayBrowser/Pages/Stats.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
@page "/stats"
@using System.Globalization
@using ReplayBrowser.Data
@using ReplayBrowser.Services
@inject AnalyticsService AnalyticsService

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<h3>Stats</h3>
@if (_errorMessage != null)
{
<p>@_errorMessage</p>
} else if (_isNotGenerated)
{
<p>Analytics data is not generated yet. Please try again later.</p>
}
else if (_analyticsData != null)
{
foreach (var data in _analyticsData.Analytics)
{
<div>
<h4>@data.Name</h4>
<p>@data.Description</p>
<canvas id="@data.Name" width="400" height="400"></canvas>
@((MarkupString)GetChartScript(data))
</div>
}
}
else
{
<p>Loading...</p>
}

@code {
private AnalyticsData? _analyticsData;
private string? _errorMessage;
private bool _isNotGenerated = false;

protected override void OnInitialized()
{
try
{
_analyticsData = AnalyticsService.GetAnalytics();
}
catch (InvalidOperationException ex)

Check warning on line 45 in ReplayBrowser/Pages/Stats.razor

View workflow job for this annotation

GitHub Actions / deploy

The variable 'ex' is declared but never used
{
_isNotGenerated = true;
}
catch (Exception ex)
{
_errorMessage = ex.Message;
}
}

private string GetChartScript(Analytics data)
{
var labels = string.Join(",", data.Data.Select(x => $"'{x.Label}'"));
var dataset = string.Join(",", data.Data.Select(x => x.Data.ToString(CultureInfo.InvariantCulture)));

return $@"
<script>
new Chart(document.getElementById('{data.Name}').getContext('2d'), {{
type: '{data.Type}',
data: {{
labels: [{labels}],
datasets: [{{
label: 'Count',
data: [{dataset}],
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1
}}]
}},
options: {{
scales: {{
y: {{
beginAtZero: true
}}
}}
}}
}});
</script>";
}
}
127 changes: 127 additions & 0 deletions ReplayBrowser/Services/AnalyticsService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using ReplayBrowser.Data;
using ReplayBrowser.Helpers;
using Serilog;

namespace ReplayBrowser.Services;

/// <summary>
/// Provides the analytics data for the analytics page.
/// </summary>
public class AnalyticsService : IHostedService, IDisposable
{
private const string CacheKey = "analytics";

private Timer? _timer = null;
private readonly IMemoryCache _cache;
private readonly IServiceScopeFactory _scopeFactory;
private readonly IConfiguration _config;

public AnalyticsService(IMemoryCache cache, IServiceScopeFactory scopeFactory, IConfiguration config)
{
_cache = cache;
_scopeFactory = scopeFactory;
_config = config;
}

public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(GenerateAnalytics, null, TimeSpan.Zero, TimeSpan.FromHours(12));
return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}

private void GenerateAnalytics(object? state)
{
var sw = new Stopwatch();
sw.Start();
Log.Information("Generating analytics data...");

using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<ReplayDbContext>();
var replayUrls = _config.GetSection("ReplayUrls").Get<StorageUrl[]>()!;
var analyticsData = new AnalyticsData
{
Analytics = new List<Analytics>()
};

var result = dbContext.Database.SqlQueryRaw<DurationResponse>(
$"""
SELECT
"ServerName",
DATE("Replays"."Date") AS date_of_replay,
AVG(EXTRACT(EPOCH FROM "Replays"."Date"::time)) / 60 AS average_duration_minutes
FROM
"Replays"
WHERE
EXTRACT(EPOCH FROM "Replays"."Date"::time) / 60 <= 180
AND EXTRACT(EPOCH FROM "Replays"."Date"::time) / 60 >= 10
GROUP BY
"ServerName",
date_of_replay
ORDER BY
"ServerName",
date_of_replay
"""); // why not use EF Core for this? Performance.

// For each in the result, add a new analytics object.
foreach (var storageUrl in replayUrls)
{
var resultsForUrl = result.Where(r => r.ServerName == storageUrl.FallBackServerName).ToList();
var analytics = new Analytics
{
Name = storageUrl.FallBackServerName,
Description = $"Average round duration for {storageUrl.FallBackServerName} in minutes.",
Type = "line",
Data = resultsForUrl.Select(r => new ChartData
{
Label = r.DateOfReplay.ToString("yyyy-MM-dd"),
Data = r.AverageDurationMinutes
}).ToList()
};

analyticsData.Analytics.Add(analytics);
}

_cache.Set(CacheKey, analyticsData, new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1)
});

sw.Stop();
Log.Information("Generated analytics data in {ElapsedMilliseconds}ms.", sw.ElapsedMilliseconds);
}

public AnalyticsData GetAnalytics()
{
if (!_cache.TryGetValue(CacheKey, out AnalyticsData data))
{
throw new InvalidOperationException("The analytics data has not been generated yet.");
}

return data;
}

private class DurationResponse
{
public required string ServerName { get; set; }
[Column("date_of_replay")]
public required DateTime DateOfReplay { get; set; }
[Column("average_duration_minutes")]
public required double AverageDurationMinutes { get; set; }
}

public void Dispose()
{
_timer?.Dispose();
_cache.Dispose();
}
}
2 changes: 2 additions & 0 deletions ReplayBrowser/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,12 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<AccountService>();
services.AddSingleton<LeaderboardService>();
services.AddSingleton<ReplayParserService>();
services.AddSingleton<AnalyticsService>();

services.AddHostedService<BackgroundServiceStarter<ReplayParserService>>();
services.AddHostedService<BackgroundServiceStarter<AccountService>>();
services.AddHostedService<BackgroundServiceStarter<LeaderboardService>>();
services.AddHostedService<BackgroundServiceStarter<AnalyticsService>>();

services.AddScoped<ReplayHelper>();

Expand Down

0 comments on commit 45975ae

Please sign in to comment.