@php /** * @api-readonly * Returns developer team statistics from GitHub */ use Illuminate\Support\Facades\Http; use Carbon\Carbon; // Configuration $token = env('GITHUB_TOKEN'); $repoStr = env('GITHUB_REPO'); // Expected format: "owner/repo" // Filter Configuration: // Commits with total line changes (additions + deletions) greater than this value // will have their line counts ignored in the stats (treated as 0 lines). // This effectively bypasses vendor/node_modules dumps or massive removals. $ignoredLinesThreshold = 5000; if (!$token || !$repoStr) { echo json_encode([ 'status' => 'error', 'message' => 'GITHUB_TOKEN or GITHUB_REPO not set in .env' ]); exit; } $parts = explode('/', $repoStr); if (count($parts) !== 2) { echo json_encode(['status' => 'error', 'message' => 'Invalid GITHUB_REPO format. Expected owner/repo']); exit; } $owner = $parts[0]; $name = $parts[1]; // GraphQL Query to fetch Commits // This allows us to see authors even if they are not verified GitHub users (unlike stats/contributors) $query = <<withHeaders([ 'User-Agent' => 'Laravel/10.0', 'Accept' => 'application/json', ]) ->post('https://api.github.com/graphql', [ 'query' => $query, 'variables' => [ 'owner' => $owner, 'name' => $name, 'cursor' => $cursor ] ]); if ($response->failed()) { throw new Exception("GitHub API Error: " . $response->body()); } $json = $response->json(); if (isset($json['errors'])) { throw new Exception("GraphQL Error: " . json_encode($json['errors'])); } $history = $json['data']['repository']['defaultBranchRef']['target']['history'] ?? null; if (!$history) { break; } $commits = $history['nodes']; $allCommits = array_merge($allCommits, $commits); $hasNextPage = $history['pageInfo']['hasNextPage']; $cursor = $history['pageInfo']['endCursor']; $pageCount++; } // Process Data $monthlyStats = []; $monthlyTotals = []; $totalCommitsProject = 0; foreach ($allCommits as $commit) { // Outlier Filter (Bypass Vendor/Large Changes) // If a commit changes more lines than the threshold, we ignore its line counts // but still count it as a commit activity. $totalChanges = $commit['additions'] + $commit['deletions']; if ($totalChanges > $ignoredLinesThreshold) { $commit['additions'] = 0; $commit['deletions'] = 0; } $date = Carbon::parse($commit['committedDate']); $monthKey = $date->format('Y-m'); $authorData = $commit['author']; // Prefer GitHub Login, fallback to Git Author Name $authorName = $authorData['user']['login'] ?? $authorData['name'] ?? 'Unknown'; // Grouping key $key = $authorName . '_' . $monthKey; // Initialize Monthly Total for relative percentage if (!isset($monthlyTotals[$monthKey])) { $monthlyTotals[$monthKey] = 0; } $monthlyTotals[$monthKey]++; // Initialize Developer Stats if (!isset($monthlyStats[$key])) { $monthlyStats[$key] = [ 'author' => $authorName, 'month' => $monthKey, 'added' => 0, 'deleted' => 0, 'commits' => 0, ]; } $monthlyStats[$key]['added'] += $commit['additions']; $monthlyStats[$key]['deleted'] += $commit['deletions']; $monthlyStats[$key]['commits']++; $totalCommitsProject++; } // Calculate percentages and format for DevExtreme $resultData = array_values($monthlyStats); // Sort by month descending usort($resultData, function($a, $b) { return strcmp($b['month'], $a['month']); }); // Add percentages foreach ($resultData as &$item) { // Global contribution percentage $item['percentage'] = $totalCommitsProject > 0 ? round(($item['commits'] / $totalCommitsProject) * 100, 2) : 0; // Monthly relative contribution percentage $monthTotal = $monthlyTotals[$item['month']] ?? 0; $item['monthly_percentage'] = $monthTotal > 0 ? round(($item['commits'] / $monthTotal) * 100, 2) : 0; } echo json_encode([ 'status' => 'success', 'data' => $resultData, 'meta' => [ 'repo' => $repoStr, 'total_commits' => $totalCommitsProject, 'fetched_commits' => count($allCommits), 'ignored_threshold' => $ignoredLinesThreshold ] ], JSON_UNESCAPED_UNICODE); } catch (Exception $e) { echo json_encode([ 'status' => 'error', 'message' => $e->getMessage() ], JSON_UNESCAPED_UNICODE); } @endphp