feat: alerts open bugs, and repeats of the same problem go to that bug

Owners and admins turn on a secret URL per project (Settings →
Integrations), POST /api/webhooks/alerts/{project}/{token}, that Sentry,
Uptime Kuma, Prometheus Alertmanager, Grafana or any tool sending JSON
with a title can call. A new problem opens a bug as TaskStream (label
«alerta», chosen type and priority); while that bug is open, the same
fingerprint goes to it (a comment at most every 30 minutes) and
«resolved» is said on it; once it is closed, a new bug names the old
one. Alerts of a project are handled one at a time, so two at once open
one bug, and a bug not yet projected still counts as open. Unknown
project, alerts off or a bad token all answer 403.

TaskStream may now comment as the system actor, like it already opened
tickets for public proposals. Stored in Mongo (alert_integrations,
alert_traces), not in the event store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 12:36:55 +02:00
parent 42a21412d9
commit 034704e46f
34 changed files with 3248 additions and 6 deletions

View File

@@ -12,6 +12,12 @@ export type AddChecklistItemInput = {
text: string;
};
export type AlertSettingsInput = {
priority?: IssuePriority | null | undefined;
/** Ticket type to open; null: the project's first bug type */
typeId?: string | null | undefined;
};
export type AreaOptionInput = {
archived?: boolean;
id: string;
@@ -424,6 +430,61 @@ export type WorkflowStatusInput = {
wipLimit?: number | null | undefined;
};
export type AlertIntegrationFieldsFragment = {
enabled: boolean;
webhookPath: string | null;
typeId: string | null;
priority: IssuePriority;
};
export type AlertIntegrationQueryVariables = Exact<{
projectId: string;
}>;
export type AlertIntegrationQuery = {
alertIntegration: {
enabled: boolean;
webhookPath: string | null;
typeId: string | null;
priority: IssuePriority;
};
};
export type SaveAlertIntegrationMutationVariables = Exact<{
projectId: string;
input: AlertSettingsInput;
}>;
export type SaveAlertIntegrationMutation = {
saveAlertIntegration: {
enabled: boolean;
webhookPath: string | null;
typeId: string | null;
priority: IssuePriority;
};
};
export type RotateAlertTokenMutationVariables = Exact<{
projectId: string;
}>;
export type RotateAlertTokenMutation = {
rotateAlertToken: {
enabled: boolean;
webhookPath: string | null;
typeId: string | null;
priority: IssuePriority;
};
};
export type DisableAlertIntegrationMutationVariables = Exact<{
projectId: string;
}>;
export type DisableAlertIntegrationMutation = {
disableAlertIntegration: boolean;
};
export type AutomationFieldsFragment = {
id: string;
name: string;
@@ -3123,6 +3184,28 @@ export type TestWebhookMutation = {
};
};
export const AlertIntegrationFieldsFragmentDoc = {
kind: 'Document',
definitions: [
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'AlertIntegrationFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'AlertIntegration' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'enabled' } },
{ kind: 'Field', name: { kind: 'Name', value: 'webhookPath' } },
{ kind: 'Field', name: { kind: 'Name', value: 'typeId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'priority' } },
],
},
},
],
} as unknown as DocumentNode<AlertIntegrationFieldsFragment, unknown>;
export const UserRefFieldsFragmentDoc = {
kind: 'Document',
definitions: [
@@ -4459,6 +4542,286 @@ export const WebhookFieldsFragmentDoc = {
},
],
} as unknown as DocumentNode<WebhookFieldsFragment, unknown>;
export const AlertIntegrationDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
name: { kind: 'Name', value: 'AlertIntegration' },
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: 'alertIntegration' },
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: 'AlertIntegrationFields' },
},
],
},
},
],
},
},
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'AlertIntegrationFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'AlertIntegration' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'enabled' } },
{ kind: 'Field', name: { kind: 'Name', value: 'webhookPath' } },
{ kind: 'Field', name: { kind: 'Name', value: 'typeId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'priority' } },
],
},
},
],
} as unknown as DocumentNode<
AlertIntegrationQuery,
AlertIntegrationQueryVariables
>;
export const SaveAlertIntegrationDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'SaveAlertIntegration' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'projectId' },
},
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
},
},
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'input' },
},
type: {
kind: 'NonNullType',
type: {
kind: 'NamedType',
name: { kind: 'Name', value: 'AlertSettingsInput' },
},
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'saveAlertIntegration' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'projectId' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'projectId' },
},
},
{
kind: 'Argument',
name: { kind: 'Name', value: 'input' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'input' },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'AlertIntegrationFields' },
},
],
},
},
],
},
},
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'AlertIntegrationFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'AlertIntegration' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'enabled' } },
{ kind: 'Field', name: { kind: 'Name', value: 'webhookPath' } },
{ kind: 'Field', name: { kind: 'Name', value: 'typeId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'priority' } },
],
},
},
],
} as unknown as DocumentNode<
SaveAlertIntegrationMutation,
SaveAlertIntegrationMutationVariables
>;
export const RotateAlertTokenDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'RotateAlertToken' },
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: 'rotateAlertToken' },
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: 'AlertIntegrationFields' },
},
],
},
},
],
},
},
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'AlertIntegrationFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'AlertIntegration' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'enabled' } },
{ kind: 'Field', name: { kind: 'Name', value: 'webhookPath' } },
{ kind: 'Field', name: { kind: 'Name', value: 'typeId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'priority' } },
],
},
},
],
} as unknown as DocumentNode<
RotateAlertTokenMutation,
RotateAlertTokenMutationVariables
>;
export const DisableAlertIntegrationDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'DisableAlertIntegration' },
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: 'disableAlertIntegration' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'projectId' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'projectId' },
},
},
],
},
],
},
},
],
} as unknown as DocumentNode<
DisableAlertIntegrationMutation,
DisableAlertIntegrationMutationVariables
>;
export const AutomationsDocument = {
kind: 'Document',
definitions: [