feat: dashboard reads the sprint, the risks, aging and bottlenecks
A new projectInsights reading (GraphQL Project.insights, REST
/projects/{project}/insights, MCP get_project_insights), computed by pure
functions over the read model and the timeline:
- the sprint under way: days left, points done, pace against what is
needed and whether it gets there;
- tickets at risk: overdue, due within a week, blocked by an open ticket
and stuck (7 days or more in an in-progress or QA status);
- aging of the work under way against the p85 cycle time;
- average days in each in-progress and QA status, with WIP limits.
The dashboard shows them in four new panels, reloaded on every change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,3 +9,4 @@ export * from './analytics.read-model';
|
||||
export * from './notification.read-model';
|
||||
export * from './saved-filter.read-model';
|
||||
export * from './sprint.read-model';
|
||||
export * from './insights.read-model';
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { UserRefReadModel } from './user-ref.read-model';
|
||||
|
||||
/** A ticket named by the dashboard, with the number that put it there. */
|
||||
export class InsightIssueReadModel {
|
||||
id!: string;
|
||||
key!: string;
|
||||
title!: string;
|
||||
/** Status id of the project's settings. */
|
||||
status!: string;
|
||||
assignee!: UserRefReadModel | null;
|
||||
/** Days in its current status (aging, stuck) or past/to its due date. */
|
||||
days!: number;
|
||||
dueDate!: string | null;
|
||||
}
|
||||
|
||||
/** The sprint under way: time gone against work done. */
|
||||
export class SprintPulseReadModel {
|
||||
sprintId!: string;
|
||||
name!: string;
|
||||
startDate!: string;
|
||||
endDate!: string;
|
||||
/** Days of the sprint, both ends included. */
|
||||
totalDays!: number;
|
||||
/** Days left after today (0 on the last day; negative once it is over). */
|
||||
daysLeft!: number;
|
||||
tickets!: number;
|
||||
doneTickets!: number;
|
||||
points!: number;
|
||||
donePoints!: number;
|
||||
/** Points closed per day so far (null on the first day). */
|
||||
pacePerDay!: number | null;
|
||||
/** Points per day still needed to finish (null when nothing is left). */
|
||||
neededPerDay!: number | null;
|
||||
/** At this pace, what is open would not be closed in time. */
|
||||
behind!: boolean;
|
||||
}
|
||||
|
||||
/** Tickets to look at now. Lists are the worst first, a few at most. */
|
||||
export class RiskReadModel {
|
||||
overdue!: InsightIssueReadModel[];
|
||||
overdueCount!: number;
|
||||
/** Due within the next 7 days. */
|
||||
dueSoon!: InsightIssueReadModel[];
|
||||
dueSoonCount!: number;
|
||||
/** Waiting for an open ticket that blocks them (or they depend on). */
|
||||
blocked!: InsightIssueReadModel[];
|
||||
blockedCount!: number;
|
||||
/** In progress or in QA for `stuckDays` or more without moving. */
|
||||
stuck!: InsightIssueReadModel[];
|
||||
stuckCount!: number;
|
||||
stuckDays!: number;
|
||||
}
|
||||
|
||||
/** How long the work under way has been where it is. */
|
||||
export class AgingReadModel {
|
||||
issues!: InsightIssueReadModel[];
|
||||
/** 85% of the tickets finished lately took at most this (cycle time). */
|
||||
thresholdDays!: number | null;
|
||||
}
|
||||
|
||||
/** Time tickets spend in a status of the flow, and how many are there. */
|
||||
export class StatusTimeReadModel {
|
||||
status!: string;
|
||||
/** Average stay of the tickets that left it in the last 90 days. */
|
||||
averageDays!: number | null;
|
||||
/** Stays measured. */
|
||||
samples!: number;
|
||||
open!: number;
|
||||
wipLimit!: number | null;
|
||||
overLimit!: boolean;
|
||||
}
|
||||
|
||||
/** What the project dashboard reads beyond the counts. */
|
||||
export class ProjectInsightsReadModel {
|
||||
generatedAt!: string;
|
||||
sprint!: SprintPulseReadModel | null;
|
||||
risks!: RiskReadModel;
|
||||
aging!: AgingReadModel;
|
||||
/** In-progress and QA statuses, in board order. */
|
||||
bottlenecks!: StatusTimeReadModel[];
|
||||
}
|
||||
Reference in New Issue
Block a user