Skip to content

Add real-time progress feedback to interview data export#678

Merged
jthrilly merged 24 commits intonextfrom
feature/export-progress
Mar 18, 2026
Merged

Add real-time progress feedback to interview data export#678
jthrilly merged 24 commits intonextfrom
feature/export-progress

Conversation

@jthrilly
Copy link
Member

@jthrilly jthrilly commented Mar 18, 2026

Summary

  • Replace the blocking export server action with an SSE route handler that streams progress events via Effect Queue/Stream
  • Add a persistent toast notification that shows pipeline stages (fetching, formatting, generating files with per-file progress bar, archiving, uploading), with cancel support
  • Introduce ExportProgressProvider context in the dashboard layout so exports run in the background — users can navigate freely while exporting
  • Support concurrent exports (each gets its own toast and SSE connection)
  • Add loading variant to the toast system with spinner icon and cancel button

Technical Details

  • generateOutputFiles converted to an Effect-returning function using Effect.forEach with unbounded concurrency and Ref-based progress tracking
  • Export pipeline accepts an Effect.Queue and emits stage/progress events between steps
  • SSE stream modeled as Queue → Stream.fromQueue → Stream.map (SSE format) → Stream.toReadableStream → Response
  • Toast lifecycle: persistent loading → success (auto-dismiss 5s) / error (stays until dismissed)
  • Closing toast or clicking Cancel aborts the export via AbortController

Test Plan

  • Typecheck passes (pnpm typecheck)
  • Lint passes (pnpm lint)
  • Unit tests pass (pnpm test) — 1842 passing
  • Manual: trigger export from interviews page, verify progress toast appears and updates through stages
  • Manual: verify zip downloads automatically on completion
  • Manual: verify Cancel button and toast close both abort the export
  • Manual: verify concurrent exports show separate progress toasts
  • Manual: verify error state displays correctly if export fails
  • Storybook: verify Loading toast story at Components → Toast → Loading

jthrilly added 14 commits March 18, 2026 18:20
Defines the architecture for real-time progress feedback during
interview data export, using SSE via Effect Stream/Queue and
persistent toast notifications.
Clarify toast persistence requires explicit timeout: 0,
detail generateOutputFiles Effect conversion, document
eager total computation, and add updateExportTime/PostHog
tracking to the client flow.
11-task plan covering: event types, Effect-based generateOutputFiles,
pipeline queue integration, SSE route handler, toast loading variant,
ExportProgressProvider, dialog refactor, and cleanup.
Copilot AI review requested due to automatic review settings March 18, 2026 17:16
@github-actions
Copy link

github-actions bot commented Mar 18, 2026

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR replaces the blocking interview export flow with an SSE-driven export pipeline that streams progress updates to the client, enabling persistent “export in progress” toasts and background exports while users navigate the dashboard.

Changes:

  • Added an SSE route (/api/export-interviews) that runs the export pipeline and streams stage/progress/complete/error events.
  • Refactored file generation into an Effect-based implementation that emits per-file progress updates.
  • Extended the toast system with a loading variant (spinner + cancel support) and introduced a dashboard-level ExportProgressProvider.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
app/api/export-interviews/route.ts New SSE endpoint that runs exportPipeline and streams events via Effect Queue/Stream
lib/export/pipeline.ts Pipeline now accepts a progress queue and emits stage events between steps
lib/network-exporters/formatters/session/generateOutputFiles.ts Converted generation step to Effect with progress events and concurrent execution
lib/export/exportEvents.ts Defines export event protocol and SSE formatting helper
components/ui/Toast.tsx Adds loading variant + spinner animation + cancel button support
components/ExportProgressProvider.tsx New client provider that consumes SSE and updates persistent progress toasts
app/dashboard/layout.tsx Wraps dashboard children with ExportProgressProvider
app/dashboard/interviews/_components/ExportInterviewsDialog.tsx Switches export trigger to provider-based background export
schemas/export.ts Adds request-body schema for the SSE route
lib/export/__tests__/pipeline.test.ts Updates tests for new queue argument + basic stage event emission check
components/ui/Toast.stories.tsx Adds Storybook demo for loading toast updates
lib/interviewer/components/InterviewToast.tsx Adds loading arrow variant styling
actions/interviews.ts Removes blocking exportInterviews server action

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

jthrilly added 10 commits March 18, 2026 19:29
Fix pipeline fiber getting interrupted when parent Effect completes
by using forkDaemon instead of fork. Add tests for formatSSE utility
and full pipeline stage event ordering.
Remove the dedicated loading variant from the toast system. Export
progress toasts now use the default variant with a spinner rendered
inline in the description content.
The interviews query was fetching full network JSON (all node/edge
attributes) and full protocol data for every interview, causing 75MB+
downloads with 2000 interviews. Now uses select to fetch only the
fields the table needs, and computes node/edge type counts server-side
instead of sending raw network data to the client.
Move codebook lookups (node/edge names and colors) into the
server-side network summary. Replace protocol.stages array with
a computed stageCount number. The interview list query now sends
only the minimal data the table needs — no more duplicated codebook
or stage definitions per interview.
Replace Prisma findMany with $queryRaw to compute network summaries
and stage counts entirely in PostgreSQL. The full network JSON and
codebook are never transferred from DB to app server — PostgreSQL
aggregates node/edge counts by type and looks up names/colors from
the codebook JSON directly. Also bypasses Prisma client extensions
that were parsing every network through NcNetworkSchema.parse() and
every protocol through VersionedProtocolSchema.parse().
Thread icon prop through toast data so custom icons (like the export
spinner) render in ToastItem. Use the Button component for the cancel
action. Clear onCancel when transitioning to success state.
Wrap request.json() in try/catch in both route handlers to return 400
instead of 500 on malformed JSON. Remove unused exports flagged by knip.
@jthrilly jthrilly merged commit 39e7f2e into next Mar 18, 2026
8 checks passed
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