ikinci temizlik tamamlandı
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
@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
|
||||
Reference in New Issue
Block a user