Leveling Up Our Project Dashboard & Taming the CI Beast
A deep dive into enriching our project dashboard with vital stats and a comprehensive fix for our failing CI pipeline, covering linting, unit tests, and E2E infrastructure.
Every now and then, a development session comes along that perfectly marries feature enhancement with critical infrastructure maintenance. This past week was one such time. Our mission: to transform a functional but sparse project list into an insightful, data-rich overview, and simultaneously, to wrestle our stubbornly failing CI pipeline back into submission.
It was a classic two-pronged attack – pushing forward with new capabilities while shoring up our foundational quality gates. By the end of the session, both objectives were not just met, but exceeded, leaving us with a more robust application and a much happier CI.
The Quest for Project Insights: A Data-Driven Dashboard
Our /dashboard/projects page, while serving its purpose, was a bit... minimalist. It listed projects, but offered no immediate context on their health or activity. We wanted to see key stats at a glance: how many workflows were running, how many discussions were active, total spend, and perhaps most crucially, a project's success rate.
Backend Brilliance: Aggregating the Data
The journey began deep in the backend, specifically within our src/server/trpc/routers/projects.ts list query. To get the rich data we needed, we implemented a multi-faceted approach:
- Counting Relations: We leveraged Prisma's
_countincludes for several related models likeworkflows,discussions,projectNotes, andactionPoints. This gave us immediate counts of associated records. - Parallel Aggregation Power: For more complex metrics, we introduced a series of parallel aggregation queries. This included:
- Counting
draftsandopen actionsfor a quick activity pulse. - Calculating
totalSpendby summing costs across workflow steps, discussion messages, reports, and blog posts. This required careful aggregation to avoid floating point inaccuracies. - Deriving a
successRatefor terminal workflows:completed / (completed + failed). If no terminal workflows existed, we gracefully returnednull.
- Counting
- Refinements for Robustness: We added small but mighty optimizations:
- Stripping the raw
_countobjects from the final response to keep the payload clean. - Rounding
totalSpendto prevent float drift issues. - An early return for empty
projectIdsto prevent unnecessary database queries, a simple but effective performance gain.
- Stripping the raw
Frontend Flourish: Bringing Data to Life
With the backend serving up a feast of data, the frontend's job was to present it elegantly. Our src/app/(dashboard)/dashboard/projects/page.tsx underwent a significant redesign:
- Compact Stat Rows: Each project card now features a compact row of statistics, each adorned with a relevant
lucideicon (thinkGitBranchfor workflows,MessageSquarefor discussions,DollarSignfor spend). This provides visual cues without clutter. - Semantic Badges: We introduced badges for key metrics. The
successRatebadge dynamically changes itsvariant(success, warning, danger) based on the rate, offering immediate visual feedback on project health. Other badges highlight open action counts, drafts, and post counts. - Accessibility & Helpers: Icons were marked
aria-hidden="true"for screen reader users, and aformatCost(usd)helper ensured consistent, readable currency display.
Taming the CI Beast: A Pipeline Comeback
While the dashboard was getting its glow-up, our CI pipeline was struggling, with three distinct job failures. A broken CI is a developer's nemesis, so this was a top priority.
1. Linting with a Vengeance
The lint job was failing consistently. The culprit? An upgrade of eslint-config-next to 14.2.35, which brought @typescript-eslint up to v8.x. This new version requires explicit plugin registration.
- The Fix: We updated
.eslintrc.jsonto explicitly include"plugins": ["@typescript-eslint"]. - Beyond the Fix: This also presented an opportunity to clean house. We added
varsIgnorePatternanddestructuredArrayIgnorePatternfor the common_prefix convention for unused variables. More importantly, this allowed us to finally tackle 40+ pre-existing lint errors across approximately 25 files! This included removing unused imports and fixing a conditional React Hook call inmarkdown-renderer.tsxby moving auseCallbackbefore an early return. This wasn't just a fix; it was a significant improvement in our codebase's health.
2. The Elusive Unit Test
A single unit test in tests/unit/services/llm/kimi.test.ts was failing.
- The Fix: A quick inspection revealed that an external LLM adapter had changed its expected model name from
kimi-k2-0711tokimi-k2-0711-preview. Updating the test's expected value brought it back in line. A reminder that even small external changes can ripple through our tests.
3. E2E's Database Dilemma
Our End-to-End (E2E) tests were failing due to a database issue.
- The Fix: The E2E tests' PostgreSQL service in
.github/workflows/ci.ymlwas usingpostgres:16-alpine. However, our application schema requires thevectortype, which is provided by thepgvectorextension. Changing the service image topgvector/pgvector:pg16resolved the issue, allowing our E2E tests to finally connect and run against a compatible database.
Lessons Learned & Overcoming Hurdles
No development session is without its minor skirmishes. Here are a couple of insights gained:
- Styling Component Variants: We initially tried to override the
Badgecomponent's color using aclassNamedirectly. This proved fragile, astailwind-mergestruggled to correctly resolve CSS variable names, leading to inconsistent styling.- Lesson: When a component offers a
variantprop for semantic styling (e.g.,success,warning,danger), it's almost always best to use it directly rather than trying to force overrides withclassName. It's more robust and aligns with the component's intended API.
- Lesson: When a component offers a
- Destructuring Unused Props in TypeScript: We attempted to prefix a destructured prop name directly (e.g.,
_teamId) in a function signature to mark it as unused. TypeScript, however, correctly flagged this as an error because the prop name must match the type definition.- Lesson: For marking unused destructured props in TypeScript, use the rename syntax:
{ teamId: _teamId }. This allows you to match the type definition while still using the_prefix convention for unused variables within the function body.
- Lesson: For marking unused destructured props in TypeScript, use the rename syntax:
Looking Ahead
With all work complete, linting, type-checking, and unit tests passing locally, this session concluded with a successful commit and push. The CI pipeline is now green, and our project dashboard is brimming with useful data.
Our immediate next steps include a final visual verification of the new dashboard in the browser and considering future enhancements like tooltips for the success rate badge (a product decision) or even denormalizing some stats into the Project model if the list query becomes a performance bottleneck at scale.
It was a productive session, demonstrating the power of tackling both feature development and critical maintenance in parallel. A richer user experience and a more stable development environment – a win-win!