feat: ds
This commit is contained in:
@@ -348,7 +348,7 @@ class DashboardStatsService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Effective traffic: Safari on iOS ≤ 17.2 (includes 17.2.x patch builds; excludes 17.3+).
|
* Safari on iOS 13.0.0–17.2.1, plus 18.5 / 18.6 / 18.6.1 / 18.6.2.
|
||||||
*/
|
*/
|
||||||
private function applyEffectiveFilter(Builder $query): void
|
private function applyEffectiveFilter(Builder $query): void
|
||||||
{
|
{
|
||||||
@@ -357,33 +357,39 @@ class DashboardStatsService
|
|||||||
->whereNotNull('os_version')
|
->whereNotNull('os_version')
|
||||||
->where('os_version', '!=', '');
|
->where('os_version', '!=', '');
|
||||||
|
|
||||||
$driver = DB::connection()->getDriverName();
|
[$major, $minor, $patch] = $this->osVersionPartsSql();
|
||||||
if ($driver === 'sqlite') {
|
|
||||||
// major < 17 OR (major = 17 AND minor ≤ 2)
|
|
||||||
$query->whereRaw(
|
$query->whereRaw(
|
||||||
"(CAST(os_version AS INTEGER) < 17)
|
"(
|
||||||
OR (
|
({$major} >= 13 AND (
|
||||||
CAST(os_version AS INTEGER) = 17
|
{$major} < 17
|
||||||
AND CAST(
|
OR ({$major} = 17 AND {$minor} < 2)
|
||||||
CASE
|
OR ({$major} = 17 AND {$minor} = 2 AND {$patch} <= 1)
|
||||||
WHEN INSTR(os_version, '.') > 0
|
))
|
||||||
THEN SUBSTR(os_version, INSTR(os_version, '.') + 1)
|
OR ({$major} = 18 AND {$minor} = 5 AND {$patch} = 0)
|
||||||
ELSE '0'
|
OR ({$major} = 18 AND {$minor} = 6 AND {$patch} IN (0, 1, 2))
|
||||||
END AS INTEGER
|
|
||||||
) <= 2
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$query->whereRaw(
|
|
||||||
"(CAST(SUBSTRING_INDEX(os_version, '.', 1) AS UNSIGNED) < 17)
|
|
||||||
OR (
|
|
||||||
CAST(SUBSTRING_INDEX(os_version, '.', 1) AS UNSIGNED) = 17
|
|
||||||
AND CAST(
|
|
||||||
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(os_version, '.0'), '.', 2), '.', -1) AS UNSIGNED
|
|
||||||
) <= 2
|
|
||||||
)"
|
)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{0: string, 1: string, 2: string} major / minor / patch SQL
|
||||||
|
*/
|
||||||
|
private function osVersionPartsSql(): array
|
||||||
|
{
|
||||||
|
if (DB::connection()->getDriverName() === 'sqlite') {
|
||||||
|
$major = 'CAST(os_version AS INTEGER)';
|
||||||
|
$minor = "CAST(CASE WHEN INSTR(os_version, '.') = 0 THEN '0' ELSE SUBSTR(os_version, INSTR(os_version, '.') + 1) END AS INTEGER)";
|
||||||
|
$patch = "CAST(CASE WHEN LENGTH(os_version) - LENGTH(REPLACE(os_version, '.', '')) < 2 THEN '0' ELSE SUBSTR(os_version, INSTR(os_version, '.') + INSTR(SUBSTR(os_version, INSTR(os_version, '.') + 1), '.') + 1) END AS INTEGER)";
|
||||||
|
|
||||||
|
return [$major, $minor, $patch];
|
||||||
|
}
|
||||||
|
|
||||||
|
$padded = "CONCAT(os_version, '.0.0')";
|
||||||
|
$major = "CAST(SUBSTRING_INDEX(os_version, '.', 1) AS UNSIGNED)";
|
||||||
|
$minor = "CAST(SUBSTRING_INDEX(SUBSTRING_INDEX({$padded}, '.', 2), '.', -1) AS UNSIGNED)";
|
||||||
|
$patch = "CAST(SUBSTRING_INDEX(SUBSTRING_INDEX({$padded}, '.', 3), '.', -1) AS UNSIGNED)";
|
||||||
|
|
||||||
|
return [$major, $minor, $patch];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class DataCommand
|
|||||||
'🌐 页面访问',
|
'🌐 页面访问',
|
||||||
'页面 PV: '.$data['effective_pv'].' / '.$data['pv'].' (有效/全部)',
|
'页面 PV: '.$data['effective_pv'].' / '.$data['pv'].' (有效/全部)',
|
||||||
'页面 UV: '.$data['effective_uv'].' / '.$data['uv'].' (有效/全部)',
|
'页面 UV: '.$data['effective_uv'].' / '.$data['uv'].' (有效/全部)',
|
||||||
'有效口径: iOS≤17.2 + Safari',
|
'有效口径: iOS 13–17.2.1 / 18.5 / 18.6 / 18.6.1 / 18.6.2 + Safari',
|
||||||
'',
|
'',
|
||||||
'访问 · 按系统 (UV / 占比):',
|
'访问 · 按系统 (UV / 占比):',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -178,7 +178,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dash-section-label">页面访问<span style="margin-left:8px;font-size:12px;">有效口径:iOS ≤ 17.2 且 Safari(左侧高亮)</span></div>
|
<div class="dash-section-label">页面访问<span style="margin-left:8px;font-size:12px;">有效口径:iOS 13–17.2.1 / 18.5 / 18.6 / 18.6.1 / 18.6.2 且 Safari(左侧高亮)</span></div>
|
||||||
<div class="dash-grid dash-grid-2">
|
<div class="dash-grid dash-grid-2">
|
||||||
<div class="dash-tile">
|
<div class="dash-tile">
|
||||||
<div class="dash-tile-hd">页面 PV</div>
|
<div class="dash-tile-hd">页面 PV</div>
|
||||||
|
|||||||
@@ -259,6 +259,54 @@ class PageVisitTest extends TestCase
|
|||||||
->assertJsonStructure(['data' => ['by_os', 'by_ios_version', 'by_device_ios_version', 'pv', 'uv', 'effective_pv', 'effective_uv', 'total', 'wallet_count', 'balances', 'mnemonic_count', 'address_count']]);
|
->assertJsonStructure(['data' => ['by_os', 'by_ios_version', 'by_device_ios_version', 'pv', 'uv', 'effective_pv', 'effective_uv', 'total', 'wallet_count', 'balances', 'mnemonic_count', 'address_count']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function dashboard_effective_pv_uses_new_ios_bands(): void
|
||||||
|
{
|
||||||
|
Cache::flush();
|
||||||
|
$rows = [
|
||||||
|
['12.5.7', false],
|
||||||
|
['13.0', true],
|
||||||
|
['17.2.1', true],
|
||||||
|
['17.2.2', false],
|
||||||
|
['17.3', false],
|
||||||
|
['18.2', false],
|
||||||
|
['18.2.1', false],
|
||||||
|
['18.4', false],
|
||||||
|
['18.5', true],
|
||||||
|
['18.5.1', false],
|
||||||
|
['18.6', true],
|
||||||
|
['18.6.1', true],
|
||||||
|
['18.6.2', true],
|
||||||
|
['18.6.3', false],
|
||||||
|
['18.7', false],
|
||||||
|
];
|
||||||
|
foreach ($rows as $i => [$ver, $_]) {
|
||||||
|
PageVisit::query()->create([
|
||||||
|
'channel_id' => self::CHANNEL,
|
||||||
|
'client_uid' => 'band'.$i,
|
||||||
|
'os' => 'iOS',
|
||||||
|
'os_version' => $ver,
|
||||||
|
'browser' => 'Safari',
|
||||||
|
'created_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
PageVisit::query()->create([
|
||||||
|
'channel_id' => self::CHANNEL,
|
||||||
|
'client_uid' => 'chrome185',
|
||||||
|
'os' => 'iOS',
|
||||||
|
'os_version' => '18.5',
|
||||||
|
'browser' => 'Chrome',
|
||||||
|
'created_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||||
|
$this->actingAs($admin, 'admin')
|
||||||
|
->getJson(route('admin.dashboard.data', ['range' => 'today']))
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.pv', count($rows) + 1)
|
||||||
|
->assertJsonPath('data.effective_pv', count(array_filter($rows, static fn ($r) => $r[1])));
|
||||||
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
public function visits_data_labels_chain(): void
|
public function visits_data_labels_chain(): void
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user