option('is-cron'); $groupId = 23; $this->updateProjectsByGroup($groupId); $this->updateLabelsByGroup($groupId); $projects = GitlabProjects::all(); foreach ($projects as $project) { $this->updateMilestonesByProject($project); $this->updateIssuesByProject($project, $isCron); } $this->syncTicketStatus(); } /** * updateProjectsByGroup function * * @param integer $groupId * @return void */ private function updateProjectsByGroup(int $groupId): void { $allProjects = []; $page = 1; do { $projects = GitLab::groups()->projects($groupId, [ 'order_by' => 'created_at', 'sort' => 'asc', 'per_page' => 100, 'page' => $page, 'include_subgroups' => true ]); $allProjects = array_merge($allProjects, $projects); $page++; } while (!empty($projects)); $this->info(sprintf( "Fetched %d projects in group %d.", count($allProjects), $groupId )); foreach ($allProjects as $project) { GitlabProjects::upsert( [ 'name' => $project['name'], 'url' => $project['web_url'], 'is_archived' => $project['archived'], 'gitlab_id' => $project['id'] ], ['gitlab_id'] ); } $this->info("Gitlab Projects upsert complete."); } /** * updateLabelsByGroup function * * @param integer $groupId * @return void */ private function updateLabelsByGroup(int $groupId): void { $allLabels = []; $page = 1; do { $labels = GitLab::groups()->labels($groupId, [ 'per_page' => 100, 'page' => $page, ]); $allLabels = array_merge($allLabels, $labels); $page++; } while (!empty($labels)); $this->info(sprintf( "Fetched %d labels in group %d.", count($allLabels), $groupId )); foreach ($allLabels as $project) { GitlabLabels::upsert( [ 'name' => $project['name'], 'gitlab_id' => $project['id'] ], ['gitlab_id'] ); } $this->info("Gitlab labels upsert complete."); } /** * updateIssuesByProject function * * @param GitlabProjects $project * @param boolean $isCron * @return void */ private function updateIssuesByProject(GitlabProjects $project, bool $isCron = false): void { $projectId = $project->gitlab_id; $allIssues = []; $page = 1; do { $params = [ 'order_by' => 'created_at', 'sort' => 'asc', 'per_page' => 100, 'page' => $page, 'state' => 'all', ]; if ($isCron) { $params = array_merge($params, ['updated_after' => now()->subDay()]); } $issues = GitLab::projects()->issues($projectId, $params); $allIssues = array_merge($allIssues, $issues); $page++; } while (!empty($issues)); $this->info(sprintf( "Fetched %d issues in project %d.", count($allIssues), $projectId )); foreach ($allIssues as $issue) { GitlabIssues::upsert( [ 'title' => $issue['title'], 'url' => $issue['web_url'], 'estimate_time' => $issue['time_stats']['time_estimate'] ?? 0, 'spent_time' => $issue['time_stats']['total_time_spent'] ?? 0, 'is_closed' => $issue['state'] === 'closed', 'gitlab_id' => $issue['id'], 'gitlab_iid' => $issue['iid'], 'milestone_iid' => $issue['milestone']['iid'] ?? null, 'gitlab_project_id' => $projectId, 'g_created_at' => Carbon::parse($issue['created_at'])->format('Y-m-d'), 'g_updated_at' => Carbon::parse($issue['updated_at'])->format('Y-m-d'), 'g_closed_at' => !is_null($issue['closed_at']) ? Carbon::parse($issue['closed_at'])->format('Y-m-d') : null, 'g_due_date' => !is_null($issue['due_date']) ? Carbon::parse($issue['due_date'])->format('Y-m-d') : null ], ['gitlab_id'] ); $issueModel = GitlabIssues::where('gitlab_id', $issue['id'])->firstOrFail(); $labelIds = []; foreach ($issue['labels'] as $labelName) { $label = Cache::remember("gitlab_label_{$labelName}", 300, function () use ($labelName) { return GitlabLabels::where('name', $labelName)->first(); }); if ($label) { $labelIds[] = $label; } } $issueModel->gitlabLabels()->sync($labelIds); }//end foreach $this->info("Gitlab issues upsert complete."); } /** * updateMilestonesByProject function * * @param GitlabProjects $project * @return void */ private function updateMilestonesByProject(GitlabProjects $project): void { $projectId = $project->gitlab_id; $allMilestones = []; $page = 1; do { $milestones = []; try { $milestones = GitLab::milestones()->all($projectId, [ 'per_page' => 100, 'page' => $page ]); } catch (\Exception $e) { if ($e->getCode() === 404) { if ($project->delete()) { Log::warning(sprintf( "Project not found (%s), it has been deleted.", $projectId )); } else { Log::warning(sprintf( "Project not found (%s).", $projectId )); } } else { Log::error(sprintf( 'API Error for project %s, Message: %d', $projectId, $e->getMessage() )); report($e); } } $allMilestones = array_merge($allMilestones, $milestones); $page++; } while (!empty($milestones)); $this->info(sprintf( "Fetched %d milestones in project %d.", count($allMilestones), $projectId )); foreach ($allMilestones as $milestone) { GitlabMilestones::upsert( [ 'title' => $milestone['title'], 'url' => $milestone['web_url'], 'is_closed' => $milestone['state'] === 'closed', 'gitlab_id' => $milestone['id'], 'gitlab_iid' => $milestone['iid'], 'gitlab_project_id' => $projectId, 'g_created_at' => Carbon::parse($milestone['created_at'])->format('Y-m-d'), 'g_updated_at' => Carbon::parse($milestone['updated_at'])->format('Y-m-d'), 'g_start_date' => !is_null($milestone['start_date']) ? Carbon::parse($milestone['start_date'])->format('Y-m-d') : null, 'g_due_date' => !is_null($milestone['due_date']) ? Carbon::parse($milestone['due_date'])->format('Y-m-d') : null ], ['gitlab_id'] ); }//end foreach $this->info("Gitlab milestones upsert complete."); } /** * Synchronizes ticket statuses based on GitLab issue labels. * * @return void */ private function syncTicketStatus(): void { $tickets = Ticket::whereNotNull('gitlab_issue')->whereNot('status', TicketStatusEnum::archived)->get(); foreach ($tickets as $ticket) { $gitlabIssue = GitlabIssues::where('url', $ticket->gitlab_issue)->with('gitlabLabels')->first(); $gitlabIssueLabels = $gitlabIssue ? $gitlabIssue->gitlabLabels->pluck('name') : []; if ($gitlabIssueLabels) { match (true) { $gitlabIssueLabels->contains('g-wip') => $this->updateTicketStatus($ticket, TicketStatusEnum::inDev), $gitlabIssueLabels->contains('g-production') => $this->updateTicketStatus($ticket, TicketStatusEnum::production), default => null, }; } } } /** * Updates the ticket status only if it has changed. * * @param Ticket $ticket The ticket to update. * @param TicketStatusEnum $status The new status to apply. * * @return void */ private function updateTicketStatus(Ticket $ticket, TicketStatusEnum $status): void { if ($ticket->status !== $status && $ticket->status !== TicketStatusEnum::waiting) { $ticket->status = $status; $ticket->saveOrFail(); } } }