Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@
</button>
</div>

<div class="edit-button">
<div
class="edit-button"
*ngIf="canEditDescription">
<button
nz-button
nzType="text"
Expand Down Expand Up @@ -109,8 +111,8 @@
<div
class="resource-description truncate-single-line"
(click)="onEditDescription(); $event.stopPropagation()">
{{ renderedDescription ? renderedDescription.slice(0, 200) : (hovering && editable) ? 'Write a description...' :
'' }}
{{ renderedDescription ? renderedDescription.slice(0, 200) : (hovering && canEditDescription) ? 'Write a
description...' : '' }}
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { NzModalService } from "ng-zorro-antd/modal";
import { of, Subject, throwError } from "rxjs";
import { ActionType, HubService } from "../../../../hub/service/hub.service";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { By } from "@angular/platform-browser";
import { RouterTestingModule } from "@angular/router/testing";
import { StubUserService } from "../../../../common/service/user/stub-user.service";
import { UserService } from "../../../../common/service/user/user.service";
Expand Down Expand Up @@ -265,7 +266,12 @@ describe("ListItemComponent", () => {

it("onEditDescription opens the edit modal and applies the change, then closes it", () => {
component.editable = true;
component.entry = { id: 1, description: "old", type: "workflow" } as unknown as DashboardEntry;
component.entry = {
id: 1,
description: "old",
type: "workflow",
accessLevel: "WRITE",
} as unknown as DashboardEntry;
const descriptionChange = new Subject<string>();
const modalRef = { componentInstance: { descriptionChange }, destroy: vi.fn() };
const createSpy = vi.spyOn(modalService, "create").mockReturnValue(modalRef as any);
Expand All @@ -288,6 +294,72 @@ describe("ListItemComponent", () => {
expect(createSpy).not.toHaveBeenCalled();
});

describe("read-only entries cannot edit the description (#3497)", () => {
// `editable` only means "private dashboard view" and is hardcoded true by the
// container, so the row itself must consult the entry's access level.
const makeEntry = (accessLevel: string) =>
({
id: 1,
name: "wf",
description: "old",
type: "workflow",
accessLevel,
workflow: { isOwner: accessLevel === "WRITE", accessLevel },
accessibleUserIds: [],
likeCount: 0,
viewCount: 0,
isLiked: false,
size: 0,
}) as unknown as DashboardEntry;

const stubModal = () =>
vi
.spyOn(modalService, "create")
.mockReturnValue({
componentInstance: { descriptionChange: new Subject<string>() },
destroy: vi.fn(),
} as any);

it("does not open the description editor without WRITE access", () => {
component.editable = true;
component.entry = makeEntry("READ");
const createSpy = stubModal();

component.onEditDescription();

expect(createSpy).not.toHaveBeenCalled();
expect(workflowPersistService.updateWorkflowDescription).not.toHaveBeenCalled();
});

it("hides the Edit Description control for read-only entries", () => {
component.editable = true;
component.isPrivateSearch = true;
component.entry = makeEntry("READ");
fixture.detectChanges();

expect(fixture.debugElement.query(By.css('button[title="Edit Description"]'))).toBeNull();
});

it("still opens the description editor with WRITE access", () => {
component.editable = true;
component.entry = makeEntry("WRITE");
const createSpy = stubModal();

component.onEditDescription();

expect(createSpy).toHaveBeenCalled();
});

it("still renders the Edit Description control with WRITE access", () => {
component.editable = true;
component.isPrivateSearch = true;
component.entry = makeEntry("WRITE");
fixture.detectChanges();

expect(fixture.debugElement.query(By.css('button[title="Edit Description"]'))).not.toBeNull();
});
});

it("onCheckboxChange toggles the entry's checked flag and emits the change", () => {
const entry = { checked: false } as unknown as DashboardEntry;
const emitSpy = vi.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,17 @@ export class ListItemComponent implements OnChanges {
}, 0);
}

/**
* `editable` only means "this is the private dashboard view" and is hardcoded to true
* by the container, so it is not an access check. Editing the description additionally
* requires write access to this specific entry (#3497).
*/
get canEditDescription(): boolean {
return this.editable && this.entry.accessLevel === "WRITE";
}

onEditDescription(): void {
if (!this.editable) return;
if (!this.canEditDescription) return;

this.originalDescription = this.entry.description;

Expand Down
Loading