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:
@@ -467,3 +467,56 @@ a.ts-key {
|
||||
.ts-mentions__option:focus-visible {
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
// Code that mentions the ticket (from the Git integration).
|
||||
.ts-dev {
|
||||
display: grid;
|
||||
gap: 0.25rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.ts-dev__item {
|
||||
display: grid;
|
||||
grid-template-columns: 4rem minmax(0, 1fr) auto auto auto;
|
||||
gap: 0.625rem;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0;
|
||||
border-top: 1px solid var(--ts-line);
|
||||
|
||||
@media (max-width: 640px) {
|
||||
grid-template-columns: 4rem minmax(0, 1fr);
|
||||
|
||||
.ts-dev__ref,
|
||||
.ts-dev__when {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ts-pill[data-state='MERGED'] {
|
||||
--tone: var(--ts-c-violet, var(--ts-brand));
|
||||
}
|
||||
|
||||
.ts-pill[data-state='OPEN'] {
|
||||
--tone: var(--ts-c-green, var(--ts-brand));
|
||||
}
|
||||
}
|
||||
|
||||
.ts-dev__kind {
|
||||
color: var(--ts-muted);
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.ts-dev__title {
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ts-dev__when {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -162,10 +162,7 @@ import { RELEASE_STATUS, releaseWhen } from '../ui/copy';
|
||||
[disabled]="drafting()"
|
||||
(execute)="draftNotes()"
|
||||
/>
|
||||
<pi-textarea
|
||||
placeholder="## Novedades - …"
|
||||
[(value)]="notes"
|
||||
>
|
||||
<pi-textarea placeholder="## Novedades - …" [(value)]="notes">
|
||||
<pi-label>Notas</pi-label>
|
||||
</pi-textarea>
|
||||
<pi-action-button
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import { NgTemplateOutlet } from '@angular/common';
|
||||
import { PiIconComponent, PiNoteComponent } from '@pi/ui';
|
||||
import { GitIntegrationComponent } from '../ui/git-integration';
|
||||
import { ProjectFacade } from '@task-stream/web/tracker/application';
|
||||
import {
|
||||
ProjectSettings,
|
||||
@@ -51,6 +52,7 @@ function idFrom(name: string): string {
|
||||
selector: 'ts-settings-page',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
GitIntegrationComponent,
|
||||
CdkDropList,
|
||||
CdkDrag,
|
||||
CdkDragHandle,
|
||||
@@ -461,6 +463,13 @@ function idFrom(name: string): string {
|
||||
}
|
||||
</form>
|
||||
|
||||
@if (facade.canManage() && facade.project(); as project) {
|
||||
<ts-git-integration
|
||||
[projectId]="project.id"
|
||||
[projectKey]="project.key"
|
||||
/>
|
||||
}
|
||||
|
||||
@if (facade.isOwner() && facade.project(); as project) {
|
||||
<section
|
||||
class="ts-panel ts-block ts-danger"
|
||||
|
||||
Reference in New Issue
Block a user