feat: link commits, branches and pull requests to tickets

A project can turn on a webhook for GitHub, Gitea or GitLab. Pushes and
pull or merge requests that name a ticket key show on that ticket under
Desarrollo, with the state of each pull request; the secret is shown and
rotated from the project settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 15:28:12 +02:00
parent 24b37b6628
commit 046d77c813
48 changed files with 2638 additions and 12 deletions

View File

@@ -38,6 +38,7 @@ import {
import {
Attachment,
Comment,
DevLink,
insertMention,
IssueLink,
IssueLinkType,
@@ -457,6 +458,39 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
}
</article>
@if (issue.devLinks.length) {
<article class="ts-block">
<h2 class="ts-section">
Desarrollo
<span class="ts-mono">{{ issue.devLinks.length }}</span>
</h2>
<ul class="ts-dev">
@for (link of devLinks(); track link.url) {
<li class="ts-dev__item">
<span class="ts-dev__kind">{{ devKind(link) }}</span>
<a
class="ts-dev__title"
[href]="link.url"
target="_blank"
rel="noopener"
>{{ link.title }}</a
>
<code class="ts-dev__ref">{{ link.ref }}</code>
@if (link.state) {
<span class="ts-pill" [attr.data-state]="link.state">{{
devState(link)
}}</span>
}
<span class="ts-muted ts-dev__when"
>{{ link.author ? link.author + ' · ' : ''
}}{{ ago(link.at) }}</span
>
</li>
}
</ul>
</article>
}
<article class="ts-block">
<h2 class="ts-section">
Comentarios
@@ -887,6 +921,26 @@ export class IssuePage {
)
.slice(0, 6);
});
/** Pull requests first, then the newest. */
protected readonly devLinks = computed(() =>
[...(this.facade.issue()?.devLinks ?? [])].sort(
(a, b) =>
Number(b.kind === 'PULL_REQUEST') - Number(a.kind === 'PULL_REQUEST') ||
b.at.localeCompare(a.at),
),
);
protected devKind = (link: DevLink) =>
link.kind === 'PULL_REQUEST'
? 'PR'
: link.kind === 'BRANCH'
? 'Rama'
: 'Commit';
protected devState = (link: DevLink) =>
link.state === 'MERGED'
? 'Fusionada'
: link.state === 'CLOSED'
? 'Cerrada'
: 'Abierta';
protected readonly checklistText = signal('');
protected readonly subtaskTitle = signal('');
protected readonly subtaskType = signal<string | null>(null);