Automating Compliance Reports: From Structured Markdown to GitHub PRs (and Taming Prisma's JSON Fields)
Join us as we recount building an automated compliance report export feature, from structured Markdown generation to seamless GitHub PR creation, and dive deep into the real-world challenges of type-safely handling Prisma JSON fields.
Compliance reports. The words alone can conjure images of tedious manual data collection, copy-pasting, and endless formatting adjustments. As developers, we instinctively recoil from such repetitive tasks, dreaming of automation. Recently, we turned that dream into a reality for our compliance analysis workflow, implementing a feature to export comprehensive reports and even automate their delivery via GitHub Pull Requests.
This post is a look back at a recent development session – the kind of internal 'letter to myself' that captures the nitty-gritty details, the triumphs, and especially, the hard-won lessons. It's about bringing a critical feature to life, from backend data assembly to a polished frontend, all while navigating the quirks of modern TypeScript development.
The Mission: Automating Compliance Reporting
Our primary goal was clear: take the rich, structured output of our compliance analysis workflow and turn it into a human-readable, exportable report. But we didn't stop there. We wanted two modes of delivery:
- Direct Download: For quick local review.
- GitHub PR Creation: For seamless integration into existing code review and documentation workflows. Imagine a compliance report automatically opening a PR against your documentation repo – that's the dream.
By the end of this session, the core feature was complete, passing type checks, linting, and even surviving a round of code review with flying colors (after some crucial fixes, of course!).
Building Blocks: From Data to Delivery
Here’s how we pieced together this automation pipeline:
1. Crafting the Report: The compliance-report-formatter
At the heart of the export feature is a new service: src/server/services/compliance-report-formatter.ts. This isn't just a string concatenator; it's a pure data assembly service. It takes the raw, structured outputs from various steps of our compliance workflow – executive metrics, per-step sections, quality gates, hallucination analysis, consistency analysis – and transforms them into a beautifully structured Markdown document.
Think of it as a sophisticated template engine for data. Its job is to ensure every piece of crucial information, from high-level summaries to granular details, is presented clearly and consistently in the Markdown format.
2. The Backend Bridge: tRPC & GitHub Integration
With the formatter in place, we needed a way to trigger it and handle the export. We extended our existing tRPC router by adding an exportComplianceReport mutation in src/server/trpc/routers/workflows.ts.
This mutation is the orchestration layer. It receives parameters indicating whether the user wants a direct download or a GitHub PR. For PR creation, it leverages our existing github-connector.ts functions, which abstract away the complexities of the GitHub API. This reuse was key, ensuring consistency and reducing development time.
3. User Experience: The Export Panel
No feature is complete without an intuitive user interface. We integrated the export functionality into our workflow details page (src/app/(dashboard)/dashboard/workflows/[id]/page.tsx).
We added an expandable card, mirroring the design pattern of our existing BundleExportPanel. This panel provides a clear "Export" button and, crucially, an optional toggle to enable GitHub PR creation. This simple toggle transforms the export from a local download into an automated workflow integration, putting the power directly in the user's hands.
Navigating the Minefield: Lessons from the Trenches
No feature development is without its bumps. Here are some of the critical insights and fixes that emerged, especially during code review:
1. Prisma JSON & Type Safety: A Recurring Gotcha
This was perhaps the most significant "pain point" and a fantastic learning opportunity. We store document references (docRefs) as a Json array in our Prisma schema. When trying to access properties like owner on elements within this array, TypeScript threw its hands up:
// Initial attempt:
const docRef = complianceWorkflow.docRefs[0];
// TS2339: Property 'owner' does not exist on type 'JsonObject | JsonArray'.
// Because Prisma's JsonValue type is very broad.
console.log(docRef.owner);
The problem is that Prisma.JsonValue (which Json fields resolve to) can be JsonObject (a plain object), JsonArray (an array), or even primitive types. TypeScript, quite rightly, doesn't know that our docRefs array actually contains objects with an owner property.
The Workaround & Lesson Learned:
The solution involves an intermediate cast to Record<string, unknown> before attempting property access within a type guard. This tells TypeScript, "Hey, I know this might be an object, let me check its properties safely."
// In src/server/trpc/routers/workflows.ts or similar
// Example: Validating docRefs structure
const validateDocRef = (ref: unknown): ref is { owner: string; repo: string; path: string } => {
if (typeof ref !== 'object' || ref === null) return false;
// The key: cast to Record<string, unknown> first
const obj = ref as Record<string, unknown>;
return (
typeof obj.owner === 'string' &&
typeof obj.repo === 'string' &&
typeof obj.path === 'string'
);
};
// Apply runtime validation
const docRefs = z.array(
z.custom((val) => validateDocRef(val), { message: "Invalid docRef structure" })
).parse(complianceWorkflow.docRefs);
// Now docRefs is type-safe and you can access properties directly
console.log(docRefs[0].owner);
This pattern (as Record<string, unknown> followed by typeof checks in a type guard) is crucial for safely working with Prisma's generic Json fields and is a recurring pattern we now apply rigorously.
2. Robust GitHub API Interactions
Integrating with external APIs, especially GitHub, requires careful consideration for robustness:
- Idempotency: We ensured our GitHub API flow was idempotent. If a branch already exists (a
422 Unprocessable Entityerror from GitHub), we don't just fail; we gracefully handle it. For file updates, we now fetch the existing file's SHA (getFileSha) to ensure we're updating the correct version, preventing accidental overwrites or stale updates. - Error Handling: Internal API errors should never leak to the client. We wrapped all GitHub API errors in
TRPCErrorto provide a consistent, user-friendly error message without exposing sensitive internal details.
3. Sanitizing Markdown for Safety
When generating Markdown tables, special characters like pipes (|) and newlines (\n) can break the table structure or even lead to injection vulnerabilities. We added an escapeTableCell() utility function to sanitize all user-generated content destined for Markdown table cells. A small detail, but critical for correctness and security.
4. Defensive Programming & Clarity
- Runtime Validation: Beyond TypeScript's compile-time checks, we added runtime validation for the structure of our Prisma Json
docRefsfield, both on the server and client. This catches malformed data that might slip through the cracks. - Safe Guards over Non-Null Assertions: In sections like consistency and hallucination analysis, we replaced aggressive non-null assertions (
!) with safer guards (e.g.,if (value === null) return;). This makes the code more robust against unexpectednullorundefinedvalues. - Clearer JSX Conditional Rendering: For better readability in our
page.tsxfile, we extracted aisComplianceWorkflowboolean variable. This makes conditional rendering logic in JSX much clearer and easier to understand at a glance.
What's Next on Our Plate
While the core feature is complete and reviewed, a few immediate next steps remain:
- Commit the Changes: Get these three files (
compliance-report-formatter.ts,workflows.ts,page.tsx) committed to ourmainbranch. - End-to-End Testing:
- Run a compliance workflow to completion, click Export, and verify the Markdown download.
- Enable the PR creation checkbox with a linked repo, then verify that a new branch, file, and PR are correctly created in GitHub.
- Refactoring the UI Panel: Our
page.tsxfile is growing quite large (currently 2164 lines!). We'll consider extracting the compliance export panel into its own dedicated component (src/components/workflow/compliance-export-panel.tsx) to improve modularity and maintainability. - Audit Logging: For compliance-related actions like PR creation, adding audit logging is a crucial next step to track who initiated which actions and when.
Conclusion
This session was a microcosm of modern web development: designing a feature, implementing it across the stack, grappling with type system challenges, and refining it through code review. Automating compliance reports with GitHub PR integration isn't just a technical win; it's a significant step forward in streamlining our operational workflows and enhancing developer experience.
The lessons learned, particularly around type-safely handling Prisma JSON fields and building robust GitHub integrations, are invaluable takeaways that will serve us well in future projects. Onwards to cleaner code and smarter automation!
{
"thingsDone": [
"Implemented compliance report formatter (Markdown generation)",
"Added tRPC mutation for compliance report export (download & GitHub PR modes)",
"Integrated compliance export UI panel in dashboard",
"Addressed critical code review issues: Markdown sanitization, idempotent GitHub API, TRPCError wrapping, Prisma Json validation, safe guards, JSX clarity"
],
"pains": [
"Struggled with type narrowing Prisma Json array elements due to `JsonValue`'s broad type",
"Initial direct `as` cast on Json array elements failed for property access"
],
"successes": [
"Successfully implemented a robust workaround for Prisma Json type narrowing using `as Record<string, unknown>` and type guards",
"Achieved feature completeness for compliance report export and GitHub PR creation",
"Ensured GitHub API calls are idempotent and errors are properly wrapped",
"Improved code quality and safety through various code review fixes"
],
"techStack": [
"TypeScript",
"tRPC",
"Prisma",
"React",
"Next.js",
"GitHub API",
"Markdown"
]
}