Skip to content

Implemented export users functionality#1026

Merged
Developing-Gamer merged 1 commit intodevfrom
export-users
Nov 21, 2025
Merged

Implemented export users functionality#1026
Developing-Gamer merged 1 commit intodevfrom
export-users

Conversation

@Developing-Gamer
Copy link
Contributor

@Developing-Gamer Developing-Gamer commented Nov 21, 2025

Screenshot 2025-11-20 at 9 42 23 PM

Features:
Export ALL users (with pagination fetching)
Support both CSV and JSON formats with user selection
Allow field selection in export dialog
Button placement next to "Create User" in page header
Option to export all users or only filtered results

Summary by CodeRabbit

  • New Features

    • User export dialog on the projects page: export members to CSV or JSON, choose scope (all or filtered), pick fields, and receive progress/error feedback.
    • Table filter changes now propagate so exports respect current search and include-anonymous settings.
  • Chores

    • Added runtime dependency "export-to-csv" to support CSV export.

✏️ Tip: You can customize this high-level summary in your review settings.

@Developing-Gamer Developing-Gamer self-assigned this Nov 21, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR adds a runtime dependency, introduces a new ExportUsersDialog component for exporting project users (CSV/JSON) with batched fetching and selectable fields, and extends UserTable to emit filter changes via an optional onFilterChange callback consumed by the users page.

Changes

Cohort / File(s) Summary
Dependency Addition
apps/dashboard/package.json
Added runtime dependency export-to-csv (^1.4.0).
New Export Component
apps/dashboard/src/components/export-users-dialog.tsx
New client-side React dialog to export users: format (CSV/JSON), scope (all/filtered), field selection, batched user fetching, transformation to flat records, CSV/JSON generation, and toast-based validation/error/success feedback.
Page Integration
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
Added exportOptions state, integrated ExportUsersDialog into page actions with a download icon trigger, and wired UserTable via onFilterChange to sync filter/export options.
Table API Change
apps/dashboard/src/components/data-table/user-table.tsx
UserTable now accepts an optional onFilterChange prop and emits { search?: string, includeAnonymous: boolean } when filters change.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Page as "Users Page\n(page-client)"
    participant Table as "UserTable"
    participant Dialog as "ExportUsersDialog"
    participant API as "User API"
    participant ExportLib as "export-to-csv / JSON"

    Note over Page,Table: Page mounts and wires exportOptions ↔ Table via onFilterChange
    User->>Page: Click Export (trigger)
    Page->>Dialog: Open ExportUsersDialog (pass exportOptions)
    User->>Dialog: Choose format/scope/fields and click Export
    Dialog->>Dialog: Validate selection
    alt scope = filtered
        Dialog->>API: Fetch users with filters (batched)
    else scope = all
        Dialog->>API: Fetch all users (batched)
    end
    API-->>Dialog: Return batched user data
    Dialog->>Dialog: Transform users to flat records (selected fields)
    Dialog->>ExportLib: Generate CSV or JSON
    ExportLib-->>Dialog: File ready
    Dialog->>User: Trigger download and show toast
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Review batched fetching/pagination and error handling in export-users-dialog.tsx.
  • Verify transform mapping for all selectable fields and handling of anonymous users.
  • Check UserTable's onFilterChange effect for debounce/re-render behavior and correct emitted shape.

Possibly related PRs

  • New table component #995: Refactors UserTable/filter handling; directly related to onFilterChange integration and filter semantics.

Suggested reviewers

  • N2D4

Poem

🐇 I hop through rows and fields so bright,
I gather users by day and night,
CSV or JSON, pick what you please,
I fetch and flatten with elegant ease,
A tiny rabbit export — click and delight! 📦✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature added: export users functionality. It accurately reflects the primary change across all modified files.
Description check ✅ Passed The description provides a screenshot, lists key features, and explains the functionality well. However, it lacks detail on implementation specifics and doesn't follow a structured format.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch export-users

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel
Copy link

vercel bot commented Nov 21, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Nov 21, 2025 3:48am
stack-dashboard Ready Ready Preview Comment Nov 21, 2025 3:48am
stack-demo Ready Ready Preview Comment Nov 21, 2025 3:48am
stack-docs Ready Ready Preview Comment Nov 21, 2025 3:48am

@Developing-Gamer Developing-Gamer marked this pull request as ready for review November 21, 2025 03:22
Copilot AI review requested due to automatic review settings November 21, 2025 03:22
@claude
Copy link
Contributor

claude bot commented Nov 21, 2025

Claude Code is working…

I'll analyze this and get back to you.

View job run

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Nov 21, 2025

Greptile Overview

Greptile Summary

Implemented user export functionality with CSV and JSON format support, field selection, and the ability to export all users or only filtered results.

Key Changes:

  • Added new ExportUsersDialog component with configurable export options (format, scope, fields)
  • Integrated export button in the users page header next to "Create User"
  • Modified UserTable to propagate filter state to parent via onFilterChange callback
  • Uses pagination to fetch all users in batches of 100
  • Properly uses runAsynchronouslyWithAlert for async button handlers per codebase guidelines

Issue Found:

  • Logic error in fetchAllUsers function: includeAnonymous defaults to true when it should default to false to match the filter behavior

Confidence Score: 4/5

  • This PR is mostly safe to merge with one logical error that needs fixing
  • Score reflects well-structured implementation following codebase patterns (proper use of runAsynchronouslyWithAlert, clean component architecture, pagination handling). However, the includeAnonymous default value bug on line 276 will cause incorrect behavior when exporting filtered users - it will include anonymous users by default instead of excluding them, contradicting the UI filter state.
  • apps/dashboard/src/components/export-users-dialog.tsx requires attention - fix the includeAnonymous default on line 276

Important Files Changed

File Analysis

Filename Score Overview
apps/dashboard/src/components/export-users-dialog.tsx 3/5 New export dialog component with CSV/JSON export functionality. Found logic error in includeAnonymous default value (line 276) that causes inconsistency with filter settings.
apps/dashboard/src/components/data-table/user-table.tsx 5/5 Added onFilterChange callback to propagate search and includeAnonymous filters to parent. Clean implementation with proper useEffect dependencies.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx 5/5 Integrated export button in page header. Clean state management for export options with proper callback to UserTable.

Sequence Diagram

sequenceDiagram
    participant User
    participant PageClient
    participant ExportDialog
    participant UserTable
    participant AdminApp
    participant Browser

    User->>PageClient: Click "Export" button
    PageClient->>ExportDialog: Open dialog
    ExportDialog->>User: Show export options
    
    Note over UserTable,PageClient: Filter changes propagate
    User->>UserTable: Apply filters (search, includeAnonymous)
    UserTable->>PageClient: onFilterChange callback
    PageClient->>ExportDialog: Update exportOptions state
    
    Note over ExportDialog: User configures export
    User->>ExportDialog: Select format (CSV/JSON)
    User->>ExportDialog: Select scope (all/filtered)
    User->>ExportDialog: Select fields to export
    User->>ExportDialog: Click "Export Users"
    
    ExportDialog->>ExportDialog: Validate fields selected
    
    alt Export filtered users
        ExportDialog->>AdminApp: fetchAllUsers(with exportOptions)
    else Export all users
        ExportDialog->>AdminApp: fetchAllUsers(no options)
    end
    
    loop Pagination
        AdminApp->>AdminApp: listUsers(limit: 100, cursor)
        AdminApp-->>ExportDialog: Batch of users + nextCursor
    end
    
    ExportDialog->>ExportDialog: Transform user data
    
    alt CSV format
        ExportDialog->>Browser: Generate CSV + download
    else JSON format
        ExportDialog->>Browser: Generate JSON blob + download
    end
    
    Browser->>User: File downloaded
    ExportDialog->>User: Show success toast
    ExportDialog->>ExportDialog: Close dialog
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link

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 implements comprehensive user export functionality allowing dashboard administrators to export user data in both CSV and JSON formats. The feature includes configurable field selection, pagination-based fetching for large datasets, and the ability to export either all users or filtered results.

Key changes:

  • Added new ExportUsersDialog component with field selection, format options, and export scope configuration
  • Integrated export functionality into the users page with proper filter state management
  • Added export-to-csv dependency for CSV export capabilities

Reviewed Changes

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

Show a summary per file
File Description
apps/dashboard/package.json Added export-to-csv dependency
pnpm-lock.yaml Updated lock file with new dependency
apps/dashboard/src/components/export-users-dialog.tsx New component implementing export dialog with CSV/JSON export logic
apps/dashboard/src/components/data-table/user-table.tsx Added filter change callback to propagate filter state
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx Integrated export dialog with filter state management
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
apps/dashboard/src/components/export-users-dialog.tsx (1)

366-376: Type assertion used for CSV generation.

Line 374 uses as any to bypass type checking. This suggests a potential type mismatch between the data structure and what generateCsv expects.

Consider investigating the correct types for the export-to-csv library or adding a type guard to ensure data compatibility:

function exportToCsv(data: Record<string, unknown>[]) {
  const csvConfig = mkConfig({
    fieldSeparator: ",",
    filename: `stack-users-export-${new Date().toISOString().split("T")[0]}`,
    decimalSeparator: ".",
    useKeysAsHeaders: true,
  });

  // Ensure data conforms to expected type
  const csvData = data as Array<Record<string, string | number | boolean>>;
  const csv = generateCsv(csvConfig)(csvData);
  download(csvConfig)(csv);
}
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx (1)

36-36: Type casting indicates missing type definitions.

Line 36 uses (stackAdminApp as any).useUsers({ limit: 1 }), which suggests the useUsers method is not properly typed on the admin app interface. While this works, it bypasses type safety.

Consider adding proper type definitions for the admin app methods to avoid type casting. If useUsers is an internal method, consider adding it to the public API or using an alternative approach to check for first user.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5c815f and d0b1235.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/dashboard/package.json (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx (3 hunks)
  • apps/dashboard/src/components/data-table/user-table.tsx (2 hunks)
  • apps/dashboard/src/components/export-users-dialog.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
🧬 Code graph analysis (2)
apps/dashboard/src/components/export-users-dialog.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (29-44)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronouslyWithAlert (312-328)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx (2)
apps/dashboard/src/components/export-users-dialog.tsx (1)
  • ExportUsersDialog (61-261)
apps/dashboard/src/components/data-table/user-table.tsx (1)
  • UserTable (176-244)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: Agent
  • GitHub Check: Vercel Agent Review
  • GitHub Check: claude-review
🔇 Additional comments (6)
apps/dashboard/src/components/data-table/user-table.tsx (2)

176-178: LGTM: Clean API extension.

The optional onFilterChange callback prop is a good design that maintains backward compatibility while enabling external components to react to filter changes.


214-221: LGTM: Proper effect implementation.

The effect correctly emits filter changes with appropriate dependencies and optional chaining.

apps/dashboard/src/components/export-users-dialog.tsx (1)

61-147: Good implementation of export functionality.

The component provides a comprehensive export experience with:

  • Proper loading states and user feedback
  • Validation (no fields selected, no users to export)
  • Error handling with toast notifications
  • Flexible field selection
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx (2)

37-40: LGTM: Clean integration of export functionality.

The export options state and its connection to the UserTable's onFilterChange callback creates a clean data flow that keeps the export dialog synchronized with table filters.

Also applies to: 76-76


52-68: Good UX: Export button placement and styling.

The export button with the Download icon is well-positioned next to the "Create User" button and uses appropriate variant styling (outline) to indicate it's a secondary action.

apps/dashboard/package.json (1)

45-45: No security concerns identified; package is current.

The npm package export-to-csv v1.4.0 has no publicly reported direct vulnerabilities in major vulnerability databases (Snyk and npm), and the specified version ^1.4.0 matches the latest release. No further action required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
apps/dashboard/src/components/export-users-dialog.tsx (3)

370-379: Avoid as any when generating CSV rows

Casting data to any weakens type safety around what is passed into generateCsv:

const csv = generateCsv(csvConfig)(data as any);

It would be better to define a concrete row type (e.g. Record<string, string | number | boolean | null>) and have both transformUserData and exportToCsv use that type so you can call generateCsv(csvConfig)(data) without an any cast. Please cross-check the export-to-csv typings for the expected row shape and adjust accordingly.


149-153: Fix non-semantic clickable wrapper for accessibility

Using a bare <div onClick> makes the trigger non-focusable and not keyboard-activatable. Wrap the trigger in a real <button> so keyboard and screen-reader users can open the dialog.

You can apply something like:

-      <div onClick={() => setOpen(true)}>
-        {trigger}
-      </div>
+      <button
+        type="button"
+        onClick={() => setOpen(true)}
+        className="contents"
+        aria-label="Open export dialog"
+      >
+        {trigger}
+      </button>

263-283: Align includeAnonymous default with filter behavior

fetchAllUsers currently defaults includeAnonymous to true:

includeAnonymous: options?.includeAnonymous ?? true,

When scope === "filtered" and exportOptions is undefined or omits this flag, this changes semantics compared to the users page, where anonymous users are excluded by default.

To keep behavior consistent with the filter UI:

-      includeAnonymous: options?.includeAnonymous ?? true,
+      includeAnonymous: options?.includeAnonymous ?? false,
🧹 Nitpick comments (2)
apps/dashboard/src/components/export-users-dialog.tsx (2)

31-40: Tighten ExportField.key typing to avoid drift

Right now ExportField.key is just string, while DEFAULT_FIELDS and transformUserData assume a fixed set of keys in the big switch. A typo or future refactor could silently create fields that are never handled.

Consider introducing a union for the allowed keys and reusing it:

type ExportFieldKey =
  | "id"
  | "displayName"
  | "primaryEmail"
  | "primaryEmailVerified"
  | "signedUpAt"
  | "lastActiveAt"
  | "isAnonymous"
  | "hasPassword"
  | "otpAuthEnabled"
  | "passkeyAuthEnabled"
  | "isMultiFactorRequired"
  | "oauthProviders"
  | "profileImageUrl"
  | "clientMetadata"
  | "clientReadOnlyMetadata"
  | "serverMetadata";

type ExportField = {
  key: ExportFieldKey;
  label: string;
  enabled: boolean;
};

This will give you exhaustiveness checking in transformUserData if a new key is added.

Also applies to: 42-59, 288-365


288-365: Safe date handling looks good; optionally guard oauthProviders

The updated handling of signedUpAt and lastActiveAt correctly copes with null/undefined and pre-constructed Date instances, returning ISO strings or an empty string, which should prevent previous runtime issues.

One small extra hardening you might consider is guarding oauthProviders in case it can ever be undefined:

data["OAuth Providers"] = (user.oauthProviders ?? []).map(p => p.id).join(", ");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39c32c9 and 650a43a.

📒 Files selected for processing (1)
  • apps/dashboard/src/components/export-users-dialog.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/dashboard/src/components/export-users-dialog.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (29-44)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronouslyWithAlert (312-328)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: Vercel Agent Review
🔇 Additional comments (2)
apps/dashboard/src/components/export-users-dialog.tsx (2)

61-71: Overall export dialog flow and UX look solid

State management for format/scope/fields, validation toasts (no fields / no users), and success/error handling are all coherent and user-friendly. The separation into helper functions keeps the component readable.

Also applies to: 89-147, 163-257


382-393: JSON export helper is correct and cleans up resources

The JSON export implementation (Blob + temporary anchor + URL.revokeObjectURL) is correct and ensures no lingering object URLs.

@Developing-Gamer Developing-Gamer merged commit 32734c9 into dev Nov 21, 2025
51 of 52 checks passed
@Developing-Gamer Developing-Gamer deleted the export-users branch November 21, 2025 16:51
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