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:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user