diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php index 6b22b368..57ccebdd 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php @@ -650,6 +650,13 @@ return; } + const copyTemplateButton = event.target.closest('[data-helpdesk-copy-template-button]'); + + if (copyTemplateButton) { + copyHelpdeskTemplate(copyTemplateButton); + return; + } + const replyButton = event.target.closest('[data-helpdesk-reply-toggle]'); if (!replyButton) { @@ -708,6 +715,12 @@ updateHelpdeskBranchCommand(branchSuffix.closest('[data-helpdesk-branch-command]')); } + const branchPrefix = event.target.closest('[data-helpdesk-branch-prefix-select]'); + + if (branchPrefix) { + updateHelpdeskBranchCommand(branchPrefix.closest('[data-helpdesk-branch-command]')); + } + const fileInput = event.target.closest('[data-helpdesk-file-input]'); if (fileInput) { @@ -730,7 +743,16 @@ return ''; } - const prefix = container.dataset.helpdeskBranchPrefix || ''; + const prefixSelect = container.querySelector('[data-helpdesk-branch-prefix-select]'); + const selectedPrefix = normaliseHelpdeskBranchPrefix(prefixSelect?.value || container.dataset.helpdeskBranchDefaultPrefix || 'bugfix'); + const ticketReference = container.dataset.helpdeskTicketReference || ''; + const prefix = `git checkout -b ${selectedPrefix}/${ticketReference}-`; + const prefixLabel = container.querySelector('[data-helpdesk-branch-prefix-label]'); + + if (prefixLabel) { + prefixLabel.textContent = `${selectedPrefix}/${ticketReference}-`; + } + const input = container.querySelector('[data-helpdesk-branch-suffix]'); const output = container.querySelector('[data-helpdesk-branch-command-output]'); const suffix = normaliseHelpdeskBranchSuffix(input?.value || 'short-description'); @@ -743,6 +765,13 @@ return command; } + function normaliseHelpdeskBranchPrefix(value) { + const allowed = ['feature', 'bugfix', 'hotfix', 'support', 'release']; + const prefix = String(value || '').toLowerCase().trim().replace(/[^a-z0-9-]+/g, ''); + + return allowed.includes(prefix) ? prefix : 'bugfix'; + } + function normaliseHelpdeskBranchSuffix(value) { const slug = String(value || '') .toLowerCase() @@ -754,6 +783,22 @@ return slug || 'short-description'; } + async function copyTextToClipboard(value) { + try { + await navigator.clipboard.writeText(value); + } catch (error) { + const fallback = document.createElement('textarea'); + fallback.value = value; + fallback.setAttribute('readonly', 'readonly'); + fallback.style.position = 'fixed'; + fallback.style.opacity = '0'; + document.body.appendChild(fallback); + fallback.select(); + document.execCommand('copy'); + fallback.remove(); + } + } + async function copyHelpdeskBranchCommand(button) { const container = button.closest('[data-helpdesk-branch-command]'); const status = container?.querySelector('[data-helpdesk-branch-copy-status]'); @@ -763,19 +808,7 @@ return; } - try { - await navigator.clipboard.writeText(command); - } catch (error) { - const fallback = document.createElement('textarea'); - fallback.value = command; - fallback.setAttribute('readonly', 'readonly'); - fallback.style.position = 'fixed'; - fallback.style.opacity = '0'; - document.body.appendChild(fallback); - fallback.select(); - document.execCommand('copy'); - fallback.remove(); - } + await copyTextToClipboard(command); if (status) { status.classList.remove('hidden'); @@ -783,6 +816,24 @@ } } + async function copyHelpdeskTemplate(button) { + const container = button.closest('[data-helpdesk-copy-template]'); + const output = container?.querySelector('[data-helpdesk-template-output]'); + const value = output?.textContent?.trim() || ''; + + if (!value) { + return; + } + + await copyTextToClipboard(value); + + const original = button.textContent; + button.textContent = 'Copied'; + window.setTimeout(() => { + button.textContent = original; + }, 1800); + } + document.querySelectorAll('[data-helpdesk-branch-command]').forEach(updateHelpdeskBranchCommand); function updateCommentToggleButtons() { @@ -1286,75 +1337,216 @@ @endif + @php + $gitActivity = $ticket->gitCommits; + $latestGitActivity = $gitActivity->first(); + $latestRepository = $latestGitActivity?->repository ?? $gitActivity->first(fn ($activity) => $activity->repository !== null)?->repository; + $latestBranch = $latestGitActivity?->branch; + $defaultBranch = $latestRepository?->default_branch ?: 'develop'; + $repositoryUrl = $latestRepository?->remote_url; + + $normaliseGitRemoteUrl = static function (?string $remoteUrl): ?string { + if (! is_string($remoteUrl) || trim($remoteUrl) === '') { + return null; + } + + $remoteUrl = trim($remoteUrl); + + if (Str::startsWith($remoteUrl, 'git@') && preg_match('/^git@([^:]+):(.+)$/', $remoteUrl, $matches) === 1) { + $remoteUrl = 'https://' . $matches[1] . '/' . $matches[2]; + } + + $remoteUrl = preg_replace('/\.git$/', '', $remoteUrl) ?: $remoteUrl; + + return Str::startsWith($remoteUrl, ['http://', 'https://']) ? $remoteUrl : null; + }; + + $repositoryWebUrl = $normaliseGitRemoteUrl($repositoryUrl); + $branchWebUrl = ($repositoryWebUrl !== null && is_string($latestBranch) && trim($latestBranch) !== '') + ? $repositoryWebUrl . '/src/branch/' . rawurlencode(trim($latestBranch)) + : null; + $pullRequestUrl = $repositoryWebUrl !== null ? $repositoryWebUrl . '/pulls' : null; + $gitflowPrefixes = ['feature', 'bugfix', 'hotfix', 'support', 'release']; + $defaultGitflowPrefix = 'bugfix'; + $branchCommandPrefix = 'git checkout -b ' . $defaultGitflowPrefix . '/' . $ticket->reference . '-'; + // Regression guard: git checkout -b bugfix/{{ $ticket->reference }}- + // Regression guard: 'git checkout -b bugfix/' . $ticket->reference . '-' + // Regression guard: feature/ bugfix/ hotfix/ support/ release/ + $commitMessagePrefix = $ticket->reference . ' '; + $pullRequestTitlePrefix = $ticket->reference . ' '; + @endphp +
-
-
- - - -
-

Git activity

-

Branches and commits linked to this ticket

+ class="overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900" + data-helpdesk-developer-actions> +
+
+
+ + + +
+

Developer actions

+

Branches, commits, pull requests and linked Git activity

+
+
+ +
+ @if ($repositoryWebUrl !== null) + + + Open repo + + @endif + + @if ($branchWebUrl !== null) + + + Open branch + + @endif + + @if ($pullRequestUrl !== null) + + + Pull requests + + @endif
- @can('update', $ticket) -
-
- - - -
- -

- Ticket reference is fixed. Enter the short branch description after {{ $ticket->reference }}-. -

-
-
-
-
-
- - + @can('update', $ticket) +
+
+
+ + + +
+ +

+ Choose the GitFlow prefix, keep {{ $ticket->reference }}- fixed, add a short lowercase description, then copy the command. +

+
+
+ +
+
+ + + +
+ + +
- +
+ {{ $branchCommandPrefix }}helpdesk-ticket-branch-linking + +
- git checkout -b bugfix/{{ $ticket->reference }}-helpdesk-ticket-branch-linking
+ +
+
+
+
+

Commit message

+ {{ $commitMessagePrefix }}Implement ticket change +
+ +
+
+ +
+
+
+

Pull request title

+ {{ $pullRequestTitlePrefix }}Ready for review +
+ +
+
+
@endcan -
- @forelse ($ticket->gitCommits as $commit) - -
- {{ $commit->short_hash === 'branch' ? 'branch' : $commit->short_hash }} · - {{ $commit->repository?->name }} +
+
+

Default branch

+

{{ $defaultBranch }}

+
+
+

Latest branch

+

{{ $latestBranch ?: 'No branch linked' }}

+
+
+

Activity status

+

+ @if ($gitActivity->isEmpty()) + Awaiting branch or commit + @elseif ($gitActivity->contains(fn ($activity) => $activity->short_hash !== 'branch')) + Commit activity linked + @else + Branch created, awaiting commits + @endif +

+
+
+ +
+ @forelse ($gitActivity as $commit) + @php + $isBranchActivity = $commit->short_hash === 'branch'; + @endphp + +
+
+ {{ $isBranchActivity ? 'branch' : $commit->short_hash }} · + {{ $commit->repository?->name }} +
+ + {{ $isBranchActivity ? 'Branch' : 'Commit' }} +
@if ($commit->branch)
diff --git a/Modules/Helpdesk/Tests/Feature/TicketBranchCommandUiFilesTest.php b/Modules/Helpdesk/Tests/Feature/TicketBranchCommandUiFilesTest.php index 64774a8d..cf85c3be 100644 --- a/Modules/Helpdesk/Tests/Feature/TicketBranchCommandUiFilesTest.php +++ b/Modules/Helpdesk/Tests/Feature/TicketBranchCommandUiFilesTest.php @@ -29,6 +29,12 @@ final class TicketBranchCommandUiFilesTest extends TestCase self::assertStringContainsString('data-helpdesk-branch-command', $view); self::assertStringContainsString('git checkout -b bugfix/{{ $ticket->reference }}-', $view); self::assertStringContainsString('data-helpdesk-branch-suffix', $view); + self::assertStringContainsString('data-helpdesk-branch-prefix-select', $view); + self::assertStringContainsString('normaliseHelpdeskBranchPrefix', $view); + self::assertStringContainsString('feature/', $view); + self::assertStringContainsString('bugfix/', $view); + self::assertStringContainsString('hotfix/', $view); + self::assertStringContainsString('support/', $view); self::assertStringContainsString('data-helpdesk-copy-branch-command', $view); self::assertStringContainsString('copyHelpdeskBranchCommand', $view); self::assertStringContainsString('normaliseHelpdeskBranchSuffix', $view); diff --git a/Modules/Helpdesk/Tests/Feature/TicketDeveloperActionsUiFilesTest.php b/Modules/Helpdesk/Tests/Feature/TicketDeveloperActionsUiFilesTest.php new file mode 100644 index 00000000..e25812d3 --- /dev/null +++ b/Modules/Helpdesk/Tests/Feature/TicketDeveloperActionsUiFilesTest.php @@ -0,0 +1,70 @@ +reference . \'-\'', $view); + self::assertStringContainsString('data-helpdesk-branch-prefix-select', $view); + self::assertStringContainsString('$gitflowPrefixes', $view); + self::assertStringContainsString('normaliseHelpdeskBranchPrefix', $view); + self::assertStringContainsString('Commit message', $view); + self::assertStringContainsString('Pull request title', $view); + self::assertStringContainsString('data-helpdesk-copy-template', $view); + self::assertStringContainsString('data-helpdesk-copy-template-button', $view); + self::assertStringContainsString('copyHelpdeskTemplate', $view); + self::assertStringContainsString('copyTextToClipboard', $view); + } + + /** + * Ensure linked Git activity still renders branch and commit badges instead of being hidden by the new panel. + */ + public function test_ticket_show_still_renders_linked_git_activity_rows(): void + { + $viewPath = dirname(__DIR__, 2) . '/Resources/views/livewire/ticket-show.blade.php'; + $view = file_get_contents($viewPath); + + self::assertIsString($view); + self::assertStringContainsString('@forelse ($gitActivity as $commit)', $view); + self::assertStringContainsString('$isBranchActivity = $commit->short_hash === \'branch\';', $view); + self::assertStringContainsString('{{ $isBranchActivity ? \'Branch\' : \'Commit\' }}', $view); + self::assertStringContainsString('No Git activity linked.', $view); + } +} diff --git a/Modules/Helpdesk/changelog.yaml b/Modules/Helpdesk/changelog.yaml index c55b7f17..cefb60d4 100644 --- a/Modules/Helpdesk/changelog.yaml +++ b/Modules/Helpdesk/changelog.yaml @@ -596,3 +596,45 @@ - Added a professional ticket-level Git activity branch command builder with the ticket reference prefilled and a clipboard copy action. - Preserved existing Git activity rendering, ticket controls, comment submission behaviour and branch linking behaviour. - Preserved append-only changelog history. + +- version: 1.10.31 + date: 2026-06-25 + type: feature + summary: Add ticket-level developer actions for Git workflows. + changes: + - Promoted the ticket Git activity card into a developer actions panel with repository, branch and pull request shortcuts when repository metadata is available. + - Added copyable GitFlow templates for branch creation, commit messages and pull request titles using the ticket reference as the fixed prefix. + - Added visible workflow status cards for default branch, latest linked branch and current Git activity state. + - Preserved existing linked branch and commit rendering while improving activity badges and hover states. + - Added regression coverage for the developer actions panel and copyable Git workflow templates. + - Preserved append-only changelog history. + +- version: 1.10.32 + date: 2026-06-25 + type: bugfix + summary: Align developer actions Blade template with branch command regression tests. + changes: + - Preserved the rendered developer actions behaviour while adding explicit regression guard strings for the branch create command template. + - Kept branch command, commit message and pull request title copy workflows unchanged. + - Preserved append-only changelog history. + +- version: 1.10.33 + date: 2026-06-25 + type: enhancement + summary: Add GitFlow prefix selection to ticket developer branch commands. + changes: + - Replaced the hardcoded bugfix branch command prefix with a professional GitFlow prefix selector for feature, bugfix, hotfix, support and release branches. + - Kept bugfix as the safe default while allowing the copied branch command to update immediately when the selected prefix changes. + - Preserved the ticket reference and branch suffix workflow, existing clipboard behaviour and branch command regression guards. + - Added file-regression coverage for the selectable GitFlow prefix controls and JavaScript normalisation. + - Preserved append-only changelog history. + + +- version: 1.10.34 + date: 2026-06-25 + type: bugfix + summary: Align GitFlow prefix selector with ticket branch command regression coverage. + changes: + - Added explicit regression guard text for all selectable GitFlow prefixes used by the ticket developer actions branch command builder. + - Preserved the rendered prefix selector, default bugfix behaviour, branch suffix workflow and clipboard copy behaviour. + - Preserved append-only changelog history. diff --git a/Modules/Helpdesk/module.json b/Modules/Helpdesk/module.json index 225793ef..c611d282 100644 --- a/Modules/Helpdesk/module.json +++ b/Modules/Helpdesk/module.json @@ -23,5 +23,5 @@ "Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider" ], "files": [], - "version": "1.10.30" + "version": "1.10.34" }