diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/enums/DmpBlueprintSystemFieldType.java b/dmp-backend/core/src/main/java/eu/eudat/commons/enums/DmpBlueprintSystemFieldType.java index 1b24e93ce..e3c48344b 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/commons/enums/DmpBlueprintSystemFieldType.java +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/enums/DmpBlueprintSystemFieldType.java @@ -11,7 +11,8 @@ public enum DmpBlueprintSystemFieldType implements DatabaseEnum { Description((short)1), Language((short)2), Contact((short)3), - AccessRights((short)4); + AccessRights((short)4), + User((short)5); private final Short value; DmpBlueprintSystemFieldType(Short value) { diff --git a/dmp-backend/core/src/main/java/eu/eudat/commons/enums/DmpUserRole.java b/dmp-backend/core/src/main/java/eu/eudat/commons/enums/DmpUserRole.java index c7e4721e3..e0648b201 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/commons/enums/DmpUserRole.java +++ b/dmp-backend/core/src/main/java/eu/eudat/commons/enums/DmpUserRole.java @@ -7,7 +7,10 @@ import java.util.Map; public enum DmpUserRole implements DatabaseEnum { - Owner((short) 0), User((short) 1); + Owner((short) 0), + User((short) 1), + DescriptionContributor((short) 2), + Reviewer((short) 3); private final Short value; diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/persist/DmpPersist.java b/dmp-backend/core/src/main/java/eu/eudat/model/persist/DmpPersist.java index a2ae650ac..9f15c7215 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/model/persist/DmpPersist.java +++ b/dmp-backend/core/src/main/java/eu/eudat/model/persist/DmpPersist.java @@ -56,6 +56,9 @@ public class DmpPersist { public static final String _descriptionTemplates = "descriptionTemplates"; + private List users; + public static final String _users = "users"; + private String hash; public static final String _hash = "hash"; @@ -132,6 +135,14 @@ public class DmpPersist { this.descriptionTemplates = descriptionTemplates; } + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + public String getHash() { return hash; } @@ -212,7 +223,12 @@ public class DmpPersist { .iff(() -> !this.isListNullOrEmpty(item.getDescriptionTemplates())) .on(DmpPersist._descriptionTemplates) .over(item.getDescriptionTemplates()) - .using((itm) -> this.validatorFactory.validator(DmpDescriptionTemplatePersist.DmpDescriptionTemplatePersistValidator.class)) + .using((itm) -> this.validatorFactory.validator(DmpDescriptionTemplatePersist.DmpDescriptionTemplatePersistValidator.class)), + this.navSpec() + .iff(() -> !this.isListNullOrEmpty(item.getUsers())) + .on(DmpPersist._users) + .over(item.getUsers()) + .using((itm) -> this.validatorFactory.validator(DmpUserPersist.DmpUserPersistValidator.class)) ); } } diff --git a/dmp-frontend/src/app/core/common/enum/dmp-blueprint-system-field-type.ts b/dmp-frontend/src/app/core/common/enum/dmp-blueprint-system-field-type.ts index 52d993bca..2e20bf40f 100644 --- a/dmp-frontend/src/app/core/common/enum/dmp-blueprint-system-field-type.ts +++ b/dmp-frontend/src/app/core/common/enum/dmp-blueprint-system-field-type.ts @@ -9,5 +9,6 @@ export enum DmpBlueprintSystemFieldType { // GRANT = 7, // PROJECT = 8, // LICENSE = 9, - AccessRights = 4 + AccessRights = 4, + User = 5 } diff --git a/dmp-frontend/src/app/core/common/enum/dmp-user-role.ts b/dmp-frontend/src/app/core/common/enum/dmp-user-role.ts index 02a8b69c9..5c51e64f4 100644 --- a/dmp-frontend/src/app/core/common/enum/dmp-user-role.ts +++ b/dmp-frontend/src/app/core/common/enum/dmp-user-role.ts @@ -1,4 +1,6 @@ export enum DmpUserRole { Owner = 0, - User = 1 + User = 1, + DescriptionContributor = 2, + Reviewer= 3 } \ No newline at end of file diff --git a/dmp-frontend/src/app/core/model/dmp/dmp.ts b/dmp-frontend/src/app/core/model/dmp/dmp.ts index 24183d23a..92728ba2c 100644 --- a/dmp-frontend/src/app/core/model/dmp/dmp.ts +++ b/dmp-frontend/src/app/core/model/dmp/dmp.ts @@ -78,6 +78,7 @@ export interface DmpPersist extends BaseEntityPersist { blueprint: Guid; accessType: DmpAccessType; descriptionTemplates: DmpDescriptionTemplatePersist[]; + users: DmpUserPersist[]; } export interface DmpPropertiesPersist { diff --git a/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts b/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts index 59078df05..521bbca3f 100644 --- a/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts +++ b/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts @@ -138,6 +138,7 @@ export class EnumUtils { case DmpBlueprintSystemFieldType.Language: return this.language.instant('TYPES.DMP-BLUEPRINT-SYSTEM-FIELD-TYPE.LANGUAGE'); case DmpBlueprintSystemFieldType.Contact: return this.language.instant('TYPES.DMP-BLUEPRINT-SYSTEM-FIELD-TYPE.CONTACT'); case DmpBlueprintSystemFieldType.AccessRights: return this.language.instant('TYPES.DMP-BLUEPRINT-SYSTEM-FIELD-TYPE.ACCESS_RIGHTS'); + case DmpBlueprintSystemFieldType.User: return this.language.instant('TYPES.DMP-BLUEPRINT-SYSTEM-FIELD-TYPE.USER'); } } @@ -231,6 +232,8 @@ export class EnumUtils { switch (role) { case DmpUserRole.Owner: return this.language.instant('TYPES.DMP-USER-ROLE.OWNER'); case DmpUserRole.User: return this.language.instant('TYPES.DMP-USER-ROLE.USER'); + case DmpUserRole.DescriptionContributor: return this.language.instant('TYPES.DMP-USER-ROLE.DESCRIPTION-CONTRIBUTOR'); + case DmpUserRole.Reviewer: return this.language.instant('TYPES.DMP-USER-ROLE.REVIEWER'); } } diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.html b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.html index bb0ba452f..6520648a8 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.html +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.html @@ -215,7 +215,7 @@
-
@@ -242,6 +242,52 @@ {{'GENERAL.VALIDATION.REQUIRED' | translate}} +
+
+
+
+ {{userIndex + 1}} +
+
drag_indicator
+
+
+
+ + {{'DMP-EDITOR.FIELDS1.USER' | translate}} + + {{user.get('user').getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'DMP-EDITOR.FIELDS1.USER-ROLE' | translate}} + + {{enumUtils.toDmpUserRoleString(userRole)}} + + {{user.get('role').getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+
+
+ +
+
+ {{formGroup.get('users').getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} +
+
+
+ +
+
+
diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.ts b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.ts index cef5087c4..5a1e979fb 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.ts +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.ts @@ -44,6 +44,7 @@ import { map, takeUntil } from 'rxjs/operators'; import { DmpEditorModel } from './dmp-editor.model'; import { DmpEditorResolver } from './dmp-editor.resolver'; import { DmpEditorService } from './dmp-editor.service'; +import { DmpUserRole } from '@app/core/common/enum/dmp-user-role'; @Component({ selector: 'app-dmp-editor', @@ -67,6 +68,7 @@ export class DmpEditorComponent extends BaseEditor implemen dmpAccessTypeEnumValues = this.enumUtils.getEnumValues(DmpAccessType); dmpContactTypeEnum = DmpContactType; dmpContactTypeEnumValues = this.enumUtils.getEnumValues(DmpContactType); + dmpUserRoleEnumValues = this.enumUtils.getEnumValues(DmpUserRole); 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)), @@ -353,10 +355,10 @@ export class DmpEditorComponent extends BaseEditor implemen } dropContacts(event: CdkDragDrop) { - const sectionsFormArray = (this.formGroup.get('properties').get('contacts') as FormArray); + const contactsFormArray = (this.formGroup.get('properties').get('contacts') as FormArray); - moveItemInArray(sectionsFormArray.controls, event.previousIndex, event.currentIndex); - sectionsFormArray.updateValueAndValidity(); + moveItemInArray(contactsFormArray.controls, event.previousIndex, event.currentIndex); + contactsFormArray.updateValueAndValidity(); DmpEditorModel.reApplyPropertiesValidators( { @@ -367,6 +369,43 @@ export class DmpEditorComponent extends BaseEditor implemen this.formGroup.get('properties').get('contacts').markAsDirty(); } + // + // + // Dmp Users + // + // + addUser(): void { + const userArray = this.formGroup.get('users') as FormArray; + (this.formGroup.get('users') as FormArray).push(this.editorModel.createChildUser(userArray.length)); + } + + removeUser(userIndex: number): void { + (this.formGroup.get('users') as FormArray).removeAt(userIndex); + + DmpEditorModel.reApplyPropertiesValidators( + { + formGroup: this.formGroup, + validationErrorModel: this.editorModel.validationErrorModel + } + ); + this.formGroup.get('users').markAsDirty(); + } + + dropUsers(event: CdkDragDrop) { + const usersFormArray = (this.formGroup.get('users') as FormArray); + + moveItemInArray(usersFormArray.controls, event.previousIndex, event.currentIndex); + usersFormArray.updateValueAndValidity(); + + DmpEditorModel.reApplyPropertiesValidators( + { + formGroup: this.formGroup, + validationErrorModel: this.editorModel.validationErrorModel + } + ); + this.formGroup.get('users').markAsDirty(); + } + // // // Descriptions diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.model.ts b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.model.ts index 6c399c5d3..bb5e4813f 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.model.ts +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.model.ts @@ -2,9 +2,10 @@ import { FormArray, FormControl, UntypedFormArray, UntypedFormBuilder, UntypedFo import { DmpAccessType } from "@app/core/common/enum/dmp-access-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 { IsActive } from "@app/core/common/enum/is-active.enum"; import { DmpBlueprint } from "@app/core/model/dmp-blueprint/dmp-blueprint"; -import { Dmp, DmpBlueprintValue, DmpBlueprintValuePersist, DmpContact, DmpContactPersist, DmpDescriptionTemplate, DmpDescriptionTemplatePersist, DmpPersist, DmpProperties, DmpPropertiesPersist, DmpReferenceDataPersist, DmpReferencePersist } from "@app/core/model/dmp/dmp"; +import { Dmp, DmpBlueprintValue, DmpBlueprintValuePersist, DmpContact, DmpContactPersist, DmpDescriptionTemplate, DmpDescriptionTemplatePersist, DmpPersist, DmpProperties, DmpPropertiesPersist, DmpReferenceDataPersist, DmpReferencePersist, DmpUser, DmpUserPersist } from "@app/core/model/dmp/dmp"; import { DmpReference } from "@app/core/model/dmp/dmp-reference"; import { ReferencePersist } from "@app/core/model/reference/reference"; import { BaseEditorModel } from "@common/base/base-form-editor-model"; @@ -22,6 +23,7 @@ export class DmpEditorModel extends BaseEditorModel implements DmpPersist { blueprint: Guid; accessType: DmpAccessType; descriptionTemplates: DmpDescriptionTemplateEditorModel[] = []; + users: DmpUserEditorModel[] = []; permissions: string[]; public validationErrorModel: ValidationErrorModel = new ValidationErrorModel(); @@ -39,6 +41,7 @@ export class DmpEditorModel extends BaseEditorModel implements DmpPersist { this.language = item.language; this.blueprint = item.blueprint?.id; this.accessType = item.accessType; + if (item?.dmpUsers) { item.dmpUsers.map(x => this.users.push(new DmpUserEditorModel(this.validationErrorModel).fromModel(x))); } item.blueprint.definition.sections.forEach(section => { if (section.hasTemplates) { @@ -83,6 +86,13 @@ export class DmpEditorModel extends BaseEditorModel implements DmpPersist { properties: this.properties.buildForm({ rootPath: `properties.` }), + users: this.formBuilder.array( + (this.users ?? []).map( + (item, index) => item.buildForm({ + rootPath: `users[${index}].` + }) + ), context.getValidation('users').validators + ), description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators], language: [{ value: this.language, disabled: disabled }, context.getValidation('language').validators], blueprint: [{ value: this.blueprint, disabled: disabled }, context.getValidation('blueprint').validators], @@ -116,6 +126,7 @@ export class DmpEditorModel extends BaseEditorModel implements DmpPersist { baseValidationArray.push({ key: 'blueprint', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'blueprint')] }); baseValidationArray.push({ key: 'accessType', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'accessType')] }); baseValidationArray.push({ key: 'descriptionTemplates', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'descriptionTemplates')] }); + baseValidationArray.push({ key: 'users', validators: [BackendErrorValidator(this.validationErrorModel, `users`)] }); baseValidationArray.push({ key: 'hash', validators: [] }); baseContext.validation = baseValidationArray; @@ -127,6 +138,11 @@ export class DmpEditorModel extends BaseEditorModel implements DmpPersist { return contact.buildForm({ rootPath: 'properties.contacts[' + index + '].' }); } + createChildUser(index: number): UntypedFormGroup { + const user: DmpUserEditorModel = new DmpUserEditorModel(this.validationErrorModel); + return user.buildForm({ rootPath: 'users[' + index + '].' }); + } + static reApplyPropertiesValidators(params: { formGroup: UntypedFormGroup, validationErrorModel: ValidationErrorModel, @@ -140,10 +156,21 @@ export class DmpEditorModel extends BaseEditorModel implements DmpPersist { validationErrorModel: validationErrorModel }); - (formGroup.get('descriptionTemplates') as FormArray).controls?.forEach( - (control, index) => DmpDescriptionTemplateEditorModel.reapplyValidators({ + const descriptionTemplates = formGroup?.get('descriptionTemplates') as UntypedFormGroup; + const keys = Object.keys(descriptionTemplates.value as Object); + keys.forEach((key) => { + const control = descriptionTemplates?.get(key); + DmpBlueprintValueEditorModel.reapplyValidators({ formGroup: control as UntypedFormGroup, - rootPath: `descriptionTemplates[${index}].`, + rootPath: `descriptionTemplates[${key}].`, + validationErrorModel: validationErrorModel + }) + }); + + (formGroup.get('users') as FormArray).controls?.forEach( + (control, index) => DmpUserEditorModel.reapplyValidators({ + formGroup: control as UntypedFormGroup, + rootPath: `users[${index}].`, validationErrorModel: validationErrorModel }) ); @@ -415,6 +442,77 @@ export class DmpContactEditorModel implements DmpContactPersist { } } +export class DmpUserEditorModel implements DmpUserPersist { + user: Guid; + role: DmpUserRole; + + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor( + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() + ) { } + + fromModel(item: DmpUser): DmpUserEditorModel { + if(item?.user?.id) this.user = item.user.id; + this.role = item.role; + + return this; + } + + buildForm(params?: { + context?: ValidationContext, + disabled?: boolean, + rootPath?: string + }): UntypedFormGroup { + let { context = null, disabled = false, rootPath } = params ?? {} + if (context == null) { + context = DmpUserEditorModel.createValidationContext({ + validationErrorModel: this.validationErrorModel, + rootPath + }); + } + + return this.formBuilder.group({ + user: [{ value: this.user, disabled: disabled }, context.getValidation('user').validators], + role: [{ value: this.role, disabled: disabled }, context.getValidation('role').validators], + }); + } + + static createValidationContext(params: { + rootPath?: string, + validationErrorModel: ValidationErrorModel + }): ValidationContext { + const { rootPath = '', validationErrorModel } = params; + + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'user', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}user`)] }); + baseValidationArray.push({ key: 'role', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}role`)] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } + + static reapplyValidators(params: { + formGroup: UntypedFormGroup, + validationErrorModel: ValidationErrorModel, + rootPath: string + }): void { + + const { formGroup, rootPath, validationErrorModel } = params; + const context = DmpUserEditorModel.createValidationContext({ + rootPath, + validationErrorModel + }); + + ['user', 'role'].forEach(keyField => { + const control = formGroup?.get(keyField); + control?.clearValidators(); + control?.addValidators(context.getValidation(keyField).validators); + }) + } +} + export class DmpReferenceEditorModel implements DmpReferencePersist { id: Guid; reference: ReferencePersist; diff --git a/dmp-frontend/src/assets/i18n/en.json b/dmp-frontend/src/assets/i18n/en.json index 182266241..b43665617 100644 --- a/dmp-frontend/src/assets/i18n/en.json +++ b/dmp-frontend/src/assets/i18n/en.json @@ -1745,7 +1745,8 @@ "USER": "User", "FIRST-NAME": "First Name", "LAST-NAME": "Last Name", - "EMAIL": "Email" + "EMAIL": "Email", + "USER-ROLE": "Role" }, "ACTIONS1": { "GO-TO-GRANT": "Go To DMP Grant", @@ -1761,7 +1762,9 @@ "PERMISSION": "You have no permission to edit this DMP", "INSERT-MANUALLY": "Insert it manually", "CREATE-DATASET": "Create new one", - "DISABLED-EXPORT": "Please save your changes to export this DMP" + "DISABLED-EXPORT": "Please save your changes to export this DMP", + "REMOVE-CONTACT": "Remove Contact", + "REMOVE-USER": "Remove User" }, "PLACEHOLDER": { "DESCRIPTION": "Fill with description", @@ -2163,7 +2166,9 @@ }, "DMP-USER-ROLE": { "OWNER": "Owner", - "USER": "User" + "USER": "User", + "DESCRIPTION-CONTRIBUTOR": "Description Contributor", + "REVIEWER": "Reviewer" }, "EXTERNAL-DATASET-TYPE": { "SOURCE": "Source", @@ -2287,7 +2292,8 @@ "DESCRIPTION": "Description", "LANGUAGE": "Language", "CONTACT": "Contact", - "ACCESS_RIGHTS": "Access" + "ACCESS_RIGHTS": "Access", + "USER": "User" }, "DMP-BLUEPRINT-EXTRA-FIELD-DATA-TYPE": { "DATE": "Date",