Add permissions ui stuff for saved views

This commit is contained in:
shamoon
2026-02-20 10:22:47 -08:00
parent e69e543ba2
commit 77554f6b36
3 changed files with 98 additions and 13 deletions

View File

@@ -27,6 +27,12 @@
<div class="col-auto"> <div class="col-auto">
@if (canDeleteSavedView(view)) { @if (canDeleteSavedView(view)) {
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label> <label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
<button
class="btn btn-sm btn-outline-secondary form-control mb-2"
type="button"
(click)="editPermissions(view)"
*pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.SavedView }"
i18n>Permissions</button>
<pngx-confirm-button <pngx-confirm-button
label="Delete" label="Delete"
i18n-label i18n-label

View File

@@ -3,9 +3,9 @@ import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing' import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing' import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs' import { Subject, of, throwError } from 'rxjs'
import { SavedView } from 'src/app/data/saved-view' import { SavedView } from 'src/app/data/saved-view'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
@@ -32,6 +32,7 @@ describe('SavedViewsComponent', () => {
let fixture: ComponentFixture<SavedViewsComponent> let fixture: ComponentFixture<SavedViewsComponent>
let savedViewService: SavedViewService let savedViewService: SavedViewService
let toastService: ToastService let toastService: ToastService
let modalService: NgbModal
beforeEach(async () => { beforeEach(async () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -79,10 +80,11 @@ describe('SavedViewsComponent', () => {
savedViewService = TestBed.inject(SavedViewService) savedViewService = TestBed.inject(SavedViewService)
toastService = TestBed.inject(ToastService) toastService = TestBed.inject(ToastService)
modalService = TestBed.inject(NgbModal)
fixture = TestBed.createComponent(SavedViewsComponent) fixture = TestBed.createComponent(SavedViewsComponent)
component = fixture.componentInstance component = fixture.componentInstance
jest.spyOn(savedViewService, 'listAll').mockReturnValue( jest.spyOn(savedViewService, 'list').mockReturnValue(
of({ of({
all: savedViews.map((v) => v.id), all: savedViews.map((v) => v.id),
count: savedViews.length, count: savedViews.length,
@@ -179,4 +181,42 @@ describe('SavedViewsComponent', () => {
.get('show_on_dashboard').value .get('show_on_dashboard').value
).toEqual(view.show_on_dashboard) ).toEqual(view.show_on_dashboard)
}) })
it('should support editing permissions', () => {
const confirmClicked = new Subject<any>()
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()
})
}) })

View File

@@ -6,8 +6,10 @@ import {
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { dirtyCheck } from '@ngneat/dirty-check-forms' import { dirtyCheck } from '@ngneat/dirty-check-forms'
import { BehaviorSubject, Observable, takeUntil } from 'rxjs' 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 { DisplayMode } from 'src/app/data/document'
import { SavedView } from 'src/app/data/saved-view' import { SavedView } from 'src/app/data/saved-view'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
@@ -48,6 +50,7 @@ export class SavedViewsComponent
private permissionsService = inject(PermissionsService) private permissionsService = inject(PermissionsService)
private settings = inject(SettingsService) private settings = inject(SettingsService)
private toastService = inject(ToastService) private toastService = inject(ToastService)
private modalService = inject(NgbModal)
DisplayMode = DisplayMode DisplayMode = DisplayMode
@@ -70,11 +73,7 @@ export class SavedViewsComponent
} }
ngOnInit(): void { ngOnInit(): void {
this.loading = true this.reloadViews()
this.savedViewService.listAll().subscribe((r) => {
this.savedViews = r.results
this.initialize()
})
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@@ -145,10 +144,7 @@ export class SavedViewsComponent
$localize`Saved view "${savedView.name}" deleted.` $localize`Saved view "${savedView.name}" deleted.`
) )
this.savedViewService.clearCache() this.savedViewService.clearCache()
this.savedViewService.listAll().subscribe((r) => { this.reloadViews()
this.savedViews = r.results
this.initialize()
})
}) })
} }
@@ -168,7 +164,7 @@ export class SavedViewsComponent
this.savedViewService.patchMany(changed).subscribe({ this.savedViewService.patchMany(changed).subscribe({
next: () => { next: () => {
this.toastService.showInfo($localize`Views saved successfully.`) this.toastService.showInfo($localize`Views saved successfully.`)
this.store.next(this.savedViewsForm.value) this.reloadViews()
}, },
error: (error) => { error: (error) => {
this.toastService.showError( this.toastService.showError(
@@ -190,4 +186,47 @@ export class SavedViewsComponent
public canDeleteSavedView(view: SavedView): boolean { public canDeleteSavedView(view: SavedView): boolean {
return this.permissionsService.currentUserOwnsObject(view) 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()
})
}
} }