From 77554f6b364ecb9442f9699af49d972be591e918 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 20 Feb 2026 10:22:47 -0800
Subject: [PATCH] Add permissions ui stuff for saved views
---
.../saved-views/saved-views.component.html | 6 ++
.../saved-views/saved-views.component.spec.ts | 46 ++++++++++++++-
.../saved-views/saved-views.component.ts | 59 +++++++++++++++----
3 files changed, 98 insertions(+), 13 deletions(-)
diff --git a/src-ui/src/app/components/manage/saved-views/saved-views.component.html b/src-ui/src/app/components/manage/saved-views/saved-views.component.html
index 9f7b5be7f..a895ce97d 100644
--- a/src-ui/src/app/components/manage/saved-views/saved-views.component.html
+++ b/src-ui/src/app/components/manage/saved-views/saved-views.component.html
@@ -27,6 +27,12 @@
@if (canDeleteSavedView(view)) {
+
{
let fixture: ComponentFixture
let savedViewService: SavedViewService
let toastService: ToastService
+ let modalService: NgbModal
beforeEach(async () => {
TestBed.configureTestingModule({
@@ -79,10 +80,11 @@ describe('SavedViewsComponent', () => {
savedViewService = TestBed.inject(SavedViewService)
toastService = TestBed.inject(ToastService)
+ modalService = TestBed.inject(NgbModal)
fixture = TestBed.createComponent(SavedViewsComponent)
component = fixture.componentInstance
- jest.spyOn(savedViewService, 'listAll').mockReturnValue(
+ jest.spyOn(savedViewService, 'list').mockReturnValue(
of({
all: savedViews.map((v) => v.id),
count: savedViews.length,
@@ -179,4 +181,42 @@ describe('SavedViewsComponent', () => {
.get('show_on_dashboard').value
).toEqual(view.show_on_dashboard)
})
+
+ it('should support editing permissions', () => {
+ const confirmClicked = new Subject()
+ const modalRef = {
+ componentInstance: {
+ confirmClicked,
+ buttonsEnabled: true,
+ },
+ close: jest.fn(),
+ } as any
+ jest.spyOn(modalService, 'open').mockReturnValue(modalRef)
+ const patchSpy = jest.spyOn(savedViewService, 'patch')
+ patchSpy.mockReturnValue(of(savedViews[0] as SavedView))
+
+ component.editPermissions(savedViews[0] as SavedView)
+ confirmClicked.next({
+ permissions: {
+ owner: 1,
+ set_permissions: {
+ view: { users: [2], groups: [] },
+ change: { users: [], groups: [3] },
+ },
+ },
+ merge: true,
+ })
+
+ expect(patchSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ id: savedViews[0].id,
+ owner: 1,
+ set_permissions: {
+ view: { users: [2], groups: [] },
+ change: { users: [], groups: [3] },
+ },
+ })
+ )
+ expect(modalRef.close).toHaveBeenCalled()
+ })
})
diff --git a/src-ui/src/app/components/manage/saved-views/saved-views.component.ts b/src-ui/src/app/components/manage/saved-views/saved-views.component.ts
index 765a01ead..4ca08657b 100644
--- a/src-ui/src/app/components/manage/saved-views/saved-views.component.ts
+++ b/src-ui/src/app/components/manage/saved-views/saved-views.component.ts
@@ -6,8 +6,10 @@ import {
FormsModule,
ReactiveFormsModule,
} from '@angular/forms'
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { dirtyCheck } from '@ngneat/dirty-check-forms'
import { BehaviorSubject, Observable, takeUntil } from 'rxjs'
+import { PermissionsDialogComponent } from 'src/app/components/common/permissions-dialog/permissions-dialog.component'
import { DisplayMode } from 'src/app/data/document'
import { SavedView } from 'src/app/data/saved-view'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
@@ -48,6 +50,7 @@ export class SavedViewsComponent
private permissionsService = inject(PermissionsService)
private settings = inject(SettingsService)
private toastService = inject(ToastService)
+ private modalService = inject(NgbModal)
DisplayMode = DisplayMode
@@ -70,11 +73,7 @@ export class SavedViewsComponent
}
ngOnInit(): void {
- this.loading = true
- this.savedViewService.listAll().subscribe((r) => {
- this.savedViews = r.results
- this.initialize()
- })
+ this.reloadViews()
}
ngOnDestroy(): void {
@@ -145,10 +144,7 @@ export class SavedViewsComponent
$localize`Saved view "${savedView.name}" deleted.`
)
this.savedViewService.clearCache()
- this.savedViewService.listAll().subscribe((r) => {
- this.savedViews = r.results
- this.initialize()
- })
+ this.reloadViews()
})
}
@@ -168,7 +164,7 @@ export class SavedViewsComponent
this.savedViewService.patchMany(changed).subscribe({
next: () => {
this.toastService.showInfo($localize`Views saved successfully.`)
- this.store.next(this.savedViewsForm.value)
+ this.reloadViews()
},
error: (error) => {
this.toastService.showError(
@@ -190,4 +186,47 @@ export class SavedViewsComponent
public canDeleteSavedView(view: SavedView): boolean {
return this.permissionsService.currentUserOwnsObject(view)
}
+
+ public editPermissions(savedView: SavedView): void {
+ if (!this.canDeleteSavedView(savedView)) {
+ return
+ }
+ const modal = this.modalService.open(PermissionsDialogComponent, {
+ backdrop: 'static',
+ })
+ const dialog = modal.componentInstance as PermissionsDialogComponent
+ dialog.object = savedView
+
+ modal.componentInstance.confirmClicked.subscribe(({ permissions }) => {
+ modal.componentInstance.buttonsEnabled = false
+ const view = {
+ id: savedView.id,
+ owner: permissions.owner,
+ }
+ view['set_permissions'] = permissions.set_permissions
+ this.savedViewService.patch(view as SavedView).subscribe({
+ next: () => {
+ this.toastService.showInfo($localize`Permissions updated`)
+ modal.close()
+ this.reloadViews()
+ },
+ error: (error) => {
+ this.toastService.showError(
+ $localize`Error updating permissions`,
+ error
+ )
+ },
+ })
+ })
+ }
+
+ private reloadViews(): void {
+ this.loading = true
+ this.savedViewService
+ .listAll(null, null, { full_perms: true })
+ .subscribe((r) => {
+ this.savedViews = r.results
+ this.initialize()
+ })
+ }
}