Serve dashboard stats from daily_stats and default visit list to today.

Stop live-scanning page_visits on every dashboard load (including today) so the admin home stays fast; keep the live path only for channel filters and cache the result for 45s.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
root
2026-10-11 09:46:38 +00:00
parent 11caef98fc
commit fcbc479c2e
9 changed files with 550 additions and 76 deletions
+243
View File
@@ -0,0 +1,243 @@
<?php
namespace Tests\Feature;
use App\Models\Admin;
use App\Models\DailyStat;
use App\Models\Device;
use App\Models\PageVisit;
use App\Services\DashboardStatsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class DashboardStatsTest extends TestCase
{
use RefreshDatabase;
private const CHANNEL = 'ffffffffffffffffffffffffffffffff';
protected function setUp(): void
{
parent::setUp();
config([
'coruna.panel.admin_hosts' => [],
'coruna.panel.agent_hosts' => [],
]);
Cache::flush();
}
#[Test]
public function collect_uses_daily_stats_when_present(): void
{
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'live-uv',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
Device::query()->create([
'device_id' => 'live-dev',
'channel_id' => self::CHANNEL,
'has_wallet' => Device::WALLET_YES,
]);
DailyStat::query()->create([
'stat_date' => now()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 99,
'uv' => 88,
'effective_pv' => 77,
'effective_uv' => 66,
'devices' => 5,
'wallet_devices' => 3,
'address_count' => 4,
'mnemonic_count' => 2,
'computed_at' => now(),
]);
$stats = app(DashboardStatsService::class)->collect(['range' => 'today']);
$this->assertSame(99, $stats['pv']);
$this->assertSame(88, $stats['uv']);
$this->assertSame(77, $stats['effective_pv']);
$this->assertSame(66, $stats['effective_uv']);
$this->assertSame(5, $stats['total']);
$this->assertSame(3, $stats['wallet_count']);
$this->assertSame(4, $stats['address_count']);
$this->assertSame(2, $stats['mnemonic_count']);
}
#[Test]
public function collect_falls_back_to_live_visits_when_rollup_missing(): void
{
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'live-uv',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'live-uv',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
$stats = app(DashboardStatsService::class)->collect(['range' => 'today']);
$this->assertSame(2, $stats['pv']);
$this->assertSame(1, $stats['uv']);
$this->assertSame(2, $stats['effective_pv']);
$this->assertSame(1, $stats['effective_uv']);
}
#[Test]
public function collect_scans_live_when_channel_filter_set(): void
{
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'mine',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
PageVisit::query()->create([
'channel_id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'client_uid' => 'other',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
DailyStat::query()->create([
'stat_date' => now()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 99,
'uv' => 88,
'effective_pv' => 77,
'effective_uv' => 66,
'devices' => 5,
'wallet_devices' => 3,
'address_count' => 0,
'mnemonic_count' => 0,
'computed_at' => now(),
]);
$stats = app(DashboardStatsService::class)->collect([
'range' => 'today',
'channel_id' => self::CHANNEL,
'channel_exact' => true,
]);
$this->assertSame(1, $stats['pv']);
$this->assertSame(1, $stats['uv']);
}
#[Test]
public function collect_caches_result(): void
{
DailyStat::query()->create([
'stat_date' => now()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 10,
'uv' => 9,
'effective_pv' => 8,
'effective_uv' => 7,
'devices' => 1,
'wallet_devices' => 0,
'address_count' => 0,
'mnemonic_count' => 0,
'computed_at' => now(),
]);
$svc = app(DashboardStatsService::class);
$first = $svc->collect(['range' => 'today']);
DailyStat::query()->update(['pv' => 50, 'uv' => 40]);
$second = $svc->collect(['range' => 'today']);
$this->assertSame(10, $first['pv']);
$this->assertSame(10, $second['pv']);
$this->assertSame(9, $second['uv']);
Cache::flush();
$third = $svc->collect(['range' => 'today']);
$this->assertSame(50, $third['pv']);
$this->assertSame(40, $third['uv']);
}
#[Test]
public function collect_sums_daily_rows_across_range(): void
{
DailyStat::query()->create([
'stat_date' => now()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 10,
'uv' => 4,
'effective_pv' => 3,
'effective_uv' => 2,
'devices' => 1,
'wallet_devices' => 1,
'address_count' => 1,
'mnemonic_count' => 1,
'computed_at' => now(),
]);
DailyStat::query()->create([
'stat_date' => now()->subDay()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 7,
'uv' => 3,
'effective_pv' => 2,
'effective_uv' => 1,
'devices' => 2,
'wallet_devices' => 0,
'address_count' => 3,
'mnemonic_count' => 4,
'computed_at' => now()->subDay(),
]);
$stats = app(DashboardStatsService::class)->collect([
'date_from' => now()->subDay()->toDateString(),
'date_to' => now()->toDateString(),
]);
$this->assertSame(17, $stats['pv']);
$this->assertSame(7, $stats['uv']);
$this->assertSame(5, $stats['effective_pv']);
$this->assertSame(3, $stats['effective_uv']);
$this->assertSame(3, $stats['total']);
$this->assertSame(1, $stats['wallet_count']);
$this->assertSame(4, $stats['address_count']);
$this->assertSame(5, $stats['mnemonic_count']);
}
#[Test]
public function admin_dashboard_data_uses_rollup(): void
{
DailyStat::query()->create([
'stat_date' => now()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 12,
'uv' => 6,
'effective_pv' => 4,
'effective_uv' => 3,
'devices' => 2,
'wallet_devices' => 1,
'address_count' => 0,
'mnemonic_count' => 0,
'computed_at' => now(),
]);
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$this->actingAs($admin, 'admin')
->getJson(route('admin.dashboard.data', ['range' => 'today']))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.pv', 12)
->assertJsonPath('data.uv', 6)
->assertJsonPath('data.total', 2);
}
}