From 7a914c03ca00e61e4436a606d0fbc669d9761689 Mon Sep 17 00:00:00 2001 From: Tanishq Gandhi <56472134+tanishqgandhi1908@users.noreply.github.com> Date: Sun, 20 Sep 2026 23:58:07 -0700 Subject: [PATCH] fix(frontend): refresh a dataset's paths after a rename, and block renaming mid-upload (#8347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What changes were proposed in this PR? Every file path on a dataset's detail page embeds the dataset name, and preview and single-file download resolve a dataset by (owner, name). `onSaveDatasetName()` updated the displayed name and nothing else, so after a rename the page kept serving the old paths: the file tree and the path above the preview still said the old name, clicking a file hung on "File content is loading" (404, then 500), and the Data Card's "Latest version file" was stale too. Only a reload recovered. The same rename was also allowed while an upload was running. The upload is addressed by the name it started with, so the data uploaded fine and then the completing call failed with `400 {"code":400,"message":"Dataset not found"}`, leaving the panel on "Uploading: 1 file(s)" forever and a row behind in `dataset_upload_session`. - `dataset-detail.component.ts` — after a successful rename, refetch the browsed version's file tree and the Data Card's latest-version facts. The selected version is kept deliberately; `retrieveDatasetVersionList()` would also refresh both but resets the picker to the newest version, which a rename is no reason to do. - `dataset-detail.component.ts` / `.html` — bind the uploader's existing `uploadsInFlightChange`, disable the name field and Save while an upload is running, show a hint saying why, and guard `onSaveDatasetName()` on the same invariant. Note the browsed file resets to the version's first file after a rename, because the dataset page does not track the open file's relative path the way the model page does. That is a small change in behaviour from a page that was previously broken; tracking the open path can follow separately if it is worth it. **Before** — renamed, but the path still says `sensor-readings` and the preview hangs (console output overlaid so it fits in one screenshot); the Data Card keeps the old path too: image image **After** — the path and the Data Card both follow the new name, and the file opens: **After** — renaming is blocked while an upload is in flight: image ### Any related issues, documentation, discussions? Closes #8345. ### How was this PR tested? Three cases added to `dataset-detail.component.spec.ts`: - `refetches the file tree and the latest-version facts, which both embed the old name` — asserts both fetches happen with the browsed `dvid`, and that the selected version is unchanged. Removing the two refresh calls fails this test. - `refuses to rename while an upload is in flight, which would strand it`. - `locks the name field while an upload is in flight` — drives the real uploader output through the rendered template and checks the input and Save button are disabled. ``` cd frontend npx ng test --include src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts # Tests 145 passed (145) ``` Also checked by hand against a local stack, with the file uploads throttled so the upload stays in flight: renaming now leaves the tree, the path header and the Data Card all on the new name and files still open, and the name field is greyed out with the hint while an upload runs. ### Was this PR authored or co-authored using generative AI tooling? (backported from commit 4ff0b8b4ac415c4d60bfd73f37da5e3039ade2ec) Generated-by: Claude Code (Opus 5) --- .../dataset-detail.component.html | 11 ++++- .../dataset-detail.component.spec.ts | 43 +++++++++++++++++++ .../dataset-detail.component.ts | 11 +++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.html b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.html index 777a1537654..74bee9c350e 100644 --- a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.html +++ b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.html @@ -456,7 +456,8 @@
Choose a Version:
[resourceName]="datasetName" [endpoint]="datasetEndpoint" [createVersion]="createDatasetVersion" - (versionCreated)="onVersionCreated()"> + (versionCreated)="onVersionCreated()" + (uploadsInFlightChange)="uploadsInFlight = $event"> @@ -474,14 +475,22 @@

General

Lowercase letters, numbers and hyphens.

+

+ An upload is in progress — finish or cancel it in Versions & Files first, or it will be left + incomplete. +

diff --git a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts index b91db9c8fe4..440e84a4654 100644 --- a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts +++ b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts @@ -1534,6 +1534,37 @@ describe("DatasetDetailComponent behavior", () => { expect(datasetServiceStub.updateDatasetName).not.toHaveBeenCalled(); }); + + it("refetches the file tree and the latest-version facts, which both embed the old name", () => { + datasetServiceStub.updateDatasetName.mockReturnValue(of({})); + createComponent(); + component.did = 5; + component.selectedVersion = makeVersion({ dvid: 12 }); + component.editedDatasetName = "new-name"; + datasetServiceStub.retrieveDatasetVersionFileTree.mockClear(); + datasetServiceStub.retrieveDatasetLatestVersion.mockClear(); + + component.onSaveDatasetName(); + + expect(datasetServiceStub.retrieveDatasetVersionFileTree).toHaveBeenCalledWith(5, 12, component.isLogin); + expect(datasetServiceStub.retrieveDatasetLatestVersion).toHaveBeenCalledWith(5); + // The browsed version stays put: a rename is not a reason to jump to the newest one. + expect(component.selectedVersion?.dvid).toBe(12); + }); + + it("refuses to rename while an upload is in flight, which would strand it", () => { + createComponent(); + component.did = 5; + component.uploadsInFlight = true; + component.editedDatasetName = "new-name"; + + component.onSaveDatasetName(); + + expect(datasetServiceStub.updateDatasetName).not.toHaveBeenCalled(); + expect(notificationServiceStub.error).toHaveBeenCalledWith( + "Finish or cancel the upload in progress before renaming this dataset" + ); + }); }); describe("onDeleteDataset", () => { @@ -2506,6 +2537,18 @@ describe("DatasetDetailComponent rendered template", () => { expect(datasetService.updateDatasetDescription).toHaveBeenCalledWith(5, "brand new"); }); + it("locks the name field while an upload is in flight", () => { + const el = render({ did: 5, datasetName: "ds", userDatasetAccessLevel: "WRITE" }); + openTab("Versions & Files"); + + fixture.debugElement.query(By.css("texera-version-uploader")).triggerEventHandler("uploadsInFlightChange", true); + flush(); + openTab("Settings"); + + expect(q(el, ".settings-name-controls input").disabled).toBe(true); + expect(q(el, ".settings-name-controls button").disabled).toBe(true); + }); + it("deletes the dataset only once the confirmation is accepted", () => { const el = render({ did: 5, datasetName: "ds", userDatasetAccessLevel: "WRITE", isOwner: true }); openTab("Settings"); diff --git a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.ts b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.ts index 535156b9541..d0af058975e 100644 --- a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.ts +++ b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.ts @@ -141,6 +141,7 @@ export class DatasetDetailComponent implements OnInit { public versions: ReadonlyArray = []; public selectedVersion: DatasetVersion | undefined; + public uploadsInFlight = false; public fileTreeNodeList: DatasetFileNode[] = []; public selectedVersionCreationTime: string = ""; // The following three fields describe the latest version for the Data Card, all @@ -599,6 +600,10 @@ export class DatasetDetailComponent implements OnInit { if (!this.did) { return; } + if (this.uploadsInFlight) { + this.notificationService.error("Finish or cancel the upload in progress before renaming this dataset"); + return; + } // Reject invalid names outright instead of silently rewriting them, matching // the shared validation used by the other rename entry points (PR #6426). const name = this.editedDatasetName; @@ -615,6 +620,12 @@ export class DatasetDetailComponent implements OnInit { next: () => { this.datasetName = name; this.editedDatasetName = name; + // Every file path embeds the dataset name, and preview and single-file download resolve + // a dataset by (owner, name) — a stale tree 404s until reload. + if (this.selectedVersion) { + this.onVersionSelected(this.selectedVersion); + } + this.retrieveLatestVersionFile(); this.notificationService.success(`Dataset name updated to '${name}'`); }, error: (err: unknown) => {