mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
Default user export to filtered scope; note Anonymous in all-users label (#1679)
## Summary
Two changes to the user data export dialog on the project Users page:
1. The export scope now defaults to **"Export only filtered/searched
users"** instead of "all users".
2. The all-users option label is now **"Export all users in the project
(includes Anonymous)"**.
To keep this scoped to the Users table (the shared export dialog is
reused by other tables), the dialog's default scope is made configurable
rather than changed globally:
- `DataGridExportOptions` gains `defaultScope?: DataGridExportScope`
(defaults to `"all"`).
- The dialog initializes `useState(exportOptions?.defaultScope ??
"all")`.
- `user-table.tsx` passes `defaultScope: "filtered"` and the updated
`allScopeLabel`.
Other tables (teams, transactions, emails) are unaffected — they keep
the `"all"` default.
Link to Devin session:
https://app.devin.ai/sessions/4996678b2b944090b6eef2f64f0a62a1
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Default the Users export dialog to filtered scope and clarify that the
"all users" option includes Anonymous; scope resets to the per-table
default only when the dialog reopens, and other tables keep "all".
- **New Features**
- Added `defaultScope` to export options and initialized scope from it;
Users table sets `defaultScope: "filtered"` and updates the all-users
label.
- Reset scope to `defaultScope` only on a closed→open transition to
avoid changing it while the dialog is open.
- **Bug Fixes**
- Stubbed `NODE_ENV` via `vi.stubEnv` in
`apps/backend/src/oauth/ssrf-protection.test.ts` to fix lint and prevent
env mutation.
<sup>Written for commit 3aa670b6d2.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1679?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: vedanta.gawande <vedanta.gawande@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
092c27dd0e
commit
4a0f2b1778
@ -1,19 +1,14 @@
|
||||
import { StatusError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import dns from "node:dns";
|
||||
import { assertSafeOAuthResolvedAddress, assertSafeOAuthUrlWithoutDns, isBlockedOAuthIpAddress, safeOAuthDnsLookup } from "./ssrf-protection";
|
||||
|
||||
async function withProductionNodeEnv<T>(callback: () => Promise<T>): Promise<T> {
|
||||
const previousNodeEnv = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = "production";
|
||||
vi.stubEnv("NODE_ENV", "production");
|
||||
try {
|
||||
return await callback();
|
||||
} finally {
|
||||
if (previousNodeEnv === undefined) {
|
||||
delete process.env.NODE_ENV;
|
||||
} else {
|
||||
process.env.NODE_ENV = previousNodeEnv;
|
||||
}
|
||||
vi.unstubAllEnvs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -426,7 +426,8 @@ function UserTableBody(props: {
|
||||
fetchRows: fetchExportRows,
|
||||
emptyExportTitle: "No users to export",
|
||||
emptyExportDescription: "There are no users matching the current filters",
|
||||
allScopeLabel: "Export all users in the project",
|
||||
defaultScope: "filtered",
|
||||
allScopeLabel: "Export all users in the project (includes Anonymous)",
|
||||
filteredScopeLabel: (
|
||||
<>
|
||||
Export only filtered/searched users
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { DownloadSimpleIcon } from "@phosphor-icons/react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { DesignButton } from "../button";
|
||||
import { DesignDialog } from "../dialog";
|
||||
@ -55,7 +55,7 @@ export function DataGridExportDialog<TRow>({
|
||||
[exportOptions?.fields, columns],
|
||||
);
|
||||
const [format, setFormat] = useState<DataGridExportFormat>("csv");
|
||||
const [scope, setScope] = useState<DataGridExportScope>("all");
|
||||
const [scope, setScope] = useState<DataGridExportScope>(exportOptions?.defaultScope ?? "all");
|
||||
const [fields, setFields] = useState<readonly DataGridExportField<TRow>[]>(resolvedFields);
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
const [progress, setProgress] = useState<ExportProgress>(idleExportProgress);
|
||||
@ -67,6 +67,22 @@ export function DataGridExportDialog<TRow>({
|
||||
}
|
||||
}, [isExporting, resolvedFields]);
|
||||
|
||||
// Reset the scope to its default each time the dialog opens. The dialog stays
|
||||
// mounted between opens, so without this the scope would retain whatever the
|
||||
// user last picked instead of honoring `defaultScope` on every open. We track
|
||||
// the previous `open` value with a ref so the reset only fires on a genuine
|
||||
// closed->open transition -- not on every render that flips other state (e.g.
|
||||
// `isExporting` going false after a failed/empty export would otherwise wipe
|
||||
// the user's current selection while the dialog is still open).
|
||||
const defaultScope = exportOptions?.defaultScope ?? "all";
|
||||
const wasOpenRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (open && !wasOpenRef.current) {
|
||||
setScope(defaultScope);
|
||||
}
|
||||
wasOpenRef.current = open;
|
||||
}, [open, defaultScope]);
|
||||
|
||||
const entityName = exportOptions?.entityName ?? "row";
|
||||
const entityNamePlural = exportOptions?.entityNamePlural ?? "rows";
|
||||
const filenamePrefix = exportOptions?.filenamePrefix ?? exportFilename;
|
||||
|
||||
@ -251,6 +251,8 @@ export type DataGridExportOptions<TRow> = {
|
||||
allScopeLabel?: ReactNode;
|
||||
filteredScopeLabel?: ReactNode;
|
||||
progressSubjectLabel?: string;
|
||||
/** Which export scope is selected by default when the dialog opens. Defaults to `"all"`. */
|
||||
defaultScope?: DataGridExportScope;
|
||||
};
|
||||
|
||||
// ─── Callbacks ───────────────────────────────────────────────────────
|
||||
|
||||
Loading…
Reference in New Issue
Block a user