A project keeps a list of repositories (api, web, admin…), each with its own secret, instead of a single webhook secret. They all call the same URL; the secret that verifies the signature tells which one it was, and every commit, branch or pull request is linked with its repository's name (shown on the ticket and in the MCP). Rotating or removing one leaves the others alone, and what it linked stays. A project that had turned the single webhook on keeps its secret, as a repository named "principal". API: the enable/disable endpoints and mutations give way to adding a repository, rotating its secret and removing it (REST and GraphQL). The web settings card lists the repositories. Docs, Word and PDF updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
424 lines
9.8 KiB
TypeScript
424 lines
9.8 KiB
TypeScript
import {
|
|
NotificationKind,
|
|
IssueLinkType,
|
|
IssuePriority,
|
|
ProjectRole,
|
|
ReleaseStatus,
|
|
SprintStatus,
|
|
StatusCategory,
|
|
} from './enums';
|
|
import { ProjectSettings } from './workflow';
|
|
|
|
export interface UserRef {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
export interface ProjectMember {
|
|
user: UserRef;
|
|
role: ProjectRole;
|
|
}
|
|
|
|
export interface Project {
|
|
id: string;
|
|
key: string;
|
|
name: string;
|
|
description: string | null;
|
|
/** Anyone can read it, also without signing in. */
|
|
isPublic: boolean;
|
|
owner: UserRef;
|
|
members: ProjectMember[];
|
|
settings: ProjectSettings;
|
|
updatedAt: string;
|
|
version: number;
|
|
}
|
|
|
|
export interface IssueLink {
|
|
type: IssueLinkType;
|
|
/** OUTWARD: this issue → other. INWARD: other → this issue. */
|
|
direction: 'OUTWARD' | 'INWARD';
|
|
issueId: string;
|
|
issueKey: string;
|
|
}
|
|
|
|
/** The ticket another one hangs from. */
|
|
export interface IssueParent {
|
|
id: string;
|
|
key: string;
|
|
title: string;
|
|
}
|
|
|
|
/** A step inside a ticket that does not deserve a ticket of its own. */
|
|
export interface ChecklistItem {
|
|
id: string;
|
|
text: string;
|
|
done: boolean;
|
|
}
|
|
|
|
/** How far the tickets hanging from one have got. */
|
|
export interface ChildProgress {
|
|
total: number;
|
|
/** Done or cancelled. */
|
|
finished: number;
|
|
percent: number;
|
|
storyPoints: number;
|
|
finishedStoryPoints: number;
|
|
}
|
|
|
|
/** A top-level ticket that groups others. */
|
|
export interface Epic {
|
|
issue: IssueSummary;
|
|
progress: ChildProgress;
|
|
}
|
|
|
|
/** What a saved view narrows the board to; empty: everything. */
|
|
export interface SavedFilterCriteria {
|
|
types?: string[] | null;
|
|
areas?: string[] | null;
|
|
priorities?: IssuePriority[] | null;
|
|
assigneeId?: string | null;
|
|
unassigned?: boolean | null;
|
|
releaseId?: string | null;
|
|
labels?: string[] | null;
|
|
search?: string | null;
|
|
}
|
|
|
|
/** A named view of the board: private, or shared with the project. */
|
|
export interface SavedFilter {
|
|
id: string;
|
|
projectId: string;
|
|
owner: UserRef;
|
|
name: string;
|
|
shared: boolean;
|
|
criteria: SavedFilterCriteria;
|
|
createdAt: string;
|
|
}
|
|
|
|
/** What boards and lists show. */
|
|
export interface IssueSummary {
|
|
id: string;
|
|
projectId: string;
|
|
key: string;
|
|
/** Ids of the project's settings. */
|
|
type: string;
|
|
area: string | null;
|
|
title: string;
|
|
status: string;
|
|
statusCategory: StatusCategory;
|
|
priority: IssuePriority;
|
|
assignee: UserRef | null;
|
|
release: { id: string; name: string } | null;
|
|
sprint: { id: string; name: string } | null;
|
|
labels: string[];
|
|
/** The epic or ticket this one breaks down. */
|
|
parent: IssueParent | null;
|
|
/** Place in the backlog and in its board column (ascending, as text). */
|
|
rank: string;
|
|
/** Estimate; null when nobody estimated it. */
|
|
storyPoints: number | null;
|
|
/** Planned start and end (`YYYY-MM-DD`), for the roadmap. */
|
|
startDate: string | null;
|
|
dueDate: string | null;
|
|
/** In the trash since then (null: not in it). */
|
|
trashedAt: string | null;
|
|
commentCount: number;
|
|
updatedAt: string;
|
|
version: number;
|
|
}
|
|
|
|
/** A page of tickets: the ones returned and how many match in total. */
|
|
export interface IssuePage {
|
|
items: IssueSummary[];
|
|
total: number;
|
|
}
|
|
|
|
/** A file on a ticket (or under one of its comments). */
|
|
export interface Attachment {
|
|
id: string;
|
|
fileName: string;
|
|
mimeType: string;
|
|
/** Bytes. */
|
|
size: number;
|
|
uploadedBy: UserRef;
|
|
uploadedAt: string;
|
|
commentId: string | null;
|
|
}
|
|
|
|
export interface Issue extends IssueSummary {
|
|
description: string | null;
|
|
reporter: UserRef;
|
|
figmaUrl: string | null;
|
|
links: IssueLink[];
|
|
attachments: Attachment[];
|
|
/** People following the ticket. */
|
|
watchers: UserRef[];
|
|
checklist: ChecklistItem[];
|
|
/** Commits, branches and pull requests that mention it. */
|
|
devLinks: DevLink[];
|
|
allowedTransitions: string[];
|
|
createdAt: string;
|
|
}
|
|
|
|
/** A commit, branch or pull request that mentions a ticket. */
|
|
export interface DevLink {
|
|
kind: 'COMMIT' | 'BRANCH' | 'PULL_REQUEST';
|
|
url: string;
|
|
title: string;
|
|
/** Short SHA, branch or PR number. */
|
|
ref: string;
|
|
state: 'OPEN' | 'MERGED' | 'CLOSED' | null;
|
|
provider: string;
|
|
/** The project's name for the repository it came from. */
|
|
repository: string | null;
|
|
author: string | null;
|
|
at: string;
|
|
}
|
|
|
|
export interface WebhookDelivery {
|
|
at: string;
|
|
event: string;
|
|
/** HTTP status, null when it never answered. */
|
|
status: number | null;
|
|
ok: boolean;
|
|
error: string | null;
|
|
}
|
|
|
|
/** Where the project tells another system what happened. */
|
|
export interface Webhook {
|
|
id: string;
|
|
url: string;
|
|
/** Event names, `family.*` or `*`. */
|
|
events: string[];
|
|
active: boolean;
|
|
createdBy: UserRef;
|
|
createdAt: string;
|
|
lastDelivery: WebhookDelivery | null;
|
|
}
|
|
|
|
export type AutomationTriggerKind =
|
|
| 'ISSUE_CREATED'
|
|
| 'STATUS_CHANGED'
|
|
| 'ISSUE_ASSIGNED'
|
|
| 'COMMENT_ADDED'
|
|
| 'PULL_REQUEST_MERGED';
|
|
|
|
export type AutomationActionKind =
|
|
| 'SET_STATUS'
|
|
| 'ASSIGN'
|
|
| 'ASSIGN_REPORTER'
|
|
| 'ADD_LABEL'
|
|
| 'SET_PRIORITY'
|
|
| 'ADD_COMMENT'
|
|
| 'ADD_TO_ACTIVE_SPRINT';
|
|
|
|
/** «When this happens to a ticket, if it is like this, do that.» */
|
|
export interface Automation {
|
|
id: string;
|
|
name: string;
|
|
active: boolean;
|
|
trigger: { kind: AutomationTriggerKind; status?: string | null };
|
|
conditions: {
|
|
types?: string[] | null;
|
|
priorities?: IssuePriority[] | null;
|
|
areas?: string[] | null;
|
|
label?: string | null;
|
|
};
|
|
action: { kind: AutomationActionKind; value?: string | null };
|
|
createdBy: UserRef;
|
|
runs: number;
|
|
lastRunAt: string | null;
|
|
lastError: string | null;
|
|
}
|
|
|
|
export type AutomationInput = Pick<
|
|
Automation,
|
|
'name' | 'trigger' | 'conditions' | 'action'
|
|
>;
|
|
|
|
/** What an import did (or, as a preview, would do). */
|
|
export interface ImportReport {
|
|
format: 'taskstream' | 'jira';
|
|
dryRun: boolean;
|
|
rows: number;
|
|
created: number;
|
|
keys: string[];
|
|
skipped: { line: number; reason?: string }[];
|
|
warnings: { line: number; message?: string }[];
|
|
}
|
|
|
|
/** The project's webhook for GitHub, Gitea or GitLab. */
|
|
/** A repository whose webhooks the project accepts. */
|
|
export interface GitRepository {
|
|
id: string;
|
|
/** The project's name for it: api, web, admin… */
|
|
name: string;
|
|
/** Only for owners and admins. */
|
|
secret: string | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface GitIntegration {
|
|
/** On while there is at least one repository. */
|
|
enabled: boolean;
|
|
/** The same for every repository: the secret tells them apart. */
|
|
webhookPath: string;
|
|
repositories: GitRepository[];
|
|
}
|
|
|
|
export interface Comment {
|
|
id: string;
|
|
issueId: string;
|
|
author: UserRef;
|
|
body: string;
|
|
createdAt: string;
|
|
editedAt: string | null;
|
|
}
|
|
|
|
export interface SprintProgress {
|
|
total: number;
|
|
/** Done or cancelled. */
|
|
finished: number;
|
|
percent: number;
|
|
storyPoints: number;
|
|
finishedStoryPoints: number;
|
|
}
|
|
|
|
/** A time-boxed iteration of a project. */
|
|
export interface Sprint {
|
|
id: string;
|
|
projectId: string;
|
|
name: string;
|
|
goal: string | null;
|
|
/** `YYYY-MM-DD`, both included. */
|
|
startDate: string;
|
|
endDate: string;
|
|
status: SprintStatus;
|
|
startedAt: string | null;
|
|
closedAt: string | null;
|
|
progress: SprintProgress;
|
|
}
|
|
|
|
/** Per day of a sprint; null for the days to come. */
|
|
export interface SprintBurndown {
|
|
sprintId: string;
|
|
dates: string[];
|
|
scope: (number | null)[];
|
|
remaining: (number | null)[];
|
|
remainingPoints: (number | null)[];
|
|
ideal: number[];
|
|
}
|
|
|
|
export interface Release {
|
|
id: string;
|
|
projectId: string;
|
|
name: string;
|
|
description: string | null;
|
|
targetDate: string | null;
|
|
status: ReleaseStatus;
|
|
releasedAt: string | null;
|
|
/** What shipped, told for people (Markdown). */
|
|
notes: string | null;
|
|
progress: { total: number; finished: number; percent: number };
|
|
}
|
|
|
|
export interface Activity {
|
|
id: string;
|
|
type: string;
|
|
issueId: string | null;
|
|
issueKey: string | null;
|
|
actor: UserRef;
|
|
summary: string;
|
|
occurredAt: string;
|
|
}
|
|
|
|
export interface CountBy<T = string> {
|
|
key: T;
|
|
count: number;
|
|
}
|
|
|
|
export interface ProjectStats {
|
|
total: number;
|
|
open: number;
|
|
byStatus: CountBy[];
|
|
byType: CountBy[];
|
|
byPriority: CountBy[];
|
|
byArea: CountBy[];
|
|
byAssignee: { user: UserRef | null; count: number }[];
|
|
}
|
|
|
|
export interface BoardColumn {
|
|
status: string;
|
|
issues: IssueSummary[];
|
|
total: number;
|
|
}
|
|
|
|
export interface DurationStats {
|
|
count: number;
|
|
averageDays: number | null;
|
|
/** 85% of tickets take at most this long. */
|
|
p85Days: number | null;
|
|
}
|
|
|
|
/** Statistics of a project over the last days. */
|
|
export interface ProjectAnalytics {
|
|
from: string;
|
|
to: string;
|
|
/** Per week (Monday, YYYY-MM-DD). */
|
|
createdVsResolved: { week: string; created: number; resolved: number }[];
|
|
/** Tickets in each status at the end of every day. */
|
|
cumulativeFlow: {
|
|
dates: string[];
|
|
series: { status: string; counts: number[] }[];
|
|
};
|
|
/** Start of work → done. */
|
|
cycleTime: DurationStats;
|
|
/** Creation → done. */
|
|
leadTime: DurationStats;
|
|
throughput: { week: string; user: UserRef | null; count: number }[];
|
|
qa: { passed: number; failed: number; failureRate: number | null };
|
|
}
|
|
|
|
/** Scope vs finished tickets of a release, day by day. */
|
|
export interface ReleaseBurnup {
|
|
releaseId: string;
|
|
targetDate: string | null;
|
|
dates: string[];
|
|
scope: number[];
|
|
done: number[];
|
|
}
|
|
|
|
export interface UserDirectoryEntry {
|
|
id: string;
|
|
name: string;
|
|
email: string;
|
|
}
|
|
|
|
/** Ack of a command: the read model catches up asynchronously. */
|
|
export interface CommandResult {
|
|
id: string;
|
|
key?: string | null;
|
|
version: number;
|
|
}
|
|
|
|
/** Something the signed-in person should know. */
|
|
export interface Notification {
|
|
id: string;
|
|
kind: NotificationKind;
|
|
projectId: string;
|
|
projectKey: string;
|
|
issueId: string | null;
|
|
issueKey: string | null;
|
|
actor: UserRef;
|
|
summary: string;
|
|
/** Null: unread. */
|
|
readAt: string | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface NotificationSettings {
|
|
/** Copies by e-mail. */
|
|
email: boolean;
|
|
/** Whether this installation can send e-mail at all. */
|
|
emailAvailable: boolean;
|
|
}
|