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

@@ -108,6 +108,16 @@ fragment IssueFields on Issue {
text
done
}
devLinks {
kind
url
title
ref
state
provider
author
at
}
attachments {
id
fileName

View File

@@ -75,6 +75,10 @@ export type CreateSprintInput = {
startDate: string;
};
export type DevLinkKind = 'BRANCH' | 'COMMIT' | 'PULL_REQUEST';
export type DevLinkState = 'CLOSED' | 'MERGED' | 'OPEN';
export type IssueFilterInput = {
areas?: Array<string> | null | undefined;
assigneeId?: string | null | undefined;
@@ -336,6 +340,16 @@ export type IssueFieldsFragment = {
}>;
watchers: Array<{ id: string; name: string }>;
checklist: Array<{ id: string; text: string; done: boolean }>;
devLinks: Array<{
kind: DevLinkKind;
url: string;
title: string;
ref: string;
state: DevLinkState | null;
provider: string;
author: string | null;
at: string;
}>;
attachments: Array<{
id: string;
fileName: string;
@@ -387,6 +401,42 @@ export type CommandResultFieldsFragment = {
version: number;
};
export type GitIntegrationFieldsFragment = {
enabled: boolean;
secret: string | null;
webhookPath: string;
};
export type GitIntegrationQueryVariables = Exact<{
projectId: string;
}>;
export type GitIntegrationQuery = {
gitIntegration: {
enabled: boolean;
secret: string | null;
webhookPath: string;
};
};
export type EnableGitIntegrationMutationVariables = Exact<{
projectId: string;
}>;
export type EnableGitIntegrationMutation = {
enableGitIntegration: {
enabled: boolean;
secret: string | null;
webhookPath: string;
};
};
export type DisableGitIntegrationMutationVariables = Exact<{
projectId: string;
}>;
export type DisableGitIntegrationMutation = { disableGitIntegration: boolean };
export type BoardQueryVariables = Exact<{
idOrKey: string;
releaseId?: string | null | undefined;
@@ -469,6 +519,16 @@ export type IssueQuery = {
}>;
watchers: Array<{ id: string; name: string }>;
checklist: Array<{ id: string; text: string; done: boolean }>;
devLinks: Array<{
kind: DevLinkKind;
url: string;
title: string;
ref: string;
state: DevLinkState | null;
provider: string;
author: string | null;
at: string;
}>;
attachments: Array<{
id: string;
fileName: string;
@@ -1944,6 +2004,23 @@ export const IssueFieldsFragmentDoc = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'devLinks' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'kind' } },
{ kind: 'Field', name: { kind: 'Name', value: 'url' } },
{ kind: 'Field', name: { kind: 'Name', value: 'title' } },
{ kind: 'Field', name: { kind: 'Name', value: 'ref' } },
{ kind: 'Field', name: { kind: 'Name', value: 'state' } },
{ kind: 'Field', name: { kind: 'Name', value: 'provider' } },
{ kind: 'Field', name: { kind: 'Name', value: 'author' } },
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'attachments' },
@@ -2219,6 +2296,27 @@ export const CommandResultFieldsFragmentDoc = {
},
],
} as unknown as DocumentNode<CommandResultFieldsFragment, unknown>;
export const GitIntegrationFieldsFragmentDoc = {
kind: 'Document',
definitions: [
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'GitIntegrationFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'GitIntegration' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'enabled' } },
{ kind: 'Field', name: { kind: 'Name', value: 'secret' } },
{ kind: 'Field', name: { kind: 'Name', value: 'webhookPath' } },
],
},
},
],
} as unknown as DocumentNode<GitIntegrationFieldsFragment, unknown>;
export const NotificationFieldsFragmentDoc = {
kind: 'Document',
definitions: [
@@ -2386,6 +2484,188 @@ export const SprintFieldsFragmentDoc = {
},
],
} as unknown as DocumentNode<SprintFieldsFragment, unknown>;
export const GitIntegrationDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
name: { kind: 'Name', value: 'GitIntegration' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'projectId' },
},
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'gitIntegration' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'projectId' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'projectId' },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'GitIntegrationFields' },
},
],
},
},
],
},
},
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'GitIntegrationFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'GitIntegration' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'enabled' } },
{ kind: 'Field', name: { kind: 'Name', value: 'secret' } },
{ kind: 'Field', name: { kind: 'Name', value: 'webhookPath' } },
],
},
},
],
} as unknown as DocumentNode<GitIntegrationQuery, GitIntegrationQueryVariables>;
export const EnableGitIntegrationDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'EnableGitIntegration' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'projectId' },
},
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'enableGitIntegration' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'projectId' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'projectId' },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'GitIntegrationFields' },
},
],
},
},
],
},
},
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'GitIntegrationFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'GitIntegration' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'enabled' } },
{ kind: 'Field', name: { kind: 'Name', value: 'secret' } },
{ kind: 'Field', name: { kind: 'Name', value: 'webhookPath' } },
],
},
},
],
} as unknown as DocumentNode<
EnableGitIntegrationMutation,
EnableGitIntegrationMutationVariables
>;
export const DisableGitIntegrationDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'DisableGitIntegration' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'projectId' },
},
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'disableGitIntegration' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'projectId' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'projectId' },
},
},
],
},
],
},
},
],
} as unknown as DocumentNode<
DisableGitIntegrationMutation,
DisableGitIntegrationMutationVariables
>;
export const BoardDocument = {
kind: 'Document',
definitions: [
@@ -2950,6 +3230,23 @@ export const IssueDocument = {
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'devLinks' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'kind' } },
{ kind: 'Field', name: { kind: 'Name', value: 'url' } },
{ kind: 'Field', name: { kind: 'Name', value: 'title' } },
{ kind: 'Field', name: { kind: 'Name', value: 'ref' } },
{ kind: 'Field', name: { kind: 'Name', value: 'state' } },
{ kind: 'Field', name: { kind: 'Name', value: 'provider' } },
{ kind: 'Field', name: { kind: 'Name', value: 'author' } },
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'attachments' },

View File

@@ -0,0 +1,21 @@
fragment GitIntegrationFields on GitIntegration {
enabled
secret
webhookPath
}
query GitIntegration($projectId: ID!) {
gitIntegration(projectId: $projectId) {
...GitIntegrationFields
}
}
mutation EnableGitIntegration($projectId: ID!) {
enableGitIntegration(projectId: $projectId) {
...GitIntegrationFields
}
}
mutation DisableGitIntegration($projectId: ID!) {
disableGitIntegration(projectId: $projectId)
}