argos/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.ts

526 lines
20 KiB
TypeScript

import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { DatePipe } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormArray, UntypedFormArray, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { DescriptionStatus } from '@app/core/common/enum/description-status';
import { DmpAccessType } from '@app/core/common/enum/dmp-access-type';
import { DmpBlueprintFieldCategory } from '@app/core/common/enum/dmp-blueprint-field-category';
import { DmpBlueprintExtraFieldDataType } from '@app/core/common/enum/dmp-blueprint-field-type';
import { DmpBlueprintStatus } from '@app/core/common/enum/dmp-blueprint-status';
import { DmpBlueprintSystemFieldType } from '@app/core/common/enum/dmp-blueprint-system-field-type';
import { DmpContactType } from '@app/core/common/enum/dmp-contact-type';
import { DmpStatus } from '@app/core/common/enum/dmp-status';
import { DmpUserRole } from '@app/core/common/enum/dmp-user-role';
import { DmpUserType } from '@app/core/common/enum/dmp-user-type';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { LockTargetType } from '@app/core/common/enum/lock-target-type';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { DescriptionSectionPermissionResolver } from '@app/core/model/description/description';
import { DmpBlueprint } from '@app/core/model/dmp-blueprint/dmp-blueprint';
import { Dmp, DmpPersist } from '@app/core/model/dmp/dmp';
import { LanguageInfo } from '@app/core/model/language-info';
import { LockPersist } from '@app/core/model/lock/lock.model';
import { AuthService } from '@app/core/services/auth/auth.service';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { LanguageInfoService } from '@app/core/services/culture/language-info-service';
import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service';
import { DescriptionService } from '@app/core/services/description/description.service';
import { DmpBlueprintService } from '@app/core/services/dmp/dmp-blueprint.service';
import { DmpService } from '@app/core/services/dmp/dmp.service';
import { LockService } from '@app/core/services/lock/lock.service';
import { LoggingService } from '@app/core/services/logging/logging-service';
import { MatomoService } from '@app/core/services/matomo/matomo-service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { UserService } from '@app/core/services/user/user.service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { QueryParamsService } from '@app/core/services/utilities/query-params.service';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { PopupNotificationDialogComponent } from '@app/library/notification/popup/popup-notification.component';
import { isNullOrUndefined } from '@app/utilities/enhancers/utils';
import { BaseEditor } from '@common/base/base-editor';
import { FormService } from '@common/forms/form-service';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { FilterService } from '@common/modules/text-filter/filter-service';
import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core';
import { interval } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { DmpEditorModel } from './dmp-editor.model';
import { DmpEditorResolver } from './dmp-editor.resolver';
import { DmpEditorService } from './dmp-editor.service';
@Component({
selector: 'app-dmp-editor',
templateUrl: './dmp-editor.component.html',
styleUrls: ['./dmp-editor.component.scss'],
providers: [DmpEditorService]
})
export class DmpEditorComponent extends BaseEditor<DmpEditorModel, Dmp> implements OnInit {
isNew = true;
isDeleted = false;
item: Dmp;
selectedBlueprint: DmpBlueprint;
lockStatus: Boolean = false;
step: number = 0;
//Enums
descriptionStatusEnum = DescriptionStatus;
dmpBlueprintSectionFieldCategoryEnum = DmpBlueprintFieldCategory;
dmpBlueprintSystemFieldTypeEnum = DmpBlueprintSystemFieldType;
dmpBlueprintExtraFieldDataTypeEnum = DmpBlueprintExtraFieldDataType;
dmpAccessTypeEnum = DmpAccessType;
dmpAccessTypeEnumValues = this.enumUtils.getEnumValues<DmpAccessType>(DmpAccessType);
dmpContactTypeEnum = DmpContactType;
dmpContactTypeEnumValues = this.enumUtils.getEnumValues<DmpContactType>(DmpContactType);
dmpUserTypeEnum = DmpUserType;
dmpUserTypeEnumValues = this.enumUtils.getEnumValues<DmpUserType>(DmpUserType);
dmpUserRoleEnumValues = this.enumUtils.getEnumValues<DmpUserRole>(DmpUserRole);
permissionPerSection: Map<Guid, string[]>;
singleAutocompleteBlueprintConfiguration: SingleAutoCompleteConfiguration = {
initialItems: (data?: any) => this.dmpBlueprintService.query(this.dmpBlueprintService.buildAutocompleteLookup(null, null, null, [DmpBlueprintStatus.Finalized])).pipe(map(x => x.items)),
filterFn: (searchQuery: string, data?: any) => this.dmpBlueprintService.query(this.dmpBlueprintService.buildAutocompleteLookup(searchQuery, null, null, [DmpBlueprintStatus.Finalized])).pipe(map(x => x.items)),
getSelectedItem: (selectedItem: any) => this.dmpBlueprintService.query(this.dmpBlueprintService.buildAutocompleteLookup(null, null, [selectedItem])).pipe(map(x => x.items[0])),
displayFn: (item: DmpBlueprint) => item.label,
titleFn: (item: DmpBlueprint) => item.label,
valueAssign: (item: DmpBlueprint) => item.id,
};
protected get canDelete(): boolean {
return !this.isDeleted && !this.isNew && (this.hasPermission(this.authService.permissionEnum.DeleteDmp) || this.item?.authorizationFlags?.some(x => x === AppPermission.DeleteDmp));
}
protected get canSave(): boolean {
return !this.isDeleted && (this.hasPermission(this.authService.permissionEnum.EditDmp) || this.item?.authorizationFlags?.some(x => x === AppPermission.EditDmp));
}
protected get canFinalize(): boolean {
return !this.isDeleted && (this.hasPermission(this.authService.permissionEnum.EditDmp) || this.item?.authorizationFlags?.some(x => x === AppPermission.EditDmp));
}
protected canEditSection(id: Guid): boolean {
return !this.isDeleted && (this.hasPermission(this.authService.permissionEnum.EditDescription) || this.item?.authorizationFlags?.some(x => x === AppPermission.EditDescription) || (
this.permissionPerSection && this.permissionPerSection[id.toString()] && this.permissionPerSection[id.toString()].some(x => x === AppPermission.EditDescription)
));
}
protected canDeleteSection(id: Guid): boolean {
return !this.isDeleted && (this.hasPermission(this.authService.permissionEnum.DeleteDescription) || this.item?.authorizationFlags?.some(x => x === AppPermission.DeleteDescription) || (
this.permissionPerSection && this.permissionPerSection[id.toString()] && this.permissionPerSection[id.toString()].some(x => x === AppPermission.DeleteDescription)
));
}
private hasPermission(permission: AppPermission): boolean {
return this.authService.hasPermission(permission) || this.item?.authorizationFlags?.some(x => x === permission) || this.editorModel?.permissions?.includes(permission);
}
constructor(
// BaseFormEditor injected dependencies
protected dialog: MatDialog,
protected language: TranslateService,
protected formService: FormService,
protected router: Router,
protected uiNotificationService: UiNotificationService,
protected httpErrorHandlingService: HttpErrorHandlingService,
protected filterService: FilterService,
protected datePipe: DatePipe,
protected route: ActivatedRoute,
protected queryParamsService: QueryParamsService,
// Rest dependencies. Inject any other needed deps here:
public authService: AuthService,
private dmpService: DmpService,
private logger: LoggingService,
public dmpBlueprintService: DmpBlueprintService,
private matomoService: MatomoService,
private lockService: LockService,
private configurationService: ConfigurationService,
// public visibilityRulesService: VisibilityRulesService,
private languageInfoService: LanguageInfoService,
public enumUtils: EnumUtils,
public descriptionTemplateService: DescriptionTemplateService,
public userService: UserService,
public descriptionService: DescriptionService
) {
super(dialog, language, formService, router, uiNotificationService, httpErrorHandlingService, filterService, datePipe, route, queryParamsService);
}
ngOnInit(): void {
this.matomoService.trackPageView('DMP Editor');
super.ngOnInit();
}
getItem(itemId: Guid, successFunction: (item: Dmp) => void) {
this.dmpService.getSingle(itemId, DmpEditorResolver.lookupFields())
.pipe(map(data => data as Dmp), takeUntil(this._destroyed))
.subscribe(
data => successFunction(data),
error => this.onCallbackError(error)
);
}
prepareForm(data: Dmp) {
try {
this.editorModel = data ? new DmpEditorModel().fromModel(data) : new DmpEditorModel();
if (data && data.descriptions) {
if (data.status == DmpStatus.Finalized) {
data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatus.Finalized);
} else {
data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatus.Canceled);
}
}
this.item = data;
this.selectedBlueprint = data?.blueprint;
this.isDeleted = data ? data.isActive === IsActive.Inactive : false;
if (data) {
const descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = {
dmpId: data.id,
sectionIds: data?.blueprint?.definition?.sections?.map(x => x.id),
permissions: [AppPermission.EditDescription, AppPermission.DeleteDescription]
}
this.descriptionService.getDescriptionSectionPermissions(descriptionSectionPermissionResolverModel)
.pipe(takeUntil(this._destroyed)).subscribe(
complete => {
this.permissionPerSection = complete,
this.buildForm();
},
error => this.onCallbackError(error)
);
} else {
this.buildForm();
}
if (this.item.id != null) {
this.checkLock(this.item.id);
}
} catch (error) {
this.logger.error('Could not parse Dmp item: ' + data + error);
this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error);
}
}
buildForm() {
const canedit = this.isNew ? this.authService.hasPermission(AppPermission.NewDmp) : this.item.authorizationFlags?.some(x => x === AppPermission.EditDmp) || this.authService.hasPermission(AppPermission.EditDmp);
this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !canedit);
if (this.editorModel.status == DmpStatus.Finalized || this.isDeleted) {
this.formGroup.disable();
}
if (this.item != null) {
this.nextStep();
}
}
refreshData(): void {
this.getItem(this.editorModel.id, (data: Dmp) => this.prepareForm(data));
}
refreshOnNavigateToData(id?: Guid): void {
this.formGroup.markAsPristine();
if (this.isNew) {
let route = [];
route.push('/plans/overview/' + id);
this.router.navigate(route, { queryParams: { 'lookup': this.queryParamsService.serializeLookup(this.lookupParams), 'lv': ++this.lv }, replaceUrl: true, relativeTo: this.route });
} else {
this.refreshData();
}
}
persistEntity(onSuccess?: (response) => void): void {
const formData = this.formService.getValue(this.formGroup.value) as DmpPersist;
//Transform to persist
//Transform descriptionTemplates
formData.descriptionTemplates = [];
for (const sectionId in (this.formGroup.get('descriptionTemplates') as UntypedFormGroup).controls) {
formData.descriptionTemplates.push(
...(this.formGroup.get('descriptionTemplates').get(sectionId).value as Guid[]).map(x => { return { sectionId: Guid.parse(sectionId), descriptionTemplateGroupId: x } })
);
}
this.dmpService.persist(formData)
.pipe(takeUntil(this._destroyed)).subscribe(
complete => onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete),
error => this.onCallbackError(error)
);
}
formSubmit(): void {
this.formService.removeAllBackEndErrors(this.formGroup);
this.formService.touchAllFormFields(this.formGroup);
if (!this.isFormValid()) {
return;
}
this.persistEntity();
}
public delete() {
const value = this.formGroup.value;
if (value.id) {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
maxWidth: '300px',
data: {
message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL')
}
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) {
this.dmpService.delete(value.id).pipe(takeUntil(this._destroyed))
.subscribe(
complete => this.onCallbackSuccess(),
error => this.onCallbackError(error)
);
}
});
}
}
clearErrorModel() {
this.editorModel.validationErrorModel.clear();
this.formService.validateAllFormFields(this.formGroup);
}
//
//
// Steps
//
//
changeStep(index: number) {
this.step = index;
this.resetScroll();
}
nextStep() {
this.step = this.step < this.selectedBlueprint.definition.sections.length ? this.step + 1 : this.step;
this.resetScroll();
}
previousStep() {
this.step = this.step !== 1 ? this.step - 1 : this.step;
this.resetScroll();
}
private resetScroll() {
if (document.getElementById('editor-form') != null) document.getElementById('editor-form').scrollTop = 0;
}
//
//
// Blueprint
//
//
selectBlueprint() {
this.dmpBlueprintService.getSingle(this.formGroup.get('blueprint').value, DmpEditorResolver.blueprintLookupFields()).pipe(takeUntil(this._destroyed)).subscribe(data => {
this.selectedBlueprint = data;
this.buildFormAfterBlueprintSelection();
this.nextStep();
});
}
selectDefaultBlueprint() {
this.dmpBlueprintService.getSingle(this.configurationService.defaultBlueprintId, DmpEditorResolver.blueprintLookupFields()).pipe(takeUntil(this._destroyed)).subscribe(data => {
this.selectedBlueprint = data;
this.formGroup.get('blueprint').setValue(this.selectedBlueprint.id);
this.buildFormAfterBlueprintSelection();
this.nextStep();
});
}
private buildFormAfterBlueprintSelection() {
const dmp: Dmp = {
label: this.formGroup.get('label').value,
description: this.formGroup.get('description').value,
blueprint: this.selectedBlueprint,
status: DmpStatus.Draft
}
this.prepareForm(dmp);
}
//
//
// Contacts
//
//
addContact(): void {
const contactArray = this.formGroup.get('properties').get('contacts') as FormArray;
(this.formGroup.get('properties').get('contacts') as FormArray).push(this.editorModel.createChildContact(contactArray.length));
}
removeContact(contactIndex: number): void {
(this.formGroup.get('properties').get('contacts') as FormArray).removeAt(contactIndex);
DmpEditorModel.reApplyPropertiesValidators(
{
formGroup: this.formGroup,
validationErrorModel: this.editorModel.validationErrorModel
}
);
this.formGroup.get('properties').get('contacts').markAsDirty();
}
dropContacts(event: CdkDragDrop<string[]>) {
const contactsFormArray = (this.formGroup.get('properties').get('contacts') as FormArray);
moveItemInArray(contactsFormArray.controls, event.previousIndex, event.currentIndex);
contactsFormArray.updateValueAndValidity();
DmpEditorModel.reApplyPropertiesValidators(
{
formGroup: this.formGroup,
validationErrorModel: this.editorModel.validationErrorModel
}
);
this.formGroup.get('properties').get('contacts').markAsDirty();
}
//
//
// Descriptions
//
//
public descriptionsInSection(sectionId: Guid) {
return this.item?.descriptions?.filter(x => x?.dmpDescriptionTemplate?.sectionId === sectionId) || [];
}
editDescription(id: string, isNew: boolean) {
if (!isNew) {
this.router.navigate(['/descriptions', 'edit', id]);
}
}
public removeDescription(descriptionId: Guid) {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
maxWidth: '300px',
restoreFocus: false,
data: {
message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.DELETE'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL'),
isDeleteConfirmation: true
}
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) {
if (descriptionId) {
this.descriptionService.delete(descriptionId)
.pipe(takeUntil(this._destroyed))
.subscribe(
complete => {
this.onCallbackSuccess();
},
error => this.onCallbackError(error)
);
}
// this.formGroup.get('datasets')['controls'].splice(index, 1);
this.step = 0;
}
});
}
//
//
// Description Template
//
//
onRemoveDescriptionTemplate(event, sectionIndex: number) {
let found = false;
const section = this.selectedBlueprint.definition.sections[sectionIndex];
let sectionDescriptionTemplates = (this.formGroup.get('descriptionTemplates') as UntypedFormArray).controls.find(x => x.get('sectionId').value === event.id);
}
onPreviewDescriptionTemplate(event, sectionIndex: number) {
// const dialogRef = this.dialog.open(DatasetPreviewDialogComponent, {
// width: '590px',
// minHeight: '200px',
// restoreFocus: false,
// data: {
// template: event
// },
// panelClass: 'custom-modalbox'
// });
// dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
// if (result) {
// this.addProfile(event, sectionIndex);
// this.profilesAutoCompleteConfiguration = {
// filterFn: this.filterProfiles.bind(this),
// initialItems: (excludedItems: any[]) => this.filterProfiles('').pipe(map(result => result.filter(resultItem => (excludedItems || []).map(x => x.id).indexOf(resultItem.id) === -1))),
// displayFn: (item) => item['label'],
// titleFn: (item) => item['label'],
// subtitleFn: (item) => item['description'],
// popupItemActionIcon: 'visibility'
// };
// }
// });
}
//
//
// Lock
//
//
private checkLock(itemId: Guid) {
if (itemId != null) {
this.isNew = false;
// check if locked.
this.lockService.checkLockStatus(itemId).pipe(takeUntil(this._destroyed)).subscribe(lockStatus => {
this.lockStatus = lockStatus;
if (lockStatus) {
this.formGroup.disable();
this.dialog.open(PopupNotificationDialogComponent, {
data: {
title: this.language.instant('DATASET-WIZARD.LOCKED.TITLE'),
message: this.language.instant('DATASET-WIZARD.LOCKED.MESSAGE')
}, maxWidth: '30em'
});
}
if (!lockStatus && !isNullOrUndefined(this.authService.currentAccountIsAuthenticated())) {
// lock it.
const lockPersist: LockPersist = {
target: itemId,
targetType: LockTargetType.Dmp,
}
this.lockService.persist(lockPersist).pipe(takeUntil(this._destroyed)).subscribe(async result => {
interval(this.configurationService.lockInterval).pipe(takeUntil(this._destroyed)).subscribe(() => this.touchLock(itemId));
});
}
});
}
}
private touchLock(targetId: Guid) {
this.lockService.checkLockStatus(targetId).pipe(takeUntil(this._destroyed)).subscribe(async result => { });
}
//
//
// Misc
//
//
public isDirty(): boolean {
return this.formGroup && this.formGroup.dirty; //&& this.hasChanges;
}
getLanguageInfos(): LanguageInfo[] {
return this.languageInfoService.getLanguageInfoValues();
}
}