From 9f0a87cdc201431dc243521defdadf435efb468e Mon Sep 17 00:00:00 2001 From: JangAyeon Date: Tue, 29 Sep 2026 15:50:28 +0900 Subject: [PATCH] [ZEPPELIN-6591] Discard unsaved note permission edits on Cancel --- .../notebook/notebook.component.html | 1 + .../permissions/permissions.component.html | 16 +-- .../permissions/permissions.component.spec.ts | 112 ++++++++++++++++++ .../permissions/permissions.component.ts | 31 +++-- 4 files changed, 143 insertions(+), 17 deletions(-) create mode 100644 zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.spec.ts diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html index 6dc08633df8b..f17bf0cdf6f3 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html @@ -61,6 +61,7 @@ [noteId]="note.id" [(activatedExtension)]="activatedExtension" [permissions]="permissions" + (permissionsSaved)="getPermissions(note)" > } @case ('revisions') { diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.html index 37595eabb3d6..3f4a73d59f26 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.html +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.html @@ -29,13 +29,13 @@

Note Permissions (Only note owners can change)

- @for (item of permissions.owners; track item) { + @for (item of draftPermissions.owners; track item) { } @for (item of listOfUserAndRole; track item) { @@ -56,13 +56,13 @@

Note Permissions (Only note owners can change)

- @for (item of permissions.writers; track item) { + @for (item of draftPermissions.writers; track item) { } @for (item of listOfUserAndRole; track item) { @@ -83,13 +83,13 @@

Note Permissions (Only note owners can change)

- @for (item of permissions.runners; track item) { + @for (item of draftPermissions.runners; track item) { } @for (item of listOfUserAndRole; track item) { @@ -110,13 +110,13 @@

Note Permissions (Only note owners can change)

- @for (item of permissions.readers; track item) { + @for (item of draftPermissions.readers; track item) { } @for (item of listOfUserAndRole; track item) { diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.spec.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.spec.ts new file mode 100644 index 000000000000..908ffdca201e --- /dev/null +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.spec.ts @@ -0,0 +1,112 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ChangeDetectorRef, SimpleChange } from '@angular/core'; +import { NzMessageService } from 'ng-zorro-antd/message'; +import { NzModalService } from 'ng-zorro-antd/modal'; +import { of } from 'rxjs'; +import { describe, expect, it, vi } from 'vitest'; + +import { Permissions } from '@zeppelin/interfaces'; +import { SecurityService, TicketService } from '@zeppelin/services'; + +import { NotebookPermissionsComponent } from './permissions.component'; + +const savedPermissions = (): Permissions => ({ + owners: ['alice'], + writers: ['bob'], + runners: ['carol'], + readers: ['dave'] +}); + +const createComponent = (permissions: Permissions) => { + const setPermissions = vi.fn(() => of(undefined)); + const component = new NotebookPermissionsComponent( + { setPermissions } as unknown as SecurityService, + { markForCheck: vi.fn() } as unknown as ChangeDetectorRef, + { success: vi.fn() } as unknown as NzMessageService, + { ticket: { principal: 'alice' } } as unknown as TicketService, + { create: vi.fn() } as unknown as NzModalService + ); + component.noteId = 'note-1'; + component.permissions = permissions; + component.ngOnInit(); + return { component, setPermissions }; +}; + +const editEveryList = (component: NotebookPermissionsComponent) => { + component.draftPermissions.owners = []; + component.draftPermissions.writers.push('eve'); + component.draftPermissions.runners = ['frank']; + component.draftPermissions.readers.pop(); +}; + +describe('NotebookPermissionsComponent cancel', () => { + it('leaves the parent permissions unchanged when edits are cancelled', () => { + const parent = savedPermissions(); + const { component, setPermissions } = createComponent(parent); + + editEveryList(component); + component.closePermissions(); + + expect(parent).toEqual(savedPermissions()); + expect(setPermissions).not.toHaveBeenCalled(); + }); + + it('shows the saved values when the panel is reopened after cancel', () => { + const parent = savedPermissions(); + const { component: first } = createComponent(parent); + editEveryList(first); + first.closePermissions(); + + const { component: reopened } = createComponent(parent); + + expect(reopened.draftPermissions).toEqual(savedPermissions()); + }); + + it('restores the draft from the saved values on reset', () => { + const { component } = createComponent(savedPermissions()); + editEveryList(component); + + component.resetPermissions(); + + expect(component.draftPermissions).toEqual(savedPermissions()); + }); + + it('refreshes the draft when new saved permissions arrive', () => { + const { component } = createComponent(savedPermissions()); + editEveryList(component); + const next: Permissions = { owners: ['zoe'], writers: [], runners: [], readers: [] }; + + component.permissions = next; + component.ngOnChanges({ permissions: new SimpleChange(undefined, next, false) }); + + expect(component.draftPermissions).toEqual(next); + }); +}); + +describe('NotebookPermissionsComponent save', () => { + it('persists all four edited lists and reports them to the parent', () => { + const parent = savedPermissions(); + const { component, setPermissions } = createComponent(parent); + const saved = vi.fn(); + component.permissionsSaved.subscribe(saved); + const expected: Permissions = { owners: ['alice'], writers: ['bob', 'eve'], runners: ['frank'], readers: [] }; + + component.draftPermissions = { ...expected, writers: [...expected.writers] }; + component.savePermissions(); + + expect(setPermissions).toHaveBeenCalledWith('note-1', expected); + expect(saved).toHaveBeenCalledWith(expected); + expect(parent).toEqual(savedPermissions()); + }); +}); diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.ts index f6015e09b313..2d4438d45225 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.ts @@ -18,7 +18,8 @@ import { Input, OnChanges, OnInit, - Output + Output, + SimpleChanges } from '@angular/core'; import { NzMessageService } from 'ng-zorro-antd/message'; @@ -27,6 +28,13 @@ import { NzModalService } from 'ng-zorro-antd/modal'; import { Permissions } from '@zeppelin/interfaces'; import { SecurityService, TicketService } from '@zeppelin/services'; +const clonePermissions = (permissions: Permissions): Permissions => ({ + owners: [...permissions.owners], + writers: [...permissions.writers], + runners: [...permissions.runners], + readers: [...permissions.readers] +}); + @Component({ selector: 'zeppelin-notebook-permissions', templateUrl: './permissions.component.html', @@ -41,7 +49,8 @@ export class NotebookPermissionsComponent implements OnInit, OnChanges { @Output() readonly activatedExtensionChange = new EventEmitter< 'interpreter' | 'permissions' | 'revisions' | 'hide' >(); - permissionsBack!: Permissions; + @Output() readonly permissionsSaved = new EventEmitter(); + draftPermissions!: Permissions; listOfUserAndRole: Array<{ text: string; children: string[] }> = []; savePermissions() { @@ -57,7 +66,7 @@ export class NotebookPermissionsComponent implements OnInit, OnChanges { 'Please fill the [Owners] field. If not, it will set as current user. ' + `Current user : [ ${this.ticketService.ticket.principal.trim()} ]`, nzOnOk: () => { - this.permissions.owners = [this.ticketService.ticket.principal]; + this.draftPermissions.owners = [this.ticketService.ticket.principal]; this.setPermissions(); }, nzOnCancel: () => { @@ -87,18 +96,20 @@ export class NotebookPermissionsComponent implements OnInit, OnChanges { } setPermissions() { - this.securityService.setPermissions(this.noteId, this.permissions).subscribe(() => { + const saved = clonePermissions(this.draftPermissions); + this.securityService.setPermissions(this.noteId, saved).subscribe(() => { this.nzMessageService.success('Permissions Saved Successfully'); + this.permissionsSaved.emit(saved); this.closePermissions(); }); } resetPermissions() { - this.permissions = { ...this.permissionsBack }; + this.draftPermissions = clonePermissions(this.permissions); } isOwnerEmpty() { - return !this.permissions.owners.some(o => o.trim().length > 0); + return !this.draftPermissions.owners.some(o => o.trim().length > 0); } searchUser(search: string) { @@ -130,10 +141,12 @@ export class NotebookPermissionsComponent implements OnInit, OnChanges { ) {} ngOnInit() { - this.permissionsBack = { ...this.permissions }; + this.resetPermissions(); } - ngOnChanges(): void { - this.permissionsBack = { ...this.permissions }; + ngOnChanges(changes: SimpleChanges): void { + if (changes.permissions) { + this.resetPermissions(); + } } }