diff --git a/apps/backend/src/oauth/ssrf-protection.test.ts b/apps/backend/src/oauth/ssrf-protection.test.ts index 330fb7f25..68b001db3 100644 --- a/apps/backend/src/oauth/ssrf-protection.test.ts +++ b/apps/backend/src/oauth/ssrf-protection.test.ts @@ -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(callback: () => Promise): Promise { - 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(); } } diff --git a/apps/dashboard/src/components/data-table/user-table.tsx b/apps/dashboard/src/components/data-table/user-table.tsx index 9d726663e..f0f44411c 100644 --- a/apps/dashboard/src/components/data-table/user-table.tsx +++ b/apps/dashboard/src/components/data-table/user-table.tsx @@ -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 diff --git a/packages/dashboard-ui-components/src/components/data-grid/data-grid-export-dialog.tsx b/packages/dashboard-ui-components/src/components/data-grid/data-grid-export-dialog.tsx index 4c4e3d722..249b158a7 100644 --- a/packages/dashboard-ui-components/src/components/data-grid/data-grid-export-dialog.tsx +++ b/packages/dashboard-ui-components/src/components/data-grid/data-grid-export-dialog.tsx @@ -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({ [exportOptions?.fields, columns], ); const [format, setFormat] = useState("csv"); - const [scope, setScope] = useState("all"); + const [scope, setScope] = useState(exportOptions?.defaultScope ?? "all"); const [fields, setFields] = useState[]>(resolvedFields); const [isExporting, setIsExporting] = useState(false); const [progress, setProgress] = useState(idleExportProgress); @@ -67,6 +67,22 @@ export function DataGridExportDialog({ } }, [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; diff --git a/packages/dashboard-ui-components/src/components/data-grid/types.ts b/packages/dashboard-ui-components/src/components/data-grid/types.ts index 965e90894..f4b6ea286 100644 --- a/packages/dashboard-ui-components/src/components/data-grid/types.ts +++ b/packages/dashboard-ui-components/src/components/data-grid/types.ts @@ -251,6 +251,8 @@ export type DataGridExportOptions = { allScopeLabel?: ReactNode; filteredScopeLabel?: ReactNode; progressSubjectLabel?: string; + /** Which export scope is selected by default when the dialog opens. Defaults to `"all"`. */ + defaultScope?: DataGridExportScope; }; // ─── Callbacks ───────────────────────────────────────────────────────