Files
citrus-cms/resources/views/admin-ajax/developer-team-stats.blade.php
T
2026-04-28 21:15:09 +03:00

199 lines
5.8 KiB
PHP

@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 = <<<GRAPHQL
query(\$owner: String!, \$name: String!, \$cursor: String) {
repository(owner: \$owner, name: \$name) {
defaultBranchRef {
target {
... on Commit {
history(first: 25, after: \$cursor) {
pageInfo {
hasNextPage
endCursor
}
nodes {
committedDate
additions
deletions
author {
name
email
user {
login
avatarUrl
}
}
}
}
}
}
}
}
}
GRAPHQL;
try {
$allCommits = [];
$cursor = null;
$hasNextPage = true;
$pageCount = 0;
$maxPages = 20; // Fetch up to 1000 commits to avoid timeout
while ($hasNextPage && $pageCount < $maxPages) {
$response = Http::withToken($token)
->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