/cartera (and GraphQL portfolio, REST /api/me/portfolio, MCP get_portfolio) shows every active project I belong to (not archived, not as a guest) at a glance: open, overdue, blocked and stuck tickets, the sprint under way and whether it gets there, planned releases with their forecast, and the open epics with their progress. It reuses the dashboard's computations. A release with no tickets now reads «Sin tickets» instead of «Todo hecho», here and on the dashboard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
213 lines
6.5 KiB
TypeScript
213 lines
6.5 KiB
TypeScript
import { EpicReadModel } from './issue.read-model';
|
|
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;
|
|
}
|
|
|
|
/** When a planned release would be finished, at the team's real pace. */
|
|
export class ReleaseForecastReadModel {
|
|
releaseId!: string;
|
|
name!: string;
|
|
targetDate!: string | null;
|
|
/** Tickets in it (cancelled ones aside). */
|
|
total!: number;
|
|
finished!: number;
|
|
/** Half of the simulated futures finish by then (null: no pace yet). */
|
|
likelyDate!: string | null;
|
|
/** 85% of them do. */
|
|
safeDate!: string | null;
|
|
/**
|
|
* DONE: nothing left. NO_DATE: no target to compare. UNKNOWN: nothing
|
|
* finished lately to measure a pace. ON_TRACK: 85% in time. AT_RISK: half
|
|
* in time. LATE: less than half.
|
|
*/
|
|
verdict!: 'DONE' | 'NO_DATE' | 'UNKNOWN' | 'ON_TRACK' | 'AT_RISK' | 'LATE';
|
|
}
|
|
|
|
/** What has happened since a moment (the viewer's last visit). */
|
|
export class ChangesSinceReadModel {
|
|
since!: string;
|
|
created!: InsightIssueReadModel[];
|
|
createdCount!: number;
|
|
finished!: InsightIssueReadModel[];
|
|
finishedCount!: number;
|
|
/** Sent back from QA. */
|
|
returned!: InsightIssueReadModel[];
|
|
returnedCount!: number;
|
|
/** Times the viewer was mentioned in this project. */
|
|
mentions!: number;
|
|
}
|
|
|
|
/** One person's open work against what they usually close. */
|
|
export class TeamLoadReadModel {
|
|
user!: UserRefReadModel;
|
|
openTickets!: number;
|
|
openPoints!: number;
|
|
/** Their open tickets waiting for another open one. */
|
|
blocked!: number;
|
|
/** Points they closed per sprint, over the last three closed sprints. */
|
|
pointsPerSprint!: number | null;
|
|
/** More open points than they usually close in a sprint. */
|
|
overloaded!: boolean;
|
|
}
|
|
|
|
/** Open tickets missing what planning needs. */
|
|
export class BacklogHealthReadModel {
|
|
open!: number;
|
|
unestimated!: number;
|
|
noArea!: number;
|
|
unassigned!: number;
|
|
/** Not updated in `staleDays` days. */
|
|
stale!: number;
|
|
staleDays!: number;
|
|
}
|
|
|
|
/** Bugs created and closed in one week (Monday, UTC). */
|
|
export class BugWeekReadModel {
|
|
week!: string;
|
|
created!: number;
|
|
closed!: number;
|
|
}
|
|
|
|
/** What QA sent back, and let through, per area (last 90 days). */
|
|
export class QaByAreaReadModel {
|
|
/** Area id; null: tickets with no area. */
|
|
area!: string | null;
|
|
returned!: number;
|
|
passed!: number;
|
|
}
|
|
|
|
/** Bugs reported in the 14 days after a release shipped. */
|
|
export class EscapedBugsReadModel {
|
|
releaseId!: string;
|
|
name!: string;
|
|
releasedAt!: string;
|
|
bugs!: number;
|
|
}
|
|
|
|
/** Bugs (types drawn with the bug icon) and QA. */
|
|
export class QualityReadModel {
|
|
/** Open bugs per priority (all five, most urgent first). */
|
|
openBugsByPriority!: { priority: string; count: number }[];
|
|
/** The last 8 weeks, oldest first. */
|
|
bugWeeks!: BugWeekReadModel[];
|
|
qaByArea!: QaByAreaReadModel[];
|
|
/** The last three shipped releases, newest first. */
|
|
escaped!: EscapedBugsReadModel[];
|
|
}
|
|
|
|
/** Open tickets per option of a list custom field. */
|
|
export class FieldBreakdownReadModel {
|
|
fieldId!: string;
|
|
counts!: { value: string; count: number }[];
|
|
/** Open tickets with no value in it. */
|
|
none!: number;
|
|
}
|
|
|
|
/** 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[];
|
|
/** Planned releases, by target date. */
|
|
releases!: ReleaseForecastReadModel[];
|
|
/** Null unless asked with a `since`. */
|
|
changes!: ChangesSinceReadModel | null;
|
|
/** Members (and others with open tickets), busiest first. */
|
|
team!: TeamLoadReadModel[];
|
|
backlog!: BacklogHealthReadModel;
|
|
quality!: QualityReadModel;
|
|
/** One per active list (SELECT) custom field. */
|
|
fields!: FieldBreakdownReadModel[];
|
|
}
|
|
|
|
/** One of my projects at a glance, for the portfolio. */
|
|
export class PortfolioProjectReadModel {
|
|
projectId!: string;
|
|
key!: string;
|
|
name!: string;
|
|
isPublic!: boolean;
|
|
total!: number;
|
|
open!: number;
|
|
overdue!: number;
|
|
blocked!: number;
|
|
stuck!: number;
|
|
/** The sprint under way, if any. */
|
|
sprint!: SprintPulseReadModel | null;
|
|
/** Planned releases with their forecast. */
|
|
releases!: ReleaseForecastReadModel[];
|
|
/** Open epics (nearest due date first), at most six. */
|
|
epics!: EpicReadModel[];
|
|
updatedAt!: string;
|
|
}
|