Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-237Done

4.4.4 Backend — `getSprintReport` (completed/incomplete lists + points via 4.3 roll-up + scope-change, bounded)

Estimate: 22m · Depends on: 4.4.3, 4.3.3

The sprint report read — what got done vs. what did not — built to real-product SCALE (finding #57: bounded aggregates + paginated lists, never load-all). Powers the complete modal's success state (4.4.6) and the standalone closed-sprint report view.

sprintsService.getSprintReport(sprintId, { completedCursor?, incompleteCursor?, limit }) → a SprintReportDto:

  • Points summary{ committed, completed, notCompleted }: committed = the locked committedPoints baseline (4.4.2); completed = SUM(storyPoints) over the sprint's done-category issues; notCompleted = the remainder — REUSE Story 4.3.3 rollupForSprint (the bounded grouped aggregate) rather than re-summing. Unestimated → the DTO returns the numbers (0/null), the UI renders "—" (the data layer stays total — the 4.5.2 pattern).
  • Counts{ completedCount, incompleteCount } from grouped aggregates scoped to the sprint (via getTerminalStatusKeys), NOT a loaded-page sum.
  • Issue listscompleted: WorkItemSummaryDto[] + incomplete: WorkItemSummaryDto[], each cursor-paginated (the first bounded page + a nextCursor); a real sprint can hold hundreds of issues, so the report shows the counts + a bounded page and a "view all" deep-link to the /issues navigator (Story 2.5) filtered to the sprint (the 4.2 "View all issues" mirror), never a full in-report dump.
  • Scope change{ addedAfterStart }: the count of issues associated with the sprint AFTER startDate (derived from the 1.4.6 revision trail — the immutable committedIssueCount baseline anchors it), the Jira "issues added during sprint" figure. Bounded (an aggregate over revisions, not a load-all).
  • Works for a complete sprint (the report) AND an active sprint (a live preview the complete modal can show before confirming). Enforce the finding-#26 workspaceId gate.

Route: GET /api/sprints/[id]/report — HTTP-only, one service call. Mapper in lib/mappers/sprintMappers.tsSprintReportDto in lib/dto/sprints.ts.

Acceptance criteria

  • getSprintReport returns a SprintReportDto with the points summary (committed = the locked baseline; completed/notCompleted via rollupForSprint), the completed/incomplete counts (grouped aggregates, not page sums), the cursor-paginated completed + incomplete issue lists (first bounded page + nextCursor + a "view all" deep-link target), and the addedAfterStart scope-change count (from the 1.4.6 revisions vs startDate).
  • An unestimated sprint returns 0/null points (the DTO stays total; the UI owns the "—"); the report works for both a completed and an active sprint; the lists are NEVER an unbounded dump (finding #57).
  • GET /api/sprints/[id]/report is HTTP-only (one service call); DTOs mapped in lib/mappers/sprintMappers.ts; the finding-#26 workspaceId gate covers the reads.
  • pnpm test:coverage keeps the changed service/mapper files ≥90% branch/fn/line (the coverage gate).

Context refs

  • Story 4.4.3 (completeSprint — sets completedAt + the done/unfinished split) + 4.4.2 (the committedPoints/committedIssueCount baseline) — the data this reports on
  • Story 4.3.3 rollupForSprint(sprintId) (committed/completed/remaining points, bounded aggregate) — the points source to REUSE, not re-sum
  • lib/services/workflowsService.ts getTerminalStatusKeys — the done-category split; the 1.4.6 workItemRevisionsService — the sprint-association revisions the scope-change count reads
  • Story 2.5 /issues navigator — the "view all" deep-link target (filtered to the sprint); finding #57 (bounded aggregates + paginated lists); motir-core/CLAUDE.md (service owns DTO mapping); motir-core-coverage-gate