66
77use Illuminate \Database \Eloquent \Builder ;
88use Illuminate \Support \Carbon ;
9- use Illuminate \Support \Facades \Cache ;
109use Illuminate \Support \Facades \DB ;
10+ use Carbon \CarbonInterval ;
1111use MeShaon \RequestAnalytics \Models \RequestAnalytics ;
1212
1313class 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