Skip to content

Commit

Permalink
Add checks on editability and invalidate form
Browse files Browse the repository at this point in the history
  • Loading branch information
minottic committed Jan 25, 2024
1 parent 7810ae9 commit f37a2c0
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 48 deletions.
4 changes: 4 additions & 0 deletions scilog/src/app/core/model/logbooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ export interface Logbooks extends Basesnippets {
thumbnail?: string;
location?: string;
readACL?: string[];
updateACL?: string[];
deleteACL?: string[];
adminACL?: string[];
expiresAt?: string;
}
2 changes: 1 addition & 1 deletion scilog/src/app/core/remote-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ export class LogbookDataService extends RemoteDataService {
httpFilter["order"] = ["defaultOrder DESC"];

let whereFilter: Object[] = [];
whereFilter.push({ "snippetType": "logbook" });
whereFilter.push({ "snippetType": "logbook", deleted: false });

httpFilter["where"] = { "and": whereFilter };

Expand Down
14 changes: 7 additions & 7 deletions scilog/src/app/overview/add-logbook/add-logbook.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
<div>
<mat-form-field appearance="standard" required>
<mat-label>Title</mat-label>
<input matInput placeholder="Title" [formControl]="optionsFormGroup.get('title')">
<input matInput placeholder="Title" [formControl]="optionsFormGroup.get('title')" matTooltip="{{ tooltips.expired }}">
<mat-error *ngIf="optionsFormGroup.get('title').hasError('required')">Please specify the title.</mat-error>
</mat-form-field>
</div>
<div>
<mat-form-field appearance="standard">
<mat-label>Location (Beamline or Instrument)</mat-label>
<mat-select [formControl]="optionsFormGroup.get('location')" (selectionChange)="selectLocation($event)">
<mat-select [formControl]="optionsFormGroup.get('location')" (selectionChange)="selectLocation($event)" matTooltip="{{ tooltips.expired }}">
<ng-container *ngFor="let location of availLocations">
<mat-option [(value)]="location.id">{{ location?.location }}</mat-option>
</ng-container>
Expand All @@ -36,7 +36,7 @@
<mat-form-field appearance="standard" required>
<mat-label>ownerGroup</mat-label>
<input matInput placeholder="ownerGroup" [matAutocomplete]="autoOwnerGroup"
[formControl]="optionsFormGroup.get('ownerGroup')">
[formControl]="optionsFormGroup.get('ownerGroup')" matTooltip="{{ tooltips.ownerGroup }}">
<mat-error *ngIf="optionsFormGroup.get('ownerGroup').hasError('required')">Please specify the owner
group.
</mat-error>
Expand Down Expand Up @@ -76,12 +76,12 @@
</mat-form-field>
</div>
<div>
<mat-slide-toggle [formControl]="optionsFormGroup.get('isPrivate')">private</mat-slide-toggle>
<mat-slide-toggle [formControl]="optionsFormGroup.get('isPrivate')" matTooltip="{{ tooltips.expired }}">private</mat-slide-toggle>
</div>
<div>
<mat-form-field appearance="standard" required>
<mat-label>Description</mat-label>
<textarea matInput placeholder="Description" [formControl]="optionsFormGroup.get('description')"></textarea>
<textarea matInput placeholder="Description" [formControl]="optionsFormGroup.get('description')" matTooltip="{{ tooltips.expired }}"></textarea>
</mat-form-field>
</div>
</form>
Expand All @@ -90,7 +90,7 @@
<img mat-card-image [src]="imageToShow" alt="" *ngIf="imageLoaded" />
<div>
<input style="display: none" type="file" (change)="onFileChanged($event)" #fileInput>
<button color="accent" aria-label="Thumbnail" class="mat-fab-bottom-right" (click)="fileInput.click()">
<button color="accent" aria-label="Thumbnail" class="mat-fab-bottom-right" (click)="fileInput.click()" disabled="{{ tooltips.expired }}" matTooltip="{{ tooltips.expired }}">
{{ thumbnailText }}
</button>
<button color="accent" aria-label="RemoveThumbnail" class="mat-fab-bottom-right" (click)="removeThumbnail()"
Expand Down Expand Up @@ -154,5 +154,5 @@
</div> -->
<mat-dialog-actions align="end">
<button mat-button (click)="close()">Cancel</button>
<button mat-button (click)="addLogbook($event)">Add</button>
<button mat-button (click)="addLogbook($event)" [disabled]="optionsFormGroup.invalid">OK</button>
</mat-dialog-actions>
118 changes: 93 additions & 25 deletions scilog/src/app/overview/add-logbook/add-logbook.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { map, startWith } from 'rxjs/operators';
import { UserPreferencesService } from '@shared/user-preferences.service';
import { Logbooks } from '@model/logbooks';
import { LogbookDataService, LogbookItemDataService } from '@shared/remote-data.service';

import { SnackbarService } from 'src/app/core/snackbar.service';

function ownerGroupMemberValidator(groups: string[]): ValidatorFn {
return (control: AbstractControl): { forbiddenGroup: {value: string} } | null => {
Expand All @@ -22,7 +22,7 @@ function ownerGroupMemberValidator(groups: string[]): ValidatorFn {
}

function groupCreationValidator(control: AbstractControl): { anyAuthGroup: {value: string | string[]} } | null {
const forbidden = control.value.includes('any-authenticated-user');
const forbidden = control.value?.includes('any-authenticated-user');
return forbidden ? { anyAuthGroup: { value: control.value } } : null;
}

Expand Down Expand Up @@ -105,6 +105,7 @@ export class AddLogbookComponent implements OnInit {
filteredAccessGroups: Observable<string[]>;
filteredOwnerGroups: Observable<string[]>;
accessGroupsAvail: string[] = [];
tooltips: {ownerGroup?: string, expired?: string} = {};


@ViewChild('accessGroupsInput') accessGroupsInput: ElementRef<HTMLInputElement>;
Expand All @@ -117,6 +118,7 @@ export class AddLogbookComponent implements OnInit {
private logbookItemDataService: LogbookItemDataService,
private userPreferences: UserPreferencesService,
private logbookDataService: LogbookDataService,
private snackBar: SnackbarService,
@Inject(MAT_DIALOG_DATA) data) {
this.optionsFormGroup = fb.group({
hideRequired: new UntypedFormControl(false),
Expand All @@ -138,34 +140,69 @@ export class AddLogbookComponent implements OnInit {
this.setDefaults();
}

private getForm(value: string) {
return this.optionsFormGroup.get(value);
}

private setForm(toKey: string, fromKey?: string) {
this.getForm(toKey).setValue(this.logbook[fromKey ?? toKey]);
}

get accessGroups() {
return this.optionsFormGroup.get('accessGroups');
return this.getForm('accessGroups');
}

private setOwnerGroupWithEditability() {
this.setForm('ownerGroup');
if (!this.logbook.ownerGroup || this.isAdmin())
return
this.getForm('ownerGroup').disable();
this.tooltips.ownerGroup = `Only members of '${this.logbook.adminACL}' can change the ownerGroup`;
}

private isAdmin() {
return this.accessGroupsAvail.some(group => this.logbook?.adminACL?.includes(group));
}

private setExpiredTooltip() {
const expiresAt = new Date(this.logbook.expiresAt);
if (!expiresAt || expiresAt > new Date()) return;
const expiresString = expiresAt.toLocaleDateString(
'en-GB',
{year: '2-digit', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'}
);
this.tooltips.expired = `Editing time (${expiresString}) has passed`;
}

private setWithEditability(toKey: string) {
let fromKey: string;
if (toKey === 'title') fromKey = 'name';
this.setForm(toKey, fromKey ?? toKey);
if (this.tooltips.expired) this.getForm(toKey).disable();
}

setDefaults(){
this.accessGroupsAvail = this.userPreferences.userInfo?.roles;
if (!this.isAdmin())
this.getForm('ownerGroup').addValidators(ownerGroupMemberValidator(this.accessGroupsAvail));
if (this.logbook) {
this.optionsFormGroup.get('title').setValue(this.logbook.name);
this.optionsFormGroup.get('description').setValue(this.logbook.description);
this.optionsFormGroup.get('location').setValue(this.logbook.location);
this.optionsFormGroup.get('ownerGroup').setValue(this.logbook.ownerGroup);
this.setExpiredTooltip();
['title', 'description', 'location', 'isPrivate'].map(field => this.setWithEditability(field));
this.setOwnerGroupWithEditability();
this.accessGroups.setValue(this.logbook.accessGroups);
this.optionsFormGroup.get('isPrivate').setValue(this.logbook.isPrivate);
this.fileId = this.logbook.thumbnail;
if (this.fileId) {
this.getImageFromService();
}
console.log("editing existing logbook");
}
else {
this.optionsFormGroup.get('ownerGroup').addValidators(groupCreationValidator);
this.accessGroups.addValidators(groupCreationValidator);
this.getForm('ownerGroup').addValidators(groupCreationValidator);
this.accessGroupsAvail = this.accessGroupsAvail.filter((g: string) => g !== 'any-authenticated-user');
}

this.filteredAccessGroups = this.accessGroupsCtrl.valueChanges.pipe(startWith(null), map((accessGroup: string | null) => accessGroup ? this._filter(accessGroup) : this.accessGroupsAvail.slice()));
this.filteredOwnerGroups = this.optionsFormGroup.get('ownerGroup').valueChanges.pipe(startWith(null), map((accessGroup: string | null) => accessGroup ? this._filter(accessGroup) : this.accessGroupsAvail.slice()));
this.optionsFormGroup.get('ownerGroup').addValidators(ownerGroupMemberValidator(this.accessGroupsAvail));
this.filteredOwnerGroups = this.getForm('ownerGroup').valueChanges.pipe(startWith(null), map((accessGroup: string | null) => accessGroup ? this._filter(accessGroup) : this.accessGroupsAvail.slice()));
}

async getLocations(){
Expand Down Expand Up @@ -203,11 +240,14 @@ export class AddLogbookComponent implements OnInit {

if (this.optionsFormGroup.invalid) {
console.log("form invalid")
const invalidKeys = [];
for (const key in this.optionsFormGroup.controls) {
if (this.optionsFormGroup.get(key).invalid){
this.optionsFormGroup.get(key).setErrors({'required': true});
if (this.getForm(key).invalid){
invalidKeys.push(key);
this.getForm(key).setErrors({'required': true});
}
}
this.showSnackbarMessage(`Invalid keys: '${invalidKeys}'`, 'warning');
return;
}

Expand All @@ -217,12 +257,12 @@ export class AddLogbookComponent implements OnInit {
logbookId = this.logbook.id;
}
this.logbook = {
name: this.optionsFormGroup.get('title').value,
location: this.optionsFormGroup.get('location').value,
ownerGroup: this.optionsFormGroup.get('ownerGroup').value,
name: this.getForm('title').value,
location: this.getForm('location').value,
ownerGroup: this.getForm('ownerGroup').value,
accessGroups: this.accessGroups.value,
isPrivate: this.optionsFormGroup.get('isPrivate').value,
description: this.optionsFormGroup.get('description').value
isPrivate: this.getForm('isPrivate').value,
description: this.getForm('description').value
}

let fileData: {id: string};
Expand All @@ -240,17 +280,45 @@ export class AddLogbookComponent implements OnInit {

if (logbookId != null) {
// update logbook
await this.logbookDataService.patchLogbook(logbookId, this.logbook);
this.logbook.id = logbookId;
try {
await this.logbookDataService.patchLogbook(logbookId, this.logbook);
} catch (error) {
console.log(error);
this.showSnackbarMessage('Error while updating the logbook. If the error persists contact an administrator', 'warning');
return;
}
finally {
this.logbook.id = logbookId;
}
this.showSnackbarMessage('Edit successful', 'resolved')
} else {
// create new logbook
let data = await this.logbookDataService.postLogbook(this.logbook);
this.logbook.id = data.id;
try {
let data = await this.logbookDataService.postLogbook(this.logbook);
this.logbook.id = data.id;
} catch (error) {
console.log(error);
this.showSnackbarMessage('Error while creating the logbook. If the error persists contact an administrator', 'warning');
return;
}
this.showSnackbarMessage('Creation successful', 'resolved');
}

this.dialogRef.close(this.logbook);
}

private showSnackbarMessage(message: string, messageClass: 'warning' | 'resolved') {
return this.snackBar._showMessage({
message: message,
panelClass: [`${messageClass}-snackbar`],
action: 'Dismiss',
show: true,
duration: 4000,
type: 'serverMessage',
horizontalPosition: 'center',
verticalPosition: 'top',
})
}

selectLocation(id: any) {
console.log("locationId:", id.value);
this.availLocations.forEach(loc => {
Expand All @@ -259,7 +327,7 @@ export class AddLogbookComponent implements OnInit {
console.log(this.selectedLocation);
if (!this.customImageLoaded) {
console.log(this.selectedLocation)
if (this.selectedLocation?.files[0]) {
if (this.selectedLocation?.files?.[0]) {
this.fileId = this.selectedLocation?.files[0].fileId;
this.getImageFromService();
} else {
Expand Down
22 changes: 13 additions & 9 deletions scilog/src/app/overview/logbook-cover/logbook-cover.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@
<mat-card-title #cardHeader style="font-size: 18px;">{{ logbook?.name }}</mat-card-title>
<mat-card-subtitle>{{ logbook?.ownerGroup }}</mat-card-subtitle>
<button mat-icon-button [matMenuTriggerFor]="menu" aria-label="Example icon-button with a menu"
class="mat-fab-top-right" *ngIf='enableEdit'>
class="mat-fab-top-right" [disabled]="tooltips.edit" matTooltip="{{ tooltips.edit }}">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="editLogbook()" [disabled]="!enableEdit? 'disabled' : null">
<mat-icon>edit</mat-icon>
<span>Edit</span>
</button>
<button mat-menu-item (click)="deleteLogbook()" [disabled]="!enableEdit? 'disabled' : null">
<mat-icon>delete</mat-icon>
<span>Delete</span>
</button>
<span [matTooltip]="tooltips.update">
<button mat-menu-item (click)="editLogbook()" [disabled]="tooltips.update">
<mat-icon>edit</mat-icon>
<span>Edit</span>
</button>
</span>
<span [matTooltip]="tooltips.delete">
<button mat-menu-item (click)="deleteLogbook()" [disabled]="tooltips.delete">
<mat-icon>delete</mat-icon>
<span>Delete</span>
</button>
</span>
</mat-menu>
</mat-card-header>
<div class="image-container">
Expand Down
26 changes: 20 additions & 6 deletions scilog/src/app/overview/logbook-cover/logbook-cover.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class LogbookWidgetComponent implements OnInit {

imageToShow: any;
isImageLoading: boolean;
enableEdit = false;
tooltips: {edit?: string, update?: string, delete?: string} = {};

constructor(
private logbookItemDataService: LogbookItemDataService,
Expand All @@ -33,12 +33,26 @@ export class LogbookWidgetComponent implements OnInit {
if (this.logbook?.thumbnail) {
this.getImageFromService();
}
if (this.userPreferences.userInfo?.roles.some(entry => {
return this.logbook?.readACL?.includes?.(entry)
})) {
this.enableEdit = true;
}
this.enableActions();
}

private enabledMembers(action: string): string[] {
const aclMembers = this.logbook?.[`${action}ACL`];
if (this.userPreferences.userInfo?.roles.some((entry: string) =>
aclMembers?.includes?.(entry) || entry === 'admin'
))
return [];
this.tooltips[action] = `Enabled for members of '${aclMembers.filter((m: string) => m != 'admin').concat('admin')}'`;
return aclMembers;
}

private enableActions() {
const groups = ['update', 'delete'].reduce((previousValue, currentValue) =>
previousValue.concat(this.enabledMembers(currentValue)),
[]
)
if (groups.length > 0)
this.tooltips.edit = `Enabled for members of '${[...new Set(groups.concat('admin'))]}'`;
}

ngAfterViewInit() {
Expand Down

0 comments on commit f37a2c0

Please sign in to comment.