From a05a8e49c68f750db28fd195ca0fda74f8fb29bf Mon Sep 17 00:00:00 2001 From: Ranjani Veena Belavadi Date: Thu, 16 Jul 2026 12:58:20 -0700 Subject: [PATCH 1/6] refactor(frontend): extract dataset settings into Settings tab --- .../dataset-detail.component.html | 770 ++++++++++-------- .../dataset-detail.component.scss | 77 ++ .../dataset-detail.component.ts | 57 +- 3 files changed, 551 insertions(+), 353 deletions(-) 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 3c2a0316170..4ed3e7d168f 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 @@ -74,47 +74,6 @@

Dataset: {{datasetName}}

nzTheme="outline"> {{ formatCount(likeCount) }} - - - - -
-
Quick Settings
-
- Visibility: - -
-
- Downloadable: - -
-
-
Dataset: {{datasetName}}
+ [editable]="false" + [enableViewMore]="true">
- - - -
-
-

- - {{ currentDisplayedFileName }} + + + + + +
+
+

+ + {{ currentDisplayedFileName }} + + + + + + + {{ formatSize(currentFileSize) }} + +

+
+
- - - - - {{ formatSize(currentFileSize) }} - -

-
-
- - - - - -
-
-
- - - - -
- - -
- -
-
-
- - -
-
Choose a Version:
-
- - - -
- -
+
-
+ nzType="compress"> + + +
-
+ nzType="right"> + + + +
- - - - - - - - - - + + - + + + + + +
+ +
+
+
+ - - -
- {{ fileName }} + nzHeader="Current Versions" + nzActive="true"> +
+
Choose a Version:
+
+ + +
- + +
+ + Version Size: {{ formatSize(currentDatasetVersionSize) }} +
+
+ + Created at: {{ selectedVersionCreationTime }} +
+
+
+ + + - - + + -
-
-
- {{ task.status }}: {{ task.filePath }} - +
+ + + + + + +
+
+
+ {{ task.status }}: {{ task.filePath }} + -
+ (click)="onClickAbortUploadProgress(task)"> + + +
-
- - - {{ formatSpeed(task.uploadSpeed) }} - - {{ formatTime(task.totalTime ?? 0) }} elapsed, - {{ formatTime(task.estimatedTimeRemaining ?? 0) }} left - +
+ + + {{ formatSpeed(task.uploadSpeed) }} - + {{ formatTime(task.totalTime ?? 0) }} elapsed, + {{ formatTime(task.estimatedTimeRemaining ?? 0) }} left + - - Upload time: {{ formatTime(task.totalTime ?? 0) }} - + + Upload time: {{ formatTime(task.totalTime ?? 0) }} + +
+
-
-
-
+ - + + + + + + +
- + +
+
+ + +
+
+ +
+
+
+ + + + +
+ +

General

- - +
+
+ +

Lowercase letters, numbers and hyphens.

+
+
+ + +
+
-
-
- - -
-
- -
+ + +
+ +
Shown on cards and the Metadata tab.
+ + +
+ + + +

Access & visibility

+ +
+
+ +

+ {{ datasetIsPublic ? "Public — anyone can view this dataset." : "Private — only you and invited + collaborators can see this dataset." }} +

+
+
+ +
+
+ + + +
+
+ +

+ {{ datasetIsDownloadable ? "Viewers can download this dataset." : "Viewers can browse files but cannot + download them." }} +

+
+
+
- - +
+
+ + +

Delete

+ +
+
+ +

This permanently deletes the dataset and all of its versions.

+
+
+ +
+
+
- - + + diff --git a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.scss b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.scss index d5ef708f889..b5bbf7dfced 100644 --- a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.scss +++ b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.scss @@ -347,3 +347,80 @@ nz-select { border-radius: 8px; margin-right: 80px; } + +nz-tabs { + margin-top: 6px; +} + +::ng-deep .ant-tabs-nav { + padding-left: 16px; +} + +// The Settings tab shares the tab area's bounded height with the Data Card +// tab. Give its content its own scroll region (mirroring the height:100% + +// overflow-y:auto pattern used by other dashboard pages) so both cards can +// be scrolled fully into view, with bottom padding for breathing room below +// the last card. +.settings-tab-content { + height: 100%; + overflow-y: auto; + padding-bottom: 24px; +} + +.settings-general-card { + max-width: 700px; + margin: 24px auto 0; + border-radius: 16px; + border: 1px solid #d9d9d9; + + .settings-card-title { + font-weight: 600; + margin-bottom: 20px; + } + + .settings-field-label { + display: block; + font-weight: 600; + } + + .settings-field-hint { + color: rgba(0, 0, 0, 0.45); + font-size: 12px; + margin-bottom: 8px; + } + + .settings-name-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + + .settings-name-label label { + font-weight: 600; + } + + .settings-hint { + color: rgba(0, 0, 0, 0.45); + font-size: 12px; + margin: 4px 0 0; + } + + .settings-name-controls { + display: flex; + gap: 8px; + flex-shrink: 0; + + input { + border-radius: 8px; + } + + button { + border-radius: 8px; + } + } + } +} + +::ng-deep .settings-general-card button { + border-radius: 8px; +} 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 c3b5c9a80fc..56e1a93863a 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 @@ -18,7 +18,8 @@ */ import { Component, EventEmitter, OnInit, Output, ViewChild } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; +import { USER_DATASET } from "../../../../../app-routing.constant"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; import { DatasetService, MultipartUploadProgress } from "../../../../service/user/dataset/dataset.service"; import { NzResizeEvent, NzResizableDirective, NzResizeHandleComponent } from "ng-zorro-antd/resizable"; @@ -51,7 +52,7 @@ import { ɵNzTransitionPatchDirective } from "ng-zorro-antd/core/transition-patc import { NzIconDirective } from "ng-zorro-antd/icon"; import { NzSpaceCompactItemDirective } from "ng-zorro-antd/space"; import { NzButtonComponent } from "ng-zorro-antd/button"; -import { NzPopoverDirective } from "ng-zorro-antd/popover"; +import { NzPopconfirmDirective } from "ng-zorro-antd/popconfirm"; import { NzSwitchComponent } from "ng-zorro-antd/switch"; import { FormsModule } from "@angular/forms"; import { MarkdownDescriptionComponent } from "../../markdown-description/markdown-description.component"; @@ -68,6 +69,7 @@ import { NzProgressComponent } from "ng-zorro-antd/progress"; import { UserDatasetStagedObjectsListComponent } from "./user-dataset-staged-objects-list/user-dataset-staged-objects-list.component"; import { NzInputDirective } from "ng-zorro-antd/input"; import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from "@angular/cdk/scrolling"; +import { NzTabsComponent, NzTabComponent } from "ng-zorro-antd/tabs"; export const THROTTLE_TIME_MS = 1000; export const ABORT_RETRY_MAX_ATTEMPTS = 10; @@ -88,7 +90,7 @@ export const ABORT_RETRY_BACKOFF_BASE_MS = 100; NzIconDirective, NzSpaceCompactItemDirective, NzButtonComponent, - NzPopoverDirective, + NzPopconfirmDirective, NzSwitchComponent, FormsModule, MarkdownDescriptionComponent, @@ -114,11 +116,14 @@ export const ABORT_RETRY_BACKOFF_BASE_MS = 100; CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf, + NzTabsComponent, + NzTabComponent, ], }) export class DatasetDetailComponent implements OnInit { public did: number | undefined; public datasetName: string = ""; + public editedDatasetName: string = ""; public datasetDescription: string = ""; public datasetCreationTime: string = ""; public datasetCreationTimeTooltip: string = ""; @@ -194,6 +199,7 @@ export class DatasetDetailComponent implements OnInit { constructor( private route: ActivatedRoute, + private router: Router, private modalService: NzModalService, private datasetService: DatasetService, private notificationService: NotificationService, @@ -359,6 +365,7 @@ export class DatasetDetailComponent implements OnInit { .subscribe(dashboardDataset => { const dataset = dashboardDataset.dataset; this.datasetName = dataset.name; + this.editedDatasetName = dataset.name; this.datasetDescription = dataset.description; this.userDatasetAccessLevel = dashboardDataset.accessPrivilege; this.datasetIsPublic = dataset.isPublic; @@ -908,6 +915,50 @@ export class DatasetDetailComponent implements OnInit { }); } + onSaveDatasetName(): void { + if (!this.did) { + return; + } + // Sanitize using the same rules as dataset creation: trim leading whitespace, + // collapse runs of non-alphanumeric characters to a single "-", then lowercase. + const sanitizedName = this.editedDatasetName + .trimStart() + .replace(/[^a-zA-Z0-9]+/g, "-") + .toLowerCase(); + + this.datasetService + .updateDatasetName(this.did, sanitizedName) + .pipe(untilDestroyed(this)) + .subscribe({ + next: () => { + this.datasetName = sanitizedName; + this.editedDatasetName = sanitizedName; + this.notificationService.success(`Dataset name updated to '${sanitizedName}'`); + }, + error: (err: unknown) => { + this.notificationService.error("Failed to update dataset name"); + }, + }); + } + + onDeleteDataset(): void { + if (!this.did) { + return; + } + this.datasetService + .deleteDatasets(this.did) + .pipe(untilDestroyed(this)) + .subscribe({ + next: () => { + this.notificationService.success(`Dataset ${this.datasetName} was deleted`); + this.router.navigate([USER_DATASET]); + }, + error: (err: unknown) => { + this.notificationService.error("Failed to delete the dataset"); + }, + }); + } + async copyCurrentFilePath(): Promise { if (!this.currentDisplayedFileName) { return; From 6b3588475890b63a7dced5efd41905bf33955c3d Mon Sep 17 00:00:00 2001 From: Ranjani Veena Belavadi Date: Fri, 17 Jul 2026 11:31:38 -0700 Subject: [PATCH 2/6] test(frontend): add coverage for dataset rename and delete --- .../dataset-detail.component.spec.ts | 94 ++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) 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 3e3e027309b..4a268556864 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 @@ -18,7 +18,8 @@ */ import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { ActivatedRoute } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; +import { USER_DATASET } from "../../../../../app-routing.constant"; import { of, Subject, throwError } from "rxjs"; import { NzModalService } from "ng-zorro-antd/modal"; import { MarkdownService } from "ngx-markdown"; @@ -429,6 +430,8 @@ describe("DatasetDetailComponent behavior", () => { updateDatasetDownloadable: vi.fn(() => of({})), updateDatasetCoverImage: vi.fn(() => of({})), updateDatasetDescription: vi.fn(() => of({})), + updateDatasetName: vi.fn(() => of({})), + deleteDatasets: vi.fn(() => of({})), deleteDatasetFile: vi.fn(() => of({})), getDatasetDiff: vi.fn(() => of([])), multipartUpload: vi.fn(() => of()), @@ -996,4 +999,93 @@ describe("DatasetDetailComponent behavior", () => { expect(notificationServiceStub.error).toHaveBeenCalledWith("Failed to copy file path"); }); }); + + describe("onSaveDatasetName", () => { + it("seeds editedDatasetName from the loaded dataset name", () => { + datasetServiceStub.getDataset.mockReturnValue( + of(makeDashboardDataset({ dataset: makeDataset({ name: "seed-name" }) })) + ); + createComponent(); + component.did = 5; + component.retrieveDatasetInfo(); + + expect(component.editedDatasetName).toBe("seed-name"); + }); + + it("sanitizes the edited name, persists it, updates local state and toasts success", () => { + datasetServiceStub.updateDatasetName.mockReturnValue(of({})); + createComponent(); + component.did = 5; + component.editedDatasetName = " My Cool_Dataset"; + + component.onSaveDatasetName(); + + expect(datasetServiceStub.updateDatasetName).toHaveBeenCalledWith(5, "my-cool-dataset"); + expect(component.datasetName).toBe("my-cool-dataset"); + expect(component.editedDatasetName).toBe("my-cool-dataset"); + expect(notificationServiceStub.success).toHaveBeenCalledWith("Dataset name updated to 'my-cool-dataset'"); + }); + + it("toasts an error and leaves the name unchanged when the rename fails", () => { + datasetServiceStub.updateDatasetName.mockReturnValue(throwError(() => new Error("boom"))); + createComponent(); + component.did = 5; + component.datasetName = "original"; + component.editedDatasetName = "new-name"; + + component.onSaveDatasetName(); + + expect(component.datasetName).toBe("original"); + expect(notificationServiceStub.error).toHaveBeenCalledWith("Failed to update dataset name"); + }); + + it("does nothing when there is no did", () => { + createComponent(); + component.did = undefined; + component.editedDatasetName = "whatever"; + + component.onSaveDatasetName(); + + expect(datasetServiceStub.updateDatasetName).not.toHaveBeenCalled(); + }); + }); + + describe("onDeleteDataset", () => { + it("deletes the dataset, toasts success and navigates back to the dataset list", () => { + datasetServiceStub.deleteDatasets.mockReturnValue(of({})); + createComponent(); + const navigateSpy = vi.spyOn(TestBed.inject(Router), "navigate").mockResolvedValue(true); + component.did = 5; + component.datasetName = "DS"; + + component.onDeleteDataset(); + + expect(datasetServiceStub.deleteDatasets).toHaveBeenCalledWith(5); + expect(notificationServiceStub.success).toHaveBeenCalledWith("Dataset DS was deleted"); + expect(navigateSpy).toHaveBeenCalledWith([USER_DATASET]); + }); + + it("toasts an error and does not navigate when the deletion fails", () => { + datasetServiceStub.deleteDatasets.mockReturnValue(throwError(() => new Error("boom"))); + createComponent(); + const navigateSpy = vi.spyOn(TestBed.inject(Router), "navigate").mockResolvedValue(true); + component.did = 5; + + component.onDeleteDataset(); + + expect(notificationServiceStub.error).toHaveBeenCalledWith("Failed to delete the dataset"); + expect(navigateSpy).not.toHaveBeenCalled(); + }); + + it("does nothing when there is no did", () => { + createComponent(); + const navigateSpy = vi.spyOn(TestBed.inject(Router), "navigate").mockResolvedValue(true); + component.did = undefined; + + component.onDeleteDataset(); + + expect(datasetServiceStub.deleteDatasets).not.toHaveBeenCalled(); + expect(navigateSpy).not.toHaveBeenCalled(); + }); + }); }); From d2b78a9563268de1487f4dcdd65969b5c518b79c Mon Sep 17 00:00:00 2001 From: Ranjani Veena Belavadi Date: Mon, 20 Jul 2026 10:59:59 -0700 Subject: [PATCH 3/6] fix(frontend): use validateDatasetName for rename, matching #6426 --- .../dataset-detail.component.spec.ts | 29 +++++++++++++++---- .../dataset-detail.component.ts | 28 +++++++++++------- 2 files changed, 40 insertions(+), 17 deletions(-) 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 ec67695ddd5..4692146e117 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 @@ -1024,18 +1024,35 @@ describe("DatasetDetailComponent behavior", () => { expect(component.editedDatasetName).toBe("seed-name"); }); - it("sanitizes the edited name, persists it, updates local state and toasts success", () => { + it("persists a valid name unchanged and toasts success", () => { datasetServiceStub.updateDatasetName.mockReturnValue(of({})); createComponent(); component.did = 5; - component.editedDatasetName = " My Cool_Dataset"; + // Mixed case, hyphen and underscore are all valid: the name must be saved + // verbatim, not rewritten. + component.editedDatasetName = "My-Cool_Dataset"; component.onSaveDatasetName(); - expect(datasetServiceStub.updateDatasetName).toHaveBeenCalledWith(5, "my-cool-dataset"); - expect(component.datasetName).toBe("my-cool-dataset"); - expect(component.editedDatasetName).toBe("my-cool-dataset"); - expect(notificationServiceStub.success).toHaveBeenCalledWith("Dataset name updated to 'my-cool-dataset'"); + expect(datasetServiceStub.updateDatasetName).toHaveBeenCalledWith(5, "My-Cool_Dataset"); + expect(component.datasetName).toBe("My-Cool_Dataset"); + expect(component.editedDatasetName).toBe("My-Cool_Dataset"); + expect(notificationServiceStub.success).toHaveBeenCalledWith("Dataset name updated to 'My-Cool_Dataset'"); + }); + + it("rejects an invalid name with a validation error and does not call the rename API", () => { + createComponent(); + component.did = 5; + component.datasetName = "original"; + component.editedDatasetName = "My Cool Dataset"; // spaces are not allowed + + component.onSaveDatasetName(); + + expect(datasetServiceStub.updateDatasetName).not.toHaveBeenCalled(); + expect(component.datasetName).toBe("original"); + expect(notificationServiceStub.error).toHaveBeenCalledWith( + "Invalid dataset name: only letters, numbers, underscores, and hyphens are allowed (max 128 characters)" + ); }); it("toasts an error and leaves the name unchanged when the rename fails", () => { 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 f2ee9da5b9a..b19c5d63782 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 @@ -21,7 +21,11 @@ import { Component, EventEmitter, OnInit, Output, ViewChild } from "@angular/cor import { ActivatedRoute, Router } from "@angular/router"; import { USER_DATASET } from "../../../../../app-routing.constant"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; -import { DatasetService, MultipartUploadProgress } from "../../../../service/user/dataset/dataset.service"; +import { + DatasetService, + MultipartUploadProgress, + validateDatasetName, +} from "../../../../service/user/dataset/dataset.service"; import { NzResizeEvent, NzResizableDirective, NzResizeHandleComponent } from "ng-zorro-antd/resizable"; import { DatasetFileNode, @@ -928,21 +932,23 @@ export class DatasetDetailComponent implements OnInit { if (!this.did) { return; } - // Sanitize using the same rules as dataset creation: trim leading whitespace, - // collapse runs of non-alphanumeric characters to a single "-", then lowercase. - const sanitizedName = this.editedDatasetName - .trimStart() - .replace(/[^a-zA-Z0-9]+/g, "-") - .toLowerCase(); + // 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; + const nameError = validateDatasetName(name); + if (nameError) { + this.notificationService.error(nameError); + return; + } this.datasetService - .updateDatasetName(this.did, sanitizedName) + .updateDatasetName(this.did, name) .pipe(untilDestroyed(this)) .subscribe({ next: () => { - this.datasetName = sanitizedName; - this.editedDatasetName = sanitizedName; - this.notificationService.success(`Dataset name updated to '${sanitizedName}'`); + this.datasetName = name; + this.editedDatasetName = name; + this.notificationService.success(`Dataset name updated to '${name}'`); }, error: (err: unknown) => { this.notificationService.error("Failed to update dataset name"); From df40d0b5c18106a1000d7f1cf263ae6a920a4ff4 Mon Sep 17 00:00:00 2001 From: Ranjani Veena Belavadi Date: Mon, 20 Jul 2026 11:34:36 -0700 Subject: [PATCH 4/6] fix(frontend): use extractErrorMessage for rename/delete error messages --- .../user-dataset-explorer/dataset-detail.component.spec.ts | 4 ++-- .../user-dataset-explorer/dataset-detail.component.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) 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 4692146e117..9bb8c8ae23f 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 @@ -1065,7 +1065,7 @@ describe("DatasetDetailComponent behavior", () => { component.onSaveDatasetName(); expect(component.datasetName).toBe("original"); - expect(notificationServiceStub.error).toHaveBeenCalledWith("Failed to update dataset name"); + expect(notificationServiceStub.error).toHaveBeenCalledWith("boom"); }); it("does nothing when there is no did", () => { @@ -1102,7 +1102,7 @@ describe("DatasetDetailComponent behavior", () => { component.onDeleteDataset(); - expect(notificationServiceStub.error).toHaveBeenCalledWith("Failed to delete the dataset"); + expect(notificationServiceStub.error).toHaveBeenCalledWith("boom"); expect(navigateSpy).not.toHaveBeenCalled(); }); 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 b19c5d63782..3581459670b 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 @@ -20,6 +20,7 @@ import { Component, EventEmitter, OnInit, Output, ViewChild } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { USER_DATASET } from "../../../../../app-routing.constant"; +import { extractErrorMessage } from "../../../../../common/util/error"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; import { DatasetService, @@ -951,7 +952,7 @@ export class DatasetDetailComponent implements OnInit { this.notificationService.success(`Dataset name updated to '${name}'`); }, error: (err: unknown) => { - this.notificationService.error("Failed to update dataset name"); + this.notificationService.error(extractErrorMessage(err)); }, }); } @@ -969,7 +970,7 @@ export class DatasetDetailComponent implements OnInit { this.router.navigate([USER_DATASET]); }, error: (err: unknown) => { - this.notificationService.error("Failed to delete the dataset"); + this.notificationService.error(extractErrorMessage(err)); }, }); } From 5a50b5f843461996ad894d1459c11326efda8fbb Mon Sep 17 00:00:00 2001 From: Ranjani Veena Belavadi Date: Mon, 20 Jul 2026 12:02:29 -0700 Subject: [PATCH 5/6] fix(frontend): restrict dataset delete to owner only --- .../dataset-detail.component.html | 1 + .../dataset-detail.component.spec.ts | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) 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 4ed3e7d168f..6ac29246cd1 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 @@ -547,6 +547,7 @@

Delete

nz-button nzType="text" title="Delete" + [disabled]="!isOwner" (nzOnConfirm)="onDeleteDataset()" nz-popconfirm nzPopconfirmTitle="Confirm to delete this dataset."> 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 9bb8c8ae23f..cae883472ae 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 @@ -1117,4 +1117,38 @@ describe("DatasetDetailComponent behavior", () => { expect(navigateSpy).not.toHaveBeenCalled(); }); }); + + describe("delete button disabled state", () => { + // The Settings tab (and its Delete card) only render for WRITE access; the + // delete button itself is owner-only, mirroring the Downloadable switch's + // [nzDisabled]="!isOwner". Renders WRITE access with the given ownership, + // activates the (inactive) Settings tab so its pane is in the DOM, then + // returns the delete button element. + const renderDeleteButton = (isOwner: boolean): HTMLButtonElement => { + datasetServiceStub.getDataset.mockReturnValue(of(makeDashboardDataset({ accessPrivilege: "WRITE", isOwner }))); + createComponent(); + fixture.detectChanges(); + + const tabButtons: NodeListOf = fixture.nativeElement.querySelectorAll(".ant-tabs-tab-btn"); + const settingsTab = Array.from(tabButtons).find(tab => tab.textContent?.includes("Settings")); + settingsTab?.click(); + fixture.detectChanges(); + + return fixture.nativeElement.querySelector('button[title="Delete"]') as HTMLButtonElement; + }; + + it("disables the delete button for a non-owner with write access", () => { + const button = renderDeleteButton(false); + + expect(button).toBeTruthy(); + expect(button.disabled).toBe(true); + }); + + it("enables the delete button for the owner", () => { + const button = renderDeleteButton(true); + + expect(button).toBeTruthy(); + expect(button.disabled).toBe(false); + }); + }); }); From 97aebf3fca19cfd25d3e8effb0ad6fd861daca9b Mon Sep 17 00:00:00 2001 From: Ranjani Veena Belavadi Date: Mon, 20 Jul 2026 12:14:15 -0700 Subject: [PATCH 6/6] refactor(frontend): remove redundant disabled check on Save button --- .../user-dataset-explorer/dataset-detail.component.html | 1 - 1 file changed, 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 6ac29246cd1..f50dcbd0a63 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 @@ -471,7 +471,6 @@

General