From 61a91a55751349f018c785631226831f00e9e738 Mon Sep 17 00:00:00 2001 From: jqueguiner Date: Wed, 29 Jul 2026 14:07:19 +0000 Subject: [PATCH] feat(datasets): let users pick a storage connection when creating a dataset Add a storage-connection selector to the "New dataset" modal. On org change the modal loads the org's storage connections from GET /organizations/{org_id}/storage-connections and offers them in a dropdown. Selecting one passes `connection` (id) to the create endpoint, which the API resolves to a managed upload target (source_type=managed, source_uri derived from the connection's bucket/prefix). "None" keeps the existing reference-only behavior. The Source URI input is disabled when a connection is chosen since the API sets it. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/web/src/hooks/useDatasets.ts | 2 ++ src/web/src/pages/Datasets.tsx | 42 +++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/web/src/hooks/useDatasets.ts b/src/web/src/hooks/useDatasets.ts index bd361e87..19d124eb 100644 --- a/src/web/src/hooks/useDatasets.ts +++ b/src/web/src/hooks/useDatasets.ts @@ -367,6 +367,7 @@ export interface CreateDatasetInput { languages?: string[]; num_examples?: number; license_use?: string; + connection?: string; } export function useCreateDataset() { @@ -387,6 +388,7 @@ export function useCreateDataset() { languages: input.languages ?? null, num_examples: input.num_examples ?? null, license_use: input.license_use ?? null, + connection: input.connection ?? null, }), }); if (!res.ok) { diff --git a/src/web/src/pages/Datasets.tsx b/src/web/src/pages/Datasets.tsx index 13abf98b..3e8458ff 100644 --- a/src/web/src/pages/Datasets.tsx +++ b/src/web/src/pages/Datasets.tsx @@ -20,6 +20,14 @@ interface Org { name: string; } +interface ConnOption { + id: string; + name: string; + purpose: string; + bucket: string; + is_default: boolean; +} + function clipsOf(d: Dataset): number | null { const c = d.metadata?.["clips"]; if (typeof c === "number") return c; @@ -254,12 +262,27 @@ function NewDatasetModal({ orgs, onClose }: { orgs: Org[]; onClose: () => void } const [sourceUri, setSourceUri] = useState(""); const [description, setDescription] = useState(""); const [numExamples, setNumExamples] = useState(""); + const [conns, setConns] = useState([]); + const [connection, setConnection] = useState(""); const [err, setErr] = useState(null); useEffect(() => { if (!orgId && orgs[0]) setOrgId(orgs[0].id); }, [orgs, orgId]); + // Load the org's storage connections so the user can bind the dataset to one. + useEffect(() => { + setConns([]); + setConnection(""); + if (!orgId) return; + let live = true; + apiFetch(`/organizations/${orgId}/storage-connections`) + .then((r) => (r.ok ? r.json() : [])) + .then((d) => { if (live) setConns(Array.isArray(d) ? d : []); }) + .catch(() => { if (live) setConns([]); }); + return () => { live = false; }; + }, [orgId]); + const submit = async () => { setErr(null); if (!orgId || !name.trim()) { @@ -278,6 +301,7 @@ function NewDatasetModal({ orgs, onClose }: { orgs: Org[]; onClose: () => void } tasks: tasks.split(",").map((t) => t.trim()).filter(Boolean), num_examples: numExamples.trim() && !Number.isNaN(Number(numExamples)) ? Number(numExamples) : undefined, license_use: licenseUse || undefined, + connection: connection || undefined, }); onClose(); } catch (e) { @@ -345,8 +369,24 @@ function NewDatasetModal({ orgs, onClose }: { orgs: Org[]; onClose: () => void } setTasks(e.target.value)} placeholder="translation, summarization" style={styles.input} /> + + + - setSourceUri(e.target.value)} placeholder="s3://… or hf:org/name" style={styles.input} /> + setSourceUri(e.target.value)} + placeholder={connection ? "set from connection" : "s3://… or hf:org/name"} + style={{ ...styles.input, ...(connection ? { opacity: 0.5 } : {}) }} + disabled={!!connection} + /> setNumExamples(e.target.value)} placeholder="120000" style={styles.input} />