Skip to content

feat(dashboard): add Work Items page with project filter and pagination#687

Merged
aaight merged 3 commits intodevfrom
feature/work-items-page
Mar 9, 2026
Merged

feat(dashboard): add Work Items page with project filter and pagination#687
aaight merged 3 commits intodevfrom
feature/work-items-page

Conversation

@aaight
Copy link
Copy Markdown
Collaborator

@aaight aaight commented Mar 9, 2026

Summary

  • Add Work Items page at /workitems route listing all tracked work items with PR count and run count
  • Add Work Items nav link to sidebar between Runs and Webhook Logs (using ClipboardList icon)
  • Add project filter dropdown for filtering work items by project
  • Support pagination (client-side, 50 items per page) with graceful empty state

Technical Details

  • New files:

    • web/src/routes/workitems.tsx — Work Items page with project filter, using trpc.workItems.list query
    • web/src/components/workitems/workitems-table.tsx — Table component with work item title (clickable external link), PR count, run count, and client-side pagination
  • Updated files:

    • src/api/routers/workItems.ts — Changed projectId from required to optional; when omitted, returns work items across all projects in the org
    • src/db/repositories/prWorkItemsRepository.ts — Added listWorkItems(orgId, projectId?) function for org-wide or project-filtered queries
    • web/src/components/layout/sidebar.tsx — Added Work Items link with ClipboardList icon between Runs and Webhook Logs
    • web/src/routes/route-tree.ts — Registered workItemsRoute
    • tests/unit/api/routers/workItems.test.ts — Updated tests for new listWorkItems API and added test for org-wide listing

Test plan

  • TypeScript type checks pass (both backend and frontend)
  • All 4261 tests pass (including updated workItems router tests)
  • Lint passes (no new errors)
  • Work Items page accessible at /workitems
  • Table shows work item title as clickable external link, PR count, and run count
  • Project dropdown filters work items by project
  • Empty state shows "No work items found" gracefully
  • Pagination appears when > 50 items exist

Trello card: https://trello.com/c/69adf4ec5c05ef22cb2e62a2

🤖 Generated with Claude Code

Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

Clean implementation that follows existing patterns well. No blocking issues — the code is correct, secure, and follows the established conventions (e.g., the webhook logs page pattern). A couple of observations below.

Should Fix

  • src/db/repositories/prWorkItemsRepository.ts:71-99listWorkItemsForProject is now dead code from production's perspective. The router was its only caller and now uses listWorkItems instead. The old function is still exported and tested in integration tests, but it's functionally superseded by listWorkItems(orgId, projectId). Consider removing it (and updating integration tests to use listWorkItems) to avoid confusion about which function to call.

  • Client-side pagination vs server-side — The webhook logs page uses server-side pagination (sends limit/offset to the API), while this page fetches all work items and paginates client-side. This is fine for now but worth noting: if an org accumulates thousands of work items, the full fetch will get expensive. The PR description explicitly calls this out as client-side pagination, so this is a conscious choice — just flagging for future consideration.

- Remove listWorkItemsForProject function that was superseded by listWorkItems
- Update integration tests to use listWorkItems instead
- All tests pass (4261 tests)

Addresses review feedback on PR #687

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@aaight
Copy link
Copy Markdown
Collaborator Author

aaight commented Mar 9, 2026

Review Feedback Addressed

Thank you for the review! I've addressed the feedback:

Changes Made

Removed dead code: Deleted listWorkItemsForProject function (lines 71-99) that was superseded by listWorkItems

  • Updated integration tests to use listWorkItems(orgId, projectId) instead
  • All 4261 tests pass

Client-side pagination note

The client-side pagination approach is noted. This works well for the current scale, and we can migrate to server-side pagination if/when work item counts grow large enough to impact performance.

Verification

  • ✅ All 4261 tests pass
  • ✅ TypeScript type checks pass
  • ✅ Lint passes (no new errors)

Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

Clean implementation that follows existing codebase patterns well. One correctness bug in the prCount aggregation for the new org-wide (no projectId) query path.

Code Issues

Should Fix

  • src/db/repositories/prWorkItemsRepository.ts:95countDistinct(prWorkItems.prNumber) undercounts PRs in the org-wide query. The prNumber column is only unique within a project (per the uq_pr_work_items_project_pr constraint). When projectId is omitted and the query spans multiple projects, two different PRs (e.g., project-A/PR#1 and project-B/PR#1) linked to the same work item will be counted as one. This was safe before this PR because the old query always filtered to a single projectId. Use countDistinct(prWorkItems.id) instead, which is globally unique.

  • Integration test gap: The integration tests for listWorkItems were updated to pass orgId but none test the org-wide path (calling without projectId). The cross-project aggregation bug above would be caught by a test that links the same work item to PRs in two different projects, then calls listWorkItems(orgId) without a projectId and asserts the combined prCount.

),
)
.where(and(eq(prWorkItems.projectId, projectId), isNotNull(prWorkItems.workItemId)))
.where(and(...conditions))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The .where(and(...conditions)) here groups across multiple projects in the org-wide path, but the prCount above uses countDistinct(prWorkItems.prNumber). Since prNumber is only unique within a project, two distinct PRs from different projects with the same number (e.g., project-A/#1 and project-B/#1) linked to the same work item would be counted as 1 instead of 2.

This was safe before this PR because the old query always filtered to a single projectId.

Fix: use countDistinct(prWorkItems.id) instead — the id column is a globally unique UUID, so it correctly counts distinct PR rows regardless of cross-project grouping.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed! Changed to countDistinct(prWorkItems.id) to correctly count PRs across multiple projects. Also added an integration test that links the same work item to PRs with duplicate numbers across different projects to catch this bug.

- Changed prCount aggregation from countDistinct(prNumber) to countDistinct(id)
- prNumber is only unique within a project, not globally
- Added integration test for org-wide query with same PR numbers across projects

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

LGTM — well-structured feature addition that follows existing patterns in the codebase.

The previous reviewer's countDistinct(prWorkItems.prNumber)countDistinct(prWorkItems.id) fix was correctly applied and verified with a targeted integration test. The authorization model is sound: org-scoped access is enforced via the project-IDs subquery when no projectId is specified, and verifyProjectOrgAccess gates the single-project path.

Minor observation (not blocking): This page uses client-side pagination (fetch all, slice in browser) while the analogous Webhook Logs page uses server-side pagination. This is fine at current scale but worth noting if work item counts grow large — it would be a straightforward follow-up to add limit/offset to the tRPC query.

@aaight aaight merged commit ecd2da6 into dev Mar 9, 2026
6 checks passed
@aaight
Copy link
Copy Markdown
Collaborator Author

aaight commented Mar 9, 2026

Checking the pipeline and moving the Work Items page PR to TODO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants