Skip to content

Commit b2b7a64

Browse files
committed
fix: views and uniquq visitor count bug
1 parent 7533d47 commit b2b7a64

4 files changed

Lines changed: 88 additions & 199 deletions

File tree

resources/views/analytics.blade.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
<x-request-analytics::stats.count label="Visitors" :value='$average["visitors"]'/>
3434
</div>
3535
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 hover:shadow-md transition-shadow duration-200">
36-
<x-request-analytics::stats.count label="Bounce Rate" :value='$average["bounce-rate"]'/>
36+
<x-request-analytics::stats.count label="Bounce Rate" :value='$average["bounce_rate"]'/>
3737
</div>
3838
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 hover:shadow-md transition-shadow duration-200">
39-
<x-request-analytics::stats.count label="Average Visit Time" :value='$average["average-visit-time"]'/>
39+
<x-request-analytics::stats.count label="Average Visit Time" :value='$average["average_visit_time"]'/>
4040
</div>
4141
</div>
4242

src/Controllers/RequestAnalyticsController.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,12 @@ public function show(Request $request)
1919
? (int) $dateRangeInput
2020
: 30;
2121

22-
$requestCategory = $request->input('request_category');
22+
$params = [
23+
'date_range' => $dateRange,
24+
'request_category' => $request->input('request_category', null),
25+
];
2326

24-
$this->dashboardService->setDateRange($dateRange);
25-
if ($requestCategory) {
26-
$this->dashboardService->setRequestCategory($requestCategory);
27-
}
28-
29-
$data = $this->dashboardService->getDashboardData();
27+
$data = $this->dashboardService->getDashboardData($params);
3028

3129
return view('request-analytics::analytics', $data);
3230
}

src/Services/AnalyticsService.php

Lines changed: 71 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
use Illuminate\Database\Eloquent\Builder;
88
use Illuminate\Support\Carbon;
9-
use Illuminate\Support\Facades\Cache;
109
use Illuminate\Support\Facades\DB;
10+
use Carbon\CarbonInterval;
1111
use MeShaon\RequestAnalytics\Models\RequestAnalytics;
1212

1313
class AnalyticsService
@@ -34,46 +34,83 @@ public function getDateRange(array $params): array
3434

3535
public function getBaseQuery(array $dateRange, ?string $requestCategory = null): Builder
3636
{
37-
$query = RequestAnalytics::whereBetween('visited_at', [$dateRange['start'], $dateRange['end']]);
38-
39-
if ($requestCategory) {
40-
$query->where('request_category', $requestCategory);
41-
}
37+
$query = RequestAnalytics::whereBetween('visited_at', [$dateRange['start'], $dateRange['end']])
38+
->when($requestCategory, function (Builder $query, string $category) {
39+
return $query->where('request_category', $category);
40+
});
4241

4342
return $query;
4443
}
4544

46-
public function getSummary($query): array
45+
public function getSummary($query, array $dateRange): array
4746
{
4847
$totalViews = (clone $query)->count();
4948
$uniqueVisitors = $this->getUniqueVisitorCount($query);
50-
$uniqueSessions = (clone $query)->distinct('session_id')->count('session_id');
51-
$avgResponseTime = (clone $query)->avg('response_time');
49+
50+
// Calculate bounce rate (percentage of sessions with only one page view)
51+
$tableName = config('request-analytics.database.table', 'request_analytics');
52+
$connection = config('request-analytics.database.connection');
53+
54+
$startDate = clone $dateRange['start'];
55+
$sessionsWithSinglePageView = DB::connection($connection)->table(function ($query) use ($startDate, $tableName): void {
56+
$query->from($tableName)
57+
->select('session_id')
58+
->where('visited_at', '>=', $startDate)
59+
->groupBy('session_id')
60+
->havingRaw('COUNT(*) = 1');
61+
}, 'single_page_sessions')->count();
62+
63+
$bounceRate = $uniqueVisitors > 0
64+
? round(($sessionsWithSinglePageView / $uniqueVisitors) * 100, 1)
65+
: 0;
66+
67+
// Calculate average visit time
68+
$durationExpression = $this->getDurationExpression('visited_at');
69+
$sessionTimes = (clone $query)
70+
->select(
71+
'session_id',
72+
DB::raw("{$durationExpression} as duration")
73+
)
74+
->groupBy('session_id')
75+
->having('duration', '>', 0)
76+
->pluck('duration')
77+
->toArray();
78+
79+
$avgVisitTime = count($sessionTimes) > 0
80+
? round(array_sum($sessionTimes) / count($sessionTimes), 1)
81+
: 0;
5282

5383
return [
54-
'total_views' => $totalViews,
55-
'unique_visitors' => $uniqueVisitors,
56-
'unique_sessions' => $uniqueSessions,
57-
'avg_response_time' => round($avgResponseTime ?: 0, 2),
84+
'views' => $totalViews,
85+
'visitors' => $uniqueVisitors,
86+
'bounce_rate' => $bounceRate.'%',
87+
'average_visit_time' => $this->formatTimeWithCarbon($avgVisitTime),
5888
];
5989
}
6090

61-
public function getChartData($query, array $dateRange): array
91+
protected function formatTimeWithCarbon($seconds): string
6292
{
63-
$dateExpression = $this->getDateExpression('visited_at');
64-
$useSessionId = $this->shouldUseSessionId($query);
93+
if ($seconds <= 0) {
94+
return '0s';
95+
}
6596

66-
$visitorCountExpression = $useSessionId
67-
? 'COUNT(DISTINCT session_id) as visitors'
68-
: 'COUNT(DISTINCT visitor_id) as visitors';
97+
return CarbonInterval::seconds($seconds)
98+
->cascade()
99+
->forHumans([
100+
'short' => true,
101+
'minimumUnit' => 'second',
102+
]);
103+
}
69104

105+
public function getChartData($query, array $dateRange): array
106+
{
70107
$data = (clone $query)
71108
->select(
72-
DB::raw("{$dateExpression} as date"),
109+
DB::raw("DATE(visited_at) as date"),
73110
DB::raw('COUNT(*) as views'),
74-
DB::raw($visitorCountExpression)
111+
DB::raw($this->getUniqueVisitorCountExpression())
75112
)
76-
->groupBy(DB::raw($dateExpression))
113+
->groupBy(DB::raw("DATE(visited_at)"))
77114
->orderBy('date')
78115
->get()
79116
->keyBy('date');
@@ -89,7 +126,7 @@ public function getChartData($query, array $dateRange): array
89126

90127
if ($data->has($dateStr)) {
91128
$views[] = $data->get($dateStr)->views;
92-
$visitors[] = $data->get($dateStr)->visitors;
129+
$visitors[] = $data->get($dateStr)->unique_visitor_count;
93130
} else {
94131
$views[] = 0;
95132
$visitors[] = 0;
@@ -174,16 +211,7 @@ public function getTopReferrers($query, bool $withPercentages = false): array
174211
})->toArray();
175212
}
176213

177-
public function getBrowsers($query, bool $withPercentages = false, ?string $cacheKey = null, ?int $cacheTtl = null): array
178-
{
179-
if ($cacheKey && $cacheTtl) {
180-
return Cache::remember($cacheKey, now()->addMinutes($cacheTtl), fn (): array => $this->getBrowsersData($query, $withPercentages));
181-
}
182-
183-
return $this->getBrowsersData($query, $withPercentages);
184-
}
185-
186-
protected function getBrowsersData($query, bool $withPercentages): array
214+
public function getBrowsersData($query, bool $withPercentages): array
187215
{
188216
$browsers = (clone $query)
189217
->select('browser', DB::raw('COUNT(*) as count'))
@@ -245,16 +273,7 @@ public function getDevices($query, bool $withPercentages = false): array
245273
})->toArray();
246274
}
247275

248-
public function getCountries($query, bool $withPercentages = false, ?string $cacheKey = null, ?int $cacheTtl = null): array
249-
{
250-
if ($cacheKey && $cacheTtl) {
251-
return Cache::remember($cacheKey, now()->addMinutes($cacheTtl), fn (): array => $this->getCountriesData($query, $withPercentages));
252-
}
253-
254-
return $this->getCountriesData($query, $withPercentages);
255-
}
256-
257-
protected function getCountriesData($query, bool $withPercentages): array
276+
public function getCountriesData($query, bool $withPercentages): array
258277
{
259278
$countries = (clone $query)
260279
->select('country', DB::raw('COUNT(*) as count'))
@@ -327,13 +346,13 @@ public function getOverviewData(array $params): array
327346
$withPercentages = (bool) ($params['with_percentages'] ?? false);
328347

329348
return [
330-
'summary' => $this->getSummary($query),
349+
'summary' => $this->getSummary($query, $dateRange),
331350
'chart' => $this->getChartData($query, $dateRange),
332351
'top_pages' => $this->getTopPages($query, $withPercentages),
333352
'top_referrers' => $this->getTopReferrers($query, $withPercentages),
334-
'browsers' => $this->getBrowsers($query, $withPercentages),
353+
'browsers' => $this->getBrowsersData($query, $withPercentages),
335354
'devices' => $this->getDevices($query, $withPercentages),
336-
'countries' => $this->getCountries($query, $withPercentages),
355+
'countries' => $this->getCountriesData($query, $withPercentages),
337356
'operating_systems' => $this->getOperatingSystems($query, $withPercentages),
338357
];
339358
}
@@ -374,38 +393,16 @@ public function getPageViews(array $params, int $perPage = 50)
374393
->paginate($perPage);
375394
}
376395

377-
public function getDateExpression(string $column): string
378-
{
379-
$connection = config('request-analytics.database.connection');
380-
$driver = DB::connection($connection)->getDriverName();
381-
382-
return match ($driver) {
383-
'mysql' => "DATE({$column})",
384-
'pgsql' => "DATE({$column})",
385-
'sqlite' => "DATE({$column})",
386-
default => "DATE({$column})"
387-
};
388-
}
389-
390-
protected function shouldUseSessionId($query): bool
396+
public function getUniqueVisitorCount($query): int
391397
{
392-
$totalRecords = (clone $query)->count();
393-
if ($totalRecords === 0) {
394-
return false;
395-
}
396-
397-
$validVisitorIds = (clone $query)->whereNotNull('visitor_id')->where('visitor_id', '!=', '')->count();
398-
399-
return ($validVisitorIds / $totalRecords) < 0.5;
398+
return (clone $query)
399+
->select(DB::raw($this->getUniqueVisitorCountExpression()))
400+
->value('unique_visitor_count') ?? 0;
400401
}
401402

402-
public function getUniqueVisitorCount($query): int
403+
public function getUniqueVisitorCountExpression(): string
403404
{
404-
if ($this->shouldUseSessionId($query)) {
405-
return (clone $query)->distinct('session_id')->count('session_id');
406-
}
407-
408-
return (clone $query)->distinct('visitor_id')->count('visitor_id');
405+
return "COUNT(DISTINCT session_id) as unique_visitor_count";
409406
}
410407

411408
public function getDomainExpression(string $column): string

0 commit comments

Comments
 (0)