create common dmpUser component for dmp ui and dmp overview popup invite

This commit is contained in:
amentis 2024-02-22 13:57:42 +02:00
parent add1bb582f
commit a387349aad
19 changed files with 251 additions and 293 deletions

View File

@ -1,6 +1,5 @@
package eu.eudat.model.persist;
import eu.eudat.commons.enums.DmpUserRole;
import eu.eudat.commons.validation.BaseValidator;
import gr.cite.tools.validation.ValidatorFactory;
import gr.cite.tools.validation.specification.Specification;
@ -17,30 +16,18 @@ import java.util.List;
public class DmpUserInvitePersist {
private List<DmpUserInviteTypePersist> users;
private List<DmpUserPersist> users;
public static final String _users = "users";
private DmpUserRole role;
public static final String _role = "role";
public List<DmpUserInviteTypePersist> getUsers() {
public List<DmpUserPersist> getUsers() {
return users;
}
public void setUsers(List<DmpUserInviteTypePersist> users) {
public void setUsers(List<DmpUserPersist> users) {
this.users = users;
}
public DmpUserRole getRole() {
return role;
}
public void setRole(DmpUserRole role) {
this.role = role;
}
@Component(DmpUserInvitePersistValidator.ValidatorName)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static class DmpUserInvitePersistValidator extends BaseValidator<DmpUserInvitePersist> {
@ -65,10 +52,6 @@ public class DmpUserInvitePersist {
@Override
protected List<Specification> specifications(DmpUserInvitePersist item) {
return Arrays.asList(
this.spec()
.must(() -> !this.isNull(item.getRole()))
.failOn(DmpUserInvitePersist._role).failWith(messageSource.getMessage("Validation_Required", new Object[]{DmpUserInvitePersist._role}, LocaleContextHolder.getLocale())),
this.spec()
.must(() -> !this.isListNullOrEmpty(item.getUsers()))
.failOn(DmpUserInvitePersist._users).failWith(messageSource.getMessage("Validation_Required", new Object[]{DmpUserInvitePersist._users}, LocaleContextHolder.getLocale())),
@ -76,7 +59,7 @@ public class DmpUserInvitePersist {
.iff(() -> !this.isListNullOrEmpty(item.getUsers()))
.on(DmpUserInvitePersist._users)
.over(item.getUsers())
.using((itm) -> this.validatorFactory.validator(DmpUserInviteTypePersist.DmpUserInviteTypePersistValidator.class))
.using((itm) -> this.validatorFactory.validator(DmpUserPersist.DmpUserPersistValidator.class))
);
}
}

View File

@ -23,7 +23,7 @@ import java.util.UUID;
public interface DmpService {
Dmp persist(DmpPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, IOException;
Dmp persist(DmpPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException, IOException;
void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException, IOException;
@ -36,7 +36,7 @@ public interface DmpService {
ResponseEntity<byte[]> export(UUID id, String exportType) throws InvalidApplicationException, IOException;
void inviteUsers(UUID id, DmpUserInvitePersist model) throws InvalidApplicationException, JAXBException, IOException;
void inviteUsers(UUID id, List<DmpUserPersist> users) throws InvalidApplicationException, JAXBException, IOException;
void dmpInvitationAccept(String token) throws InvalidApplicationException, IOException;

View File

@ -166,7 +166,7 @@ public class DmpServiceImpl implements DmpService {
this.dmpTouchedIntegrationEventHandler = dmpTouchedIntegrationEventHandler;
}
public Dmp persist(DmpPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, IOException {
public Dmp persist(DmpPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException, IOException {
this.authorizationService.authorizeForce(Permission.EditDmp);
DmpEntity data = this.patchAndSave(model);
@ -188,6 +188,10 @@ public class DmpServiceImpl implements DmpService {
this.elasticService.persistDmp(data);
if (!this.conventionService.isListNullOrEmpty(model.getUsers())){
this.inviteUsers(data.getId(), model.getUsers());
}
return this.builderFactory.builder(DmpBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).build(BaseFieldSet.build(fields, Dmp._id, Dmp._hash), data);
}
@ -777,7 +781,7 @@ public class DmpServiceImpl implements DmpService {
}
// invites
public void inviteUsers(UUID id, DmpUserInvitePersist model) throws InvalidApplicationException, JAXBException, IOException {
public void inviteUsers(UUID id, List<DmpUserPersist> users) throws InvalidApplicationException, JAXBException, IOException {
this.authorizationService.authorizeForce(Permission.InviteDmpUsers);
@ -786,15 +790,22 @@ public class DmpServiceImpl implements DmpService {
throw new InvalidApplicationException("Dmp does not exist!");
}
for (DmpUserInviteTypePersist type :model.getUsers()) {
if (type.getUserId() != null){
DmpUserPersist dmpUserPersist = new DmpUserPersist();
dmpUserPersist.setUser(type.getUserId());
dmpUserPersist.setRole(model.getRole());
this.assignUsers(id, List.of(dmpUserPersist), null);
this.sendDmpInvitationExistingUser(type.getUserId(), dmp, model.getRole());
} else if (type.getEmail() != null) {
this.sendDmpInvitationExternalUser(type.getEmail(),dmp, model.getRole());
for (DmpUserPersist user :users) {
UUID userId = null;
if (user.getUser() != null){
userId = user.getUser();
} else if (user.getEmail() != null) {
UserContactInfoEntity contactInfoEntity = this.queryFactory.query(UserContactInfoQuery.class).values(user.getEmail()).types(ContactInfoType.Email).first();
if (contactInfoEntity != null){
userId = contactInfoEntity.getUserId();
}
}
if (userId != null){
user.setUser(userId);
this.assignUsers(id, List.of(user), null);
this.sendDmpInvitationExistingUser(user.getUser(), dmp, user.getRole());
}else if (user.getEmail() != null) {
this.sendDmpInvitationExternalUser(user.getEmail(),dmp, user.getRole());
}
}

View File

@ -126,7 +126,7 @@ public class DmpController {
@PostMapping("persist")
@Transactional
@ValidationFilterAnnotation(validator = DmpPersist.DmpPersistValidator.ValidatorName, argumentName = "model")
public Dmp Persist(@RequestBody DmpPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException {
public Dmp Persist(@RequestBody DmpPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException, JAXBException {
logger.debug(new MapLogEntry("persisting" + Dmp.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
Dmp persisted = this.dmpService.persist(model, fieldSet);
@ -228,7 +228,7 @@ public class DmpController {
public boolean inviteUsers(@PathVariable("id") UUID id, @RequestBody DmpUserInvitePersist model) throws InvalidApplicationException, JAXBException, IOException {
logger.debug(new MapLogEntry("inviting users to dmp").And("model", model));
this.dmpService.inviteUsers(id, model);
this.dmpService.inviteUsers(id, model.getUsers());
this.auditService.track(AuditableAction.Dmp_Invite_Users, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", model)

View File

@ -143,12 +143,5 @@ export interface DmpUserRemovePersist {
}
export interface DmpUserInvitePersist {
users: DmpUserInviteTypePersist[];
role: DmpUserRole;
users: DmpUserPersist[];
}
export interface DmpUserInviteTypePersist {
userId: Guid;
email: string;
}

View File

@ -243,76 +243,8 @@
</mat-form-field>
</div>
<div *ngIf="field.systemFieldType == dmpBlueprintSystemFieldTypeEnum.User">
<div cdkDropList class="col-12" (cdkDropListDropped)="dropUsers($event)">
<div *ngFor="let user of formGroup.get('users').controls; let userIndex=index;" cdkDrag class="row align-items-center" [cdkDragDisabled]="formGroup.disabled">
<div class="col-auto">
<span style="font-size: 15px;">{{userIndex + 1}}</span>
</div>
<div class="col-auto d-flex"><mat-icon [ngClass]="{'drag-handle-disabled': formGroup.disabled}" cdkDragHandle class="drag-handle">drag_indicator</mat-icon></div>
<div class="col-auto">
<mat-button-toggle-group name="fontStyle" aria-label="Font Style" [formControl]="user.get('userType')">
<div *ngFor="let userType of dmpUserTypeEnumValues">
<mat-button-toggle class="lang-button" [value]="userType">{{enumUtils.toDmpUserTypeString(userType)}}</mat-button-toggle>
</div>
</mat-button-toggle-group>
</div>
<div class="col pt-4">
<div class="row">
<div class="col" *ngIf="user.get('userType').value == dmpUserTypeEnum.Internal">
<mat-form-field class="w-100">
<mat-label>{{'DMP-EDITOR.FIELDS1.USER' | translate}}*</mat-label>
<app-single-auto-complete [formControl]="user.get('user')" [hidePlaceholder]="true" [configuration]="userService.singleAutocompleteConfiguration"></app-single-auto-complete>
<mat-error *ngIf="user.get('user').hasError('backendError')">{{user.get('user').getError('backendError').message}}</mat-error>
<mat-error *ngIf="user.get('user').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col" *ngIf="user.get('userType').value == dmpUserTypeEnum.External">
<mat-form-field class="w-100">
<mat-label>{{'DMP-EDITOR.FIELDS1.EMAIL' | translate}}*</mat-label>
<input matInput type="text" name="email" [formControl]="user.get('email')">
<mat-error *ngIf="user.get('email').hasError('backendError')">{{user.get('email').getError('backendError').message}}</mat-error>
<mat-error *ngIf="user.get('email').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col">
<mat-form-field class="w-100">
<mat-label>{{'DMP-EDITOR.FIELDS1.USER-ROLE' | translate}}</mat-label>
<mat-select [formControl]="user.get('role')">
<mat-option *ngFor="let userRole of dmpUserRoleEnumValues" [value]="userRole">{{enumUtils.toDmpUserRoleString(userRole)}}</mat-option>
</mat-select>
<mat-error *ngIf="user.get('role').hasError('backendError')">{{user.get('role').getError('backendError').message}}</mat-error>
<mat-error *ngIf="user.get('role').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col">
<mat-form-field class="w-100">
<mat-label>{{'DMP-EDITOR.FIELDS1.SECTION' | translate}}</mat-label>
<mat-select [formControl]="user.get('sectionId')">
<mat-option *ngFor="let section of selectedBlueprint.definition.sections" [value]="section.id">
{{ section.label }}
</mat-option>
</mat-select>
<mat-error *ngIf="user.get('sectionId').hasError('backendError')">{{user.get('sectionId').getError('backendError').message}}</mat-error>
<mat-error *ngIf="user.get('sectionId').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' |translate}}</mat-error>
</mat-form-field>
</div>
</div>
</div>
<div class="col-auto">
<button mat-icon-button class="action-list-icon" matTooltip="{{'DMP-EDITOR.ACTIONS1.REMOVE-USER' | translate}}" (click)="removeUser(userIndex)" [disabled]="formGroup.disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
<mat-error *ngIf="formGroup.get('users').hasError('backendError')">{{formGroup.get('users').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('users').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</div>
<div class="row">
<div class="col">
<button mat-icon-button (click)="addUser()" [disabled]="formGroup.disabled">
<mat-icon>add</mat-icon>
</button>
</div>
<div>
<app-user-field-component [form]="formGroup" [validationErrorModel]="editorModel.validationErrorModel" [sections]="selectedBlueprint.definition.sections"></app-user-field-component>
</div>
</div>
</div>

View File

@ -196,7 +196,7 @@ export class DmpEditorComponent extends BaseEditor<DmpEditorModel, Dmp> implemen
}
buildForm() {
this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.authService.hasPermission(AppPermission.EditDescription));
this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.authService.hasPermission(AppPermission.EditDmp));
if (this.editorModel.status == DmpStatus.Finalized || this.isDeleted) {
this.formGroup.disable();
@ -372,42 +372,6 @@ export class DmpEditorComponent extends BaseEditor<DmpEditorModel, Dmp> 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<string[]>) {
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();
}
//
//

View File

@ -139,10 +139,6 @@ 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,
@ -500,8 +496,8 @@ export class DmpUserEditorModel implements DmpUserPersist {
return this.formBuilder.group({
user: [{ value: this.user, disabled: disabled }, context.getValidation('user').validators],
role: [{ value: this.role, disabled: disabled }, context.getValidation('role').validators],
email: [{ value: this.role, disabled: disabled }, context.getValidation('email').validators],
sectionId: [{ value: this.role, disabled: disabled }, context.getValidation('sectionId').validators],
email: [{ value: this.email, disabled: disabled }, context.getValidation('email').validators],
sectionId: [{ value: this.sectionId, disabled: disabled }, context.getValidation('sectionId').validators],
userType: [{ value: this.userType, disabled: disabled }, context.getValidation('userType').validators],
});
}

View File

@ -9,6 +9,7 @@ import { DmpEditorRoutingModule } from './dmp-editor.routing';
import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.module';
import { ReferenceFieldModule } from '@app/ui/reference/reference-field/reference-field.module';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { UserFieldModule } from '../user-field/user-field.module';
@NgModule({
imports: [
@ -21,6 +22,7 @@ import { DragDropModule } from '@angular/cdk/drag-drop';
AutoCompleteModule,
ReferenceFieldModule,
DragDropModule,
UserFieldModule
],
declarations: [
DmpEditorComponent,

View File

@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Description } from '@app/core/model/description/description';
import { DescriptionTemplatesInSection, DmpBlueprint, DmpBlueprintDefinition, DmpBlueprintDefinitionSection, ExtraFieldInSection, FieldInSection, ReferenceTypeFieldInSection, SystemFieldInSection } from '@app/core/model/dmp-blueprint/dmp-blueprint';
import { Dmp, DmpBlueprintValue, DmpContact, DmpDescriptionTemplate, DmpProperties } from '@app/core/model/dmp/dmp';
import { Dmp, DmpBlueprintValue, DmpContact, DmpDescriptionTemplate, DmpProperties, DmpUser } from '@app/core/model/dmp/dmp';
import { DmpReference, DmpReferenceData } from '@app/core/model/dmp/dmp-reference';
import { ReferenceType } from '@app/core/model/reference-type/reference-type';
import { Reference } from '@app/core/model/reference/reference';
@ -54,10 +54,12 @@ export class DmpEditorResolver extends BaseEditorResolver {
[nameof<Dmp>(x => x.descriptions), nameof<Description>(x => x.dmpDescriptionTemplate), nameof<DmpDescriptionTemplate>(x => x.id)].join('.'),
[nameof<Dmp>(x => x.descriptions), nameof<Description>(x => x.dmpDescriptionTemplate), nameof<DmpDescriptionTemplate>(x => x.sectionId)].join('.'),
// [nameof<Dmp>(x => x.dmpUsers), nameof<DmpUser>(x => x.id)].join('.'),
// [nameof<Dmp>(x => x.dmpUsers), nameof<DmpUser>(x => x.user.id)].join('.'),
// [nameof<Dmp>(x => x.dmpUsers), nameof<DmpUser>(x => x.user.name)].join('.'),
// [nameof<Dmp>(x => x.dmpUsers), nameof<DmpUser>(x => x.role)].join('.'),
[nameof<Dmp>(x => x.dmpUsers), nameof<DmpUser>(x => x.id)].join('.'),
[nameof<Dmp>(x => x.dmpUsers), nameof<DmpUser>(x => x.user.id)].join('.'),
[nameof<Dmp>(x => x.dmpUsers), nameof<DmpUser>(x => x.user.name)].join('.'),
[nameof<Dmp>(x => x.dmpUsers), nameof<DmpUser>(x => x.role)].join('.'),
[nameof<Dmp>(x => x.dmpUsers), nameof<DmpUser>(x => x.sectionId)].join('.'),
[nameof<Dmp>(x => x.dmpReferences), nameof<DmpReference>(x => x.id)].join('.'),
[nameof<Dmp>(x => x.dmpReferences), nameof<DmpReference>(x => x.isActive)].join('.'),
[nameof<Dmp>(x => x.dmpReferences), nameof<DmpReference>(x => x.data), nameof<DmpReferenceData>(x => x.blueprintFieldId)].join('.'),

View File

@ -8,25 +8,12 @@
</div>
</div>
<div mat-dialog-content class="row content">
<mat-form-field class="col pt-0 pb-2 mb-4 search">
<app-multiple-auto-complete [formControl]="formGroup.get('users')" placeholder="{{'DMP-USER-INVITATION-DIALOG.FIELDS.USERS-PLACEHOLDER' | translate}}" [configuration]="usersAutoCompleteConfiguration" [showNoResultsLabel]="false" [separatorKeysCodes]="separatorKeysCodes" [minLength]="3">
</app-multiple-auto-complete>
</mat-form-field>
<p class="d-flex m-0 hint">
<mat-icon class="align-self-center mr-1">info_outlined</mat-icon>
{{'DMP-USER-INVITATION-DIALOG.FIELDS.USERS-HINT' | translate}}
</p>
<div class="col-12 d-flex justify-content-end align-items-center row m-0 pt-1 pb-1">
<div class="col">
<mat-form-field class="select-role">
<mat-select [formControl]="formGroup.get('role')">
<mat-option [value]="dmpUserRoleEnum.Owner">{{enumUtils.toDmpUserRoleString(dmpUserRoleEnum.Owner)}}</mat-option>
<mat-option [value]="dmpUserRoleEnum.User">{{enumUtils.toDmpUserRoleString(dmpUserRoleEnum.User)}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="col mt-2">
<button mat-raised-button *ngIf="hasValue()" (click)="send()" type="button" class="invite-btn">{{'DMP-USER-INVITATION-DIALOG.ACTIONS.INVITE' | translate}}</button>
</div>
<div>
<app-user-field-component [form]="formGroup" [validationErrorModel]="editorModel.validationErrorModel" [sections]="selectedBlueprint.definition.sections"></app-user-field-component>
</div>
<div class="col mt-2">
<button mat-raised-button *ngIf="hasValue()" (click)="send()" type="button" class="invite-btn">{{'DMP-USER-INVITATION-DIALOG.ACTIONS.INVITE' | translate}}</button>
<mat-error *ngIf="formGroup.get('users').hasError('backendError')">{{formGroup.get('users').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('users').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</div>
</div>

View File

@ -5,23 +5,18 @@ import { UntypedFormGroup } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { DmpUserRole } from '@app/core/common/enum/dmp-user-role';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { DmpUserInvitePersist, DmpUserInviteTypePersist } from '@app/core/model/dmp/dmp';
import { DmpAssociatedUser, User } from '@app/core/model/user/user';
import { UserLookup } from '@app/core/query/user.lookup';
import { DmpUserPersist } from '@app/core/model/dmp/dmp';
import { DmpService } from '@app/core/services/dmp/dmp.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 { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration';
import { BaseComponent } from '@common/base/base.component';
import { FilterService } from '@common/modules/text-filter/filter-service';
import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
import { DmpInvitationDialogEditorModel } from './dmp-invitation-dialog.editor.model';
import { DmpEditorModel } from '../../dmp-editor-blueprint/dmp-editor.model';
import { takeUntil } from 'rxjs/operators';
import { DmpBlueprint } from '@app/core/model/dmp-blueprint/dmp-blueprint';
@Component({
selector: 'app-invitation-dialog-component',
@ -31,31 +26,12 @@ import { DmpInvitationDialogEditorModel } from './dmp-invitation-dialog.editor.m
export class DmpInvitationDialogComponent extends BaseComponent implements OnInit {
dmpId: Guid;
editorModel: DmpInvitationDialogEditorModel;
editorModel: DmpEditorModel;
formGroup: UntypedFormGroup;
dmpUserRoleEnum = DmpUserRole;
selectedBlueprint: DmpBlueprint;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
usersAutoCompleteConfiguration: MultipleAutoCompleteConfiguration = {
filterFn: this.filterUsers.bind(this),
initialItems: (excludedItems: any[]) => this.filterUsers('').pipe(map(result => result.filter(resultItem => (excludedItems || []).map(x => x.id).indexOf(resultItem.id) === -1))),
displayFn: (item) => typeof (item) === 'string' ? item : item.name,
titleFn: (item) => typeof (item) === 'string' ? item : item.name,
subtitleFn: (item) => item.email,
valueAssign: (item) => {
const result = typeof (item) === 'string' ? item : item.id;
return result;
},
autoSelectFirstOptionOnBlur: true,
appendClassToItem: [{
class: 'invalid-email', applyFunc: (item) => {
const val = typeof (item) === 'string' ? item : item.email;
const regexp = new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
return !regexp.test(val);
}
}]
};
constructor(
public enumUtils: EnumUtils,
public route: ActivatedRoute,
@ -70,42 +46,19 @@ export class DmpInvitationDialogComponent extends BaseComponent implements OnIni
) {
super();
this.dmpId = data.dmpId;
this.editorModel = data ? new DmpEditorModel().fromModel(data) : new DmpEditorModel();
this.selectedBlueprint = data?.blueprint;
}
ngOnInit() {
this.editorModel = new DmpInvitationDialogEditorModel();
this.formGroup = this.editorModel.buildForm();
}
send() {
if (!this.formGroup.valid) { return; }
let inviteTypeValues: DmpUserInviteTypePersist[] = [];
this.formGroup.get('users').value.forEach(x => {
let inviteType: DmpUserInviteTypePersist;
if (!(/^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$/).test(x)) {
inviteType = {
userId: null,
email: x
};
}else{
inviteType = {
userId: Guid.parse(x),
email: null
};
}
inviteTypeValues.push(inviteType)
})
const value: DmpUserInvitePersist = {
role: this.formGroup.get('role').value,
users: inviteTypeValues
}
// invitationObject.users.push(...(<any[]>this.formGroup.get('users').value).filter(user => typeof (user) === 'string').map(email => ({ email: email, name: email })));
// invitationObject.users.push(...(<any[]>this.formGroup.get('users').value).filter(user => typeof (user) !== 'string'));
// this.emails.forEach(email => {
// invitationObject.users.push({ email: email, name: email });
// });
if (!this.formGroup.get("users").valid) { return; }
const userFormData = this.formGroup.get("users").value as DmpUserPersist[];
this.dmpService.inviteUsers(this.dmpId, value)
this.dmpService.inviteUsers(this.dmpId, {users: userFormData})
.pipe(takeUntil(this._destroyed))
.subscribe(
complete => {
@ -120,23 +73,6 @@ export class DmpInvitationDialogComponent extends BaseComponent implements OnIni
this.dialogRef.close();
}
filterUsers(like: string): Observable<DmpAssociatedUser[]> {
const lookup: UserLookup = new UserLookup();
lookup.page = { size: 100, offset: 0 };
// if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; }
// if (ids && ids.length > 0) { lookup.ids = ids; }
lookup.isActive = [IsActive.Active];
lookup.project = {
fields: [
nameof<User>(x => x.id),
nameof<User>(x => x.name)
]
};
lookup.order = { items: [nameof<User>(x => x.name)] };
if (like) { lookup.like = this.filterService.transformLike(like); }
return this.userService.queryDmpAssociated(lookup).pipe(takeUntil(this._destroyed), map(x => x.items));
}
hasValue(): boolean {
return this.formGroup.get('users') && this.formGroup.get('users').value && this.formGroup.get('users').value.length > 0;
}

View File

@ -1,34 +0,0 @@
import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { DmpUserRole } from "@app/core/common/enum/dmp-user-role";
import { DmpUserInvitePersist, DmpUserInviteTypePersist } from "@app/core/model/dmp/dmp";
import { BackendErrorValidator } from '@common/forms/validation/custom-validator';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
import { Validation, ValidationContext } from '@common/forms/validation/validation-context';
export class DmpInvitationDialogEditorModel implements DmpUserInvitePersist {
users: DmpUserInviteTypePersist[];
role: DmpUserRole = DmpUserRole.User;
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor() { }
buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup {
if (context == null) { context = this.createValidationContext(); }
return this.formBuilder.group({
users: [{ value: this.users, disabled: disabled }, context.getValidation('users').validators],
role: [{ value: this.role, disabled: disabled }, context.getValidation('role').validators],
});
}
createValidationContext(): ValidationContext {
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'users', validators: [BackendErrorValidator(this.validationErrorModel, 'users')] });
baseValidationArray.push({ key: 'role', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'role')] });
baseContext.validation = baseValidationArray;
return baseContext;
}
}

View File

@ -4,9 +4,10 @@ import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.mod
import { RichTextEditorModule } from "@app/library/rich-text-editor/rich-text-editor.module";
import { CommonUiModule } from '@common/ui/common-ui.module';
import { DmpInvitationDialogComponent } from './dmp-invitation-dialog.component';
import { UserFieldModule } from '../../user-field/user-field.module';
@NgModule({
imports: [CommonUiModule, FormsModule, ReactiveFormsModule, AutoCompleteModule, RichTextEditorModule],
imports: [CommonUiModule, FormsModule, ReactiveFormsModule, AutoCompleteModule, RichTextEditorModule, UserFieldModule],
declarations: [DmpInvitationDialogComponent],
exports: [DmpInvitationDialogComponent]
})

View File

@ -38,6 +38,7 @@ import { takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
import { DmpInvitationDialogComponent } from '../invitation/dialog/dmp-invitation-dialog.component';
import { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.service';
import { DmpBlueprint, DmpBlueprintDefinition, DmpBlueprintDefinitionSection } from '@app/core/model/dmp-blueprint/dmp-blueprint';
@Component({
selector: 'app-dmp-overview',
@ -47,6 +48,7 @@ import { ReferenceTypeService } from '@app/core/services/reference-type/referenc
export class DmpOverviewComponent extends BaseComponent implements OnInit {
dmp: Dmp;
selectedBlueprint: DmpBlueprint;
researchers: DmpReference[] = [];
isNew = true;
isFinalized = false;
@ -108,6 +110,7 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
.pipe(takeUntil(this._destroyed))
.subscribe(data => {
this.dmp = data;
this.selectedBlueprint= data.blueprint;
this.researchers = this.referenceService.getReferencesForTypes(this.dmp?.dmpReferences, [this.referenceTypeService.getResearcherReferenceType()]);
if (!this.hasDoi()) {
this.selectedModel = this.dmp.entityDois[0];
@ -566,7 +569,8 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
restoreFocus: false,
data: {
dmpId: rowId,
dmpName: rowName
dmpName: rowName,
blueprint: this.selectedBlueprint
}
});
}
@ -738,6 +742,11 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
[nameof<Dmp>(x => x.dmpReferences), nameof<DmpReference>(x => x.reference), nameof<Reference>(x => x.type)].join('.'),
[nameof<Dmp>(x => x.dmpReferences), nameof<DmpReference>(x => x.reference), nameof<Reference>(x => x.source)].join('.'),
[nameof<Dmp>(x => x.dmpReferences), nameof<DmpReference>(x => x.reference), nameof<Reference>(x => x.reference)].join('.'),
[nameof<Dmp>(x => x.blueprint), nameof<DmpBlueprint>(x => x.id)].join('.'),
[nameof<Dmp>(x => x.blueprint), nameof<DmpBlueprint>(x => x.definition)].join('.'),
[nameof<Dmp>(x => x.blueprint), nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.id)].join('.'),
[nameof<Dmp>(x => x.blueprint), nameof<DmpBlueprint>(x => x.definition), nameof<DmpBlueprintDefinition>(x => x.sections), nameof<DmpBlueprintDefinitionSection>(x => x.label)].join('.'),
]
}
}

View File

@ -0,0 +1,71 @@
<div cdkDropList class="col-12" (cdkDropListDropped)="dropUsers($event)">
<div *ngFor="let user of form.get('users').controls; let userIndex=index;" cdkDrag class="row align-items-center" [cdkDragDisabled]="form.disabled">
<div class="col-auto">
<span style="font-size: 15px;">{{userIndex + 1}}</span>
</div>
<div class="col-auto d-flex"><mat-icon [ngClass]="{'drag-handle-disabled': form.disabled}" cdkDragHandle class="drag-handle">drag_indicator</mat-icon></div>
<div class="col-auto">
<mat-button-toggle-group name="fontStyle" aria-label="Font Style" [formControl]="user.get('userType')">
<div *ngFor="let userType of dmpUserTypeEnumValues">
<mat-button-toggle class="lang-button" [value]="userType">{{enumUtils.toDmpUserTypeString(userType)}}</mat-button-toggle>
</div>
</mat-button-toggle-group>
</div>
<div class="col pt-4">
<div class="row">
<div class="col" *ngIf="user.get('userType').value == dmpUserTypeEnum.Internal">
<mat-form-field class="w-100">
<mat-label>{{'DMP-EDITOR.FIELDS1.USER' | translate}}*</mat-label>
<app-single-auto-complete [formControl]="user.get('user')" [hidePlaceholder]="true" [configuration]="userService.singleAutoCompleteDmpAssociatedUserConfiguration"></app-single-auto-complete>
<mat-error *ngIf="user.get('user').hasError('backendError')">{{user.get('user').getError('backendError').message}}</mat-error>
<mat-error *ngIf="user.get('user').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col" *ngIf="user.get('userType').value == dmpUserTypeEnum.External">
<mat-form-field class="w-100">
<mat-label>{{'DMP-EDITOR.FIELDS1.EMAIL' | translate}}*</mat-label>
<input matInput type="text" name="email" [formControl]="user.get('email')">
<mat-error *ngIf="user.get('email').hasError('backendError')">{{user.get('email').getError('backendError').message}}</mat-error>
<mat-error *ngIf="user.get('email').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col">
<mat-form-field class="w-100">
<mat-label>{{'DMP-EDITOR.FIELDS1.USER-ROLE' | translate}}</mat-label>
<mat-select [formControl]="user.get('role')">
<mat-option *ngFor="let userRole of dmpUserRoleEnumValues" [value]="userRole">{{enumUtils.toDmpUserRoleString(userRole)}}</mat-option>
</mat-select>
<mat-error *ngIf="user.get('role').hasError('backendError')">{{user.get('role').getError('backendError').message}}</mat-error>
<mat-error *ngIf="user.get('role').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col" *ngIf="sections">
<mat-form-field class="w-100">
<mat-label>{{'DMP-EDITOR.FIELDS1.SECTION' | translate}}</mat-label>
<mat-select [formControl]="user.get('sectionId')">
<mat-option *ngFor="let section of sections" [value]="section.id">
{{ section.label }}
</mat-option>
</mat-select>
<mat-error *ngIf="user.get('sectionId').hasError('backendError')">{{user.get('sectionId').getError('backendError').message}}</mat-error>
<mat-error *ngIf="user.get('sectionId').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' |translate}}</mat-error>
</mat-form-field>
</div>
</div>
</div>
<div class="col-auto">
<button mat-icon-button class="action-list-icon" matTooltip="{{'DMP-EDITOR.ACTIONS1.REMOVE-USER' | translate}}" (click)="removeUser(userIndex)" [disabled]="form.disabled">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
<mat-error *ngIf="form.get('users').dirty && form.get('users').hasError('required')">{{'DMP-EDITOR.USERS-REQUIRED' | translate}}</mat-error>
<mat-error *ngIf="form.get('users').hasError('backendError')">{{form.get('users').getError('backendError').message}}</mat-error>
</div>
<div class="row">
<div class="col">
<button mat-icon-button (click)="addUser()" [disabled]="form.disabled">
<mat-icon>add</mat-icon>
</button>
</div>
</div>

View File

@ -0,0 +1,9 @@
.drag-handle {
cursor: move;
color: var(--primary-color);
}
.drag-handle-disabled {
cursor: auto;
color: rgba(0, 0, 0, 0.38);;
}

View File

@ -0,0 +1,74 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormArray, UntypedFormArray, UntypedFormGroup } from '@angular/forms';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration';
import { BaseComponent } from '@common/base/base.component';
import { DmpEditorModel, DmpUserEditorModel } from '../dmp-editor-blueprint/dmp-editor.model';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { DmpUserType } from '@app/core/common/enum/dmp-user-type';
import { DmpUserRole } from '@app/core/common/enum/dmp-user-role';
import { UserService } from '@app/core/services/user/user.service';
import { DmpBlueprintDefinitionSection } from '@app/core/model/dmp-blueprint/dmp-blueprint';
@Component({
selector: 'app-user-field-component',
templateUrl: 'user-field.component.html',
styleUrls: ['./user-field.component.scss']
})
export class UserFieldComponent extends BaseComponent implements OnInit {
@Input() form;
@Input() validationErrorModel: ValidationErrorModel;
@Input() label: string = null;
@Input() required: boolean = false;
@Input() placeholder: string;
@Input() sections: DmpBlueprintDefinitionSection[] = null;
dmpUserTypeEnum = DmpUserType;
dmpUserTypeEnumValues = this.enumUtils.getEnumValues<DmpUserType>(DmpUserType);
dmpUserRoleEnumValues = this.enumUtils.getEnumValues<DmpUserRole>(DmpUserRole);
multipleAutoCompleteSearchConfiguration: MultipleAutoCompleteConfiguration;
constructor(
public enumUtils: EnumUtils,
public userService: UserService
) { super(); }
ngOnInit() {
}
addUser(): void {
const userArray = this.form.get('users') as FormArray;
const dmpUser: DmpUserEditorModel = new DmpUserEditorModel(this.validationErrorModel);
userArray.push(dmpUser.buildForm({rootPath: "users[" + userArray.length + "]."}));
}
removeUser(userIndex: number): void {
(this.form.get('users') as FormArray).removeAt(userIndex);
DmpEditorModel.reApplyPropertiesValidators(
{
formGroup: this.form,
validationErrorModel: this.validationErrorModel
}
);
this.form.get('users').markAsDirty();
}
dropUsers(event: CdkDragDrop<string[]>) {
const usersFormArray = (this.form.get('users') as FormArray);
moveItemInArray(usersFormArray.controls, event.previousIndex, event.currentIndex);
usersFormArray.updateValueAndValidity();
DmpEditorModel.reApplyPropertiesValidators(
{
formGroup: this.form,
validationErrorModel: this.validationErrorModel
}
);
this.form.get('users').markAsDirty();
}
}

View File

@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { FormattingModule } from '@app/core/formatting.module';
import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.module';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { UserFieldComponent } from './user-field.component';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
FormattingModule,
AutoCompleteModule
],
declarations: [
UserFieldComponent
],
exports: [
UserFieldComponent
]
})
export class UserFieldModule { }