feat: Add Gitlab some datas sync

This commit is contained in:
Vincent BRIDIER 2026-01-02 15:30:52 +01:00
parent 90f27bf560
commit aa9483a155
13 changed files with 548 additions and 3 deletions

View file

@ -0,0 +1,260 @@
<?php
namespace App\Console\Commands;
use App\Models\GitlabIssues;
use App\Models\GitlabLabels;
use App\Models\GitlabMilestones;
use App\Models\GitlabProjects;
use Carbon\Carbon;
use GrahamCampbell\GitLab\Facades\GitLab;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
class UpdateGitlabDatas extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:update-gitlab-datas {--is-cron}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Fetch some Gitlab datas';
/**
* Execute the console command.
*
* @return void
*/
public function handle(): void
{
$isCron = $this->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.");
}
}

View file

@ -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);
}
/**

View file

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class GitlabIssues extends Model
{
/**
* Fillable array model
*
* @var array
*/
protected $fillable = [
'title',
'url',
'estimate_time',
'spent_time',
'is_closed',
'gitlab_id',
'gitlab_iid',
'milestone_iid',
'gitlab_project_id',
'g_created_at',
'g_updated_at',
'g_closed_at',
'g_due_date'
];
/**
* Gitlab Labels relationship
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function gitlabLabels()
{
return $this->belongsToMany(GitlabLabels::class, 'gitlab_issues_labels', 'issue_id', 'label_id')
->withTimestamps();
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class GitlabLabels extends Model
{
/**
* Fillable array model
*
* @var array
*/
protected $fillable = [
'name',
'gitlab_id'
];
/**
* Gitlab Issues relationship
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function issues()
{
return $this->belongsToMany(GitlabIssues::class, 'gitlab_issues_labels', 'label_id', 'issue_id')
->withTimestamps();
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class GitlabMilestones extends Model
{
/**
* Fillable array model
*
* @var array
*/
protected $fillable = [
'title',
'url',
'is_closed',
'gitlab_id',
'gitlab_iid',
'gitlab_project_id',
'g_created_at',
'g_updated_at',
'g_start_date',
'g_due_date'
];
}

View file

@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class GitlabProjects extends Model
{
/**
* Fillable array model
*
* @var array
*/
protected $fillable = [
'name',
'url',
'is_archived',
'gitlab_id'
];
}

View file

@ -17,7 +17,7 @@
"codegreencreative/laravel-samlidp": "^5.2",
"diglactic/laravel-breadcrumbs": "^9.0",
"doctrine/dbal": "^3.1",
"graham-campbell/gitlab": "^7.0",
"graham-campbell/gitlab": "^7.6",
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^11.0",
"laravel/tinker": "^2.8",

4
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7c22201effe7a464dcc8e3048d370b4f",
"content-hash": "e7bf9833354cd32d54b40e311c64f916",
"packages": [
{
"name": "brick/math",
@ -12600,5 +12600,5 @@
"php": "^8.3"
},
"platform-dev": {},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.9.0"
}

View file

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('gitlab_projects', function (Blueprint $table) {
$table->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');
}
};

View file

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('gitlab_issues', function (Blueprint $table) {
$table->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"],

View file

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('gitlab_labels', function (Blueprint $table) {
$table->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');
}
};

View file

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('gitlab_milestones', function (Blueprint $table) {
$table->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');
}
};

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('gitlab_issues_labels', function (Blueprint $table) {
$table->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');
}
};