@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 = <<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