feat: dashboard forecasts releases, tells what changed and weighs the load
The insights reading grows (GraphQL insights(since:), REST ?since=, MCP): - planned releases with a likely (p50) and safe (p85) finish date from a Monte Carlo over the last six weeks of finished work, one after another, and a verdict against their target; - with `since` (the viewer's last visit, kept in the browser): tickets created, finished and sent back from QA, and their mentions; - each person's open points against what they closed per sprint lately; - open tickets without estimate, area or assignee, or untouched for 90 days, each count opening the list filtered (new noEstimate, noArea and updatedBefore filters). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -103,6 +103,11 @@ export interface IssueListFilter {
|
||||
unassigned?: boolean;
|
||||
releaseId?: string;
|
||||
sprintId?: string;
|
||||
/** Only tickets nobody estimated / with no area. */
|
||||
noEstimate?: boolean;
|
||||
noArea?: boolean;
|
||||
/** Only tickets not updated since this instant (ISO). */
|
||||
updatedBefore?: string;
|
||||
/** Only tickets holding these custom values. */
|
||||
customFields?: CustomFieldValue[];
|
||||
limit: number;
|
||||
|
||||
@@ -55,6 +55,53 @@ export interface StatusTime {
|
||||
overLimit: boolean;
|
||||
}
|
||||
|
||||
/** When a planned release would be finished, at the team's real pace. */
|
||||
export interface ReleaseForecast {
|
||||
releaseId: string;
|
||||
name: string;
|
||||
targetDate: string | null;
|
||||
total: number;
|
||||
finished: number;
|
||||
/** Half of the simulated futures finish by then. */
|
||||
likelyDate: string | null;
|
||||
/** 85% of them do. */
|
||||
safeDate: string | null;
|
||||
verdict: 'DONE' | 'NO_DATE' | 'UNKNOWN' | 'ON_TRACK' | 'AT_RISK' | 'LATE';
|
||||
}
|
||||
|
||||
/** What happened after a moment (my last visit). */
|
||||
export interface ChangesSince {
|
||||
since: string;
|
||||
created: InsightIssue[];
|
||||
createdCount: number;
|
||||
finished: InsightIssue[];
|
||||
finishedCount: number;
|
||||
/** Sent back from QA. */
|
||||
returned: InsightIssue[];
|
||||
returnedCount: number;
|
||||
mentions: number;
|
||||
}
|
||||
|
||||
/** One person's open work against what they usually close per sprint. */
|
||||
export interface TeamLoad {
|
||||
user: UserRef;
|
||||
openTickets: number;
|
||||
openPoints: number;
|
||||
blocked: number;
|
||||
pointsPerSprint: number | null;
|
||||
overloaded: boolean;
|
||||
}
|
||||
|
||||
/** Open tickets missing what planning needs. */
|
||||
export interface BacklogHealth {
|
||||
open: number;
|
||||
unestimated: number;
|
||||
noArea: number;
|
||||
unassigned: number;
|
||||
stale: number;
|
||||
staleDays: number;
|
||||
}
|
||||
|
||||
/** The dashboard's reading of a project beyond the counts. */
|
||||
export interface ProjectInsights {
|
||||
generatedAt: string;
|
||||
@@ -62,4 +109,9 @@ export interface ProjectInsights {
|
||||
risks: Risks;
|
||||
aging: { issues: InsightIssue[]; thresholdDays: number | null };
|
||||
bottlenecks: StatusTime[];
|
||||
releases: ReleaseForecast[];
|
||||
/** Only when asked with a `since`. */
|
||||
changes: ChangesSince | null;
|
||||
team: TeamLoad[];
|
||||
backlog: BacklogHealth;
|
||||
}
|
||||
|
||||
@@ -64,8 +64,11 @@ export abstract class ProjectsGateway {
|
||||
abstract publicProjects(): Observable<Project[]>;
|
||||
abstract project(idOrKey: string): Observable<Project>;
|
||||
abstract stats(projectId: string): Observable<ProjectStats>;
|
||||
/** Sprint under way, risks, aging and bottlenecks (the dashboard). */
|
||||
abstract insights(projectId: string): Observable<ProjectInsights>;
|
||||
/** The dashboard; with `since`, also what changed after it. */
|
||||
abstract insights(
|
||||
projectId: string,
|
||||
since: string | null,
|
||||
): Observable<ProjectInsights>;
|
||||
/** Statistics of the last `days` days. */
|
||||
abstract analytics(
|
||||
projectId: string,
|
||||
|
||||
Reference in New Issue
Block a user