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:
@@ -943,6 +943,69 @@ export class StatusTimeDto {
|
||||
@ApiProperty() overLimit!: boolean;
|
||||
}
|
||||
|
||||
export class ReleaseForecastDto {
|
||||
@ApiProperty({ format: 'uuid' }) releaseId!: string;
|
||||
@ApiProperty() name!: string;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date' })
|
||||
targetDate!: string | null;
|
||||
@ApiProperty() total!: number;
|
||||
@ApiProperty() finished!: number;
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
nullable: true,
|
||||
format: 'date',
|
||||
description: 'Half of the simulated futures finish by then',
|
||||
})
|
||||
likelyDate!: string | null;
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
nullable: true,
|
||||
format: 'date',
|
||||
description: '85% finish by then',
|
||||
})
|
||||
safeDate!: string | null;
|
||||
@ApiProperty({
|
||||
enum: ['DONE', 'NO_DATE', 'UNKNOWN', 'ON_TRACK', 'AT_RISK', 'LATE'],
|
||||
})
|
||||
verdict!: string;
|
||||
}
|
||||
|
||||
export class ChangesSinceDto {
|
||||
@ApiProperty() since!: string;
|
||||
@ApiProperty({ type: [InsightIssueDto] }) created!: InsightIssueDto[];
|
||||
@ApiProperty() createdCount!: number;
|
||||
@ApiProperty({ type: [InsightIssueDto] }) finished!: InsightIssueDto[];
|
||||
@ApiProperty() finishedCount!: number;
|
||||
@ApiProperty({ type: [InsightIssueDto], description: 'Sent back from QA' })
|
||||
returned!: InsightIssueDto[];
|
||||
@ApiProperty() returnedCount!: number;
|
||||
@ApiProperty({ description: 'Times you were mentioned here' })
|
||||
mentions!: number;
|
||||
}
|
||||
|
||||
export class TeamLoadDto {
|
||||
@ApiProperty({ type: UserRefDto }) user!: UserRefDto;
|
||||
@ApiProperty() openTickets!: number;
|
||||
@ApiProperty() openPoints!: number;
|
||||
@ApiProperty() blocked!: number;
|
||||
@ApiProperty({
|
||||
type: Number,
|
||||
nullable: true,
|
||||
description: 'Points closed per sprint, last three closed sprints',
|
||||
})
|
||||
pointsPerSprint!: number | null;
|
||||
@ApiProperty() overloaded!: boolean;
|
||||
}
|
||||
|
||||
export class BacklogHealthDto {
|
||||
@ApiProperty() open!: number;
|
||||
@ApiProperty() unestimated!: number;
|
||||
@ApiProperty() noArea!: number;
|
||||
@ApiProperty() unassigned!: number;
|
||||
@ApiProperty() stale!: number;
|
||||
@ApiProperty() staleDays!: number;
|
||||
}
|
||||
|
||||
export class ProjectInsightsDto {
|
||||
@ApiProperty() generatedAt!: string;
|
||||
@ApiProperty({ type: SprintPulseDto, nullable: true })
|
||||
@@ -950,4 +1013,13 @@ export class ProjectInsightsDto {
|
||||
@ApiProperty({ type: RisksDto }) risks!: RisksDto;
|
||||
@ApiProperty({ type: AgingDto }) aging!: AgingDto;
|
||||
@ApiProperty({ type: [StatusTimeDto] }) bottlenecks!: StatusTimeDto[];
|
||||
@ApiProperty({ type: [ReleaseForecastDto] }) releases!: ReleaseForecastDto[];
|
||||
@ApiProperty({
|
||||
type: ChangesSinceDto,
|
||||
nullable: true,
|
||||
description: 'Only when asked with `since`',
|
||||
})
|
||||
changes!: ChangesSinceDto | null;
|
||||
@ApiProperty({ type: [TeamLoadDto] }) team!: TeamLoadDto[];
|
||||
@ApiProperty({ type: BacklogHealthDto }) backlog!: BacklogHealthDto;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user