funnel->steps; if (empty($steps)) { return []; } $websiteId = $this->funnel->website_id; // Step 1: get all visitor IDs who completed the first step $currentVisitorIds = $this->visitorIdsForStep($steps[0], $websiteId); $firstStepCount = $currentVisitorIds->count(); $results = []; foreach ($steps as $i => $step) { if ($i === 0) { $stepVisitors = $firstStepCount; } else { $stepVisitorIds = $this->visitorIdsForStep($step, $websiteId); $currentVisitorIds = $currentVisitorIds->intersect($stepVisitorIds); $stepVisitors = $currentVisitorIds->count(); } $prevCount = $i > 0 ? $results[$i - 1]['visitors'] : $stepVisitors; $dropOff = $prevCount - $stepVisitors; $dropOffPct = $prevCount > 0 ? round(($dropOff / $prevCount) * 100, 1) : 0; $conversionPct = $firstStepCount > 0 ? round(($stepVisitors / $firstStepCount) * 100, 1) : 0; $results[] = [ 'label' => $step['label'] ?? $step['value'], 'type' => $step['type'], 'value' => $step['value'], 'visitors' => $stepVisitors, 'drop_off' => $dropOff, 'drop_off_pct' => $dropOffPct, 'conversion_pct' => $conversionPct, ]; } return $results; } private function visitorIdsForStep(array $step, int $websiteId): Collection { if ($step['type'] === 'pageview') { return PageView::where('website_id', $websiteId) ->whereBetween('viewed_at', [$this->from, $this->to]) ->where('path', $step['value']) ->distinct() ->pluck('visitor_id'); } return Event::where('website_id', $websiteId) ->whereBetween('occurred_at', [$this->from, $this->to]) ->where('name', $step['value']) ->distinct() ->pluck('visitor_id'); } }