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:
@@ -1435,6 +1435,89 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/projects/{project}/insights": {
|
||||
"get": {
|
||||
"description": "Sprint under way (pace against time), overdue, due-soon, blocked and stuck tickets, aging of the work under way against the p85 cycle time, and average days in each in-progress and QA status.",
|
||||
"operationId": "ProjectsController_insights",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "project",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project id or key",
|
||||
"schema": {
|
||||
"example": "WEB",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProjectInsightsDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or rejected Pireon token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Not a member of the project (or not allowed)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"pireon-oauth": []
|
||||
},
|
||||
{
|
||||
"pireon": []
|
||||
},
|
||||
{}
|
||||
],
|
||||
"summary": "The dashboard",
|
||||
"tags": [
|
||||
"Projects"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/projects/{project}/activity": {
|
||||
"get": {
|
||||
"operationId": "ProjectsController_activity",
|
||||
@@ -11804,6 +11887,264 @@
|
||||
"qa"
|
||||
]
|
||||
},
|
||||
"SprintPulseDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sprintId": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"startDate": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"endDate": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"totalDays": {
|
||||
"type": "number"
|
||||
},
|
||||
"daysLeft": {
|
||||
"type": "number",
|
||||
"description": "Days left after today"
|
||||
},
|
||||
"tickets": {
|
||||
"type": "number"
|
||||
},
|
||||
"doneTickets": {
|
||||
"type": "number"
|
||||
},
|
||||
"points": {
|
||||
"type": "number"
|
||||
},
|
||||
"donePoints": {
|
||||
"type": "number"
|
||||
},
|
||||
"pacePerDay": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
},
|
||||
"neededPerDay": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
},
|
||||
"behind": {
|
||||
"type": "boolean",
|
||||
"description": "At this pace, what is open would not be closed"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sprintId",
|
||||
"name",
|
||||
"startDate",
|
||||
"endDate",
|
||||
"totalDays",
|
||||
"daysLeft",
|
||||
"tickets",
|
||||
"doneTickets",
|
||||
"points",
|
||||
"donePoints",
|
||||
"pacePerDay",
|
||||
"neededPerDay",
|
||||
"behind"
|
||||
]
|
||||
},
|
||||
"InsightIssueDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"example": "WEB-12"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "Status id"
|
||||
},
|
||||
"assignee": {
|
||||
"nullable": true,
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/UserRefDto"
|
||||
}
|
||||
]
|
||||
},
|
||||
"days": {
|
||||
"type": "number",
|
||||
"description": "Days in its status (aging, stuck) or past / to its due date"
|
||||
},
|
||||
"dueDate": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"format": "date"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"key",
|
||||
"title",
|
||||
"status",
|
||||
"assignee",
|
||||
"days",
|
||||
"dueDate"
|
||||
]
|
||||
},
|
||||
"RisksDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"overdue": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/InsightIssueDto"
|
||||
}
|
||||
},
|
||||
"overdueCount": {
|
||||
"type": "number"
|
||||
},
|
||||
"dueSoon": {
|
||||
"description": "Due within 7 days",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/InsightIssueDto"
|
||||
}
|
||||
},
|
||||
"dueSoonCount": {
|
||||
"type": "number"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/InsightIssueDto"
|
||||
}
|
||||
},
|
||||
"blockedCount": {
|
||||
"type": "number"
|
||||
},
|
||||
"stuck": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/InsightIssueDto"
|
||||
}
|
||||
},
|
||||
"stuckCount": {
|
||||
"type": "number"
|
||||
},
|
||||
"stuckDays": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"overdue",
|
||||
"overdueCount",
|
||||
"dueSoon",
|
||||
"dueSoonCount",
|
||||
"blocked",
|
||||
"blockedCount",
|
||||
"stuck",
|
||||
"stuckCount",
|
||||
"stuckDays"
|
||||
]
|
||||
},
|
||||
"AgingDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"issues": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/InsightIssueDto"
|
||||
}
|
||||
},
|
||||
"thresholdDays": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "p85 cycle time of the last 90 days"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"issues",
|
||||
"thresholdDays"
|
||||
]
|
||||
},
|
||||
"StatusTimeDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"averageDays": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
},
|
||||
"samples": {
|
||||
"type": "number"
|
||||
},
|
||||
"open": {
|
||||
"type": "number"
|
||||
},
|
||||
"wipLimit": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
},
|
||||
"overLimit": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"averageDays",
|
||||
"samples",
|
||||
"open",
|
||||
"wipLimit",
|
||||
"overLimit"
|
||||
]
|
||||
},
|
||||
"ProjectInsightsDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"generatedAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"sprint": {
|
||||
"nullable": true,
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/SprintPulseDto"
|
||||
}
|
||||
]
|
||||
},
|
||||
"risks": {
|
||||
"$ref": "#/components/schemas/RisksDto"
|
||||
},
|
||||
"aging": {
|
||||
"$ref": "#/components/schemas/AgingDto"
|
||||
},
|
||||
"bottlenecks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/StatusTimeDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"generatedAt",
|
||||
"sprint",
|
||||
"risks",
|
||||
"aging",
|
||||
"bottlenecks"
|
||||
]
|
||||
},
|
||||
"ActivityDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Reference in New Issue
Block a user