out new baseline
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* GitWebhookIngestionService.php
|
||||
*
|
||||
* Normalises Git provider webhooks and links commits to Helpdesk tickets.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Services;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Modules\Helpdesk\Models\GitCommit;
|
||||
use Modules\Helpdesk\Models\GitRepository;
|
||||
@@ -14,7 +23,10 @@ use Throwable;
|
||||
|
||||
final class GitWebhookIngestionService
|
||||
{
|
||||
private const TICKET_REFERENCE_PATTERN = '/\bHD-\d{4}-\d{5}\b/i';
|
||||
/**
|
||||
* Matches the generated Helpdesk reference format, for example HD-2026-00042.
|
||||
*/
|
||||
private const TICKET_REFERENCE_PATTERN = '/\bHD[-\s_]?(\d{4})[-\s_]?(\d{1,8})\b/i';
|
||||
|
||||
/**
|
||||
* Persist supported Git provider commits and link them to matching Helpdesk ticket references.
|
||||
@@ -39,14 +51,14 @@ final class GitWebhookIngestionService
|
||||
return $result;
|
||||
}
|
||||
|
||||
$branch = $this->branchFromRef($payload['ref'] ?? null);
|
||||
$branch = $this->branchFromPayload($payload);
|
||||
$commits = $this->commitsFromPayload($payload);
|
||||
$commitCount = 0;
|
||||
$linkedCount = 0;
|
||||
$ignoredCount = 0;
|
||||
|
||||
foreach ($commits as $commit) {
|
||||
$hash = $this->stringOrNull($commit['id'] ?? $commit['sha'] ?? null);
|
||||
$hash = $this->commitHash($commit);
|
||||
|
||||
if ($hash === null) {
|
||||
$ignoredCount++;
|
||||
@@ -54,13 +66,19 @@ final class GitWebhookIngestionService
|
||||
continue;
|
||||
}
|
||||
|
||||
$message = $this->stringOrNull($commit['message'] ?? null) ?? '';
|
||||
$url = $this->stringOrNull($commit['url'] ?? null);
|
||||
$message = $this->stringOrNull($commit['message'] ?? $commit['title'] ?? $commit['summary'] ?? null) ?? '';
|
||||
$url = $this->commitUrl($commit);
|
||||
$author = is_array($commit['author'] ?? null) ? $commit['author'] : [];
|
||||
$committer = is_array($commit['committer'] ?? null) ? $commit['committer'] : [];
|
||||
$authorName = $this->stringOrNull($author['name'] ?? $committer['name'] ?? null);
|
||||
$authorEmail = $this->stringOrNull($author['email'] ?? $committer['email'] ?? null);
|
||||
$committedAt = $this->parseTimestamp($commit['timestamp'] ?? $commit['committed_at'] ?? null);
|
||||
$authorName = $this->stringOrNull($author['name'] ?? $author['username'] ?? $committer['name'] ?? null);
|
||||
$authorEmail = $this->stringOrNull($author['email'] ?? $author['raw'] ?? $committer['email'] ?? null);
|
||||
$committedAt = $this->parseTimestamp(
|
||||
$commit['timestamp']
|
||||
?? $commit['committed_at']
|
||||
?? $commit['date']
|
||||
?? Arr::get($commit, 'author.date')
|
||||
?? null,
|
||||
);
|
||||
$ticket = $this->ticketForCommit($repository, $message, $branch, $url);
|
||||
|
||||
GitCommit::query()->updateOrCreate(
|
||||
@@ -134,7 +152,7 @@ final class GitWebhookIngestionService
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the first Helpdesk ticket reference from arbitrary Git text.
|
||||
* Extract and normalise the first Helpdesk ticket reference from arbitrary Git text.
|
||||
*/
|
||||
public function extractTicketReference(string $value): ?string
|
||||
{
|
||||
@@ -142,7 +160,7 @@ final class GitWebhookIngestionService
|
||||
return null;
|
||||
}
|
||||
|
||||
return strtoupper($matches[0]);
|
||||
return sprintf('HD-%s-%05d', $matches[1], (int) $matches[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,24 +168,40 @@ final class GitWebhookIngestionService
|
||||
*/
|
||||
private function isPushEvent(string $provider, ?string $event): bool
|
||||
{
|
||||
$normalised = strtolower((string) $event);
|
||||
$normalisedProvider = Str::lower(trim($provider));
|
||||
$normalisedEvent = Str::of((string) $event)->lower()->replace(['_', '-'], ' ')->squish()->value();
|
||||
|
||||
return match ($provider) {
|
||||
'github' => $normalised === 'push',
|
||||
'gitlab' => in_array($normalised, ['push hook', 'push'], true),
|
||||
default => $normalised === '' || $normalised === 'push',
|
||||
return match ($normalisedProvider) {
|
||||
'github' => in_array($normalisedEvent, ['push', ''], true),
|
||||
'gitlab' => in_array($normalisedEvent, ['push hook', 'push', ''], true),
|
||||
'bitbucket' => in_array($normalisedEvent, ['repo push', 'push', ''], true),
|
||||
default => in_array($normalisedEvent, ['push', 'repo push', 'push hook', ''], true),
|
||||
};
|
||||
}
|
||||
|
||||
private function branchFromRef(mixed $value): ?string
|
||||
/**
|
||||
* Read the branch name from common Git provider payload shapes.
|
||||
*
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
private function branchFromPayload(array $payload): ?string
|
||||
{
|
||||
$ref = $this->stringOrNull($value);
|
||||
$ref = $this->stringOrNull(
|
||||
$payload['ref']
|
||||
?? Arr::get($payload, 'push.changes.0.new.name')
|
||||
?? Arr::get($payload, 'object_attributes.ref')
|
||||
?? null,
|
||||
);
|
||||
|
||||
if ($ref === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str_replace('refs/heads/', '', $ref);
|
||||
return Str::of($ref)
|
||||
->replaceStart('refs/heads/', '')
|
||||
->replaceStart('refs/tags/', '')
|
||||
->trim()
|
||||
->value();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,13 +211,46 @@ final class GitWebhookIngestionService
|
||||
*/
|
||||
private function commitsFromPayload(array $payload): array
|
||||
{
|
||||
$commits = $payload['commits'] ?? [];
|
||||
$commits = $payload['commits'] ?? null;
|
||||
|
||||
if (! is_array($commits)) {
|
||||
return [];
|
||||
if (is_array($commits) && $commits !== []) {
|
||||
return array_values(array_filter($commits, static fn (mixed $commit): bool => is_array($commit)));
|
||||
}
|
||||
|
||||
return array_values(array_filter($commits, static fn (mixed $commit): bool => is_array($commit)));
|
||||
$headCommit = $payload['head_commit'] ?? null;
|
||||
|
||||
if (is_array($headCommit)) {
|
||||
return [$headCommit];
|
||||
}
|
||||
|
||||
$bitbucketCommits = Arr::get($payload, 'push.changes.0.commits', []);
|
||||
|
||||
if (is_array($bitbucketCommits)) {
|
||||
return array_values(array_filter($bitbucketCommits, static fn (mixed $commit): bool => is_array($commit)));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $commit
|
||||
*/
|
||||
private function commitHash(array $commit): ?string
|
||||
{
|
||||
return $this->stringOrNull($commit['id'] ?? $commit['sha'] ?? $commit['hash'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $commit
|
||||
*/
|
||||
private function commitUrl(array $commit): ?string
|
||||
{
|
||||
return $this->stringOrNull(
|
||||
$commit['url']
|
||||
?? $commit['web_url']
|
||||
?? Arr::get($commit, 'links.html.href')
|
||||
?? null,
|
||||
);
|
||||
}
|
||||
|
||||
private function ticketForCommit(GitRepository $repository, string $message, ?string $branch, ?string $url): ?Ticket
|
||||
@@ -194,17 +261,19 @@ final class GitWebhookIngestionService
|
||||
return null;
|
||||
}
|
||||
|
||||
$query = Ticket::query()->where('reference', $reference);
|
||||
$baseQuery = Ticket::query()->where('reference', $reference);
|
||||
|
||||
if ($repository->company_id !== null) {
|
||||
$query->where('company_id', $repository->company_id);
|
||||
$baseQuery->where('company_id', $repository->company_id);
|
||||
}
|
||||
|
||||
if ($repository->project_id !== null) {
|
||||
$query->where('project_id', $repository->project_id);
|
||||
}
|
||||
$strictProjectTicket = (clone $baseQuery)
|
||||
->when($repository->project_id !== null, static function (Builder $query) use ($repository): void {
|
||||
$query->where('project_id', $repository->project_id);
|
||||
})
|
||||
->first();
|
||||
|
||||
return $query->first();
|
||||
return $strictProjectTicket ?? $baseQuery->first();
|
||||
}
|
||||
|
||||
private function parseTimestamp(mixed $value): ?CarbonImmutable
|
||||
|
||||
Reference in New Issue
Block a user