Skip to content

feat(cycle): compute real cycle and module completion progress - #273

Merged
martian56 merged 3 commits into
Devlaner:mainfrom
cavidelizade:feat/cycle-module-real-progress
Jul 10, 2026
Merged

feat(cycle): compute real cycle and module completion progress#273
martian56 merged 3 commits into
Devlaner:mainfrom
cavidelizade:feat/cycle-module-real-progress

Conversation

@cavidelizade

@cavidelizade cavidelizade commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Feature summary

Cycle and module list pages now show real completion progress computed from work-item states, instead of a faked 0%/100% from the cycle/module status.

Linked issues / discussion

Closes #185

User-facing behavior

On the Cycles and Modules list pages, each item's progress circle/bar reflects completed / total work items (by state group). The modules "sort by progress" now sorts on the real value.

What changed

API (apps/api/)

  • store/cycle.go, store/module.go: StateDistributionByProject returns per-cycle / per-module issue counts grouped by state group in one query.
  • service/cycle.go, service/module.go: ProgressBulk adds a total and returns the map keyed by id.
  • handler/cycle.go, handler/module.go + routes: GET .../cycles-progress/ and GET .../modules-progress/, mirroring the existing epics-progress endpoint.

UI (apps/web/)

  • cycleService.listProgress / moduleService.listProgress.
  • CyclesPage / ModulesPage: fetch the bulk progress and compute getProgress from completed / total.

Database

  • No schema changes (reads existing cycle_issues / module_issues + states).

Why this design

Mirrors the proven epics-progress bulk pattern so the list pages make one request instead of one per item, and reuses the same state-group distribution shape the epic progress bar already consumes.

Test plan

  • go vet ./... and full go test ./... green (testcontainers).
  • New TestCycle_ProgressBulk: a cycle with two backlog issues reports total: 2, completed: 0 via the endpoint.
  • npm run typecheck, npm run lint, npm run build green.

Out of scope (follow-ups)

Rollout notes

None.

AI assistance

  • AI tools were used — tool(s): Claude Code — and AI-assisted commits include a Co-Authored-By: trailer

Checklist

  • PR title follows Conventional Commits and is <= 100 chars
  • Trailing slashes on new routes match neighboring routes
  • No --no-verify bypass
  • Acceptance criteria from the linked issue are all met

Summary by CodeRabbit

  • New Features
    • Added authenticated API endpoints to retrieve per-cycle and per-module progress (issue-state counts with totals).
    • Updated Cycles and Modules pages to load and use real completion progress from the new endpoints.
  • Bug Fixes
    • Progress calculations and sorting now rely on fetched {completed, total} values with fallbacks, rather than status-derived estimates.
    • UI now safely falls back to empty progress when progress data can’t be loaded.
  • Tests
    • Added an API test covering bulk cycle progress retrieval.

Cycle and module list progress was faked from status (0% or 100%). Adds
bulk per-project state-group distribution queries and cycles-progress /
modules-progress endpoints (mirroring epics-progress), and renders both
list pages' progress from completed/total. The modules progress sort now
uses the real value too.

Closes Devlaner#185

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cavidelizade
cavidelizade requested a review from a team as a code owner July 5, 2026 17:33
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 14d76e31-75bc-43c8-8bfb-21e2453d1717

📥 Commits

Reviewing files that changed from the base of the PR and between c56634c and c775543.

📒 Files selected for processing (8)
  • apps/api/internal/handler/cycle.go
  • apps/api/internal/handler/cycle_test.go
  • apps/api/internal/handler/module.go
  • apps/api/internal/router/router.go
  • apps/api/internal/service/cycle.go
  • apps/api/internal/service/module.go
  • apps/api/internal/store/cycle.go
  • apps/web/src/services/cycleService.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/api/internal/router/router.go
  • apps/api/internal/service/module.go
  • apps/api/internal/handler/cycle.go
  • apps/api/internal/handler/module.go
  • apps/api/internal/handler/cycle_test.go
  • apps/api/internal/store/cycle.go
  • apps/web/src/services/cycleService.ts
  • apps/api/internal/service/cycle.go

📝 Walkthrough

Walkthrough

Adds real cycle and module progress endpoints backed by state-group counts, then updates the web pages to fetch and render completion from completed/total data instead of status-based heuristics.

Changes

Cycle/Module Progress Feature

Layer / File(s) Summary
State distribution queries
apps/api/internal/store/cycle.go, apps/api/internal/store/module.go
Aggregate SQL queries count non-deleted issues per state group for each cycle and module, initializing fixed group maps and defaulting unknown groups to backlog.
Progress aggregation service
apps/api/internal/service/cycle.go, apps/api/internal/service/module.go
ProgressBulk methods enforce project access, fetch state distributions, and add computed total counts per entity.
API endpoints and wiring
apps/api/internal/handler/cycle.go, apps/api/internal/handler/module.go, apps/api/internal/router/router.go, apps/api/internal/handler/cycle_test.go
Authenticated progress handlers validate UUIDs, map service errors, register protected routes, and test cycle progress totals.
Frontend service clients
apps/web/src/services/cycleService.ts, apps/web/src/services/moduleService.ts
Progress interfaces and listProgress methods fetch typed per-cycle and per-module progress maps.
Page-level progress rendering
apps/web/src/pages/CyclesPage.tsx, apps/web/src/pages/ModulesPage.tsx
Pages fetch and refresh progress, then calculate sorting and display percentages from completed/total values.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CyclesPage
  participant cycleService
  participant CycleHandler
  participant CycleService
  participant CycleStore

  CyclesPage->>cycleService: listProgress(workspaceSlug, projectId)
  cycleService->>CycleHandler: GET /cycles-progress/
  CycleHandler->>CycleService: ProgressBulk(ctx, slug, projectID, userID)
  CycleService->>CycleStore: StateDistributionByProject(ctx, projectID)
  CycleStore-->>CycleService: per-cycle grouped counts
  CycleService-->>CycleService: compute total per cycle
  CycleService-->>CycleHandler: progress map
  CycleHandler-->>cycleService: 200 JSON progress
  cycleService-->>CyclesPage: Record<string, CycleProgress>
Loading
sequenceDiagram
  participant ModulesPage
  participant moduleService
  participant ModuleHandler
  participant ModuleService
  participant ModuleStore

  ModulesPage->>moduleService: listProgress(workspaceSlug, projectId)
  moduleService->>ModuleHandler: GET /modules-progress/
  ModuleHandler->>ModuleService: ProgressBulk(ctx, slug, projectID, userID)
  ModuleService->>ModuleStore: StateDistributionByProject(ctx, projectID)
  ModuleStore-->>ModuleService: per-module grouped counts
  ModuleService-->>ModuleService: compute total per module
  ModuleService-->>ModuleHandler: progress map
  ModuleHandler-->>moduleService: 200 JSON progress
  moduleService-->>ModulesPage: Record<string, ModuleProgress>
Loading

Possibly related PRs

Suggested labels: enhancement, API, UI

Suggested reviewers: nazarli-shabnam, martian56

Poem

A rabbit hopped to count with care,
Each state in buckets laid out fair.
No fake percent, just totals true,
And pages bloom in progress view.
Hop hop! The bars now tell the tale 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is conventional and accurately summarizes the main change: real cycle and module progress computation.
Description check ✅ Passed The description covers the summary, linked issue, changed areas, rationale, tests, rollout notes, and AI disclosure; only a few template sections are missing.
Linked Issues check ✅ Passed The PR satisfies #185 by adding state-distribution queries, bulk progress endpoints, UI progress rendering, and real module progress sorting.
Out of Scope Changes check ✅ Passed No unrelated code changes are apparent; the burndown chart is explicitly called out as out of scope rather than implemented here.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/api/internal/handler/cycle.go (1)

66-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

prog == nil branch is unreachable.

ProgressBulk always returns a non-nil map from StateDistributionByProject (initialized via make(...)), even with zero rows. So if prog == nil at Line 87 never triggers; an empty progress dict is instead returned via the final c.JSON(http.StatusOK, prog) at Line 91, which already serializes identically to gin.H{}. Harmless but dead code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/internal/handler/cycle.go` around lines 66 - 92, The nil check in
CycleHandler.CyclesProgress is dead code because Cycle.ProgressBulk always
returns a non-nil map from StateDistributionByProject, even when empty. Remove
the unreachable prog == nil branch and rely on the existing
c.JSON(http.StatusOK, prog) path, since an empty map already serializes to an
empty JSON object; keep the error handling around middleware.GetUser,
uuid.Parse, and ProgressBulk unchanged.
apps/web/src/pages/ModulesPage.tsx (1)

272-275: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate progress computation between sort comparator and render helper.

getProgress is defined twice with identical logic (inline in the sort comparator and again as a module-scope function). Consider extracting one shared helper to prevent the two from drifting apart.

♻️ Proposed consolidation
+  const getProgress = (mod: ModuleApiResponse) => {
+    const pr = moduleProgress[mod.id];
+    const total = pr?.total ?? mod.issue_count ?? 0;
+    if (!total) return 0;
+    return Math.round(((pr?.completed ?? 0) / total) * 100);
+  };
+
   const sortedModules = [...filteredModules].sort((a, b) => {
-    const getProgress = (mod: ModuleApiResponse) => {
-      const pr = moduleProgress[mod.id];
-      const total = pr?.total ?? mod.issue_count ?? 0;
-      if (!total) return 0;
-      return Math.round(((pr?.completed ?? 0) / total) * 100);
-    };
     let cmp = 0;
     switch (sortBy) {
       ...
     }
     return order === 'desc' ? -cmp : cmp;
   });
-
-  const getProgress = (mod: ModuleApiResponse) => {
-    const pr = moduleProgress[mod.id];
-    const total = pr?.total ?? mod.issue_count ?? 0;
-    if (!total) return 0;
-    return Math.round(((pr?.completed ?? 0) / total) * 100);
-  };

Also applies to: 383-386

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/pages/ModulesPage.tsx` around lines 272 - 275, The progress
calculation for modules is duplicated between the sort comparator and the
render-time helper, so consolidate it into a single shared function. Extract the
repeated logic from the inline `getProgress` in `ModulesPage.tsx` and the
module-scope `getProgress` helper into one reusable function, then use that
shared helper in both the sorting code and the UI rendering path. Keep the
existing behavior with `moduleProgress`, `mod.issue_count`, and the zero-total
guard unchanged while routing both call sites through the same symbol.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/pages/CyclesPage.tsx`:
- Around line 398-414: The PROJECT_CYCLES_REFRESH_EVENT refresh path in
CyclesPage currently reloads cycles and issues but leaves cycleProgress stale,
so update the event handler to also call
cycleService.listProgress(workspaceSlug, projectId) and setCycleProgress with
the result (or clear it on failure) using the same workspaceSlug/projectId flow
already used in the useEffect that loads progress.

---

Nitpick comments:
In `@apps/api/internal/handler/cycle.go`:
- Around line 66-92: The nil check in CycleHandler.CyclesProgress is dead code
because Cycle.ProgressBulk always returns a non-nil map from
StateDistributionByProject, even when empty. Remove the unreachable prog == nil
branch and rely on the existing c.JSON(http.StatusOK, prog) path, since an empty
map already serializes to an empty JSON object; keep the error handling around
middleware.GetUser, uuid.Parse, and ProgressBulk unchanged.

In `@apps/web/src/pages/ModulesPage.tsx`:
- Around line 272-275: The progress calculation for modules is duplicated
between the sort comparator and the render-time helper, so consolidate it into a
single shared function. Extract the repeated logic from the inline `getProgress`
in `ModulesPage.tsx` and the module-scope `getProgress` helper into one reusable
function, then use that shared helper in both the sorting code and the UI
rendering path. Keep the existing behavior with `moduleProgress`,
`mod.issue_count`, and the zero-total guard unchanged while routing both call
sites through the same symbol.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dffd8b89-0ce5-45e2-8891-33aae645c003

📥 Commits

Reviewing files that changed from the base of the PR and between 7992430 and 4d028e4.

📒 Files selected for processing (12)
  • apps/api/internal/handler/cycle.go
  • apps/api/internal/handler/cycle_test.go
  • apps/api/internal/handler/module.go
  • apps/api/internal/router/router.go
  • apps/api/internal/service/cycle.go
  • apps/api/internal/service/module.go
  • apps/api/internal/store/cycle.go
  • apps/api/internal/store/module.go
  • apps/web/src/pages/CyclesPage.tsx
  • apps/web/src/pages/ModulesPage.tsx
  • apps/web/src/services/cycleService.ts
  • apps/web/src/services/moduleService.ts

Comment thread apps/web/src/pages/CyclesPage.tsx
@nazarli-shabnam

Copy link
Copy Markdown
Member

fix the comments tho

The PROJECT_CYCLES_REFRESH_EVENT handler reloaded cycles and issues but left
cycleProgress stale, so progress could lag after add/remove. Reload it too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@martian56

Copy link
Copy Markdown
Member

@cavidelizade Can you resolve the merge conflicts please?

@Musa-Qureshi-01

Copy link
Copy Markdown

Great work on this PR! If the merge conflicts are still pending, I'd be happy to resolve them. Feel free to assign it to me.

@martian56

Copy link
Copy Markdown
Member

@Musa-Qureshi-01 Thanks!

@martian56

Copy link
Copy Markdown
Member

@Musa-Qureshi-01 are you still considering to work on this PR?

@martian56 martian56 added API improvement Enhancement to an existing, partial feature UI/UX labels Jul 8, 2026
@Musa-Qureshi-01

Musa-Qureshi-01 commented Jul 8, 2026 via email

Copy link
Copy Markdown

@martian56

Copy link
Copy Markdown
Member

@Musa-Qureshi-01 Alright, good luck on your exams 🙏

…eal-progress

# Conflicts:
#	apps/api/internal/handler/cycle_test.go
@martian56
martian56 merged commit 68a0e64 into Devlaner:main Jul 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API improvement Enhancement to an existing, partial feature UI/UX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[IMPROVEMENT] Compute real cycle/module completion progress from work-item states

4 participants