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

303 lines
9.3 KiB
PHP

@php
/**
* @api-readonly
* Returns developer project statistics from GitHub
*/
use Illuminate\Support\Facades\Http;
use Carbon\Carbon;
// Configuration
$token = env('GITHUB_TOKEN');
$org = 'Truncgil-Techology';
$projectNumber = 2;
if (!$token) {
echo json_encode(['status' => 'error', 'message' => 'GITHUB_TOKEN not set']);
exit;
}
// GraphQL Query to fetch Project V2 items and fields
$query = <<<GRAPHQL
query {
organization(login: "$org") {
projectV2(number: $projectNumber) {
title
items(first: 100) {
nodes {
fieldValues(first: 20) {
nodes {
... on ProjectV2ItemFieldSingleSelectValue {
name
field {
... on ProjectV2FieldCommon {
name
}
}
}
... on ProjectV2ItemFieldUserValue {
users(first: 5) {
nodes {
login
avatarUrl
name
}
}
field {
... on ProjectV2FieldCommon {
name
}
}
}
}
}
content {
... on Issue {
title
url
state
number
createdAt
updatedAt
closedAt
}
... on DraftIssue {
title
createdAt
updatedAt
}
... on PullRequest {
title
url
state
number
createdAt
updatedAt
closedAt
}
}
}
}
}
}
}
GRAPHQL;
try {
$response = Http::withToken($token)->post('https://api.github.com/graphql', [
'query' => $query
]);
if ($response->failed()) {
throw new Exception("GitHub API Error: " . $response->body());
}
$data = $response->json();
if (isset($data['errors'])) {
throw new Exception("GraphQL Error: " . json_encode($data['errors']));
}
$project = $data['data']['organization']['projectV2'] ?? null;
if (!$project) {
throw new Exception("Project not found or access denied.");
}
$items = $project['items']['nodes'];
// Data structures for advanced analysis
$developerStats = [];
$timelineStats = [
'weekly' => [],
'monthly' => []
];
$detailedItems = []; // Array for DataGrid
$statusMapping = [
'Done' => 'completed',
'Ready' => 'active',
'In progress' => 'active',
'In review' => 'active',
'Backlog' => 'pending',
'No Status' => 'pending'
];
foreach ($items as $item) {
$status = 'No Status';
$size = 'No Size';
$assignees = [];
// Parse Field Values
foreach ($item['fieldValues']['nodes'] as $fieldValue) {
if (!isset($fieldValue['field']['name'])) continue;
$fieldName = $fieldValue['field']['name'];
if ($fieldName === 'Status') {
$status = $fieldValue['name'];
} elseif ($fieldName === 'Size') {
$size = $fieldValue['name'];
} elseif ($fieldName === 'Assignees') {
foreach ($fieldValue['users']['nodes'] as $user) {
$assignees[] = $user['login'];
}
}
}
if (empty($assignees)) {
$assignees[] = 'Unassigned';
}
// Content details
$content = $item['content'] ?? [];
$title = $content['title'] ?? 'Untitled';
$url = $content['url'] ?? '#';
$state = $content['state'] ?? 'OPEN';
$createdAt = $content['createdAt'] ?? null;
$updatedAt = $content['updatedAt'] ?? null;
$closedAt = $content['closedAt'] ?? null;
// Determine completion date
$completionDate = ($state === 'CLOSED' || $state === 'MERGED' || $status === 'Done')
? ($closedAt ?? $updatedAt)
: null;
$isCompleted = ($completionDate !== null);
// Duration Calculation
$duration = 'N/A';
$durationSeconds = 0;
if ($createdAt) {
try {
$cStart = Carbon::parse($createdAt);
$endDateStr = $isCompleted ? $completionDate : $updatedAt;
if (!$endDateStr) $endDateStr = $createdAt;
$cEnd = Carbon::parse($endDateStr);
$diff = $cStart->diff($cEnd);
$parts = [];
if ($diff->y > 0) $parts[] = $diff->y . 'y';
if ($diff->m > 0) $parts[] = $diff->m . 'mo';
if ($diff->d > 0) $parts[] = $diff->d . 'd';
if ($diff->h > 0) $parts[] = $diff->h . 'h';
if (empty($parts) && $diff->i > 0) $parts[] = $diff->i . 'm';
if (empty($parts)) $parts[] = '0m';
$duration = implode(' ', array_slice($parts, 0, 2));
$durationSeconds = $cStart->diffInSeconds($cEnd);
} catch (Exception $e) {
$duration = 'Error';
}
}
// Add to detailed items list (For DataGrid)
$detailedItems[] = [
'title' => $title,
'status' => $status,
'size' => $size,
'assignees' => implode(', ', $assignees),
'url' => $url,
'duration' => $duration,
'duration_seconds' => $durationSeconds,
'created_at' => $createdAt ? Carbon::parse($createdAt)->format('Y-m-d H:i') : '-',
'end_date' => $completionDate ? Carbon::parse($completionDate)->format('Y-m-d H:i') : '-'
];
// Analyze for each assignee (For Charts/Cards)
foreach ($assignees as $assignee) {
if (!isset($developerStats[$assignee])) {
$developerStats[$assignee] = [
'total_tasks' => 0,
'completed_tasks' => 0,
'active_tasks' => 0,
'pending_tasks' => 0,
'sizes' => [],
'completion_history' => []
];
}
$developerStats[$assignee]['total_tasks']++;
$generalStatus = $statusMapping[$status] ?? 'active';
if ($isCompleted) {
$developerStats[$assignee]['completed_tasks']++;
if ($completionDate) {
$cDate = Carbon::parse($completionDate);
$monthKey = $cDate->format('Y-m');
if (!isset($developerStats[$assignee]['completion_history']['monthly'][$monthKey])) {
$developerStats[$assignee]['completion_history']['monthly'][$monthKey] = 0;
}
$developerStats[$assignee]['completion_history']['monthly'][$monthKey]++;
}
} else {
if ($generalStatus === 'active') {
$developerStats[$assignee]['active_tasks']++;
} else {
$developerStats[$assignee]['pending_tasks']++;
}
}
if (!isset($developerStats[$assignee]['sizes'][$size])) {
$developerStats[$assignee]['sizes'][$size] = 0;
}
$developerStats[$assignee]['sizes'][$size]++;
}
}
// Format Developer Stats for Frontend
$performanceData = [];
foreach ($developerStats as $assignee => $stats) {
$performanceData[] = [
'assignee' => $assignee,
'total' => $stats['total_tasks'],
'completed' => $stats['completed_tasks'],
'active' => $stats['active_tasks'],
'pending' => $stats['pending_tasks'],
'completion_rate' => $stats['total_tasks'] > 0 ? round(($stats['completed_tasks'] / $stats['total_tasks']) * 100, 1) : 0,
'sizes' => $stats['sizes'],
'history' => $stats['completion_history']
];
}
// Format Timeline Data
$timelineChartData = [];
$allMonths = [];
foreach ($developerStats as $assignee => $stats) {
if (isset($stats['completion_history']['monthly'])) {
$allMonths = array_merge($allMonths, array_keys($stats['completion_history']['monthly']));
}
}
$allMonths = array_unique($allMonths);
sort($allMonths);
foreach ($allMonths as $month) {
$row = ['month' => $month];
foreach ($developerStats as $assignee => $stats) {
$row[$assignee] = $stats['completion_history']['monthly'][$month] ?? 0;
}
$timelineChartData[] = $row;
}
echo json_encode([
'status' => 'success',
'project_title' => $project['title'],
'developer_performance' => $performanceData,
'timeline_chart' => $timelineChartData,
'raw_items' => $detailedItems, // Restored this field for DataGrid
'meta' => [
'total_tasks' => count($items)
]
], JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
}
@endphp