feat: CSV issue export from issues list and machine detail pages#1159
feat: CSV issue export from issues list and machine detail pages#1159timothyfroehlich wants to merge 14 commits intomainfrom
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests click the export-csv-button on the issues list page and machine detail page, verifying a download event fires with the correct filename pattern. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Updates to Preview Branch (claude/modest-villani) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
There was a problem hiding this comment.
Pull request overview
Adds CSV export of issues from both the issues list (respecting current filters) and the machine detail page (machine-scoped), using a server action to generate an Excel-friendly CSV and a small client button to trigger downloads.
Changes:
- Added CSV generation + first-paragraph extraction utilities with unit tests.
- Added
exportIssuesActionserver action + Zod schemas to generate and return CSV/filename. - Added export UI entry points (issues list + machine detail expando) and Playwright smoke tests for downloads.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/tiptap/first-paragraph.ts | New utility to extract first-paragraph plain text from ProseMirror JSON for CSV Description. |
| src/lib/tiptap/first-paragraph.test.ts | Unit tests for first-paragraph extraction behavior. |
| src/lib/export/csv.ts | New RFC 4180-style CSV generator with BOM + CRLF endings. |
| src/lib/export/csv.test.ts | Unit tests for quoting/escaping/BOM behavior. |
| src/components/issues/IssueList.tsx | Adds export button to issues list toolbar. |
| src/components/issues/ExportButton.tsx | Client “download CSV” button that calls the server action and triggers a Blob download. |
| src/app/(app)/m/[initials]/issues-expando.tsx | Adds export button to the machine detail issues expando header. |
| src/app/(app)/issues/export-schema.ts | Zod schemas for export inputs and permissive filter parsing. |
| src/app/(app)/issues/export-action.ts | Server action to authenticate, query issues, format rows, and generate CSV + filename. |
| e2e/smoke/machine-details-redesign.spec.ts | Smoke test that clicking export triggers a CSV download on machine detail. |
| e2e/smoke/issue-list.spec.ts | Smoke test that clicking export triggers a CSV download on issues list. |
| docs/superpowers/specs/2026-04-06-csv-export-design.md | Design spec documenting behavior, columns, and approach. |
| docs/superpowers/plans/2026-04-06-csv-export.md | Implementation plan documenting steps and test strategy. |
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
…hines, JSON validation - Add CSV formula injection protection (prefix =, +, -, @ with single quote) - Set includeInactiveMachines=true for machine-page exports so off-the-floor machines can still export their issues - Return VALIDATION error on malformed filter JSON instead of silently falling back to "export all" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The comment said "Recursively" but the function only iterates the given node array without recursion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts: # src/app/(app)/m/[initials]/issues-expando.tsx
| export const exportFiltersSchema = z.object({ | ||
| q: z.string().optional(), | ||
| status: z.array(z.enum(ISSUE_STATUS_VALUES)).optional(), | ||
| machine: z.array(z.string()).optional(), | ||
| severity: z | ||
| .array(z.enum(["cosmetic", "minor", "major", "unplayable"])) | ||
| .optional(), | ||
| priority: z.array(z.enum(["low", "medium", "high"])).optional(), | ||
| frequency: z | ||
| .array(z.enum(["intermittent", "frequent", "constant"])) | ||
| .optional(), | ||
| assignee: z.array(z.string()).optional(), | ||
| owner: z.array(z.string()).optional(), | ||
| reporter: z.array(z.string()).optional(), | ||
| watching: z.boolean().optional(), | ||
| includeInactiveMachines: z.boolean().optional(), | ||
| sort: z.string().optional(), | ||
| }); |
There was a problem hiding this comment.
exportFiltersSchema doesn’t include the date-range fields (createdFrom, createdTo, updatedFrom, updatedTo) that are part of IssueFilters and are used by buildWhereConditions(). Since ExportButton serializes the current IssueFilters, any active date filters will be silently dropped during parsing and the export won’t match what the user is viewing. Add these fields to the schema (ideally with coercion from ISO strings and a permissive fallback so a single bad date doesn’t cause all filters to be ignored).
| // 6. Generate CSV | ||
| const csv = generateCsv(CSV_HEADERS, rows); | ||
| const dateStr = format(new Date(), "yyyy-MM-dd"); | ||
| const fileName = machineInitials | ||
| ? `pinpoint-${machineInitials.toUpperCase()}-issues-${dateStr}.csv` | ||
| : `pinpoint-issues-${dateStr}.csv`; | ||
|
|
||
| return ok({ csv, fileName }); |
There was a problem hiding this comment.
The new export action currently has no integration/unit test that validates CSV headers/row formatting and that filters are applied correctly. The added Playwright checks only assert that a download starts, so regressions in column ordering, description extraction, or filter behavior (e.g., machineInitials export) won’t be caught. Please add at least one integration test around the export action’s query+CSV generation using the existing worker-scoped PGlite pattern.
Summary
Origin
Discord discussion (Eric E., Becca S., Neil Wilson, Tim) — 2026-03-16. Users wanted to identify which machines have the most issues and spot recurring problems for tournament bank decisions.
Test plan
🤖 Generated with Claude Code