feat: per-project workflow settings, statistics and "my work" home

Per-project settings (owners/admins, «Configuración» tab):
- Statuses with category (TODO, IN_PROGRESS, QA, DONE, CANCELLED), colour and
  "requires reason"; configurable transitions; ticket types (icon, colour) and
  areas. Defaults are the previous fixed values; projects created before keep
  them without migration.
- Values are archived, never deleted: tickets, history and statistics keep
  referring to them. Validated in the domain (ProjectSettingsChangedEvent).
- Releases, board, tracking and "my work" use categories, so custom statuses
  keep working. Status changes carry name/category metadata for history.
- GraphQL: status/type/area are strings (breaking for clients sending enum
  literals); Project.settings, updateProjectSettings, statusCategory,
  myIssues(categories). REST: PUT /projects/{project}/settings, category filters.
- Read model: statusCategory on issues (backfilled at startup).

Statistics («Estadísticas» tab):
- New issue_timeline projection; pure functions compute created vs resolved,
  cumulative flow, cycle/lead time (avg + p85), throughput per person, QA
  pass/fail and release burnup. GraphQL Project.analytics(days),
  Release.burnup; REST /projects/{project}/analytics, /releases/{id}/burnup.
- Web: SVG charts with validated light/dark palette, crosshair tooltip,
  keyboard navigation, legend and data table.

Home "Mi trabajo": my open tickets across projects grouped by category (QA
failures first), tickets waiting for QA, upcoming releases and "Ir a…".

Tests: domain/application specs, e2e scenario for settings and analytics,
facade tests. Docs, READMEs, schema.gql/openapi.json and Word/PDF updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 20:53:45 +02:00
parent 240ea9a539
commit 0e301ad545
143 changed files with 10291 additions and 1052 deletions

View File

@@ -1,12 +1,11 @@
import {
IssueArea,
IssueLinkType,
IssuePriority,
IssueStatus,
IssueType,
ProjectRole,
ReleaseStatus,
StatusCategory,
} from './enums';
import { ProjectSettings } from './workflow';
export interface UserRef {
id: string;
@@ -25,6 +24,7 @@ export interface Project {
description: string | null;
owner: UserRef;
members: ProjectMember[];
settings: ProjectSettings;
updatedAt: string;
version: number;
}
@@ -42,10 +42,12 @@ export interface IssueSummary {
id: string;
projectId: string;
key: string;
type: IssueType;
area: IssueArea | null;
/** Ids of the project's settings. */
type: string;
area: string | null;
title: string;
status: IssueStatus;
status: string;
statusCategory: StatusCategory;
priority: IssuePriority;
assignee: UserRef | null;
release: { id: string; name: string } | null;
@@ -55,6 +57,12 @@ export interface IssueSummary {
version: number;
}
/** A page of tickets: the ones returned and how many match in total. */
export interface IssuePage {
items: IssueSummary[];
total: number;
}
export interface Issue extends IssueSummary {
description: string | null;
reporter: UserRef;
@@ -62,7 +70,7 @@ export interface Issue extends IssueSummary {
storyPoints: number | null;
dueDate: string | null;
links: IssueLink[];
allowedTransitions: IssueStatus[];
allowedTransitions: string[];
createdAt: string;
}
@@ -104,7 +112,7 @@ export interface CountBy<T = string> {
export interface ProjectStats {
total: number;
open: number;
byStatus: CountBy<IssueStatus>[];
byStatus: CountBy[];
byType: CountBy[];
byPriority: CountBy[];
byArea: CountBy[];
@@ -112,11 +120,46 @@ export interface ProjectStats {
}
export interface BoardColumn {
status: IssueStatus;
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;