Basic frontend respect saved view perms

This commit is contained in:
shamoon
2026-02-20 08:45:11 -08:00
parent e0e517358d
commit e9d87ef049
6 changed files with 147 additions and 53 deletions

View File

@@ -25,15 +25,17 @@
</div>
</div>
<div class="col-auto">
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
<pngx-confirm-button
label="Delete"
i18n-label
(confirm)="deleteSavedView(view)"
*pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }"
buttonClasses="btn-sm btn-outline-danger form-control"
iconName="trash">
</pngx-confirm-button>
@if (canDeleteSavedView(view)) {
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
<pngx-confirm-button
label="Delete"
i18n-label
(confirm)="deleteSavedView(view)"
*pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }"
buttonClasses="btn-sm btn-outline-danger form-control"
iconName="trash">
</pngx-confirm-button>
}
</div>
</div>
<div class="row">

View File

@@ -3,7 +3,6 @@ import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { By } from '@angular/platform-browser'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs'
@@ -57,6 +56,8 @@ describe('SavedViewsComponent', () => {
provide: PermissionsService,
useValue: {
currentUserCan: () => true,
currentUserHasObjectPermissions: () => true,
currentUserOwnsObject: () => true,
},
},
{
@@ -96,12 +97,11 @@ describe('SavedViewsComponent', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError')
const toastSpy = jest.spyOn(toastService, 'show')
const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany')
const toggle = fixture.debugElement.query(
By.css('.form-check.form-switch input')
)
toggle.nativeElement.checked = true
toggle.nativeElement.dispatchEvent(new Event('change'))
const control = component.savedViewsForm
.get('savedViews')
.get(savedViews[0].id.toString())
.get('show_on_dashboard')
control.setValue(!savedViews[0].show_on_dashboard)
// saved views error first
savedViewPatchSpy.mockReturnValueOnce(
@@ -116,6 +116,7 @@ describe('SavedViewsComponent', () => {
// succeed saved views
savedViewPatchSpy.mockReturnValueOnce(of(savedViews as SavedView[]))
control.setValue(savedViews[0].show_on_dashboard)
component.save()
expect(toastErrorSpy).not.toHaveBeenCalled()
expect(savedViewPatchSpy).toHaveBeenCalled()
@@ -127,25 +128,21 @@ describe('SavedViewsComponent', () => {
expect(patchSpy).not.toHaveBeenCalled()
const view = savedViews[0]
const toggle = fixture.debugElement.query(
By.css('.form-check.form-switch input')
)
toggle.nativeElement.checked = true
toggle.nativeElement.dispatchEvent(new Event('change'))
// register change
component.savedViewsForm.get('savedViews').get(view.id.toString()).value[
'show_on_dashboard'
] = !view.show_on_dashboard
component.savedViewsForm
.get('savedViews')
.get(view.id.toString())
.get('show_on_dashboard')
.setValue(!view.show_on_dashboard)
fixture.detectChanges()
component.save()
expect(patchSpy).toHaveBeenCalledWith([
{
expect.objectContaining({
id: view.id,
name: view.name,
show_in_sidebar: view.show_in_sidebar,
show_on_dashboard: !view.show_on_dashboard,
},
}),
])
})
@@ -162,14 +159,17 @@ describe('SavedViewsComponent', () => {
it('should support reset', () => {
const view = savedViews[0]
component.savedViewsForm.get('savedViews').get(view.id.toString()).value[
'show_on_dashboard'
] = !view.show_on_dashboard
component.savedViewsForm
.get('savedViews')
.get(view.id.toString())
.get('show_on_dashboard')
.setValue(!view.show_on_dashboard)
component.reset()
expect(
component.savedViewsForm.get('savedViews').get(view.id.toString()).value[
'show_on_dashboard'
]
component.savedViewsForm
.get('savedViews')
.get(view.id.toString())
.get('show_on_dashboard').value
).toEqual(view.show_on_dashboard)
})
})

View File

@@ -11,6 +11,10 @@ import { BehaviorSubject, Observable, takeUntil } from 'rxjs'
import { DisplayMode } from 'src/app/data/document'
import { SavedView } from 'src/app/data/saved-view'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import {
PermissionAction,
PermissionsService,
} from 'src/app/services/permissions.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
@@ -41,6 +45,7 @@ export class SavedViewsComponent
implements OnInit, OnDestroy
{
private savedViewService = inject(SavedViewService)
private permissionsService = inject(PermissionsService)
private settings = inject(SettingsService)
private toastService = inject(ToastService)
@@ -95,16 +100,20 @@ export class SavedViewsComponent
display_mode: view.display_mode,
display_fields: view.display_fields,
}
const canEdit = this.canEditSavedView(view)
this.savedViewsGroup.addControl(
view.id.toString(),
new FormGroup({
id: new FormControl(null),
name: new FormControl(null),
show_on_dashboard: new FormControl(null),
show_in_sidebar: new FormControl(null),
page_size: new FormControl(null),
display_mode: new FormControl(null),
display_fields: new FormControl([]),
id: new FormControl({ value: null, disabled: !canEdit }),
name: new FormControl({ value: null, disabled: !canEdit }),
show_on_dashboard: new FormControl({
value: null,
disabled: !canEdit,
}),
show_in_sidebar: new FormControl({ value: null, disabled: !canEdit }),
page_size: new FormControl({ value: null, disabled: !canEdit }),
display_mode: new FormControl({ value: null, disabled: !canEdit }),
display_fields: new FormControl({ value: [], disabled: !canEdit }),
})
)
}
@@ -126,6 +135,9 @@ export class SavedViewsComponent
}
public deleteSavedView(savedView: SavedView) {
if (!this.canDeleteSavedView(savedView)) {
return
}
this.savedViewService.delete(savedView).subscribe(() => {
this.savedViewsGroup.removeControl(savedView.id.toString())
this.savedViews.splice(this.savedViews.indexOf(savedView), 1)
@@ -148,9 +160,9 @@ export class SavedViewsComponent
// only patch views that have actually changed
const changed: SavedView[] = []
Object.values(this.savedViewsGroup.controls)
.filter((g: FormGroup) => !g.pristine)
.filter((g: FormGroup) => g.enabled && !g.pristine)
.forEach((group: FormGroup) => {
changed.push(group.value)
changed.push(group.getRawValue())
})
if (changed.length) {
this.savedViewService.patchMany(changed).subscribe({
@@ -167,4 +179,15 @@ export class SavedViewsComponent
})
}
}
public canEditSavedView(view: SavedView): boolean {
return this.permissionsService.currentUserHasObjectPermissions(
PermissionAction.Change,
view
)
}
public canDeleteSavedView(view: SavedView): boolean {
return this.permissionsService.currentUserOwnsObject(view)
}
}