feat: dashboard maps the activity, follows a ticket's way and the proposals
Three more blocks on the project dashboard, also by REST, GraphQL and MCP: - Activity: what moved each day of the last 12 weeks, in a heatmap, with the busiest weekday and the longest lull. - A ticket's way: for the tickets done in the last 90 days, where the time from created to done went, status by status (waiting included), and how much of it was work (flow efficiency). - The public's proposals: received, waiting, under way, done and discarded, and the most voted still unanswered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1415,6 +1415,16 @@ describe('task-stream (e2e)', () => {
|
||||
);
|
||||
expect(project.insights.risks.overdueCount).toBe(1);
|
||||
|
||||
// Activity per day (today's creations among it), the journey, no proposals.
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
expect(insights.body['heatmap']).toHaveLength(84);
|
||||
expect(
|
||||
insights.body['heatmap'].find((d: { day: string }) => d.day === today)
|
||||
?.count,
|
||||
).toBeGreaterThan(0);
|
||||
expect(insights.body['journey']).toMatchObject({ samples: 0, steps: [] });
|
||||
expect(insights.body['proposals']).toBeNull();
|
||||
|
||||
// What changed since a moment, and what the backlog lacks.
|
||||
const since = new Date(Date.now() - 60_000).toISOString();
|
||||
const later = await rest(
|
||||
|
||||
@@ -13403,6 +13403,142 @@
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"ActivityDayDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"day": {
|
||||
"type": "string",
|
||||
"example": "2026-10-15"
|
||||
},
|
||||
"count": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"day",
|
||||
"count"
|
||||
]
|
||||
},
|
||||
"JourneyStepDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "Status id of the project settings"
|
||||
},
|
||||
"days": {
|
||||
"type": "number",
|
||||
"description": "Average days a closed ticket spent in it"
|
||||
},
|
||||
"share": {
|
||||
"type": "number",
|
||||
"description": "Its part of the whole lead time, 0-100"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"days",
|
||||
"share"
|
||||
]
|
||||
},
|
||||
"JourneyDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"samples": {
|
||||
"type": "number",
|
||||
"description": "Tickets done in the last 90 days"
|
||||
},
|
||||
"leadDays": {
|
||||
"type": "number",
|
||||
"nullable": true
|
||||
},
|
||||
"flowEfficiency": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "Part of the lead time in an in-progress status, 0-100"
|
||||
},
|
||||
"steps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/JourneyStepDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"samples",
|
||||
"leadDays",
|
||||
"flowEfficiency",
|
||||
"steps"
|
||||
]
|
||||
},
|
||||
"WaitingProposalDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"votes": {
|
||||
"type": "number"
|
||||
},
|
||||
"days": {
|
||||
"type": "number",
|
||||
"description": "Days since it came in"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"key",
|
||||
"title",
|
||||
"votes",
|
||||
"days"
|
||||
]
|
||||
},
|
||||
"ProposalsFunnelDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"received": {
|
||||
"type": "number"
|
||||
},
|
||||
"receivedLately": {
|
||||
"type": "number",
|
||||
"description": "In the last 30 days"
|
||||
},
|
||||
"waiting": {
|
||||
"type": "number",
|
||||
"description": "Open, not started, in no sprint nor release"
|
||||
},
|
||||
"underWay": {
|
||||
"type": "number"
|
||||
},
|
||||
"done": {
|
||||
"type": "number"
|
||||
},
|
||||
"discarded": {
|
||||
"type": "number"
|
||||
},
|
||||
"topWaiting": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/WaitingProposalDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"received",
|
||||
"receivedLately",
|
||||
"waiting",
|
||||
"underWay",
|
||||
"done",
|
||||
"discarded",
|
||||
"topWaiting"
|
||||
]
|
||||
},
|
||||
"ProjectInsightsDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13464,6 +13600,26 @@
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/FieldBreakdownDto"
|
||||
}
|
||||
},
|
||||
"heatmap": {
|
||||
"description": "The last 12 weeks from a Monday, oldest first (84 days)",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ActivityDayDto"
|
||||
}
|
||||
},
|
||||
"journey": {
|
||||
"$ref": "#/components/schemas/JourneyDto"
|
||||
},
|
||||
"proposals": {
|
||||
"nullable": true,
|
||||
"description": "Null while the project has never had a proposal",
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ProposalsFunnelDto"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -13477,7 +13633,10 @@
|
||||
"team",
|
||||
"backlog",
|
||||
"quality",
|
||||
"fields"
|
||||
"fields",
|
||||
"heatmap",
|
||||
"journey",
|
||||
"proposals"
|
||||
]
|
||||
},
|
||||
"ActivityDto": {
|
||||
|
||||
@@ -13,6 +13,14 @@ type Activity {
|
||||
type: String!
|
||||
}
|
||||
|
||||
"""How much happened on one day"""
|
||||
type ActivityDay {
|
||||
count: Int!
|
||||
|
||||
"""YYYY-MM-DD"""
|
||||
day: String!
|
||||
}
|
||||
|
||||
input AddChecklistItemInput {
|
||||
issueId: ID!
|
||||
text: String!
|
||||
@@ -815,6 +823,28 @@ input IssueTypeOptionInput {
|
||||
template: IssueTemplateInput
|
||||
}
|
||||
|
||||
"""
|
||||
Where the time of the tickets done in the last 90 days went, created to done
|
||||
"""
|
||||
type Journey {
|
||||
"""Part of the lead time in an in-progress status, 0-100"""
|
||||
flowEfficiency: Int
|
||||
leadDays: Float
|
||||
samples: Int!
|
||||
steps: [JourneyStep!]!
|
||||
}
|
||||
|
||||
type JourneyStep {
|
||||
"""Average days a closed ticket spent in it"""
|
||||
days: Float!
|
||||
|
||||
"""Its part of the whole lead time, 0-100"""
|
||||
share: Int!
|
||||
|
||||
"""Status id of the project settings"""
|
||||
status: String!
|
||||
}
|
||||
|
||||
enum LinkDirection {
|
||||
INWARD
|
||||
OUTWARD
|
||||
@@ -1283,6 +1313,13 @@ type ProjectInsights {
|
||||
"""One per active list custom field"""
|
||||
fields: [FieldBreakdown!]!
|
||||
generatedAt: String!
|
||||
|
||||
"""The last 12 weeks from a Monday, oldest first (84 days)"""
|
||||
heatmap: [ActivityDay!]!
|
||||
journey: Journey!
|
||||
|
||||
"""Null while the project has never had a proposal"""
|
||||
proposals: ProposalsFunnel
|
||||
quality: Quality!
|
||||
releases: [ReleaseForecast!]!
|
||||
risks: Risks!
|
||||
@@ -1361,6 +1398,23 @@ type ProjectStats {
|
||||
total: Int!
|
||||
}
|
||||
|
||||
"""The public's proposals, from received to done or discarded"""
|
||||
type ProposalsFunnel {
|
||||
discarded: Int!
|
||||
done: Int!
|
||||
received: Int!
|
||||
|
||||
"""In the last 30 days"""
|
||||
receivedLately: Int!
|
||||
|
||||
"""Most voted waiting, at most 5"""
|
||||
topWaiting: [WaitingProposal!]!
|
||||
underWay: Int!
|
||||
|
||||
"""Open, not started, in no sprint nor release"""
|
||||
waiting: Int!
|
||||
}
|
||||
|
||||
"""A browser's Web Push subscription (PushSubscription.toJSON())"""
|
||||
input PushSubscriptionInput {
|
||||
auth: String!
|
||||
@@ -1914,6 +1968,15 @@ type UserRef {
|
||||
name: String!
|
||||
}
|
||||
|
||||
type WaitingProposal {
|
||||
"""Days since it came in"""
|
||||
days: Int!
|
||||
id: ID!
|
||||
key: String!
|
||||
title: String!
|
||||
votes: Int!
|
||||
}
|
||||
|
||||
"""
|
||||
Where the project tells another system what happened (signed POST, HMAC-SHA256)
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user