diff --git a/app/Console/Commands/UpdateGitlabDatas.php b/app/Console/Commands/UpdateGitlabDatas.php new file mode 100644 index 0000000..e85a6c1 --- /dev/null +++ b/app/Console/Commands/UpdateGitlabDatas.php @@ -0,0 +1,260 @@ +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); + } + } + + /** + * 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(); + + foreach ($issue['labels'] as $labelName) { + $label = Cache::remember("gitlab_label_{$labelName}", 300, function () use ($labelName) { + return GitlabLabels::where('name', $labelName)->first(); + }); + + if ($label) { + $issueModel->gitlabLabels()->syncWithoutDetaching($label); + } + } + }//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 = GitLab::milestones()->all($projectId, [ + 'per_page' => 100, + 'page' => $page + ]); + $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."); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index deaa85a..f226000 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -28,6 +28,7 @@ class Kernel extends ConsoleKernel $schedule->job(ExportTicketsState::class)->weekly()->mondays()->at('07:00'); $schedule->job(AutoCloseOldTickets::class)->dailyAt('08:00'); $schedule->job(WarnBeforeAutoCloseTicket::class)->dailyAt('08:05'); + $schedule->command('app:update-gitlab-datas --is-cron')->twiceDaily(1, 13); } /** diff --git a/app/Enums/Tickets/TicketTypeEnum.php b/app/Enums/Tickets/TicketTypeEnum.php index 620fcf1..7d203a1 100644 --- a/app/Enums/Tickets/TicketTypeEnum.php +++ b/app/Enums/Tickets/TicketTypeEnum.php @@ -15,7 +15,7 @@ enum TicketTypeEnum: int case qrcode = 5; case advice = 6; case complaints = 7; - case mailing = 8; + case mailing = 8; /** * Optionnal labels definition diff --git a/app/Http/Controllers/Bo/TicketController.php b/app/Http/Controllers/Bo/TicketController.php index 0b4fe66..7053cb0 100644 --- a/app/Http/Controllers/Bo/TicketController.php +++ b/app/Http/Controllers/Bo/TicketController.php @@ -881,12 +881,12 @@ class TicketController extends Controller } /** - * Update ticket priority. - * - * @param UpdateTicketPriorityRequest $request - * @param Ticket $ticket - * @return void - */ + * Update ticket priority. + * + * @param UpdateTicketPriorityRequest $request + * @param Ticket $ticket + * @return void + */ public function changePriority(UpdateTicketPriorityRequest $request, Ticket $ticket) { $newPriority = $request->validated('priority'); diff --git a/app/Http/Requests/Bo/UpdateTicketPriorityRequest.php b/app/Http/Requests/Bo/UpdateTicketPriorityRequest.php index f3a5d45..79c4a06 100644 --- a/app/Http/Requests/Bo/UpdateTicketPriorityRequest.php +++ b/app/Http/Requests/Bo/UpdateTicketPriorityRequest.php @@ -27,7 +27,7 @@ class UpdateTicketPriorityRequest extends FormRequest protected function prepareForValidation(): void { $this->ticket = $this->route('ticket'); - $max = Ticket::query()->where('site_id', $this->ticket->site_id)->max('order'); + $max = Ticket::query()->where('site_id', $this->ticket->site_id)->max('order'); $this->merge([ 'priority' => min($this->integer('priority'), $max) diff --git a/app/Models/GitlabIssues.php b/app/Models/GitlabIssues.php new file mode 100644 index 0000000..769705a --- /dev/null +++ b/app/Models/GitlabIssues.php @@ -0,0 +1,40 @@ +belongsToMany(GitlabLabels::class, 'gitlab_issues_labels', 'issue_id', 'label_id') + ->withTimestamps(); + } +} diff --git a/app/Models/GitlabLabels.php b/app/Models/GitlabLabels.php new file mode 100644 index 0000000..4701b71 --- /dev/null +++ b/app/Models/GitlabLabels.php @@ -0,0 +1,29 @@ +belongsToMany(GitlabIssues::class, 'gitlab_issues_labels', 'label_id', 'issue_id') + ->withTimestamps(); + } +} diff --git a/app/Models/GitlabMilestones.php b/app/Models/GitlabMilestones.php new file mode 100644 index 0000000..36af898 --- /dev/null +++ b/app/Models/GitlabMilestones.php @@ -0,0 +1,26 @@ +id(); + $table->string('name')->comment("Gitlab project name"); + $table->string('url')->comment("Gitlab URL"); + $table->boolean('is_archived')->comment("Gitlab archive status"); + $table->integer('gitlab_id')->unique()->comment("Gitlab unique ID"); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('gitlab_projects'); + } +}; diff --git a/database/migrations/2026_01_02_111055_create_gitlab_issues_table.php b/database/migrations/2026_01_02_111055_create_gitlab_issues_table.php new file mode 100644 index 0000000..c4496f0 --- /dev/null +++ b/database/migrations/2026_01_02_111055_create_gitlab_issues_table.php @@ -0,0 +1,42 @@ +id(); + $table->string('title')->comment("Gitlab issue title"); + $table->string('url')->comment("Gitlab URL"); + $table->integer('estimate_time')->default(0)->comment("Gitlab issue estimate time in seconds"); + $table->integer('spent_time')->default(0)->comment("Gitlab issue spent time in seconds"); + $table->boolean('is_closed')->comment("Gitlab issue status"); + $table->integer('gitlab_id')->comment("Gitlab issue id"); + $table->integer('gitlab_iid')->comment("Gitlab issue iid in project"); + $table->integer('milestone_iid')->nullable()->comment("Gitlab issue linked milestone iid in project"); + $table->integer('gitlab_project_id')->comment("Gitlab project id"); + $table->date('g_created_at')->comment("Gitlab issue created at"); + $table->date('g_updated_at')->comment("Gitlab issue updated at"); + $table->date('g_closed_at')->nullable()->comment("Gitlab issue closed at"); + $table->date('g_due_date')->nullable()->comment("Gitlab issue due date"); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('gitlab_issues'); + } +}; + +//"labels" : ["foo", "bar"], \ No newline at end of file diff --git a/database/migrations/2026_01_02_121235_create_gitlab_labels_table.php b/database/migrations/2026_01_02_121235_create_gitlab_labels_table.php new file mode 100644 index 0000000..8da813b --- /dev/null +++ b/database/migrations/2026_01_02_121235_create_gitlab_labels_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('name')->unique()->comment("Label name"); + $table->integer('gitlab_id')->comment("Label Gitlab id"); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('gitlab_labels'); + } +}; diff --git a/database/migrations/2026_01_02_124727_create_gitlab_milestones_table.php b/database/migrations/2026_01_02_124727_create_gitlab_milestones_table.php new file mode 100644 index 0000000..bc593a0 --- /dev/null +++ b/database/migrations/2026_01_02_124727_create_gitlab_milestones_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('title')->comment("Milestone title"); + $table->string('url')->comment("Gitlab URL"); + $table->boolean('is_closed')->comment("Gitlab milestone status"); + $table->integer('gitlab_id')->comment("Gitlab milestone id"); + $table->integer('gitlab_iid')->comment("Gitlab milestone iid in project"); + $table->integer('gitlab_project_id')->comment("Gitlab project id"); + $table->date('g_created_at')->comment("Gitlab milestone created at"); + $table->date('g_updated_at')->comment("Gitlab milestone updated at"); + $table->date('g_start_date')->nullable()->comment("Gitlab milestone start date"); + $table->date('g_due_date')->nullable()->comment("Gitlab milestone due date"); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('gitlab_milestones'); + } +}; diff --git a/database/migrations/2026_01_02_142743_create_pivot_gitlab_issues_labels.php b/database/migrations/2026_01_02_142743_create_pivot_gitlab_issues_labels.php new file mode 100644 index 0000000..0b7d793 --- /dev/null +++ b/database/migrations/2026_01_02_142743_create_pivot_gitlab_issues_labels.php @@ -0,0 +1,30 @@ +foreignId('issue_id')->constrained('gitlab_issues')->cascadeOnDelete(); + $table->foreignId('label_id')->constrained('gitlab_labels')->cascadeOnDelete(); + $table->timestamps(); + + $table->primary(['issue_id', 'label_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('gitlab_issues_labels'); + } +};