Merge branch 'dmp-refactoring' of code-repo.d4science.org:MaDgiK-CITE/argos into dmp-refactoring

This commit is contained in:
Efstratios Giannopoulos 2024-06-07 12:03:34 +03:00
commit e322b6adbc
68 changed files with 1994 additions and 897 deletions

View File

@ -308,4 +308,14 @@ public class ErrorThesaurusProperties {
public void setImportDescriptionWithoutDmpDescriptionTemplate(ErrorDescription importDescriptionWithoutDmpDescriptionTemplate) { public void setImportDescriptionWithoutDmpDescriptionTemplate(ErrorDescription importDescriptionWithoutDmpDescriptionTemplate) {
this.importDescriptionWithoutDmpDescriptionTemplate = importDescriptionWithoutDmpDescriptionTemplate; this.importDescriptionWithoutDmpDescriptionTemplate = importDescriptionWithoutDmpDescriptionTemplate;
} }
private ErrorDescription duplicateDmpUser;
public ErrorDescription getDuplicateDmpUser() {
return duplicateDmpUser;
}
public void setDuplicateDmpUser(ErrorDescription duplicateDmpUser) {
this.duplicateDmpUser = duplicateDmpUser;
}
} }

View File

@ -81,12 +81,6 @@ public class DescriptionTemplateDeleter implements Deleter {
UserDescriptionTemplateDeleter deleter = this.deleterFactory.deleter(UserDescriptionTemplateDeleter.class); UserDescriptionTemplateDeleter deleter = this.deleterFactory.deleter(UserDescriptionTemplateDeleter.class);
deleter.delete(items); deleter.delete(items);
} }
{
logger.debug("checking related - {}", DmpDescriptionTemplateEntity.class.getSimpleName());
List<DmpDescriptionTemplateEntity> items = this.queryFactory.query(DmpDescriptionTemplateQuery.class).descriptionTemplateGroupIds(groupIds).collect();
DmpDescriptionTemplateDeleter deleter = this.deleterFactory.deleter(DmpDescriptionTemplateDeleter.class);
deleter.delete(items);
}
//TODO can not delete profile if has Datasets //TODO can not delete profile if has Datasets
@ -94,7 +88,6 @@ public class DescriptionTemplateDeleter implements Deleter {
for (DescriptionTemplateEntity item : data) { for (DescriptionTemplateEntity item : data) {
logger.trace("deleting item {}", item.getId()); logger.trace("deleting item {}", item.getId());
if(item.getVersionStatus().equals(DescriptionTemplateVersionStatus.Current)) throw new MyApplicationException("Description is current can not deleted");
item.setIsActive(IsActive.Inactive); item.setIsActive(IsActive.Inactive);
item.setUpdatedAt(now); item.setUpdatedAt(now);
logger.trace("updating item"); logger.trace("updating item");

View File

@ -1145,7 +1145,7 @@ public class DescriptionServiceImpl implements DescriptionService {
List<TagEntity> tagsEntities = this.queryFactory.query(TagQuery.class).disableTracking().descriptionTagSubQuery(descriptionTagQuery).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).isActive(IsActive.Active).collect(); List<TagEntity> tagsEntities = this.queryFactory.query(TagQuery.class).disableTracking().descriptionTagSubQuery(descriptionTagQuery).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).isActive(IsActive.Active).collect();
if (!this.conventionService.isListNullOrEmpty(tagsEntities)) xml.setTags(tagsEntities.stream().map(TagEntity::getLabel).collect(Collectors.toList())); if (!this.conventionService.isListNullOrEmpty(tagsEntities)) xml.setTags(tagsEntities.stream().map(TagEntity::getLabel).collect(Collectors.toList()));
DescriptionTemplateEntity descriptionTemplateEntity = this.queryFactory.query(DescriptionTemplateQuery.class).disableTracking().ids(data.getDescriptionTemplateId()).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).isActive(IsActive.Active).first(); DescriptionTemplateEntity descriptionTemplateEntity = this.queryFactory.query(DescriptionTemplateQuery.class).disableTracking().ids(data.getDescriptionTemplateId()).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).first();
if (descriptionTemplateEntity != null) { if (descriptionTemplateEntity != null) {
xml.setDescriptionTemplate(this.descriptionTemplateService.exportXmlEntity(descriptionTemplateEntity.getId(), true)); xml.setDescriptionTemplate(this.descriptionTemplateService.exportXmlEntity(descriptionTemplateEntity.getId(), true));
} }

View File

@ -572,8 +572,8 @@ public class DescriptionTemplateServiceImpl implements DescriptionTemplateServic
if (previousFinalized != null){ if (previousFinalized != null){
previousFinalized.setVersionStatus(DescriptionTemplateVersionStatus.Current); previousFinalized.setVersionStatus(DescriptionTemplateVersionStatus.Current);
this.entityManager.merge(previousFinalized); this.entityManager.merge(previousFinalized);
data.setVersionStatus(DescriptionTemplateVersionStatus.NotFinalized);
} }
data.setVersionStatus(DescriptionTemplateVersionStatus.NotFinalized);
this.entityManager.merge(data); this.entityManager.merge(data);
this.entityManager.flush(); this.entityManager.flush();
} }
@ -908,7 +908,7 @@ public class DescriptionTemplateServiceImpl implements DescriptionTemplateServic
logger.debug(new MapLogEntry("exportXml").And("id", id)); logger.debug(new MapLogEntry("exportXml").And("id", id));
if (!ignoreAuthorize) this.authorizationService.authorizeForce(Permission.ExportDescriptionTemplate); if (!ignoreAuthorize) this.authorizationService.authorizeForce(Permission.ExportDescriptionTemplate);
DescriptionTemplateEntity data = this.queryFactory.query(DescriptionTemplateQuery.class).disableTracking().ids(id).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).isActive(IsActive.Active).first(); DescriptionTemplateEntity data = this.queryFactory.query(DescriptionTemplateQuery.class).disableTracking().ids(id).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).first();
if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, DescriptionTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, DescriptionTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale()));
DefinitionEntity definition = this.xmlHandlingService.fromXml(DefinitionEntity.class, data.getDefinition()); DefinitionEntity definition = this.xmlHandlingService.fromXml(DefinitionEntity.class, data.getDefinition());
@ -921,7 +921,7 @@ public class DescriptionTemplateServiceImpl implements DescriptionTemplateServic
logger.debug(new MapLogEntry("exportXml").And("id", id)); logger.debug(new MapLogEntry("exportXml").And("id", id));
this.authorizationService.authorizeForce(Permission.ExportDescriptionTemplate); this.authorizationService.authorizeForce(Permission.ExportDescriptionTemplate);
DescriptionTemplateEntity data = this.queryFactory.query(DescriptionTemplateQuery.class).disableTracking().ids(id).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).isActive(IsActive.Active).first(); DescriptionTemplateEntity data = this.queryFactory.query(DescriptionTemplateQuery.class).disableTracking().ids(id).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).first();
if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, DescriptionTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, DescriptionTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale()));
String xml = this.xmlHandlingService.toXml(this.exportXmlEntity(id, false)); String xml = this.xmlHandlingService.toXml(this.exportXmlEntity(id, false));

View File

@ -768,7 +768,9 @@ public class DmpServiceImpl implements DmpService {
this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(dmpId)), Permission.AssignDmpUsers); this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(dmpId)), Permission.AssignDmpUsers);
if (!disableDelete && (model == null || model.stream().noneMatch(x-> x.getUser() != null && DmpUserRole.Owner.equals(x.getRole())))) throw new MyApplicationException("At least one owner required"); if (!disableDelete && (model == null || model.stream().noneMatch(x-> x.getUser() != null && DmpUserRole.Owner.equals(x.getRole())))) throw new MyApplicationException("At least one owner required");
this.checkDuplicateDmpUser(model);
DmpEntity dmpEntity = this.entityManager.find(DmpEntity.class, dmpId, true); DmpEntity dmpEntity = this.entityManager.find(DmpEntity.class, dmpId, true);
if (dmpEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{dmpId, Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (dmpEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{dmpId, Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale()));
@ -812,6 +814,20 @@ public class DmpServiceImpl implements DmpService {
return this.builderFactory.builder(DmpUserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fieldSet, DmpUser._id, DmpUser._hash), persisted); return this.builderFactory.builder(DmpUserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fieldSet, DmpUser._id, DmpUser._hash), persisted);
} }
private void checkDuplicateDmpUser(List<DmpUserPersist> model){
for (DmpUserPersist user: model) {
List<DmpUserPersist> duplicateUser = null;
if (user.getUser() != null){
duplicateUser = model.stream().filter(x -> x.getUser().equals(user.getUser()) && x.getRole().equals(user.getRole()) && Objects.equals(user.getSectionId(), x.getSectionId())).collect(Collectors.toList());
} else {
duplicateUser = model.stream().filter(x -> x.getEmail().equals(user.getEmail()) && x.getRole().equals(user.getRole()) && Objects.equals(user.getSectionId(), x.getSectionId())).collect(Collectors.toList());
}
if (duplicateUser.size() > 1) {
throw new MyValidationException(this.errors.getDuplicateDmpUser().getCode(), this.errors.getDuplicateDmpUser().getMessage());
}
}
}
@Override @Override
public Dmp removeUser(DmpUserRemovePersist model, FieldSet fields) throws InvalidApplicationException, IOException { public Dmp removeUser(DmpUserRemovePersist model, FieldSet fields) throws InvalidApplicationException, IOException {
this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(model.getDmpId())), Permission.AssignDmpUsers); this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(model.getDmpId())), Permission.AssignDmpUsers);

View File

@ -37,6 +37,8 @@ public interface UserService {
void sendRemoveCredentialConfirmation(RemoveCredentialRequestPersist model) throws InvalidApplicationException, JAXBException; void sendRemoveCredentialConfirmation(RemoveCredentialRequestPersist model) throws InvalidApplicationException, JAXBException;
boolean doesTokenBelongToLoggedInUser(String token) throws InvalidApplicationException, IOException;
void confirmMergeAccount(String token) throws InvalidApplicationException, IOException; void confirmMergeAccount(String token) throws InvalidApplicationException, IOException;
void confirmRemoveCredential(String token) throws InvalidApplicationException; void confirmRemoveCredential(String token) throws InvalidApplicationException;

View File

@ -601,6 +601,12 @@ public class UserServiceImpl implements UserService {
return (String.format("%02d", hour) + ":" + String.format("%02d", min) + ":" + String.format("%02d", sec)); return (String.format("%02d", hour) + ":" + String.format("%02d", min) + ":" + String.format("%02d", sec));
} }
public boolean doesTokenBelongToLoggedInUser(String token) throws IOException, InvalidApplicationException {
UserEntity userToBeMerge = this.getUserEntityFromToken(token);
return this.userScope.getUserIdSafe().equals(userToBeMerge.getId());
}
public void confirmMergeAccount(String token) throws IOException, InvalidApplicationException { public void confirmMergeAccount(String token) throws IOException, InvalidApplicationException {
ActionConfirmationEntity action = this.queryFactory.query(ActionConfirmationQuery.class).tokens(token).types(ActionConfirmationType.MergeAccount).isActive(IsActive.Active).first(); ActionConfirmationEntity action = this.queryFactory.query(ActionConfirmationQuery.class).tokens(token).types(ActionConfirmationType.MergeAccount).isActive(IsActive.Active).first();
if (action == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{token, ActionConfirmationEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (action == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{token, ActionConfirmationEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
@ -842,4 +848,22 @@ public class UserServiceImpl implements UserService {
} }
} }
private UserEntity getUserEntityFromToken(String token) throws MyForbiddenException, MyNotFoundException {
ActionConfirmationEntity action = this.queryFactory.query(ActionConfirmationQuery.class).tokens(token).types(ActionConfirmationType.MergeAccount).isActive(IsActive.Active).first();
if (action == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{token, ActionConfirmationEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.checkActionState(action);
MergeAccountConfirmationEntity mergeAccountConfirmationEntity = this.xmlHandlingService.fromXmlSafe(MergeAccountConfirmationEntity.class, action.getData());
if (mergeAccountConfirmationEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{action.getId(), MergeAccountConfirmationEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
UserContactInfoEntity userContactInfoEntity = this.queryFactory.query(UserContactInfoQuery.class).values(mergeAccountConfirmationEntity.getEmail()).types(ContactInfoType.Email).first();
if (userContactInfoEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{mergeAccountConfirmationEntity.getEmail(), User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
UserEntity userToBeMerge = this.queryFactory.query(UserQuery.class).ids(userContactInfoEntity.getUserId()).isActive(IsActive.Active).first();
if (userToBeMerge == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{userContactInfoEntity.getUserId(), User.class.getSimpleName()}, LocaleContextHolder.getLocale()));
return userToBeMerge;
}
} }

View File

@ -23,6 +23,7 @@ import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.commons.enums.DmpAccessType; import org.opencdmp.commons.enums.DmpAccessType;
import org.opencdmp.commons.enums.DmpStatus; import org.opencdmp.commons.enums.DmpStatus;
import org.opencdmp.commons.enums.IsActive; import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.controllers.swagger.SwaggerHelpers;
import org.opencdmp.convention.ConventionService; import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.StorageFileEntity; import org.opencdmp.data.StorageFileEntity;
import org.opencdmp.model.DescriptionValidationResult; import org.opencdmp.model.DescriptionValidationResult;
@ -151,16 +152,16 @@ public class DescriptionController {
@PostMapping("query") @PostMapping("query")
@Operation( @Operation(
summary = "Query all descriptions", summary = "Query all descriptions",
description = SwaggerHelpers.endpoint_query, description = SwaggerHelpers.Description.endpoint_query,
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = SwaggerHelpers.endpoint_query_request_body, description = SwaggerHelpers.Description.endpoint_query_request_body,
content = { content = {
@Content( @Content(
examples = { examples = {
@ExampleObject( @ExampleObject(
name = "Pagination and projection", name = "Pagination and projection",
description = "Simple paginated request using a property projection list and pagination info", description = "Simple paginated request using a property projection list and pagination info",
value = SwaggerHelpers.endpoint_query_request_body_example value = SwaggerHelpers.Description.endpoint_query_request_body_example
) )
} }
) )
@ -184,8 +185,8 @@ public class DescriptionController {
@Operation(summary = "Fetch a specific description by id") @Operation(summary = "Fetch a specific description by id")
public Description get( public Description get(
@Parameter(name = "id", description = "The id of a description to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, @Parameter(name = "id", description = "The id of a description to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id,
@Parameter(name = "fieldSet", description = "This is an object containing a list of the properties you wish to include in the response, similar to the 'project' attribute on queries.", required = true) FieldSet fieldSet) @Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
throws MyApplicationException, MyForbiddenException, MyNotFoundException { ) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
logger.debug(new MapLogEntry("retrieving" + Description.class.getSimpleName()).And("id", id).And("fields", fieldSet)); logger.debug(new MapLogEntry("retrieving" + Description.class.getSimpleName()).And("id", id).And("fields", fieldSet));
fieldSet = this.fieldSetExpanderService.expand(fieldSet); fieldSet = this.fieldSetExpanderService.expand(fieldSet);
@ -208,7 +209,10 @@ public class DescriptionController {
@Operation(summary = "Create a new or update an existing description") @Operation(summary = "Create a new or update an existing description")
@Transactional @Transactional
@ValidationFilterAnnotation(validator = DescriptionPersist.DescriptionPersistValidator.ValidatorName, argumentName = "model") @ValidationFilterAnnotation(validator = DescriptionPersist.DescriptionPersistValidator.ValidatorName, argumentName = "model")
public Description persist(@RequestBody DescriptionPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException { public Description persist(
@RequestBody DescriptionPersist model,
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException {
logger.debug(new MapLogEntry("persisting" + Description.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); logger.debug(new MapLogEntry("persisting" + Description.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
fieldSet = this.fieldSetExpanderService.expand(fieldSet); fieldSet = this.fieldSetExpanderService.expand(fieldSet);
@ -226,7 +230,10 @@ public class DescriptionController {
@Operation(summary = "Update the status of an existing description") @Operation(summary = "Update the status of an existing description")
@Transactional @Transactional
@ValidationFilterAnnotation(validator = DescriptionStatusPersist.DescriptionStatusPersistValidator.ValidatorName, argumentName = "model") @ValidationFilterAnnotation(validator = DescriptionStatusPersist.DescriptionStatusPersistValidator.ValidatorName, argumentName = "model")
public Description persistStatus(@RequestBody DescriptionStatusPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, IOException, InvalidApplicationException { public Description persistStatus(
@RequestBody DescriptionStatusPersist model,
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, IOException, InvalidApplicationException {
logger.debug(new MapLogEntry("persisting" + Description.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); logger.debug(new MapLogEntry("persisting" + Description.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
fieldSet = this.fieldSetExpanderService.expand(fieldSet); fieldSet = this.fieldSetExpanderService.expand(fieldSet);
@ -275,7 +282,9 @@ public class DescriptionController {
@DeleteMapping("{id}") @DeleteMapping("{id}")
@Operation(summary = "Delete a description by id") @Operation(summary = "Delete a description by id")
@Transactional @Transactional
public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException, IOException { public void delete(
@Parameter(name = "id", description = "The id of a description to delete", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id
) throws MyForbiddenException, InvalidApplicationException, IOException {
logger.debug(new MapLogEntry("retrieving" + Description.class.getSimpleName()).And("id", id)); logger.debug(new MapLogEntry("retrieving" + Description.class.getSimpleName()).And("id", id));
this.descriptionService.deleteAndSave(id); this.descriptionService.deleteAndSave(id);
@ -285,7 +294,10 @@ public class DescriptionController {
@GetMapping("{id}/export/{type}") @GetMapping("{id}/export/{type}")
@Operation(summary = "Export a description in various formats by id") @Operation(summary = "Export a description in various formats by id")
public ResponseEntity<byte[]> export(@PathVariable("id") UUID id, @PathVariable("type") String exportType) throws InvalidApplicationException, IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { public ResponseEntity<byte[]> export(
@Parameter(name = "id", description = "The id of a description to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id,
@Parameter(name = "type", description = "The type of the export", example = "rda", required = true) @PathVariable("type") String exportType
) throws InvalidApplicationException, IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
logger.debug(new MapLogEntry("exporting description")); logger.debug(new MapLogEntry("exporting description"));
return this.descriptionService.export(id, exportType); return this.descriptionService.export(id, exportType);
@ -295,7 +307,11 @@ public class DescriptionController {
@Operation(summary = "Upload a file attachment on a field that supports it") @Operation(summary = "Upload a file attachment on a field that supports it")
@Transactional @Transactional
@ValidationFilterAnnotation(validator = DescriptionFieldFilePersist.PersistValidator.ValidatorName, argumentName = "model") @ValidationFilterAnnotation(validator = DescriptionFieldFilePersist.PersistValidator.ValidatorName, argumentName = "model")
public StorageFile uploadFieldFiles(@RequestParam("file") MultipartFile file, @RequestParam("model") DescriptionFieldFilePersist model, FieldSet fieldSet) throws IOException { public StorageFile uploadFieldFiles(
@RequestParam("file") MultipartFile file,
@RequestParam("model") DescriptionFieldFilePersist model,
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
) throws IOException {
logger.debug(new MapLogEntry("uploadFieldFiles" + Description.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); logger.debug(new MapLogEntry("uploadFieldFiles" + Description.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
fieldSet = this.fieldSetExpanderService.expand(fieldSet); fieldSet = this.fieldSetExpanderService.expand(fieldSet);
@ -311,7 +327,10 @@ public class DescriptionController {
@GetMapping("{id}/field-file/{fileId}") @GetMapping("{id}/field-file/{fileId}")
@Operation(summary = "Fetch a field file attachment as byte array") @Operation(summary = "Fetch a field file attachment as byte array")
public ResponseEntity<ByteArrayResource> getFieldFile(@PathVariable("id") UUID id, @PathVariable("fileId") UUID fileId) throws MyApplicationException, MyForbiddenException, MyNotFoundException { public ResponseEntity<ByteArrayResource> getFieldFile(
@Parameter(name = "id", description = "The id of a description", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id,
@Parameter(name = "fileIid", description = "The id of the file we want to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("fileId") UUID fileId
) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
logger.debug(new MapLogEntry("get Field File" + Description.class.getSimpleName()).And("id", id).And("fileId", fileId)); logger.debug(new MapLogEntry("get Field File" + Description.class.getSimpleName()).And("id", id).And("fileId", fileId));
StorageFileEntity storageFile = this.descriptionService.getFieldFile(id, fileId); StorageFileEntity storageFile = this.descriptionService.getFieldFile(id, fileId);
@ -349,7 +368,9 @@ public class DescriptionController {
@RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml") @RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml")
@Operation(summary = "Export a description in xml format by id") @Operation(summary = "Export a description in xml format by id")
public @ResponseBody ResponseEntity<byte[]> getXml(@PathVariable UUID id) throws JAXBException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException, InvalidApplicationException { public @ResponseBody ResponseEntity<byte[]> getXml(
@Parameter(name = "id", description = "The id of a description to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable UUID id
) throws JAXBException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException, InvalidApplicationException {
logger.debug(new MapLogEntry("export" + DmpBlueprint.class.getSimpleName()).And("id", id)); logger.debug(new MapLogEntry("export" + DmpBlueprint.class.getSimpleName()).And("id", id));
ResponseEntity<byte[]> response = this.descriptionService.exportXml(id); ResponseEntity<byte[]> response = this.descriptionService.exportXml(id);
@ -360,181 +381,4 @@ public class DescriptionController {
return response; return response;
} }
protected static class SwaggerHelpers {
static final String endpoint_query =
"""
This endpoint is used to fetch all the available descriptions.<br/>
It also allows to restrict the results using a query object passed in the request body.<br/>
""";
static final String endpoint_query_request_body =
"""
Let's explore the options this object gives us.
### <u>General query parameters:</u>
<ul>
<li><b>page:</b>
This is an object controlling the pagination of the results. It contains two properties.
</li>
<ul>
<li><b>offset:</b>
How many records to omit.
</li>
<li><b>size:</b>
How many records to include in each page.
</li>
</ul>
</ul>
For example, if we want the third page, and our pages to contain 15 elements, we would pass the following object:
```JSON
{
"offset": 30,
"size": 15
}
```
<ul>
<li><b>order:</b>
This is an object controlling the ordering of the results.
It contains a list of strings called <i>items</i> with the names of the properties to use.
<br/>If the name of the property is prefixed with a <b>'-'</b>, the ordering direction is <b>DESC</b>. Otherwise, it is <b>ASC</b>.
</li>
</ul>
For example, if we wanted to order based on the field 'createdAt' in descending order, we would pass the following object:
```JSON
{
"items": [
"-createdAt"
],
}
```
<ul>
<li><b>metadata:</b>
This is an object containing metadata for the request. There is only one available option.
<ul>
<li><b>countAll:</b>
If this is set to true, the count property included in the response will account for all the records regardless the pagination,
with all the rest of filtering options applied of course.
Otherwise, if it is set to false or not present, only the returned results will be counted.
<br/>The first option is useful for the UI clients to calculate how many result pages are available.
</li>
</ul>
</li>
<li><b>project:</b>
This is an object controlling the data projection of the results.
It contains a list of strings called <i>fields</i> with the names of the properties to project.
<br/>You can also include properties that are deeper in the object tree by prefixing them with dots.
</li>
</ul>
### <u>Description specific query parameters:</u>
<ul>
<li><b>like:</b>
If there is a like parameter present in the query, only the description entities that include the contents of the parameter either in their labels or the descriptions will be in the response.
</li>
<li><b>ids:</b>
This is a list and contains the ids we want to include in the response. <br/>If empty, every record is included.
</li>
<li><b>excludedIds:</b>
This is a list and contains the ids we want to exclude from the response. <br/>If empty, no record gets excluded.
</li>
<li><b>isActive:</b>
This is a list and determines which records we want to include in the response, based on if they are deleted or not.
This filter works like this. If we want to view only the active records we pass [1] and for only the deleted records we pass [0].
<br/>If not present or if we pass [0,1], every record is included.
</li>
<li><b>statuses:</b>
This is a list and determines which records we want to include in the response, based on their status.
The status can be <i>Draft</i>, <i>Finalized</i> or <i>Canceled</i>. We add 0, 1 or 2 to the list respectively.
<br/>If not present, every record is included.
</li>
<li><b>createdAfter:</b>
This is a date and determines which records we want to include in the response, based on their creation date.
Specifically, only the records created after the given date are included.
<br/>If not present, every record is included.
</li>
<li><b>createdBefore:</b>
This is a date and determines which records we want to include in the response, based on their creation date.
Specifically, only the records created before the given date are included.
<br/>If not present, every record is included.
</li>
<li><b>finalizedAfter:</b>
This is a date and determines which records we want to include in the response, based on their finalization date.
Specifically, only the records finalized after the given date are included.
<br/>If not present, every record is included.
</li>
<li><b>finalizedBefore:</b>
This is a date and determines which records we want to include in the response, based on their finalization date.
Specifically, only the records finalized before the given date are included.
<br/>If not present, every record is included.
</li>
</ul>
""";
static final String endpoint_query_request_body_example =
"""
{
"project":{
"fields":[
"id",
"label",
"status",
"updatedAt",
"belongsToCurrentTenant",
"finalizedAt",
"descriptionTemplate.id",
"descriptionTemplate.label",
"descriptionTemplate.groupId",
"dmp.id",
"dmp.label",
"dmp.status",
"dmp.accessType",
"dmp.blueprint.id",
"dmp.blueprint.label",
"dmp.blueprint.definition.sections.id",
"dmp.blueprint.definition.sections.label",
"dmp.blueprint.definition.sections.hasTemplates",
"dmp.dmpReferences.id",
"dmp.dmpReferences.reference.id",
"dmp.dmpReferences.reference.label",
"dmp.dmpReferences.reference.type.id",
"dmp.dmpReferences.reference.reference",
"dmp.dmpReferences.isActive",
"dmpDescriptionTemplate.id",
"dmpDescriptionTemplate.dmp.id",
"dmpDescriptionTemplate.descriptionTemplateGroupId",
"dmpDescriptionTemplate.sectionId"
]
},
"page":{
"size":5,
"offset":0
},
"order":{
"items":[
"-updatedAt"
]
},
"metadata":{
"countAll":true
},
"isActive":[
1
]
}
""";
static final String endpoint_get =
"""
""";
}
} }

View File

@ -11,9 +11,12 @@ import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry; import gr.cite.tools.logging.MapLogEntry;
import gr.cite.tools.validation.ValidationFilterAnnotation; import gr.cite.tools.validation.ValidationFilterAnnotation;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.JAXBException;
import org.opencdmp.audit.AuditableAction; import org.opencdmp.audit.AuditableAction;
@ -21,6 +24,7 @@ import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.commons.enums.DmpAccessType; import org.opencdmp.commons.enums.DmpAccessType;
import org.opencdmp.commons.enums.DmpStatus; import org.opencdmp.commons.enums.DmpStatus;
import org.opencdmp.commons.enums.IsActive; import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.controllers.swagger.SwaggerHelpers;
import org.opencdmp.model.DescriptionsToBeFinalized; import org.opencdmp.model.DescriptionsToBeFinalized;
import org.opencdmp.model.DmpUser; import org.opencdmp.model.DmpUser;
import org.opencdmp.model.DmpValidationResult; import org.opencdmp.model.DmpValidationResult;
@ -99,6 +103,7 @@ public class DmpController {
@PostMapping("public/query") @PostMapping("public/query")
@Operation(summary = "Query public published plans") @Operation(summary = "Query public published plans")
@Hidden
public QueryResult<PublicDmp> publicQuery(@RequestBody DmpLookup lookup) throws MyApplicationException, MyForbiddenException { public QueryResult<PublicDmp> publicQuery(@RequestBody DmpLookup lookup) throws MyApplicationException, MyForbiddenException {
logger.debug("querying {}", Dmp.class.getSimpleName()); logger.debug("querying {}", Dmp.class.getSimpleName());
@ -114,6 +119,7 @@ public class DmpController {
@GetMapping("public/{id}") @GetMapping("public/{id}")
@Operation(summary = "Fetch a specific public published plan by id") @Operation(summary = "Fetch a specific public published plan by id")
@Hidden
public PublicDmp publicGet(@PathVariable("id") UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException { public PublicDmp publicGet(@PathVariable("id") UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
logger.debug(new MapLogEntry("retrieving" + Dmp.class.getSimpleName()).And("id", id).And("fields", fieldSet)); logger.debug(new MapLogEntry("retrieving" + Dmp.class.getSimpleName()).And("id", id).And("fields", fieldSet));
@ -136,21 +142,38 @@ public class DmpController {
@PostMapping("query") @PostMapping("query")
@Operation( @Operation(
summary = "Query all plans", summary = "Query all plans",
description = SwaggerHelpers.endpoint_query, description = SwaggerHelpers.Dmp.endpoint_query,
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = SwaggerHelpers.endpoint_query_request_body, description = SwaggerHelpers.Dmp.endpoint_query_request_body,
content = { content = {
@Content( @Content(
examples = { examples = {
@ExampleObject( @ExampleObject(
name = "Pagination and projection", name = "Pagination and projection",
description = "Simple paginated request using a property projection list and pagination info", description = "Simple paginated request using a property projection list and pagination info",
value = SwaggerHelpers.endpoint_query_request_body_example value = SwaggerHelpers.Dmp.endpoint_query_request_body_example
) )
} }
) )
} }
) ),
responses = {
@ApiResponse(
description = "OK",
responseCode = "200",
content = {
@Content(
examples = {
@ExampleObject(
name = "First page",
description = "Example with the first page of paginated results",
value = SwaggerHelpers.Dmp.endpoint_query_response_example
)
}
)
}
)
}
) )
public QueryResult<Dmp> Query(@RequestBody DmpLookup lookup) throws MyApplicationException, MyForbiddenException { public QueryResult<Dmp> Query(@RequestBody DmpLookup lookup) throws MyApplicationException, MyForbiddenException {
logger.debug("querying {}", Dmp.class.getSimpleName()); logger.debug("querying {}", Dmp.class.getSimpleName());
@ -166,7 +189,11 @@ public class DmpController {
@GetMapping("{id}") @GetMapping("{id}")
@Operation(summary = "Fetch a specific plan by id") @Operation(summary = "Fetch a specific plan by id")
public Dmp Get(@PathVariable("id") UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException { public Dmp Get(
@Parameter(name = "id", description = "The id of a plan to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id,
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet,
Locale locale
) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
logger.debug(new MapLogEntry("retrieving" + Dmp.class.getSimpleName()).And("id", id).And("fields", fieldSet)); logger.debug(new MapLogEntry("retrieving" + Dmp.class.getSimpleName()).And("id", id).And("fields", fieldSet));
this.censorFactory.censor(DmpCensor.class).censor(fieldSet, null); this.censorFactory.censor(DmpCensor.class).censor(fieldSet, null);
@ -188,7 +215,10 @@ public class DmpController {
@Operation(summary = "Create a new or update an existing plan") @Operation(summary = "Create a new or update an existing plan")
@Transactional @Transactional
@ValidationFilterAnnotation(validator = DmpPersist.DmpPersistValidator.ValidatorName, argumentName = "model") @ValidationFilterAnnotation(validator = DmpPersist.DmpPersistValidator.ValidatorName, argumentName = "model")
public Dmp Persist(@RequestBody DmpPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException, JAXBException { public Dmp Persist(
@RequestBody DmpPersist model,
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException, JAXBException {
logger.debug(new MapLogEntry("persisting" + Dmp.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); logger.debug(new MapLogEntry("persisting" + Dmp.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
Dmp persisted = this.dmpService.persist(model, fieldSet); Dmp persisted = this.dmpService.persist(model, fieldSet);
@ -204,7 +234,9 @@ public class DmpController {
@DeleteMapping("{id}") @DeleteMapping("{id}")
@Operation(summary = "Delete a plan by id") @Operation(summary = "Delete a plan by id")
@Transactional @Transactional
public void Delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException, IOException { public void Delete(
@Parameter(name = "id", description = "The id of a plan to delete", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id
) throws MyForbiddenException, InvalidApplicationException, IOException {
logger.debug(new MapLogEntry("retrieving" + Dmp.class.getSimpleName()).And("id", id)); logger.debug(new MapLogEntry("retrieving" + Dmp.class.getSimpleName()).And("id", id));
this.dmpService.deleteAndSave(id); this.dmpService.deleteAndSave(id);
@ -215,7 +247,10 @@ public class DmpController {
@PostMapping("finalize/{id}") @PostMapping("finalize/{id}")
@Operation(summary = "Finalize a plan by id") @Operation(summary = "Finalize a plan by id")
@Transactional @Transactional
public boolean finalize(@PathVariable("id") UUID id, @RequestBody DescriptionsToBeFinalized descriptions) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException { public boolean finalize(
@Parameter(name = "id", description = "The id of a plan to finalize", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id,
@RequestBody DescriptionsToBeFinalized descriptions
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException {
logger.debug(new MapLogEntry("finalizing" + Dmp.class.getSimpleName()).And("id", id).And("descriptionIds", descriptions.getDescriptionIds())); logger.debug(new MapLogEntry("finalizing" + Dmp.class.getSimpleName()).And("id", id).And("descriptionIds", descriptions.getDescriptionIds()));
this.dmpService.finalize(id, descriptions.getDescriptionIds()); this.dmpService.finalize(id, descriptions.getDescriptionIds());
@ -231,7 +266,10 @@ public class DmpController {
@GetMapping("undo-finalize/{id}") @GetMapping("undo-finalize/{id}")
@Operation(summary = "Undo the finalization of a plan by id (only possible if it is not already deposited)") @Operation(summary = "Undo the finalization of a plan by id (only possible if it is not already deposited)")
@Transactional @Transactional
public boolean undoFinalize(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException, JAXBException { public boolean undoFinalize(
@Parameter(name = "id", description = "The id of a plan to revert the finalization", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id,
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, IOException, JAXBException {
logger.debug(new MapLogEntry("undo-finalizing" + Dmp.class.getSimpleName()).And("id", id)); logger.debug(new MapLogEntry("undo-finalizing" + Dmp.class.getSimpleName()).And("id", id));
this.censorFactory.censor(DmpCensor.class).censor(fieldSet, null); this.censorFactory.censor(DmpCensor.class).censor(fieldSet, null);
@ -247,6 +285,7 @@ public class DmpController {
@GetMapping("validate/{id}") @GetMapping("validate/{id}")
@Operation(summary = "Validate if a plan is ready for finalization by id") @Operation(summary = "Validate if a plan is ready for finalization by id")
@Hidden
public DmpValidationResult validate(@PathVariable("id") UUID id) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException { public DmpValidationResult validate(@PathVariable("id") UUID id) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException {
logger.debug(new MapLogEntry("validating" + Dmp.class.getSimpleName()).And("id", id)); logger.debug(new MapLogEntry("validating" + Dmp.class.getSimpleName()).And("id", id));
@ -265,7 +304,10 @@ public class DmpController {
@Operation(summary = "Create a clone of an existing plan") @Operation(summary = "Create a clone of an existing plan")
@Transactional @Transactional
@ValidationFilterAnnotation(validator = CloneDmpPersist.CloneDmpPersistValidator.ValidatorName, argumentName = "model") @ValidationFilterAnnotation(validator = CloneDmpPersist.CloneDmpPersistValidator.ValidatorName, argumentName = "model")
public Dmp buildClone(@RequestBody CloneDmpPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, IOException, InvalidApplicationException { public Dmp buildClone(
@RequestBody CloneDmpPersist model,
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, IOException, InvalidApplicationException {
logger.debug(new MapLogEntry("clone" + Dmp.class.getSimpleName()).And("model", model).And("fields", fieldSet)); logger.debug(new MapLogEntry("clone" + Dmp.class.getSimpleName()).And("model", model).And("fields", fieldSet));
this.censorFactory.censor(DmpCensor.class).censor(fieldSet, null); this.censorFactory.censor(DmpCensor.class).censor(fieldSet, null);
@ -284,7 +326,10 @@ public class DmpController {
@Operation(summary = "Create a new version of an existing plan") @Operation(summary = "Create a new version of an existing plan")
@Transactional @Transactional
@ValidationFilterAnnotation(validator = NewVersionDmpPersist.NewVersionDmpPersistValidator.ValidatorName, argumentName = "model") @ValidationFilterAnnotation(validator = NewVersionDmpPersist.NewVersionDmpPersistValidator.ValidatorName, argumentName = "model")
public Dmp createNewVersion(@RequestBody NewVersionDmpPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, JAXBException, IOException, TransformerException, InvalidApplicationException, ParserConfigurationException { public Dmp createNewVersion(
@RequestBody NewVersionDmpPersist model,
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
) throws MyApplicationException, MyForbiddenException, MyNotFoundException, JAXBException, IOException, TransformerException, InvalidApplicationException, ParserConfigurationException {
logger.debug(new MapLogEntry("persisting" + NewVersionDmpPersist.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); logger.debug(new MapLogEntry("persisting" + NewVersionDmpPersist.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
Dmp persisted = this.dmpService.createNewVersion(model, fieldSet); Dmp persisted = this.dmpService.createNewVersion(model, fieldSet);
@ -301,6 +346,7 @@ public class DmpController {
@Operation(summary = "Assign users to the plan by id") @Operation(summary = "Assign users to the plan by id")
@Transactional @Transactional
@ValidationFilterAnnotation(validator = DmpUserPersist.DmpUserPersistValidator.ValidatorName, argumentName = "model") @ValidationFilterAnnotation(validator = DmpUserPersist.DmpUserPersistValidator.ValidatorName, argumentName = "model")
@Hidden
public QueryResult<DmpUser> assignUsers(@PathVariable("id") UUID id, @RequestBody List<DmpUserPersist> model, FieldSet fieldSet) throws InvalidApplicationException, IOException { public QueryResult<DmpUser> assignUsers(@PathVariable("id") UUID id, @RequestBody List<DmpUserPersist> model, FieldSet fieldSet) throws InvalidApplicationException, IOException {
logger.debug(new MapLogEntry("assigning users to dmp").And("model", model).And("fieldSet", fieldSet)); logger.debug(new MapLogEntry("assigning users to dmp").And("model", model).And("fieldSet", fieldSet));
@ -318,6 +364,7 @@ public class DmpController {
@Operation(summary = "Remove a user association with the plan") @Operation(summary = "Remove a user association with the plan")
@Transactional @Transactional
@ValidationFilterAnnotation(validator = DmpUserRemovePersist.DmpUserRemovePersistValidator.ValidatorName, argumentName = "model") @ValidationFilterAnnotation(validator = DmpUserRemovePersist.DmpUserRemovePersistValidator.ValidatorName, argumentName = "model")
@Hidden
public QueryResult<Dmp> removeUser(@RequestBody DmpUserRemovePersist model, FieldSet fieldSet) throws InvalidApplicationException, IOException { public QueryResult<Dmp> removeUser(@RequestBody DmpUserRemovePersist model, FieldSet fieldSet) throws InvalidApplicationException, IOException {
logger.debug(new MapLogEntry("remove user from dmp").And("model", model).And("fieldSet", fieldSet)); logger.debug(new MapLogEntry("remove user from dmp").And("model", model).And("fieldSet", fieldSet));
@ -333,7 +380,11 @@ public class DmpController {
@GetMapping("{id}/export/{transformerId}/{type}") @GetMapping("{id}/export/{transformerId}/{type}")
@Operation(summary = "Export a plan in various formats by id") @Operation(summary = "Export a plan in various formats by id")
public ResponseEntity<byte[]> export(@PathVariable("id") UUID id, @PathVariable("transformerId") String transformerId, @PathVariable("type") String exportType) throws InvalidApplicationException, IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { public ResponseEntity<byte[]> export(
@Parameter(name = "id", description = "The id of a plan to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id,
@PathVariable("transformerId") String transformerId,
@PathVariable("type") String exportType
) throws InvalidApplicationException, IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
logger.debug(new MapLogEntry("exporting dmp").And("id", id).And("transformerId", transformerId).And("exportType", exportType)); logger.debug(new MapLogEntry("exporting dmp").And("id", id).And("transformerId", transformerId).And("exportType", exportType));
ResponseEntity<byte[]> bytes = this.dmpService.export(id, transformerId, exportType); ResponseEntity<byte[]> bytes = this.dmpService.export(id, transformerId, exportType);
@ -349,6 +400,7 @@ public class DmpController {
@Operation(summary = "Send user invitations for the plan by id") @Operation(summary = "Send user invitations for the plan by id")
@Transactional @Transactional
@ValidationFilterAnnotation(validator = DmpUserInvitePersist.DmpUserInvitePersistValidator.ValidatorName, argumentName = "model") @ValidationFilterAnnotation(validator = DmpUserInvitePersist.DmpUserInvitePersistValidator.ValidatorName, argumentName = "model")
@Hidden
public boolean inviteUsers(@PathVariable("id") UUID id, @RequestBody DmpUserInvitePersist model) throws InvalidApplicationException, JAXBException, IOException { 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)); logger.debug(new MapLogEntry("inviting users to dmp").And("model", model));
@ -364,6 +416,7 @@ public class DmpController {
@GetMapping("{id}/token/{token}/invite-accept") @GetMapping("{id}/token/{token}/invite-accept")
@Operation(summary = "Accept an invitation token for a plan by id") @Operation(summary = "Accept an invitation token for a plan by id")
@Transactional @Transactional
@Hidden
public boolean acceptInvitation(@PathVariable("id") UUID id, @PathVariable("token") String token) throws InvalidApplicationException, JAXBException, IOException { public boolean acceptInvitation(@PathVariable("id") UUID id, @PathVariable("token") String token) throws InvalidApplicationException, JAXBException, IOException {
logger.debug(new MapLogEntry("inviting users to dmp").And("id", id)); logger.debug(new MapLogEntry("inviting users to dmp").And("id", id));
@ -378,7 +431,9 @@ public class DmpController {
@RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml") @RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml")
@Operation(summary = "Export a plan in xml format by id") @Operation(summary = "Export a plan in xml format by id")
public @ResponseBody ResponseEntity<byte[]> getXml(@PathVariable UUID id) throws JAXBException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException, InvalidApplicationException { public @ResponseBody ResponseEntity<byte[]> getXml(
@Parameter(name = "id", description = "The id of a plan to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable UUID id
) throws JAXBException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException, InvalidApplicationException {
logger.debug(new MapLogEntry("export" + Dmp.class.getSimpleName()).And("id", id)); logger.debug(new MapLogEntry("export" + Dmp.class.getSimpleName()).And("id", id));
ResponseEntity<byte[]> response = this.dmpService.exportXml(id); ResponseEntity<byte[]> response = this.dmpService.exportXml(id);
@ -392,7 +447,11 @@ public class DmpController {
@RequestMapping(method = RequestMethod.POST, value = "/xml/import") @RequestMapping(method = RequestMethod.POST, value = "/xml/import")
@Operation(summary = "Import a plan from an xml file") @Operation(summary = "Import a plan from an xml file")
@Transactional @Transactional
public Dmp importXml(@RequestParam("file") MultipartFile file, @RequestParam("label") String label, FieldSet fields) throws JAXBException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException, InvalidApplicationException, TransformerException { public Dmp importXml(
@RequestParam("file") MultipartFile file,
@RequestParam("label") String label,
@Parameter(name = "fields", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fields
) throws JAXBException, ParserConfigurationException, IOException, InstantiationException, IllegalAccessException, SAXException, InvalidApplicationException, TransformerException {
logger.debug(new MapLogEntry("import xml" + Dmp.class.getSimpleName()).And("file", file).And("label", label)); logger.debug(new MapLogEntry("import xml" + Dmp.class.getSimpleName()).And("file", file).And("label", label));
Dmp model = this.dmpService.importXml(file.getBytes(), label, fields); Dmp model = this.dmpService.importXml(file.getBytes(), label, fields);
@ -407,7 +466,13 @@ public class DmpController {
@PostMapping("json/import") @PostMapping("json/import")
@Operation(summary = "Import a plan from an json file") @Operation(summary = "Import a plan from an json file")
@Transactional @Transactional
public Dmp importJson(@RequestParam("file") MultipartFile file, @RequestParam("label") String label, @RequestParam("repositoryId") String repositoryId, @RequestParam("format") String format, FieldSet fields) throws InvalidAlgorithmParameterException, JAXBException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, IOException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { public Dmp importJson(
@RequestParam("file") MultipartFile file,
@RequestParam("label") String label,
@RequestParam("repositoryId") String repositoryId,
@RequestParam("format") String format,
@Parameter(name = "fields", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fields
) throws InvalidAlgorithmParameterException, JAXBException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, IOException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
logger.debug(new MapLogEntry("import json" + Dmp.class.getSimpleName()).And("transformerId", repositoryId).And("file", file).And("label", label)); logger.debug(new MapLogEntry("import json" + Dmp.class.getSimpleName()).And("transformerId", repositoryId).And("file", file).And("label", label));
Dmp model = this.dmpService.importJson(file, label, repositoryId, format, fields); Dmp model = this.dmpService.importJson(file, label, repositoryId, format, fields);
@ -421,171 +486,4 @@ public class DmpController {
return model; return model;
} }
protected static class SwaggerHelpers {
static final String endpoint_query =
"""
This endpoint is used to fetch all the available plans.<br/>
It also allows to restrict the results using a query object passed in the request body.<br/>
""";
static final String endpoint_query_request_body =
"""
Let's explore the options this object gives us.
### <u>General query parameters:</u>
<ul>
<li><b>page:</b>
This is an object controlling the pagination of the results. It contains two properties.
</li>
<ul>
<li><b>offset:</b>
How many records to omit.
</li>
<li><b>size:</b>
How many records to include in each page.
</li>
</ul>
</ul>
For example, if we want the third page, and our pages to contain 15 elements, we would pass the following object:
```JSON
{
"offset": 30,
"size": 15
}
```
<ul>
<li><b>order:</b>
This is an object controlling the ordering of the results.
It contains a list of strings called <i>items</i> with the names of the properties to use.
<br/>If the name of the property is prefixed with a <b>'-'</b>, the ordering direction is <b>DESC</b>. Otherwise, it is <b>ASC</b>.
</li>
</ul>
For example, if we wanted to order based on the field 'createdAt' in descending order, we would pass the following object:
```JSON
{
"items": [
"-createdAt"
],
}
```
<ul>
<li><b>metadata:</b>
This is an object containing metadata for the request. There is only one available option.
<ul>
<li><b>countAll:</b>
If this is set to true, the count property included in the response will account for all the records regardless the pagination,
with all the rest of filtering options applied of course.
Otherwise, if it is set to false or not present, only the returned results will be counted.
<br/>The first option is useful for the UI clients to calculate how many result pages are available.
</li>
</ul>
</li>
<li><b>project:</b>
This is an object controlling the data projection of the results.
It contains a list of strings called <i>fields</i> with the names of the properties to project.
<br/>You can also include properties that are deeper in the object tree by prefixing them with dots.
</li>
</ul>
### <u>Plan specific query parameters:</u>
<ul>
<li><b>like:</b>
If there is a like parameter present in the query, only the description entities that include the contents of the parameter either in their labels or the descriptions will be in the response.
</li>
<li><b>ids:</b>
This is a list and contains the ids we want to include in the response. <br/>If empty, every record is included.
</li>
<li><b>excludedIds:</b>
This is a list and contains the ids we want to exclude from the response. <br/>If empty, no record gets excluded.
</li>
<li><b>groupIds:</b>
This is a list and contains the group ids we want the plans to have. Every plan and all its versions, have the same groupId. <br/>If empty, every record is included.
</li>
<li><b>isActive:</b>
This is a list and determines which records we want to include in the response, based on if they are deleted or not.
This filter works like this. If we want to view only the active records we pass [1] and for only the deleted records we pass [0].
<br/>If not present or if we pass [0,1], every record is included.
</li>
<li><b>statuses:</b>
This is a list and determines which records we want to include in the response, based on their status.
The status can be <i>Draft</i> or <i>Finalized</i>. We add 0 or 1 to the list respectively.
<br/>If not present, every record is included.
</li>
<li><b>versionStatuses:</b>
This is a list and determines which records we want to include in the response, based on their version status.
The status can be <i>Current</i>, <i>Previous</i> or <i>NotFinalized</i>. We add 0, 1 or 2 to the list respectively.
<br/>If not present, every record is included.
</li>
<li><b>accessTypes:</b>
This is a list and determines which records we want to include in the response, based on their access type.
The access type can be <i>Public</i> or <i>Restricted</i>. We add 0 or 1 to the list respectively.
<br/>If not present, every record is included.
</li>
</ul>
""";
static final String endpoint_query_request_body_example =
"""
{
"project":{
"fields":[
"id",
"label",
"description",
"status",
"accessType",
"version",
"versionStatus",
"groupId",
"updatedAt",
"belongsToCurrentTenant",
"finalizedAt",
"hash",
"descriptions.id",
"descriptions.label",
"descriptions.status",
"descriptions.descriptionTemplate.groupId",
"descriptions.isActive",
"blueprint.id",
"blueprint.label",
"blueprint.definition.sections.id"
]
},
"page":{
"size":5,
"offset":0
},
"order":{
"items":[
"-updatedAt"
]
},
"metadata":{
"countAll":true
},
"isActive":[
1
],
"versionStatuses":[
0,
2
]
}
""";
static final String endpoint_ =
"""
""";
}
} }

View File

@ -297,6 +297,14 @@ public class UserController {
return true; return true;
} }
@GetMapping("mine/get-permission/token/{token}")
@Transactional
public Boolean getUserTokenPermission(@PathVariable("token") String token) throws InvalidApplicationException, IOException {
logger.debug(new MapLogEntry("confirm merge account to user").And("token", token));
return this.userTypeService.doesTokenBelongToLoggedInUser(token);
}
@PostMapping("mine/remove-credential-request") @PostMapping("mine/remove-credential-request")
@Transactional @Transactional
@ValidationFilterAnnotation(validator = RemoveCredentialRequestPersist.RemoveCredentialRequestPersistValidator.ValidatorName, argumentName = "model") @ValidationFilterAnnotation(validator = RemoveCredentialRequestPersist.RemoveCredentialRequestPersistValidator.ValidatorName, argumentName = "model")

File diff suppressed because it is too large Load Diff

View File

@ -97,4 +97,7 @@ error-thesaurus:
message: missing contact info for this user message: missing contact info for this user
import-description-without-dmp-description-template: import-description-without-dmp-description-template:
code: 136 code: 136
message: Error creating description without dmp description template message: Error creating description without dmp description template
duplicate-dmp-user:
code: 137
message: Duplicate Dmp User not allowed

View File

@ -958,6 +958,7 @@ permissions:
DeleteLock: DeleteLock:
roles: roles:
- Admin - Admin
- TenantAdmin
dmp: dmp:
roles: roles:
- Owner - Owner

View File

@ -3,12 +3,11 @@ import { RouterModule, Routes } from '@angular/router';
import { AppPermission } from './core/common/enum/permission.enum'; import { AppPermission } from './core/common/enum/permission.enum';
import { BreadcrumbService } from './ui/misc/breadcrumb/breadcrumb.service'; import { BreadcrumbService } from './ui/misc/breadcrumb/breadcrumb.service';
import { ReloadHelperComponent } from './ui/misc/reload-helper/reload-helper.component'; import { ReloadHelperComponent } from './ui/misc/reload-helper/reload-helper.component';
import { DepositOauth2DialogComponent } from './ui/misc/deposit-oauth2-dialog/deposit-oauth2-dialog.component';
const appRoutes: Routes = [ const appRoutes: Routes = [
{ {
path: '', path: '',
redirectTo:'home', redirectTo: 'home',
pathMatch: 'full' pathMatch: 'full'
}, },
{ {
@ -35,7 +34,7 @@ const appRoutes: Routes = [
}, },
{ {
path: 'explore-descriptions', path: 'explore-descriptions',
loadChildren: () => import('./ui/description/description.module').then(m => m.DescriptionModule), loadChildren: () => import('./ui/description/description.module').then(m => m.PublicDescriptionModule),
data: { data: {
breadcrumb: true, breadcrumb: true,
...BreadcrumbService.generateRouteDataConfiguration({ ...BreadcrumbService.generateRouteDataConfiguration({
@ -60,7 +59,7 @@ const appRoutes: Routes = [
}, },
{ {
path: 'explore-plans', path: 'explore-plans',
loadChildren: () => import('./ui/dmp/dmp.module').then(m => m.DmpModule), loadChildren: () => import('./ui/dmp/dmp.module').then(m => m.PublicDmpModule),
data: { data: {
breadcrumb: true, breadcrumb: true,
...BreadcrumbService.generateRouteDataConfiguration({ ...BreadcrumbService.generateRouteDataConfiguration({
@ -183,14 +182,6 @@ const appRoutes: Routes = [
title: 'GENERAL.TITLES.COOKIES-POLICY' title: 'GENERAL.TITLES.COOKIES-POLICY'
} }
}, },
// {
// path: 'splash',
// loadChildren: () => import('./ui/splash/splash.module').then(m => m.SplashModule),
// data: {
// breadcrumb: true
// }
// },
{ {
path: 'unauthorized', path: 'unauthorized',
loadChildren: () => import('./ui/misc/unauthorized/unauthorized.module').then(m => m.UnauthorizedModule), loadChildren: () => import('./ui/misc/unauthorized/unauthorized.module').then(m => m.UnauthorizedModule),
@ -387,8 +378,22 @@ const appRoutes: Routes = [
} }
]; ];
const tenantEnrichedRoutes: Routes = [
{
path: 't/:tenant_code',
data: {
breadcrumb: true,
hideItem: true
},
children: [
...appRoutes
]
},
...appRoutes
];
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(appRoutes, {})], imports: [RouterModule.forRoot(tenantEnrichedRoutes, {})],
exports: [RouterModule], exports: [RouterModule],
}) })
export class AppRoutingModule { } export class AppRoutingModule { }

View File

@ -3,7 +3,7 @@ import { of as observableOf, Subscription } from 'rxjs';
import { switchMap, filter, map, takeUntil } from 'rxjs/operators'; import { switchMap, filter, map, takeUntil } from 'rxjs/operators';
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'; import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { ActivatedRoute, NavigationEnd, NavigationStart, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { AuthService, LoginStatus } from './core/services/auth/auth.service'; import { AuthService, LoginStatus } from './core/services/auth/auth.service';
import { CultureService } from './core/services/culture/culture-service'; import { CultureService } from './core/services/culture/culture-service';
@ -23,6 +23,7 @@ import { TenantConfigurationService } from './core/services/tenant-configuration
import { TenantConfigurationType } from './core/common/enum/tenant-configuration-type'; import { TenantConfigurationType } from './core/common/enum/tenant-configuration-type';
import { CssColorsTenantConfiguration, TenantConfiguration } from './core/model/tenant-configuaration/tenant-configuration'; import { CssColorsTenantConfiguration, TenantConfiguration } from './core/model/tenant-configuaration/tenant-configuration';
import { nameof } from 'ts-simple-nameof'; import { nameof } from 'ts-simple-nameof';
import { TenantHandlingService } from './core/services/tenant/tenant-handling.service';
declare const gapi: any; declare const gapi: any;
@ -43,7 +44,7 @@ export class AppComponent implements OnInit, AfterViewInit {
onlySplash = true; onlySplash = true;
showOnlyRouterOutlet = false; showOnlyRouterOutlet = false;
@ViewChild('sidenav') sidenav:MatSidenav; @ViewChild('sidenav') sidenav: MatSidenav;
constructor( constructor(
private router: Router, private router: Router,
@ -61,7 +62,8 @@ export class AppComponent implements OnInit, AfterViewInit {
private location: Location, private location: Location,
private matomoService: MatomoService, private matomoService: MatomoService,
private tenantConfigurationService: TenantConfigurationService, private tenantConfigurationService: TenantConfigurationService,
private sidenavService: SideNavService private sidenavService: SideNavService,
private tenantHandlingService: TenantHandlingService
) { ) {
this.initializeServices(); this.initializeServices();
this.matomoService.init(); this.matomoService.init();
@ -69,30 +71,30 @@ export class AppComponent implements OnInit, AfterViewInit {
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
setTimeout(() => { setTimeout(() => {
this.sideNavSubscription = this.sidenavService.status().subscribe(isopen=>{ this.sideNavSubscription = this.sidenavService.status().subscribe(isopen => {
const hamburger = document.getElementById('hamburger'); const hamburger = document.getElementById('hamburger');
if(isopen){ if (isopen) {
//update value of hamburfer //update value of hamburfer
if(!hamburger){//try later if (!hamburger) {//try later
setTimeout(() => { setTimeout(() => {
const hamburger = document.getElementById('hamburger'); const hamburger = document.getElementById('hamburger');
if(hamburger){ if (hamburger) {
hamburger.classList.add('change'); hamburger.classList.add('change');
} }
}, 300); }, 300);
}else{ } else {
hamburger.classList.add('change'); hamburger.classList.add('change');
} }
this.sidenav.open() this.sidenav.open()
}else{//closed } else {//closed
if(!hamburger){//try later if (!hamburger) {//try later
setTimeout(() => { setTimeout(() => {
const hamburger = document.getElementById('hamburger'); const hamburger = document.getElementById('hamburger');
if(hamburger){ if (hamburger) {
hamburger.classList.remove('change'); hamburger.classList.remove('change');
} }
}, 300); }, 300);
}else{ } else {
hamburger.classList.remove('change'); hamburger.classList.remove('change');
} }
this.sidenav.close(); this.sidenav.close();
@ -114,7 +116,7 @@ export class AppComponent implements OnInit, AfterViewInit {
if (this.location.path() === '') { if (this.location.path() === '') {
if (!this.configurationService.useSplash) { if (!this.configurationService.useSplash) {
this.onlySplash = false; this.onlySplash = false;
this.router.navigate(['/reload']).then(() => this.router.navigate(['/home'])); this.router.navigate(['/home']);
} else { } else {
this.onlySplash = true; this.onlySplash = true;
this.router.navigate(['/reload']).then(() => this.router.navigate(['/splash'])); this.router.navigate(['/reload']).then(() => this.router.navigate(['/splash']));
@ -126,7 +128,7 @@ export class AppComponent implements OnInit, AfterViewInit {
} }
if (!this.cookieService.check("cookiesConsent")) { if (!this.cookieService.check("cookiesConsent")) {
// this.cookieService.set("cookiesConsent", "false", 356); // this.cookieService.set("cookiesConsent", "false", 356);
this.cookieService.set("cookiesConsent", "false", 356,null,null,false, 'Lax'); this.cookieService.set("cookiesConsent", "false", 356, null, null, false, 'Lax');
} }
@ -171,17 +173,29 @@ export class AppComponent implements OnInit, AfterViewInit {
return { title: child.snapshot.data['title'], usePrefix: usePrefix }; return { title: child.snapshot.data['title'], usePrefix: usePrefix };
} }
} }
return { title: appTitle, usePrefix: true}; return { title: appTitle, usePrefix: true };
}) })
).subscribe((titleOptions: { title: string, usePrefix: boolean}) => { ).subscribe((titleOptions: { title: string, usePrefix: boolean }) => {
this.translateTitle(titleOptions.title, titleOptions.usePrefix); this.translateTitle(titleOptions.title, titleOptions.usePrefix);
this.translate.onLangChange.subscribe(() => this.translateTitle(titleOptions.title, titleOptions.usePrefix)); this.translate.onLangChange.subscribe(() => this.translateTitle(titleOptions.title, titleOptions.usePrefix));
}); });
this.router
.events.pipe(
filter(event => event instanceof NavigationEnd)
)
.subscribe((event: NavigationStart) => {
const enrichedUrl = this.tenantHandlingService.getUrlEnrichedWithTenantCode(event.url, this.authentication.selectedTenant() ?? 'default');
if (event.url != enrichedUrl) {
this.router.navigate([enrichedUrl]);
}
});
this.statusChangeSubscription = this.ccService.statusChange$.subscribe((event: NgcStatusChangeEvent) => { this.statusChangeSubscription = this.ccService.statusChange$.subscribe((event: NgcStatusChangeEvent) => {
if (event.status == "dismiss") { if (event.status == "dismiss") {
// this.cookieService.set("cookiesConsent", "true", 365); // this.cookieService.set("cookiesConsent", "true", 365);
this.cookieService.set("cookiesConsent", "true", 356,null,null,false, 'Lax'); this.cookieService.set("cookiesConsent", "true", 356, null, null, false, 'Lax');
} }
}); });
@ -203,7 +217,7 @@ export class AppComponent implements OnInit, AfterViewInit {
} }
this.ccService.destroy(); this.ccService.destroy();
this.ccService.init(this.ccService.getConfig()); this.ccService.init(this.ccService.getConfig());
}); });
} }
translateTitle(ttl: string, usePrefix: boolean) { translateTitle(ttl: string, usePrefix: boolean) {
@ -226,7 +240,7 @@ export class AppComponent implements OnInit, AfterViewInit {
ngOnDestroy() { ngOnDestroy() {
this.statusChangeSubscription.unsubscribe(); this.statusChangeSubscription.unsubscribe();
if(this.sideNavSubscription){ if (this.sideNavSubscription) {
this.sideNavSubscription.unsubscribe(); this.sideNavSubscription.unsubscribe();
} }
} }
@ -269,24 +283,24 @@ export class AppComponent implements OnInit, AfterViewInit {
private loadCssColors() { private loadCssColors() {
if (this.authentication.currentAccountIsAuthenticated() && this.authentication.selectedTenant()) { if (this.authentication.currentAccountIsAuthenticated() && this.authentication.selectedTenant()) {
this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.CssColors, [ this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.CssColors, [
nameof<TenantConfiguration>(x => x.type), nameof<TenantConfiguration>(x => x.type),
[nameof<TenantConfiguration>(x => x.cssColors), nameof<CssColorsTenantConfiguration>(x => x.primaryColor)].join('.'), [nameof<TenantConfiguration>(x => x.cssColors), nameof<CssColorsTenantConfiguration>(x => x.primaryColor)].join('.'),
[nameof<TenantConfiguration>(x => x.cssColors), nameof<CssColorsTenantConfiguration>(x => x.primaryColor2)].join('.'), [nameof<TenantConfiguration>(x => x.cssColors), nameof<CssColorsTenantConfiguration>(x => x.primaryColor2)].join('.'),
[nameof<TenantConfiguration>(x => x.cssColors), nameof<CssColorsTenantConfiguration>(x => x.primaryColor3)].join('.'), [nameof<TenantConfiguration>(x => x.cssColors), nameof<CssColorsTenantConfiguration>(x => x.primaryColor3)].join('.'),
[nameof<TenantConfiguration>(x => x.cssColors), nameof<CssColorsTenantConfiguration>(x => x.secondaryColor)].join('.'), [nameof<TenantConfiguration>(x => x.cssColors), nameof<CssColorsTenantConfiguration>(x => x.secondaryColor)].join('.'),
]) ])
.pipe(map(data => data as TenantConfiguration)) .pipe(map(data => data as TenantConfiguration))
.subscribe( .subscribe(
data => { data => {
if (data?.cssColors) { if (data?.cssColors) {
if (data.cssColors.primaryColor) document.documentElement.style.setProperty(`--primary-color`, data.cssColors.primaryColor); if (data.cssColors.primaryColor) document.documentElement.style.setProperty(`--primary-color`, data.cssColors.primaryColor);
if (data.cssColors.primaryColor2) document.documentElement.style.setProperty(`--primary-color-2`, data.cssColors.primaryColor2); if (data.cssColors.primaryColor2) document.documentElement.style.setProperty(`--primary-color-2`, data.cssColors.primaryColor2);
if (data.cssColors.primaryColor3) document.documentElement.style.setProperty(`--primary-color-3`, data.cssColors.primaryColor3); if (data.cssColors.primaryColor3) document.documentElement.style.setProperty(`--primary-color-3`, data.cssColors.primaryColor3);
if (data.cssColors.secondaryColor) document.documentElement.style.setProperty(`--secondary-color`, data.cssColors.secondaryColor); if (data.cssColors.secondaryColor) document.documentElement.style.setProperty(`--secondary-color`, data.cssColors.secondaryColor);
} }
}, },
); );
} }
} }

View File

@ -41,6 +41,7 @@ import { CoreAnnotationServiceModule } from 'annotation-service/services/core-se
import { CoreNotificationServiceModule } from '@notification-service/services/core-service.module'; import { CoreNotificationServiceModule } from '@notification-service/services/core-service.module';
import { DepositOauth2DialogModule } from './ui/misc/deposit-oauth2-dialog/deposit-oauth2-dialog.module'; import { DepositOauth2DialogModule } from './ui/misc/deposit-oauth2-dialog/deposit-oauth2-dialog.module';
import { AnalyticsService } from './core/services/matomo/analytics-service'; import { AnalyticsService } from './core/services/matomo/analytics-service';
import { TenantHandlingService } from './core/services/tenant/tenant-handling.service';
// AoT requires an exported function for factories // AoT requires an exported function for factories
export function HttpLoaderFactory(languageHttpService: LanguageHttpService) { export function HttpLoaderFactory(languageHttpService: LanguageHttpService) {
@ -82,7 +83,7 @@ const appearance: MatFormFieldDefaultOptions = {
// appearance: 'standard' // appearance: 'standard'
}; };
export function InstallationConfigurationFactory(appConfig: ConfigurationService, keycloak: KeycloakService, authService: AuthService, languageService: LanguageService) { export function InstallationConfigurationFactory(appConfig: ConfigurationService, keycloak: KeycloakService, authService: AuthService, languageService: LanguageService, tenantHandlingService:TenantHandlingService) {
return () => appConfig.loadConfiguration().then(() => { return () => appConfig.loadConfiguration().then(() => {
return languageService.loadAvailableLanguages().toPromise(); return languageService.loadAvailableLanguages().toPromise();
}).then(x => keycloak.init({ }).then(x => keycloak.init({
@ -109,77 +110,81 @@ export function InstallationConfigurationFactory(appConfig: ConfigurationService
InterceptorType.UnauthorizedResponse, InterceptorType.UnauthorizedResponse,
] ]
}; };
const tenantCode = tenantHandlingService.extractTenantCodeFromUrlPath(window.location.pathname) ?? authService.selectedTenant() ?? 'default';
const tokenPromise = keycloak.getToken(); const tokenPromise = keycloak.getToken();
return authService.prepareAuthRequest(from(tokenPromise), { params }).toPromise().catch(error => authService.onAuthenticateError(error)); return authService.prepareAuthRequest(from(tokenPromise), tenantCode, { params }).toPromise().catch(error => authService.onAuthenticateError(error));
})); }));
} }
@NgModule({ declarations: [ @NgModule({
AppComponent, declarations: [
ReloadHelperComponent AppComponent,
], ReloadHelperComponent
bootstrap: [AppComponent], imports: [BrowserModule, ],
BrowserAnimationsModule, bootstrap: [AppComponent], imports: [BrowserModule,
KeycloakAngularModule, BrowserAnimationsModule,
CoreServiceModule.forRoot(), KeycloakAngularModule,
CoreAnnotationServiceModule.forRoot(), CoreServiceModule.forRoot(),
CoreNotificationServiceModule.forRoot(), CoreAnnotationServiceModule.forRoot(),
AppRoutingModule, CoreNotificationServiceModule.forRoot(),
CommonUiModule, AppRoutingModule,
TranslateModule.forRoot({ CommonUiModule,
compiler: { provide: TranslateCompiler, useClass: OpenDMPCustomTranslationCompiler }, TranslateModule.forRoot({
loader: { compiler: { provide: TranslateCompiler, useClass: OpenDMPCustomTranslationCompiler },
provide: TranslateLoader, loader: {
useFactory: HttpLoaderFactory, provide: TranslateLoader,
deps: [LanguageHttpService] useFactory: HttpLoaderFactory,
} deps: [LanguageHttpService]
}), }
OverlayModule, }),
CommonHttpModule, OverlayModule,
MatMomentDateModule, CommonHttpModule,
LoginModule, MatMomentDateModule,
//Ui LoginModule,
NotificationModule, //Ui
// BreadcrumbModule, NotificationModule,
ReactiveFormsModule, // BreadcrumbModule,
FormsModule, ReactiveFormsModule,
NavbarModule, FormsModule,
SidebarModule, NavbarModule,
NgcCookieConsentModule.forRoot(cookieConfig), SidebarModule,
DepositOauth2DialogModule, NgcCookieConsentModule.forRoot(cookieConfig),
GuidedTourModule.forRoot(), DepositOauth2DialogModule,
DragulaModule.forRoot(), GuidedTourModule.forRoot(),
NgxMatomoModule.forRoot({ DragulaModule.forRoot(),
mode: MatomoInitializationMode.AUTO_DEFERRED, NgxMatomoModule.forRoot({
})], providers: [ mode: MatomoInitializationMode.AUTO_DEFERRED,
ConfigurationService, })], providers: [
{ ConfigurationService,
provide: APP_INITIALIZER, {
useFactory: InstallationConfigurationFactory, provide: APP_INITIALIZER,
deps: [ConfigurationService, KeycloakService, AuthService, LanguageService], useFactory: InstallationConfigurationFactory,
multi: true deps: [ConfigurationService, KeycloakService, AuthService, LanguageService, TenantHandlingService],
}, multi: true
{ },
provide: MAT_DATE_LOCALE, {
deps: [CultureService], provide: MAT_DATE_LOCALE,
useFactory: (cultureService) => cultureService.getCurrentCulture().name deps: [CultureService],
}, useFactory: (cultureService) => cultureService.getCurrentCulture().name
{ provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS }, },
{ provide: DateAdapter, useClass: MomentUtcDateAdapter }, { provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS },
{ { provide: DateAdapter, useClass: MomentUtcDateAdapter },
provide: LOCALE_ID, {
deps: [CultureService], provide: LOCALE_ID,
useFactory: (cultureService) => cultureService.getCurrentCulture().name deps: [CultureService],
}, useFactory: (cultureService) => cultureService.getCurrentCulture().name
{ },
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, {
useValue: appearance provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
}, useValue: appearance
Title, },
CookieService, Title,
MatomoService, CookieService,
AnalyticsService, MatomoService,
provideHttpClient(withInterceptorsFromDi()) AnalyticsService,
] }) provideHttpClient(withInterceptorsFromDi())
]
})
export class AppModule { } export class AppModule { }

View File

@ -34,6 +34,7 @@ export enum ResponseErrorCode {
DmpInactiveUser = 134, DmpInactiveUser = 134,
DmpMissingUserContactInfo = 135, DmpMissingUserContactInfo = 135,
ImportDescriptionWithoutDmpDescriptionTemplate = 136, ImportDescriptionWithoutDmpDescriptionTemplate = 136,
DuplicateDmpUser = 137,
// Notification & Annotation Errors // Notification & Annotation Errors
InvalidApiKey = 200, InvalidApiKey = 200,
@ -116,6 +117,8 @@ export class ResponseErrorCodeHelper {
return language.instant("GENERAL.BACKEND-ERRORS.IMPORT-DESCRIPTION-WITHOUT-DMP-DESCRIPTION-TEMPLATE"); return language.instant("GENERAL.BACKEND-ERRORS.IMPORT-DESCRIPTION-WITHOUT-DMP-DESCRIPTION-TEMPLATE");
case ResponseErrorCode.InvalidApiKey: case ResponseErrorCode.InvalidApiKey:
return language.instant("GENERAL.BACKEND-ERRORS.INVALID-API-KEY"); return language.instant("GENERAL.BACKEND-ERRORS.INVALID-API-KEY");
case ResponseErrorCode.DuplicateDmpUser:
return language.instant("GENERAL.BACKEND-ERRORS.DUPLICATE-DMP-USER");
case ResponseErrorCode.StaleApiKey: case ResponseErrorCode.StaleApiKey:
return language.instant("GENERAL.BACKEND-ERRORS.STALE-API-KEY"); return language.instant("GENERAL.BACKEND-ERRORS.STALE-API-KEY");
case ResponseErrorCode.SensitiveInfo: case ResponseErrorCode.SensitiveInfo:

View File

@ -45,6 +45,7 @@ import { VisibilityRulesService } from '@app/ui/description/editor/description-f
import { StorageFileService } from './services/storage-file/storage-file.service'; import { StorageFileService } from './services/storage-file/storage-file.service';
import { TenantConfigurationService } from './services/tenant-configuration/tenant-configuration.service'; import { TenantConfigurationService } from './services/tenant-configuration/tenant-configuration.service';
import { DefaultUserLocaleService } from './services/default-user-locale/default-user-locale.service'; import { DefaultUserLocaleService } from './services/default-user-locale/default-user-locale.service';
import { TenantHandlingService } from './services/tenant/tenant-handling.service';
// //
// //
// This is shared module that provides all the services. Its imported only once on the AppModule. // This is shared module that provides all the services. Its imported only once on the AppModule.
@ -109,7 +110,8 @@ export class CoreServiceModule {
PrefillingSourceService, PrefillingSourceService,
VisibilityRulesService, VisibilityRulesService,
TenantConfigurationService, TenantConfigurationService,
StorageFileService StorageFileService,
TenantHandlingService
], ],
}; };
} }

View File

@ -154,11 +154,11 @@ export class AuthService extends BaseService {
public isLoggedIn(): boolean { public isLoggedIn(): boolean {
return this.authState(); return this.authState();
} }
public prepareAuthRequest(observable: Observable<string>, httpParams?: Object): Observable<boolean> { public prepareAuthRequest(observable: Observable<string>, tenantCode: string, httpParams?: Object): Observable<boolean> {
return observable.pipe( return observable.pipe(
map((x) => this.currentAuthenticationToken(x)), map((x) => this.currentAuthenticationToken(x)),
exhaustMap(() => forkJoin([ exhaustMap(() => forkJoin([
this.accessToken ? this.ensureTenant() : of(false), this.accessToken ? this.ensureTenant(tenantCode ?? this.selectedTenant() ?? 'default') : of(false),
this.accessToken ? this.principalService.me(httpParams) : of(null), this.accessToken ? this.principalService.me(httpParams) : of(null),
])), ])),
map((item) => { map((item) => {
@ -176,10 +176,7 @@ export class AuthService extends BaseService {
); );
} }
public ensureTenant(): Observable<string> { private ensureTenant(tenantCode: string): Observable<string> {
if (!this.selectedTenant()) {
this.selectedTenant('default');
}
const params = new BaseHttpParams(); const params = new BaseHttpParams();
params.interceptorContext = { params.interceptorContext = {
excludedInterceptors: [InterceptorType.TenantHeaderInterceptor] excludedInterceptors: [InterceptorType.TenantHeaderInterceptor]
@ -188,10 +185,10 @@ export class AuthService extends BaseService {
map( map(
(myTenants) => { (myTenants) => {
if (myTenants) { if (myTenants) {
if (this.selectedTenant()) { if (myTenants.some(x => x.code.toLocaleLowerCase() == tenantCode.toLocaleLowerCase())) {
if (myTenants.findIndex(x => x.code.toLocaleLowerCase() == this.selectedTenant().toLocaleLowerCase()) < 0) { this.selectedTenant(tenantCode);
this.selectedTenant(null); } else {
} this.selectedTenant(null);
} }
if (!this.selectedTenant()) { if (!this.selectedTenant()) {
if (myTenants.length > 0) { if (myTenants.length > 0) {
@ -326,6 +323,7 @@ export class AuthService extends BaseService {
return this.prepareAuthRequest( return this.prepareAuthRequest(
from(this.keycloakService.getToken()), from(this.keycloakService.getToken()),
this.selectedTenant(),
httpParams httpParams
) )
.pipe(takeUntil(this._destroyed)) .pipe(takeUntil(this._destroyed))

View File

@ -57,21 +57,34 @@ export class CultureService {
// Set angular locale based on user selection. // Set angular locale based on user selection.
// This is a very hacky way to map cultures with angular cultures, since there is no mapping. We first try to // This is a very hacky way to map cultures with angular cultures, since there is no mapping. We first try to
// use the culture with the specialization (ex en-US), and if not exists we import the base culture (first part). // use the culture with the specialization (ex en-US), and if not exists we import the base culture (first part).
let locale = newCulture.name; // let locale = newCulture.name;
import(`/node_modules/@angular/common/locales/${locale}.mjs`).catch(reason => { // const base = import(
this.logger.error('Could not load locale: ' + locale); // /* webpackExclude: /\.d\.ts$/ */
}).then(selectedLocale => { // /* webpackMode: "lazy-once" */
if (selectedLocale) { // /* webpackChunkName: "i18n-base" */
registerLocaleData(selectedLocale.default); // `@angular/common/locales/${locale}.mjs`)//.then(m => m[basePkg]);
} else {
locale = newCulture.name.split('-')[0]; // const extra = import(
import(`/node_modules/@angular/common/locales/${locale}.mjs`).catch(reason => { // /* webpackExclude: /\.d\.ts$/ */
this.logger.error('Could not load locale: ' + locale); // /* webpackMode: "lazy-once" */
}).then(selectedDefaultLocale => { // /* webpackChunkName: "i18n-extra" */
registerLocaleData(selectedDefaultLocale.default); // `@angular/common/locales/extra/${locale.split('-')[0]}.mjs`)//.then(m => m[extraPkg]);
});
}
}); // import(`/node_modules/ @angular/common/locales/${locale}.mjs`).catch(reason => {
// this.logger.error('Could not load locale: ' + locale);
// }).then(selectedLocale => {
// if (selectedLocale) {
// registerLocaleData(selectedLocale.default);
// } else {
// locale = newCulture.name.split('-')[0];
// import(`/node_modules/@angular/common/locales/${locale}.mjs`).catch(reason => {
// this.logger.error('Could not load locale: ' + locale);
// }).then(selectedDefaultLocale => {
// registerLocaleData(selectedDefaultLocale.default);
// });
// }
// });
} }
getCultureChangeObservable(): Observable<CultureInfo> { getCultureChangeObservable(): Observable<CultureInfo> {

View File

@ -148,9 +148,9 @@ export class DescriptionTemplateService {
// //
// tslint:disable-next-line: member-ordering // tslint:disable-next-line: member-ordering
descriptionTempalteGroupSingleAutocompleteConfiguration: SingleAutoCompleteConfiguration = { descriptionTempalteGroupSingleAutocompleteConfiguration: SingleAutoCompleteConfiguration = {
initialItems: (data?: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup()).pipe(map(x => x.items)), initialItems: (data?: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active])).pipe(map(x => x.items)),
filterFn: (searchQuery: string, data?: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup(searchQuery)).pipe(map(x => x.items)), filterFn: (searchQuery: string, data?: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active], searchQuery)).pipe(map(x => x.items)),
getSelectedItem: (selectedItem: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup(null, null, [selectedItem])).pipe(map(x => x.items[0])), getSelectedItem: (selectedItem: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active, IsActive.Inactive], null, null, [selectedItem])).pipe(map(x => x.items[0])),
displayFn: (item: DescriptionTemplate) => item.label, displayFn: (item: DescriptionTemplate) => item.label,
titleFn: (item: DescriptionTemplate) => item.label, titleFn: (item: DescriptionTemplate) => item.label,
subtitleFn: (item: DescriptionTemplate) => item.description, subtitleFn: (item: DescriptionTemplate) => item.description,
@ -160,9 +160,9 @@ export class DescriptionTemplateService {
// tslint:disable-next-line: member-ordering // tslint:disable-next-line: member-ordering
descriptionTempalteGroupMultipleAutocompleteConfiguration: MultipleAutoCompleteConfiguration = { descriptionTempalteGroupMultipleAutocompleteConfiguration: MultipleAutoCompleteConfiguration = {
initialItems: (excludedItems: any[], data?: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup(null, excludedItems ? excludedItems : null)).pipe(map(x => x.items)), initialItems: (excludedItems: any[], data?: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active], null, excludedItems ? excludedItems : null)).pipe(map(x => x.items)),
filterFn: (searchQuery: string, excludedItems: any[]) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup(searchQuery, excludedItems)).pipe(map(x => x.items)), filterFn: (searchQuery: string, excludedItems: any[]) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active], searchQuery, excludedItems)).pipe(map(x => x.items)),
getSelectedItems: (selectedItems: any[]) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup(null, null, selectedItems)).pipe(map(x => x.items)), getSelectedItems: (selectedItems: any[]) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active, IsActive.Inactive], null, null, selectedItems)).pipe(map(x => x.items)),
displayFn: (item: DescriptionTemplate) => item.label, displayFn: (item: DescriptionTemplate) => item.label,
titleFn: (item: DescriptionTemplate) => item.label, titleFn: (item: DescriptionTemplate) => item.label,
subtitleFn: (item: DescriptionTemplate) => item.description, subtitleFn: (item: DescriptionTemplate) => item.description,
@ -170,14 +170,14 @@ export class DescriptionTemplateService {
popupItemActionIcon: 'visibility' popupItemActionIcon: 'visibility'
}; };
public buildDescriptionTempalteGroupAutocompleteLookup(like?: string, excludedIds?: Guid[], groupIds?: Guid[], excludedGroupIds?: Guid[]): DescriptionTemplateLookup { public buildDescriptionTempalteGroupAutocompleteLookup(isActive: IsActive[], like?: string, excludedIds?: Guid[], groupIds?: Guid[], excludedGroupIds?: Guid[]): DescriptionTemplateLookup {
const lookup: DescriptionTemplateLookup = new DescriptionTemplateLookup(); const lookup: DescriptionTemplateLookup = new DescriptionTemplateLookup();
lookup.page = { size: 100, offset: 0 }; lookup.page = { size: 100, offset: 0 };
if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; } if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; }
if (groupIds && groupIds.length > 0) { lookup.groupIds = groupIds; } if (groupIds && groupIds.length > 0) { lookup.groupIds = groupIds; }
if (excludedGroupIds && excludedGroupIds.length > 0) { lookup.excludedGroupIds = excludedGroupIds; } if (excludedGroupIds && excludedGroupIds.length > 0) { lookup.excludedGroupIds = excludedGroupIds; }
lookup.isActive = [IsActive.Active]; lookup.isActive = isActive;
lookup.versionStatuses = [DescriptionTemplateVersionStatus.Current, DescriptionTemplateVersionStatus.NotFinalized]; lookup.versionStatuses = [DescriptionTemplateVersionStatus.Current, DescriptionTemplateVersionStatus.NotFinalized];
lookup.statuses = [DescriptionTemplateStatus.Finalized]; lookup.statuses = [DescriptionTemplateStatus.Finalized];
lookup.project = { lookup.project = {

View File

@ -0,0 +1,58 @@
import { DOCUMENT, LocationStrategy } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { PRIMARY_OUTLET, Router, UrlSegment, UrlSegmentGroup, UrlTree } from '@angular/router';
@Injectable()
export class TenantHandlingService {
constructor(
@Inject(DOCUMENT) private readonly document: Document,
private readonly locationStrategy: LocationStrategy,
private readonly router: Router,
) {
}
extractTenantCodeFromUrlPath(path: string): string {
//Searches for "/t/<tenant_code>/" in a url;
const tenantRegex = new RegExp("\/t\/([^\/]+)");
const regexResult = tenantRegex.exec(path);
let tenantCode = null;
if (Array.isArray(regexResult) && regexResult.length > 0) {
tenantCode = regexResult[1];
}
return tenantCode;
}
getCurrentUrlEnrichedWithTenantCode(tenantCode: string, withOrigin: boolean) {
const path = this.getUrlEnrichedWithTenantCode(this.router.routerState.snapshot.url, tenantCode)
return withOrigin ? this.getBaseUrl() + path.substring(1) : path;
}
getUrlEnrichedWithTenantCode(url: string, tenantCode: string) {
const urlTree: UrlTree = this.router.parseUrl(url);
const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
const urlSegments: UrlSegment[] = urlSegmentGroup.segments;
const tenantParamIndex = urlSegments.findIndex(x => x.path == 't');
if (tenantParamIndex >= 0) {
if (tenantCode == 'default') {
urlSegments.splice(tenantParamIndex, 2);
} else {
const tenantCodeSegment = urlSegments.at(tenantParamIndex + 1);
tenantCodeSegment.path = tenantCode;
}
} else {
if (tenantCode != 'default') {
urlTree.root.children[PRIMARY_OUTLET].segments = [new UrlSegment('t', {}), new UrlSegment(tenantCode, {}), ...urlSegments];
}
}
return urlTree.toString();
}
getBaseUrl(): string {
return this.document.location.origin + this.locationStrategy.getBaseHref();
}
}

View File

@ -117,6 +117,14 @@ export class UserService {
catchError((error: any) => throwError(error))); catchError((error: any) => throwError(error)));
} }
getUserTokenPermission(token: Guid): Observable<boolean> {
const url = `${this.apiBase}/mine/get-permission/token/${token}`;
return this.http
.get<boolean>(url).pipe(
catchError((error: any) => throwError(error)));
}
confirmMergeAccount(token: Guid): Observable<boolean> { confirmMergeAccount(token: Guid): Observable<boolean> {
const url = `${this.apiBase}/mine/confirm-merge-account/token/${token}`; const url = `${this.apiBase}/mine/confirm-merge-account/token/${token}`;

View File

@ -76,7 +76,7 @@ export class DescriptionTemplatePreviewDialogComponent extends BaseComponent imp
} }
buildForm() { buildForm() {
this.formGroup = this.editorModel.buildForm(null, true); this.formGroup = this.editorModel.buildForm(null, true, this.visibilityRulesService);
this.previewPropertiesFormGroup = this.editorModel.properties.buildForm() as UntypedFormGroup; this.previewPropertiesFormGroup = this.editorModel.properties.buildForm() as UntypedFormGroup;
this.visibilityRulesService.setContext(this.descriptionTemplate.definition, this.previewPropertiesFormGroup); this.visibilityRulesService.setContext(this.descriptionTemplate.definition, this.previewPropertiesFormGroup);
} }

View File

@ -31,13 +31,13 @@
<div style="position: relative;" class="col-12" *ngIf="hasFocus" [@fade-in]> <div style="position: relative;" class="col-12" *ngIf="hasFocus" [@fade-in]>
<div *ngIf="showDescription" class="mb-4"> <div *ngIf="showDescription" class="mb-4">
<h5 style="font-weight: bold" class="row">{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.DESCRIPTION' | translate}}</h5> <h5 style="font-weight: bold" class="row">{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.DESCRIPTION' | translate}}</h5>
<rich-text-editor-component [form]="form.get('description')" [id]="'editor1'" [placeholder]="'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.DESCRIPTION'" [wrapperClasses]="'row'" [editable]="!viewOnly"> <rich-text-editor-component [form]="form.get('description')" [placeholder]="'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.DESCRIPTION'" [wrapperClasses]="'row'" [editable]="!viewOnly">
</rich-text-editor-component> </rich-text-editor-component>
<mat-error *ngIf="this.form.get('description').hasError('backendError')">{{form.get('description').getError('backendError').message}}</mat-error> <mat-error *ngIf="this.form.get('description').hasError('backendError')">{{form.get('description').getError('backendError').message}}</mat-error>
</div> </div>
<div *ngIf="showExtendedDescription" class="mb-4"> <div *ngIf="showExtendedDescription" class="mb-4">
<h5 style="font-weight: bold" class="row">{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.EXTENDED-DESCRIPTION' | translate}}</h5> <h5 style="font-weight: bold" class="row">{{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.EXTENDED-DESCRIPTION' | translate}}</h5>
<rich-text-editor-component [form]="form.get('extendedDescription')" [id]="'editor2'" [placeholder]="'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.EXTENDED-DESCRIPTION'" [wrapperClasses]="'row'" [editable]="!viewOnly"> <rich-text-editor-component [form]="form.get('extendedDescription')" [placeholder]="'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.COMPOSITE-FIELD.FIELDS.EXTENDED-DESCRIPTION'" [wrapperClasses]="'row'" [editable]="!viewOnly">
</rich-text-editor-component> </rich-text-editor-component>
<mat-error *ngIf="this.form.get('extendedDescription').hasError('backendError')">{{form.get('extendedDescription').getError('backendError').message}}</mat-error> <mat-error *ngIf="this.form.get('extendedDescription').hasError('backendError')">{{form.get('extendedDescription').getError('backendError').message}}</mat-error>
</div> </div>

View File

@ -72,9 +72,9 @@ export class DmpBlueprintEditorComponent extends BaseEditor<DmpBlueprintEditorMo
public dmpBlueprintExtraFieldDataTypeEnum = this.enumUtils.getEnumValues<DmpBlueprintExtraFieldDataType>(DmpBlueprintExtraFieldDataType); public dmpBlueprintExtraFieldDataTypeEnum = this.enumUtils.getEnumValues<DmpBlueprintExtraFieldDataType>(DmpBlueprintExtraFieldDataType);
public dmpBlueprintFieldCategoryEnum = this.enumUtils.getEnumValues<DmpBlueprintFieldCategory>(DmpBlueprintFieldCategory); public dmpBlueprintFieldCategoryEnum = this.enumUtils.getEnumValues<DmpBlueprintFieldCategory>(DmpBlueprintFieldCategory);
descriptionTempalteGroupSingleAutocompleteConfiguration: SingleAutoCompleteConfiguration = { descriptionTempalteGroupSingleAutocompleteConfiguration: SingleAutoCompleteConfiguration = {
initialItems: (data?: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup(null, null, null, this.getUsedDescriptionTemplateGroupIds())).pipe(map(x => x.items)), initialItems: (data?: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active], null, null, null, this.getUsedDescriptionTemplateGroupIds())).pipe(map(x => x.items)),
filterFn: (searchQuery: string, data?: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup(searchQuery, null, null, this.getUsedDescriptionTemplateGroupIds() ? this.getUsedDescriptionTemplateGroupIds() : null)).pipe(map(x => x.items)), filterFn: (searchQuery: string, data?: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active], searchQuery, null, null, this.getUsedDescriptionTemplateGroupIds() ? this.getUsedDescriptionTemplateGroupIds() : null)).pipe(map(x => x.items)),
getSelectedItem: (selectedItem: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup(null, null, [selectedItem])).pipe(map(x => x.items[0])), getSelectedItem: (selectedItem: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active, IsActive.Inactive], null, null, [selectedItem])).pipe(map(x => x.items[0])),
displayFn: (item: DescriptionTemplate) => item.label, displayFn: (item: DescriptionTemplate) => item.label,
titleFn: (item: DescriptionTemplate) => item.label, titleFn: (item: DescriptionTemplate) => item.label,
subtitleFn: (item: DescriptionTemplate) => item.description, subtitleFn: (item: DescriptionTemplate) => item.description,

View File

@ -88,7 +88,7 @@
<mat-icon>more_horiz</mat-icon> <mat-icon>more_horiz</mat-icon>
</button> </button>
<mat-menu #actionsMenu="matMenu"> <mat-menu #actionsMenu="matMenu">
<button mat-menu-item [routerLink]="['/dmp-blueprints/', row.id]"> <button *ngIf="(row.status != null && row.status === dmpBlueprintStatuses.Draft)" mat-menu-item [routerLink]="['/dmp-blueprints/', row.id]">
<mat-icon>edit</mat-icon>{{'DMP-BLUEPRINT-LISTING.ACTIONS.EDIT' | translate}} <mat-icon>edit</mat-icon>{{'DMP-BLUEPRINT-LISTING.ACTIONS.EDIT' | translate}}
</button> </button>
<button *ngIf="row.belongsToCurrentTenant != false && (row.status === dmpBlueprintStatuses.Finalized || row.status == null)" mat-menu-item [routerLink]="['/dmp-blueprints/new-version' , row.id]"> <button *ngIf="row.belongsToCurrentTenant != false && (row.status === dmpBlueprintStatuses.Finalized || row.status == null)" mat-menu-item [routerLink]="['/dmp-blueprints/new-version' , row.id]">

View File

@ -1,10 +1,8 @@
import { Component, Input, NgZone, OnInit } from '@angular/core'; import { Component, Input, NgZone, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { AuthService } from '@app/core/services/auth/auth.service'; import { AuthService } from '@app/core/services/auth/auth.service';
import { PrincipalService } from '@app/core/services/http/principal.service'; import { TenantHandlingService } from '@app/core/services/tenant/tenant-handling.service';
import { BaseComponent } from '@common/base/base.component'; import { BaseComponent } from '@common/base/base.component';
import { BaseHttpParams } from '@common/http/base-http-params';
import { InterceptorType } from '@common/http/interceptors/interceptor-type';
import { KeycloakService } from 'keycloak-angular'; import { KeycloakService } from 'keycloak-angular';
import { from } from 'rxjs'; import { from } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -27,7 +25,7 @@ export class LoginComponent extends BaseComponent implements OnInit {
private router: Router, private router: Router,
private authService: AuthService, private authService: AuthService,
private route: ActivatedRoute, private route: ActivatedRoute,
private principalService: PrincipalService, private tenantHandlingService: TenantHandlingService,
private keycloakService: KeycloakService private keycloakService: KeycloakService
) { super(); } ) { super(); }
@ -36,7 +34,8 @@ export class LoginComponent extends BaseComponent implements OnInit {
if (!this.keycloakService.isLoggedIn()) { if (!this.keycloakService.isLoggedIn()) {
this.authService.authenticate(this.returnUrl); this.authService.authenticate(this.returnUrl);
} else { } else {
this.authService.prepareAuthRequest(from(this.keycloakService.getToken())).pipe(takeUntil(this._destroyed)).subscribe( const tenantCode = this.tenantHandlingService.extractTenantCodeFromUrlPath(this.returnUrl) ?? this.authService.selectedTenant() ?? 'default';
this.authService.prepareAuthRequest(from(this.keycloakService.getToken()), tenantCode).pipe(takeUntil(this._destroyed)).subscribe(
() => { () => {
let returnUrL = this.returnUrl; let returnUrL = this.returnUrl;
this.zone.run(() => this.router.navigateByUrl(returnUrL)); this.zone.run(() => this.router.navigateByUrl(returnUrL));

View File

@ -4,7 +4,7 @@
<div class="col merge-account-title">{{'MERGE-ACCOUNT.TITLE' | translate}}</div> <div class="col merge-account-title">{{'MERGE-ACCOUNT.TITLE' | translate}}</div>
</div> </div>
<div *ngIf="showForm" class="row merge-account-content"> <div *ngIf="showForm" class="row merge-account-content">
<div class="col"> <div *ngIf="isTokenValid" class="col">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-auto"> <div class="col-auto">
<span> <span>
@ -20,6 +20,9 @@
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="!isTokenValid" class="col">
<span>{{'MERGE-ACCOUNT.MESSAGES.INVALID-TOKEN' | translate}}</span>
</div>
</div> </div>
<ng-template #loading> <ng-template #loading>
</ng-template> </ng-template>

View File

@ -16,6 +16,8 @@ import { takeUntil } from "rxjs/operators";
}) })
export class MergeEmailConfirmation extends BaseComponent implements OnInit { export class MergeEmailConfirmation extends BaseComponent implements OnInit {
isTokenValid: boolean = false;
private token: Guid; private token: Guid;
get showForm(): boolean { get showForm(): boolean {
@ -37,9 +39,19 @@ export class MergeEmailConfirmation extends BaseComponent implements OnInit {
.subscribe(params => { .subscribe(params => {
const token = params['token'] const token = params['token']
if (token != null) { if (token != null) {
this.token = token; this.userService.getUserTokenPermission(token)
.subscribe(result => {
this.isTokenValid = result
this.token = token;
});
} }
},
error => {
this.isTokenValid = false;
this.token = Guid.createEmpty();
this.onCallbackError(error);
}); });
} }
onConfirm(): void { onConfirm(): void {
@ -64,7 +76,7 @@ export class MergeEmailConfirmation extends BaseComponent implements OnInit {
onCallbackError(errorResponse: HttpErrorResponse) { onCallbackError(errorResponse: HttpErrorResponse) {
const errorOverrides = new Map<number, string>(); const errorOverrides = new Map<number, string>();
errorOverrides.set(302, this.language.instant('EMAIL-CONFIRMATION.EMAIL-FOUND')); errorOverrides.set(302, this.language.instant('EMAIL-CONFIRMATION.EMAIL-FOUND'));
errorOverrides.set(-1, this.language.instant('EMAIL-CONFIRMATION.EXPIRED-EMAIL')); errorOverrides.set(403, this.language.instant('EMAIL-CONFIRMATION.EXPIRED-EMAIL'));
this.httpErrorHandlingService.handleBackedRequestError(errorResponse, errorOverrides, SnackBarNotificationLevel.Error) this.httpErrorHandlingService.handleBackedRequestError(errorResponse, errorOverrides, SnackBarNotificationLevel.Error)
const error: HttpError = this.httpErrorHandlingService.getError(errorResponse); const error: HttpError = this.httpErrorHandlingService.getError(errorResponse);

View File

@ -1,5 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '@app/core/services/auth/auth.service'; import { AuthService } from '@app/core/services/auth/auth.service';
import { KeycloakService } from 'keycloak-angular'; import { KeycloakService } from 'keycloak-angular';
@ -11,7 +10,7 @@ export class LogoutComponent implements OnInit {
constructor( constructor(
private keycloak: KeycloakService, private keycloak: KeycloakService,
private authService: AuthService, private authService: AuthService,
) {} ) { }
ngOnInit() { ngOnInit() {
this.authService.clear(); this.authService.clear();
@ -19,9 +18,5 @@ export class LogoutComponent implements OnInit {
localStorage.clear(); localStorage.clear();
// this.router.navigate(['./'], { replaceUrl: true }); // this.router.navigate(['./'], { replaceUrl: true });
}); });
// this.tokenService.logout(() => {
// localStorage.clear();
// this.router.navigate(["./"], { replaceUrl: true });
// });
} }
} }

View File

@ -1,6 +1,6 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormattingModule } from '@app/core/formatting.module'; import { FormattingModule } from '@app/core/formatting.module';
import { DescriptionRoutingModule } from '@app/ui/description/description.routing'; import { DescriptionRoutingModule, PublicDescriptionRoutingModule } from '@app/ui/description/description.routing';
import { CommonFormsModule } from '@common/forms/common-forms.module'; import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module'; import { CommonUiModule } from '@common/ui/common-ui.module';
@ -17,3 +17,17 @@ import { CommonUiModule } from '@common/ui/common-ui.module';
] ]
}) })
export class DescriptionModule { } export class DescriptionModule { }
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
FormattingModule,
PublicDescriptionRoutingModule,
],
declarations: [
],
exports: [
]
})
export class PublicDescriptionModule { }

View File

@ -1,13 +1,13 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@app/core/auth-guard.service';
import { BreadcrumbService } from '../misc/breadcrumb/breadcrumb.service'; import { BreadcrumbService } from '../misc/breadcrumb/breadcrumb.service';
// import { DescriptionWizardComponent } from './description-wizard/description-wizard.component';
// import { DescriptionOverviewComponent } from './overview/description-overview.component';
const routes: Routes = [ const routes: Routes = [
{ {
path: 'overview', path: 'overview',
loadChildren: () => import('./overview/description-overview.module').then(m => m.DescriptionOverviewModule), loadChildren: () => import('./overview/description-overview.module').then(m => m.DescriptionOverviewModule),
canActivate: [AuthGuard],
data: { data: {
breadcrumb: true, breadcrumb: true,
...BreadcrumbService.generateRouteDataConfiguration({ ...BreadcrumbService.generateRouteDataConfiguration({
@ -18,6 +18,28 @@ const routes: Routes = [
{ {
path: 'edit', path: 'edit',
loadChildren: () => import('./editor/description-editor.module').then(m => m.DescriptionEditorModule), loadChildren: () => import('./editor/description-editor.module').then(m => m.DescriptionEditorModule),
canActivate: [AuthGuard],
data: {
breadcrumb: true,
...BreadcrumbService.generateRouteDataConfiguration({
hideNavigationItem: true
}),
}
},
{
path: '',
canActivate: [AuthGuard],
loadChildren: () => import('./listing/description-listing.module').then(m => m.DescriptionListingModule),
data: {
breadcrumb: true
},
},
];
const publicRoutes: Routes = [
{
path: 'overview',
loadChildren: () => import('./overview/description-overview.module').then(m => m.DescriptionOverviewModule),
data: { data: {
breadcrumb: true, breadcrumb: true,
...BreadcrumbService.generateRouteDataConfiguration({ ...BreadcrumbService.generateRouteDataConfiguration({
@ -40,3 +62,10 @@ const routes: Routes = [
providers: [] providers: []
}) })
export class DescriptionRoutingModule { } export class DescriptionRoutingModule { }
@NgModule({
imports: [RouterModule.forChild(publicRoutes)],
exports: [RouterModule],
providers: []
})
export class PublicDescriptionRoutingModule { }

View File

@ -220,7 +220,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
permissionPerSection => { permissionPerSection => {
this.canEdit = permissionPerSection && permissionPerSection[this.item.dmpDescriptionTemplate.sectionId.toString()] && permissionPerSection[this.item.dmpDescriptionTemplate.sectionId.toString()].some(x => x === AppPermission.EditDescription); this.canEdit = permissionPerSection && permissionPerSection[this.item.dmpDescriptionTemplate.sectionId.toString()] && permissionPerSection[this.item.dmpDescriptionTemplate.sectionId.toString()].some(x => x === AppPermission.EditDescription);
this.canReview = permissionPerSection && permissionPerSection[this.item.dmpDescriptionTemplate.sectionId.toString()] && permissionPerSection[this.item.dmpDescriptionTemplate.sectionId.toString()].some(x => x === AppPermission.ReviewDescription); this.canReview = permissionPerSection && permissionPerSection[this.item.dmpDescriptionTemplate.sectionId.toString()] && permissionPerSection[this.item.dmpDescriptionTemplate.sectionId.toString()].some(x => x === AppPermission.ReviewDescription);
this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.canEdit); this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.canEdit, this.visibilityRulesService);
if (this.item.descriptionTemplate?.definition) this.visibilityRulesService.setContext(this.item.descriptionTemplate.definition, this.formGroup.get('properties')); if (this.item.descriptionTemplate?.definition) this.visibilityRulesService.setContext(this.item.descriptionTemplate.definition, this.formGroup.get('properties'));
if (this.item.descriptionTemplate?.definition) this.pageToFieldSetMap = this.mapPageToFieldSet(this.item.descriptionTemplate);; if (this.item.descriptionTemplate?.definition) this.pageToFieldSetMap = this.mapPageToFieldSet(this.item.descriptionTemplate);;
@ -681,7 +681,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
this.initialTemplateId = descriptionTemplateId.toString(); this.initialTemplateId = descriptionTemplateId.toString();
this.editorModel.properties = new DescriptionPropertyDefinitionEditorModel(this.editorModel.validationErrorModel).fromModel(null, descriptionTemplate, null); this.editorModel.properties = new DescriptionPropertyDefinitionEditorModel(this.editorModel.validationErrorModel).fromModel(null, descriptionTemplate, null);
this.formGroup.setControl('properties', this.editorModel.buildProperties()); this.formGroup.setControl('properties', this.editorModel.buildProperties(this.visibilityRulesService));
this.item.descriptionTemplate = descriptionTemplate; this.item.descriptionTemplate = descriptionTemplate;
const sectionId = this.item.dmpDescriptionTemplate.sectionId; const sectionId = this.item.dmpDescriptionTemplate.sectionId;
@ -755,6 +755,8 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
finalize() { finalize() {
this.formService.removeAllBackEndErrors(this.formGroup); this.formService.removeAllBackEndErrors(this.formGroup);
this.formService.touchAllFormFields(this.formGroup); this.formService.touchAllFormFields(this.formGroup);
this.formService.validateAllFormFields(this.formGroup);
this.tocValidationService.validateForm(); this.tocValidationService.validateForm();
if (!this.isFormValid()) { if (!this.isFormValid()) {
this.dialog.open(FormValidationErrorsDialogComponent, { this.dialog.open(FormValidationErrorsDialogComponent, {

View File

@ -1,15 +1,17 @@
import { FormControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; import { FormControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { DescriptionStatus } from "@app/core/common/enum/description-status"; import { DescriptionStatus } from "@app/core/common/enum/description-status";
import { DescriptionTemplateFieldType } from "@app/core/common/enum/description-template-field-type"; import { DescriptionTemplateFieldType } from "@app/core/common/enum/description-template-field-type";
import { DescriptionTemplateFieldValidationType } from "@app/core/common/enum/description-template-field-validation-type";
import { IsActive } from "@app/core/common/enum/is-active.enum"; import { IsActive } from "@app/core/common/enum/is-active.enum";
import { DescriptionTemplate, DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTemplateSection } from "@app/core/model/description-template/description-template"; import { DescriptionTemplate, DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTemplateSection } from "@app/core/model/description-template/description-template";
import { Description, DescriptionExternalIdentifier, DescriptionExternalIdentifierPersist, DescriptionField, DescriptionFieldPersist, DescriptionPersist, DescriptionPropertyDefinition, DescriptionPropertyDefinitionFieldSet, DescriptionPropertyDefinitionFieldSetItem, DescriptionPropertyDefinitionFieldSetItemPersist, DescriptionPropertyDefinitionFieldSetPersist, DescriptionPropertyDefinitionPersist, DescriptionReference, DescriptionReferencePersist } from "@app/core/model/description/description"; import { Description, DescriptionExternalIdentifier, DescriptionExternalIdentifierPersist, DescriptionField, DescriptionFieldPersist, DescriptionPersist, DescriptionPropertyDefinition, DescriptionPropertyDefinitionFieldSet, DescriptionPropertyDefinitionFieldSetItem, DescriptionPropertyDefinitionFieldSetItemPersist, DescriptionPropertyDefinitionFieldSetPersist, DescriptionPropertyDefinitionPersist, DescriptionReference, DescriptionReferencePersist } from "@app/core/model/description/description";
import { ReferencePersist } from "@app/core/model/reference/reference"; import { ReferencePersist } from "@app/core/model/reference/reference";
import { BaseEditorModel } from "@common/base/base-form-editor-model"; import { BaseEditorModel } from "@common/base/base-form-editor-model";
import { BackendErrorValidator } from '@common/forms/validation/custom-validator'; import { BackendErrorValidator, MinMaxValidator, RequiredWithVisibilityRulesValidator, UrlValidator } from '@common/forms/validation/custom-validator';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model'; import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
import { Validation, ValidationContext } from '@common/forms/validation/validation-context'; import { Validation, ValidationContext } from '@common/forms/validation/validation-context';
import { Guid } from "@common/types/guid"; import { Guid } from "@common/types/guid";
import { VisibilityRulesService } from "./description-form/visibility-rules/visibility-rules.service";
export class DescriptionEditorModel extends BaseEditorModel implements DescriptionPersist { export class DescriptionEditorModel extends BaseEditorModel implements DescriptionPersist {
label: string; label: string;
@ -42,7 +44,7 @@ export class DescriptionEditorModel extends BaseEditorModel implements Descripti
return this; return this;
} }
buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup { buildForm(context: ValidationContext = null, disabled: boolean = false, visibilityRulesService: VisibilityRulesService): UntypedFormGroup {
if (context == null) { context = this.createValidationContext(); } if (context == null) { context = this.createValidationContext(); }
return this.formBuilder.group({ return this.formBuilder.group({
@ -54,14 +56,15 @@ export class DescriptionEditorModel extends BaseEditorModel implements Descripti
status: [{ value: this.status, disabled: disabled }, context.getValidation('status').validators], status: [{ value: this.status, disabled: disabled }, context.getValidation('status').validators],
description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators], description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators],
tags: [{ value: this.tags, disabled: disabled }, context.getValidation('tags').validators], tags: [{ value: this.tags, disabled: disabled }, context.getValidation('tags').validators],
properties: this.buildProperties(), properties: this.buildProperties(visibilityRulesService),
hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators] hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators]
}); });
} }
buildProperties() { buildProperties(visibilityRulesService: VisibilityRulesService) {
return this.properties.buildForm({ return this.properties.buildForm({
rootPath: `properties.` rootPath: `properties.`,
visibilityRulesService: visibilityRulesService
}); });
} }
@ -82,18 +85,37 @@ export class DescriptionEditorModel extends BaseEditorModel implements Descripti
return baseContext; return baseContext;
} }
static reApplyPropertiesValidators(params: { static getFieldValueControlName(fieldType: DescriptionTemplateFieldType, multipleSelect: boolean): string {
formGroup: UntypedFormGroup, switch (fieldType) {
validationErrorModel: ValidationErrorModel, case DescriptionTemplateFieldType.FREE_TEXT:
}): void { case DescriptionTemplateFieldType.TEXT_AREA:
case DescriptionTemplateFieldType.UPLOAD:
const { formGroup, validationErrorModel } = params; case DescriptionTemplateFieldType.RICH_TEXT_AREA:
const control = formGroup?.get('properties'); case DescriptionTemplateFieldType.RADIO_BOX:
DescriptionPropertyDefinitionEditorModel.reapplyValidators({ return 'textValue';
formGroup: control.get('fieldSets') as UntypedFormGroup, case DescriptionTemplateFieldType.DATASET_IDENTIFIER:
rootPath: `properties.`, case DescriptionTemplateFieldType.VALIDATION:
validationErrorModel: validationErrorModel return 'externalIdentifier';
}); case DescriptionTemplateFieldType.DATE_PICKER:
return 'dateValue';
case DescriptionTemplateFieldType.CHECK_BOX:
case DescriptionTemplateFieldType.BOOLEAN_DECISION:
return 'booleanValue';
case DescriptionTemplateFieldType.INTERNAL_ENTRIES_DESCRIPTIONS:
if (multipleSelect) return 'textListValue';
else return 'textValue';
case DescriptionTemplateFieldType.INTERNAL_ENTRIES_DMPS:
if (multipleSelect) return 'textListValue';
else return 'textValue';
case DescriptionTemplateFieldType.REFERENCE_TYPES:
if (multipleSelect) return 'references';
else return 'reference';
case DescriptionTemplateFieldType.SELECT:
if (multipleSelect) return 'textListValue';
else return 'textValue';
case DescriptionTemplateFieldType.TAGS:
return 'tags';
}
} }
} }
@ -113,7 +135,8 @@ export class DescriptionPropertyDefinitionEditorModel implements DescriptionProp
buildForm(params?: { buildForm(params?: {
context?: ValidationContext, context?: ValidationContext,
disabled?: boolean, disabled?: boolean,
rootPath?: string rootPath?: string,
visibilityRulesService: VisibilityRulesService
}): UntypedFormGroup { }): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {} let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) { if (context == null) {
@ -128,7 +151,8 @@ export class DescriptionPropertyDefinitionEditorModel implements DescriptionProp
const fieldSetsFormGroup = this.formBuilder.group({}); const fieldSetsFormGroup = this.formBuilder.group({});
if (this.fieldSets.size > 0) { if (this.fieldSets.size > 0) {
this.fieldSets.forEach((value, key) => fieldSetsFormGroup.addControl(key.toString(), value.buildForm({ this.fieldSets.forEach((value, key) => fieldSetsFormGroup.addControl(key.toString(), value.buildForm({
rootPath: `${rootPath}fieldSets[${key}].` rootPath: `${rootPath}fieldSets[${key}].`,
visibilityRulesService: params.visibilityRulesService
})), context.getValidation('fieldSets')); })), context.getValidation('fieldSets'));
formGroup.addControl('fieldSets', fieldSetsFormGroup); formGroup.addControl('fieldSets', fieldSetsFormGroup);
} }
@ -149,25 +173,6 @@ export class DescriptionPropertyDefinitionEditorModel implements DescriptionProp
return baseContext; return baseContext;
} }
static reapplyValidators(params: {
formGroup: UntypedFormGroup,
validationErrorModel: ValidationErrorModel,
rootPath: string
}): void {
const { formGroup, rootPath, validationErrorModel } = params;
const keys = Object.keys(formGroup.value as Object);
keys.forEach((key) => {
const formArray = formGroup?.get(key);
DescriptionPropertyDefinitionFieldSetEditorModel.reapplyValidators({
formArray: formArray.get('items') as UntypedFormArray,
rootPath: `${rootPath}fieldSets[${key}].`,
validationErrorModel: validationErrorModel
})
});
}
private calculateProperties(item: DescriptionPropertyDefinition, descriptionTemplate: DescriptionTemplate, descriptionReferences: DescriptionReference[]): Map<string, DescriptionPropertyDefinitionFieldSetEditorModel> { private calculateProperties(item: DescriptionPropertyDefinition, descriptionTemplate: DescriptionTemplate, descriptionReferences: DescriptionReference[]): Map<string, DescriptionPropertyDefinitionFieldSetEditorModel> {
let result: Map<string, DescriptionPropertyDefinitionFieldSetEditorModel> = new Map<string, DescriptionPropertyDefinitionFieldSetEditorModel>(); let result: Map<string, DescriptionPropertyDefinitionFieldSetEditorModel> = new Map<string, DescriptionPropertyDefinitionFieldSetEditorModel>();
if (descriptionTemplate) ( if (descriptionTemplate) (
@ -265,11 +270,14 @@ export class DescriptionPropertyDefinitionFieldSetEditorModel implements Descrip
items?: DescriptionPropertyDefinitionFieldSetItemEditorModel[] = []; items?: DescriptionPropertyDefinitionFieldSetItemEditorModel[] = [];
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
fieldSetDefinition: DescriptionTemplateFieldSet;
constructor( constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { } ) { }
public fromModel(item: DescriptionPropertyDefinitionFieldSet, descriptionReferences: DescriptionReference[], definitionFieldSet: DescriptionTemplateFieldSet): DescriptionPropertyDefinitionFieldSetEditorModel { public fromModel(item: DescriptionPropertyDefinitionFieldSet, descriptionReferences: DescriptionReference[], definitionFieldSet: DescriptionTemplateFieldSet): DescriptionPropertyDefinitionFieldSetEditorModel {
this.fieldSetDefinition = definitionFieldSet;
if (item) { if (item) {
if (item.items) { item.items.map(x => this.items.push(new DescriptionPropertyDefinitionFieldSetItemEditorModel(this.validationErrorModel).fromModel(x, descriptionReferences, definitionFieldSet))); } if (item.items) { item.items.map(x => this.items.push(new DescriptionPropertyDefinitionFieldSetItemEditorModel(this.validationErrorModel).fromModel(x, descriptionReferences, definitionFieldSet))); }
} }
@ -279,13 +287,15 @@ export class DescriptionPropertyDefinitionFieldSetEditorModel implements Descrip
buildForm(params?: { buildForm(params?: {
context?: ValidationContext, context?: ValidationContext,
disabled?: boolean, disabled?: boolean,
rootPath?: string rootPath?: string,
visibilityRulesService: VisibilityRulesService
}): UntypedFormGroup { }): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {} let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) { if (context == null) {
context = DescriptionPropertyDefinitionFieldSetEditorModel.createValidationContext({ context = DescriptionPropertyDefinitionFieldSetEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel, validationErrorModel: this.validationErrorModel,
rootPath rootPath,
fieldSetDefinition: this.fieldSetDefinition
}); });
} }
@ -293,7 +303,8 @@ export class DescriptionPropertyDefinitionFieldSetEditorModel implements Descrip
items: this.formBuilder.array( items: this.formBuilder.array(
(this.items ?? []).map( (this.items ?? []).map(
(item, index) => item.buildForm({ (item, index) => item.buildForm({
rootPath: `${rootPath}items[${index}].` rootPath: `${rootPath}items[${index}].`,
visibilityRulesService: params.visibilityRulesService
}) })
), context.getValidation('items').validators ), context.getValidation('items').validators
) )
@ -302,14 +313,25 @@ export class DescriptionPropertyDefinitionFieldSetEditorModel implements Descrip
static createValidationContext(params: { static createValidationContext(params: {
rootPath?: string, rootPath?: string,
validationErrorModel: ValidationErrorModel validationErrorModel: ValidationErrorModel,
fieldSetDefinition: DescriptionTemplateFieldSet
}): ValidationContext { }): ValidationContext {
const { rootPath = '', validationErrorModel } = params; const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext(); const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>(); const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'items', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}items`)] }); const validators = [];
validators.push(BackendErrorValidator(validationErrorModel, `${rootPath}items`));
if (params.fieldSetDefinition?.multiplicity?.min >= 0 && params.fieldSetDefinition?.multiplicity?.max >= 0) {
validators.push(MinMaxValidator(params.fieldSetDefinition.multiplicity.min, params.fieldSetDefinition.multiplicity.min));
} else if (params.fieldSetDefinition?.multiplicity?.min >= 0) {
validators.push(Validators.min(params.fieldSetDefinition.multiplicity.min));
}
else if (params.fieldSetDefinition?.multiplicity?.max >= 0) {
validators.push(Validators.max(params.fieldSetDefinition.multiplicity.max));
}
baseValidationArray.push({ key: 'items', validators: validators });
baseContext.validation = baseValidationArray; baseContext.validation = baseValidationArray;
return baseContext; return baseContext;
} }
@ -317,14 +339,18 @@ export class DescriptionPropertyDefinitionFieldSetEditorModel implements Descrip
static reapplyValidators(params: { static reapplyValidators(params: {
formArray: UntypedFormArray, formArray: UntypedFormArray,
validationErrorModel: ValidationErrorModel, validationErrorModel: ValidationErrorModel,
rootPath: string rootPath: string,
fieldSetDefinition: DescriptionTemplateFieldSet,
visibilityRulesService: VisibilityRulesService
}): void { }): void {
const { validationErrorModel, rootPath, formArray } = params; const { validationErrorModel, rootPath, formArray, fieldSetDefinition } = params;
formArray?.controls?.forEach( formArray?.controls?.forEach(
(control, index) => DescriptionPropertyDefinitionFieldSetItemEditorModel.reapplyValidators({ (control, index) => DescriptionPropertyDefinitionFieldSetItemEditorModel.reapplyValidators({
formGroup: control as UntypedFormGroup, formGroup: control as UntypedFormGroup,
rootPath: `${rootPath}items[${index}].`, rootPath: `${rootPath}items[${index}].`,
validationErrorModel: validationErrorModel validationErrorModel: validationErrorModel,
fieldSetDefinition: fieldSetDefinition,
visibilityRulesService: params.visibilityRulesService
}) })
); );
} }
@ -359,7 +385,8 @@ export class DescriptionPropertyDefinitionFieldSetItemEditorModel implements Des
buildForm(params?: { buildForm(params?: {
context?: ValidationContext, context?: ValidationContext,
disabled?: boolean, disabled?: boolean,
rootPath?: string rootPath?: string,
visibilityRulesService: VisibilityRulesService
}): UntypedFormGroup { }): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {} let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) { if (context == null) {
@ -376,7 +403,9 @@ export class DescriptionPropertyDefinitionFieldSetItemEditorModel implements Des
const fieldsFormGroup = this.formBuilder.group({}); const fieldsFormGroup = this.formBuilder.group({});
this.fields.forEach((value, key) => fieldsFormGroup.addControl(key.toString(), value.buildForm({ this.fields.forEach((value, key) => fieldsFormGroup.addControl(key.toString(), value.buildForm({
rootPath: `${rootPath}fields[${key}].` rootPath: `${rootPath}fields[${key}].`,
visibilityRulesService: params.visibilityRulesService,
visibilityRulesKey: key + '_' + formGroup.get('ordinal').value
})), context.getValidation('fields') })), context.getValidation('fields')
) )
formGroup.addControl('fields', fieldsFormGroup); formGroup.addControl('fields', fieldsFormGroup);
@ -403,10 +432,12 @@ export class DescriptionPropertyDefinitionFieldSetItemEditorModel implements Des
static reapplyValidators(params: { static reapplyValidators(params: {
formGroup: UntypedFormGroup, formGroup: UntypedFormGroup,
validationErrorModel: ValidationErrorModel, validationErrorModel: ValidationErrorModel,
rootPath: string rootPath: string,
fieldSetDefinition: DescriptionTemplateFieldSet,
visibilityRulesService: VisibilityRulesService
}): void { }): void {
const { formGroup, rootPath, validationErrorModel } = params; const { formGroup, rootPath, validationErrorModel, fieldSetDefinition } = params;
const context = DescriptionPropertyDefinitionFieldSetItemEditorModel.createValidationContext({ const context = DescriptionPropertyDefinitionFieldSetItemEditorModel.createValidationContext({
rootPath, rootPath,
validationErrorModel validationErrorModel
@ -419,7 +450,10 @@ export class DescriptionPropertyDefinitionFieldSetItemEditorModel implements Des
DescriptionFieldEditorModel.reapplyValidators({ DescriptionFieldEditorModel.reapplyValidators({
formGroup: control as UntypedFormGroup, formGroup: control as UntypedFormGroup,
rootPath: `${rootPath}fields[${key}].`, rootPath: `${rootPath}fields[${key}].`,
validationErrorModel: validationErrorModel validationErrorModel: validationErrorModel,
fieldDefinition: fieldSetDefinition.fields.find(x => x.id == key),
visibilityRulesService: params.visibilityRulesService,
visibilityRulesKey: key + '_' + formGroup.get('ordinal').value
}) })
}); });
@ -442,6 +476,8 @@ export class DescriptionFieldEditorModel implements DescriptionFieldPersist {
tags: string[] = []; tags: string[] = [];
externalIdentifier?: DescriptionExternalIdentifierEditorModel = new DescriptionExternalIdentifierEditorModel(this.validationErrorModel); externalIdentifier?: DescriptionExternalIdentifierEditorModel = new DescriptionExternalIdentifierEditorModel(this.validationErrorModel);
fieldDefinition: DescriptionTemplateField;
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor( constructor(
@ -449,6 +485,7 @@ export class DescriptionFieldEditorModel implements DescriptionFieldPersist {
) { } ) { }
public fromModel(item: DescriptionField, descriptionTemplateField: DescriptionTemplateField, descriptionReferences: DescriptionReference[]): DescriptionFieldEditorModel { public fromModel(item: DescriptionField, descriptionTemplateField: DescriptionTemplateField, descriptionReferences: DescriptionReference[]): DescriptionFieldEditorModel {
this.fieldDefinition = descriptionTemplateField;
if (item) { if (item) {
this.textValue = item.textValue; this.textValue = item.textValue;
this.textListValue = item.textListValue; this.textListValue = item.textListValue;
@ -488,46 +525,89 @@ export class DescriptionFieldEditorModel implements DescriptionFieldPersist {
buildForm(params?: { buildForm(params?: {
context?: ValidationContext, context?: ValidationContext,
disabled?: boolean, disabled?: boolean,
rootPath?: string rootPath?: string,
visibilityRulesService: VisibilityRulesService,
visibilityRulesKey: string
}): UntypedFormGroup { }): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {} let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) { if (context == null) {
context = DescriptionFieldEditorModel.createValidationContext({ context = DescriptionFieldEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel, validationErrorModel: this.validationErrorModel,
rootPath rootPath,
fieldDefinition: this.fieldDefinition,
visibilityRulesService: params.visibilityRulesService,
visibilityRulesKey: params.visibilityRulesKey
}); });
} }
return this.formBuilder.group({ const fieldType = this.fieldDefinition.data.fieldType;
textValue: [{ value: this.textValue, disabled: disabled }, context.getValidation('textValue').validators], const multipleSelect = this.fieldDefinition.data.multipleSelect;
textListValue: [{ value: this.textListValue, disabled: disabled }, context.getValidation('textListValue').validators], const fieldValueControlName = DescriptionEditorModel.getFieldValueControlName(fieldType, multipleSelect);
dateValue: [{ value: this.dateValue, disabled: disabled }, context.getValidation('dateValue').validators], const formGroup = this.formBuilder.group({});
booleanValue: [{ value: this.booleanValue, disabled: disabled }, context.getValidation('booleanValue').validators], switch (fieldType) {
references: [{ value: this.references, disabled: disabled }, context.getValidation('references').validators], case DescriptionTemplateFieldType.FREE_TEXT:
reference: [{ value: this.reference, disabled: disabled }, context.getValidation('reference').validators], case DescriptionTemplateFieldType.TEXT_AREA:
tags: [{ value: this.tags, disabled: disabled }, context.getValidation('tags').validators], case DescriptionTemplateFieldType.UPLOAD:
externalIdentifier: this.externalIdentifier.buildForm({ case DescriptionTemplateFieldType.RICH_TEXT_AREA:
rootPath: `${rootPath}externalIdentifier.` case DescriptionTemplateFieldType.RADIO_BOX:
}), formGroup.addControl(fieldValueControlName, new FormControl({ value: this.textValue, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
}); case DescriptionTemplateFieldType.DATASET_IDENTIFIER:
case DescriptionTemplateFieldType.VALIDATION:
formGroup.addControl(fieldValueControlName, this.externalIdentifier.buildForm({
rootPath: `${rootPath}externalIdentifier.`
}));
case DescriptionTemplateFieldType.DATE_PICKER:
formGroup.addControl(fieldValueControlName, new FormControl({ value: this.dateValue, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
case DescriptionTemplateFieldType.CHECK_BOX:
case DescriptionTemplateFieldType.BOOLEAN_DECISION:
formGroup.addControl(fieldValueControlName, new FormControl({ value: this.booleanValue, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
case DescriptionTemplateFieldType.INTERNAL_ENTRIES_DESCRIPTIONS:
if (multipleSelect) formGroup.addControl(fieldValueControlName, new FormControl({ value: this.textListValue, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
else formGroup.addControl(fieldValueControlName, new FormControl({ value: this.textValue, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
case DescriptionTemplateFieldType.INTERNAL_ENTRIES_DMPS:
if (multipleSelect) formGroup.addControl(fieldValueControlName, new FormControl({ value: this.textListValue, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
else formGroup.addControl(fieldValueControlName, new FormControl({ value: this.textValue, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
case DescriptionTemplateFieldType.REFERENCE_TYPES:
if (multipleSelect) formGroup.addControl(fieldValueControlName, new FormControl({ value: this.references, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
else formGroup.addControl(fieldValueControlName, new FormControl({ value: this.reference, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
case DescriptionTemplateFieldType.SELECT:
if (multipleSelect) formGroup.addControl(fieldValueControlName, new FormControl({ value: this.textListValue, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
else formGroup.addControl(fieldValueControlName, new FormControl({ value: this.textValue, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
case DescriptionTemplateFieldType.TAGS:
formGroup.addControl(fieldValueControlName, new FormControl({ value: this.tags, disabled: disabled }, context.getValidation(fieldValueControlName).validators));
}
return formGroup;
} }
static createValidationContext(params: { static createValidationContext(params: {
rootPath?: string, rootPath?: string,
validationErrorModel: ValidationErrorModel validationErrorModel: ValidationErrorModel,
fieldDefinition: DescriptionTemplateField,
visibilityRulesService: VisibilityRulesService,
visibilityRulesKey: string
}): ValidationContext { }): ValidationContext {
const { rootPath = '', validationErrorModel } = params; const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext(); const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>(); const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'textValue', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}textValue`)] });
baseValidationArray.push({ key: 'textListValue', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}textListValue`)] }); const fieldValueControlName = DescriptionEditorModel.getFieldValueControlName(params.fieldDefinition.data.fieldType, params.fieldDefinition.data.multipleSelect);
baseValidationArray.push({ key: 'dateValue', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}dateValue`)] }); const validators = [];
baseValidationArray.push({ key: 'booleanValue', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}booleanValue`)] }); validators.push(BackendErrorValidator(validationErrorModel, `${rootPath}${fieldValueControlName}`));
baseValidationArray.push({ key: 'references', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}references`)] });
baseValidationArray.push({ key: 'reference', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}references`)] }); params.fieldDefinition.validations.forEach(validation => {
baseValidationArray.push({ key: 'tags', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}tags`)] }); switch (validation) {
baseValidationArray.push({ key: 'externalIdentifier', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}externalIdentifier`)] }); case DescriptionTemplateFieldValidationType.Required:
validators.push(RequiredWithVisibilityRulesValidator(params.visibilityRulesService, params.visibilityRulesKey));
break;
case DescriptionTemplateFieldValidationType.Url:
validators.push(UrlValidator());
break;
}
});
baseValidationArray.push({ key: fieldValueControlName, validators: validators });
baseContext.validation = baseValidationArray; baseContext.validation = baseValidationArray;
return baseContext; return baseContext;
} }
@ -535,13 +615,20 @@ export class DescriptionFieldEditorModel implements DescriptionFieldPersist {
static reapplyValidators(params: { static reapplyValidators(params: {
formGroup: UntypedFormGroup, formGroup: UntypedFormGroup,
validationErrorModel: ValidationErrorModel, validationErrorModel: ValidationErrorModel,
rootPath: string rootPath: string,
fieldDefinition: DescriptionTemplateField,
visibilityRulesService: VisibilityRulesService,
visibilityRulesKey: string
}): void { }): void {
const { formGroup, rootPath, validationErrorModel } = params; const { formGroup, rootPath, validationErrorModel, fieldDefinition } = params;
const context = DescriptionFieldEditorModel.createValidationContext({ const context = DescriptionFieldEditorModel.createValidationContext({
rootPath, rootPath,
validationErrorModel validationErrorModel,
fieldDefinition: fieldDefinition,
visibilityRulesService: params.visibilityRulesService,
visibilityRulesKey: params.visibilityRulesKey
}); });
['textValue', 'textListValue', 'dateValue', 'booleanValue'].forEach(keyField => { ['textValue', 'textListValue', 'dateValue', 'booleanValue'].forEach(keyField => {
@ -717,45 +804,6 @@ export class DescriptionFieldIndicator {
this.sectionIds = sectionIds; this.sectionIds = sectionIds;
this.fieldSetId = fieldSetId; this.fieldSetId = fieldSetId;
this.fieldId = fieldId; this.fieldId = fieldId;
this.type = DescriptionEditorModel.getFieldValueControlName(type, multipleSelect);
switch (type) {
case DescriptionTemplateFieldType.FREE_TEXT:
case DescriptionTemplateFieldType.TEXT_AREA:
case DescriptionTemplateFieldType.UPLOAD:
case DescriptionTemplateFieldType.RICH_TEXT_AREA:
case DescriptionTemplateFieldType.RADIO_BOX:
this.type = "textValue";
break;
case DescriptionTemplateFieldType.DATASET_IDENTIFIER:
case DescriptionTemplateFieldType.VALIDATION:
this.type = "externalIdentifier";
break;
case DescriptionTemplateFieldType.DATE_PICKER:
this.type = "dateValue";
break;
case DescriptionTemplateFieldType.CHECK_BOX:
case DescriptionTemplateFieldType.BOOLEAN_DECISION:
this.type = "booleanValue";
break;
case DescriptionTemplateFieldType.INTERNAL_ENTRIES_DESCRIPTIONS:
if (multipleSelect) this.type = "textListValue";
else this.type = "textValue"
break;
case DescriptionTemplateFieldType.INTERNAL_ENTRIES_DMPS:
if (multipleSelect) this.type = "textListValue";
else this.type = "textValue";
break;
case DescriptionTemplateFieldType.REFERENCE_TYPES:
if (multipleSelect) this.type = "references";
else this.type = "reference";
break;
case DescriptionTemplateFieldType.SELECT:
if (multipleSelect) this.type = "textListValue";
else this.type = "textValue";
break;
case DescriptionTemplateFieldType.TAGS:
this.type = "tags";
break;
}
} }
} }

View File

@ -82,11 +82,11 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
} }
const properties: DescriptionPropertyDefinitionFieldSet = this.propertiesFormGroup.value; const properties: DescriptionPropertyDefinitionFieldSet = this.propertiesFormGroup.value;
let ordinal = 0; let ordinal = 0;
if (properties?.items && properties.items.map(x => x.ordinal).filter(val => !isNaN(val)).length > 0) { if (properties?.items && properties.items.map(x => x.ordinal).filter(val => !isNaN(val)).length > 0) {
ordinal = Math.max(...properties.items.map(x => x.ordinal).filter(val => !isNaN(val))) + 1; ordinal = Math.max(...properties.items.map(x => x.ordinal).filter(val => !isNaN(val))) + 1;
} }
const item: DescriptionPropertyDefinitionFieldSetEditorModel = new DescriptionPropertyDefinitionEditorModel(this.validationErrorModel).calculateFieldSetProperties(this.fieldSet, ordinal, null, null); const item: DescriptionPropertyDefinitionFieldSetEditorModel = new DescriptionPropertyDefinitionEditorModel(this.validationErrorModel).calculateFieldSetProperties(this.fieldSet, ordinal, null, null);
formArray.push((item.buildForm({ rootPath: `properties.fieldSets[${this.fieldSet.id}].` }).get('items') as UntypedFormArray).at(0)); formArray.push((item.buildForm({ rootPath: `properties.fieldSets[${this.fieldSet.id}].`, visibilityRulesService: this.visibilityRulesService }).get('items') as UntypedFormArray).at(0));
this.visibilityRulesService.reloadVisibility(); this.visibilityRulesService.reloadVisibility();
} }
@ -102,7 +102,9 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
{ {
formArray: formArray, formArray: formArray,
validationErrorModel: this.validationErrorModel, validationErrorModel: this.validationErrorModel,
rootPath: `properties.fieldSets[${this.fieldSet.id}].` rootPath: `properties.fieldSets[${this.fieldSet.id}].`,
fieldSetDefinition: this.fieldSet,
visibilityRulesService: this.visibilityRulesService
} }
); );
formArray.markAsDirty(); formArray.markAsDirty();

View File

@ -11,7 +11,7 @@
<div class="col-12"> <div class="col-12">
<mat-form-field class="w-100"> <mat-form-field class="w-100">
<mat-label>{{ field.data.label }}</mat-label> <mat-label>{{ field.data.label }}</mat-label>
<input matInput [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" placeholder="{{(field.data.label) + (isRequired? ' *': '')}}" [required]="isRequired"> <input matInput [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" placeholder="{{(field.data.label) + (isRequired? ' *': '')}}">
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textValue').getError('backendError').message}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textValue').getError('backendError').message}}</mat-error>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('pattern')">{{'GENERAL.VALIDATION.URL.MESSAGE' | translate}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('pattern')">{{'GENERAL.VALIDATION.URL.MESSAGE' | translate}}</mat-error>
@ -31,14 +31,14 @@
<div class="row"> <div class="row">
<mat-form-field class="col-md-12"> <mat-form-field class="col-md-12">
<ng-container *ngIf="field.data.multipleSelect"> <ng-container *ngIf="field.data.multipleSelect">
<mat-select [formControl]="propertiesFormGroup?.get(field.id).get('textListValue')" placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [required]="isRequired" [multiple]="field.data.multipleSelect"> <mat-select [formControl]="propertiesFormGroup?.get(field.id).get('textListValue')" placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [multiple]="field.data.multipleSelect">
<mat-option *ngFor="let opt of field.data.options" [value]="opt.value">{{opt.label}}</mat-option> <mat-option *ngFor="let opt of field.data.options" [value]="opt.value">{{opt.label}}</mat-option>
</mat-select> </mat-select>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textListValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textListValue').getError('backendError').message}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textListValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textListValue').getError('backendError').message}}</mat-error>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textListValue').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textListValue').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</ng-container> </ng-container>
<ng-container *ngIf="!(field.data.multipleSelect)"> <ng-container *ngIf="!(field.data.multipleSelect)">
<mat-select [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [required]="isRequired" [multiple]="field.data.multipleSelect"> <mat-select [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [multiple]="field.data.multipleSelect">
<mat-option *ngFor="let opt of field.data.options" [value]="opt.value">{{opt.label}} <mat-option *ngFor="let opt of field.data.options" [value]="opt.value">{{opt.label}}
</mat-option> </mat-option>
</mat-select> </mat-select>
@ -51,25 +51,25 @@
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.INTERNAL_ENTRIES_DESCRIPTIONS" class="col-12"> <div *ngSwitchCase="descriptionTemplateFieldTypeEnum.INTERNAL_ENTRIES_DESCRIPTIONS" class="col-12">
<div class="row"> <div class="row">
<ng-container *ngIf="field.data.multipleSelect"> <ng-container *ngIf="field.data.multipleSelect">
<mat-form-field class="col-md-12"> <mat-form-field class="col-md-12">
<mat-label>{{ field.data.label }}</mat-label> <mat-label>{{ field.data.label }}</mat-label>
<app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup?.get(field.id).get('textListValue')" [configuration]="descriptionService.multipleAutocompleteConfiguration"> <app-multiple-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup?.get(field.id).get('textListValue')" [configuration]="descriptionService.multipleAutocompleteConfiguration">
</app-multiple-auto-complete> </app-multiple-auto-complete>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textListValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textListValue').getError('backendError').message}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textListValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textListValue').getError('backendError').message}}</mat-error>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textListValue').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textListValue').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint> <mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field> </mat-form-field>
</ng-container> </ng-container>
<ng-container *ngIf="!(field.data.multipleSelect)"> <ng-container *ngIf="!(field.data.multipleSelect)">
<mat-form-field class="col-md-12"> <mat-form-field class="col-md-12">
<mat-label>{{ field.data.label }}</mat-label> <mat-label>{{ field.data.label }}</mat-label>
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" [configuration]="descriptionService.singleAutocompleteConfiguration" [required]="isRequired"> <app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" [configuration]="descriptionService.singleAutocompleteConfiguration">
</app-single-auto-complete> </app-single-auto-complete>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textValue').getError('backendError').message}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textValue').getError('backendError').message}}</mat-error>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
<mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint> <mat-hint>{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.EXTERNAL-SOURCE-HINT" | translate }}</mat-hint>
</mat-form-field> </mat-form-field>
</ng-container> </ng-container>
</div> </div>
</div> </div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.INTERNAL_ENTRIES_DMPS" class="col-12"> <div *ngSwitchCase="descriptionTemplateFieldTypeEnum.INTERNAL_ENTRIES_DMPS" class="col-12">
@ -87,7 +87,7 @@
<ng-container *ngIf="!(field.data.multipleSelect)"> <ng-container *ngIf="!(field.data.multipleSelect)">
<mat-form-field class="col-md-12"> <mat-form-field class="col-md-12">
<mat-label>{{ field.data.label }}</mat-label> <mat-label>{{ field.data.label }}</mat-label>
<app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" [configuration]="dmpService.singleAutocompleteConfiguration" [required]="isRequired"> <app-single-auto-complete placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" [configuration]="dmpService.singleAutocompleteConfiguration">
</app-single-auto-complete> </app-single-auto-complete>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textValue').getError('backendError').message}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textValue').getError('backendError').message}}</mat-error>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
@ -105,7 +105,7 @@
<div class="col-12"> <div class="col-12">
<mat-form-field *ngSwitchCase="descriptionTemplateFieldTypeEnum.TEXT_AREA" class="w-100"> <mat-form-field *ngSwitchCase="descriptionTemplateFieldTypeEnum.TEXT_AREA" class="w-100">
<mat-label>{{ field.data.label }}</mat-label> <mat-label>{{ field.data.label }}</mat-label>
<textarea matInput class="text-area" [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" matTextareaAutosize matAutosizeMinRows="3" matAutosizeMaxRows="15" [required]="isRequired" placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}"></textarea> <textarea matInput class="text-area" [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" matTextareaAutosize matAutosizeMinRows="3" matAutosizeMaxRows="15" placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}"></textarea>
<button mat-icon-button type="button" *ngIf="!propertiesFormGroup?.get(field.id).get('textValue').disabled && propertiesFormGroup?.get(field.id).get('textValue').value" matSuffix aria-label="Clear" (click)="this.propertiesFormGroup?.get(field.id).get('textValue').patchValue('')"> <button mat-icon-button type="button" *ngIf="!propertiesFormGroup?.get(field.id).get('textValue').disabled && propertiesFormGroup?.get(field.id).get('textValue').value" matSuffix aria-label="Clear" (click)="this.propertiesFormGroup?.get(field.id).get('textValue').patchValue('')">
<mat-icon>close</mat-icon> <mat-icon>close</mat-icon>
</button> </button>
@ -116,7 +116,7 @@
<ng-container *ngSwitchCase="descriptionTemplateFieldTypeEnum.RICH_TEXT_AREA"> <ng-container *ngSwitchCase="descriptionTemplateFieldTypeEnum.RICH_TEXT_AREA">
<div class="col-12"> <div class="col-12">
<rich-text-editor-component [form]="propertiesFormGroup?.get(field.id).get('textValue')" [placeholder]="field.data.label" [required]="isRequired" [wrapperClasses]="'full-width editor ' + <rich-text-editor-component [form]="propertiesFormGroup?.get(field.id).get('textValue')" [placeholder]="field.data.label" [wrapperClasses]="'full-width editor ' +
((isRequired && propertiesFormGroup?.get(field.id).get('textValue').touched && propertiesFormGroup?.get(field.id).get('textValue').hasError('required')) ? 'required' : '')" [editable]="!propertiesFormGroup?.get(field.id).get('textValue').disabled"> ((isRequired && propertiesFormGroup?.get(field.id).get('textValue').touched && propertiesFormGroup?.get(field.id).get('textValue').hasError('required')) ? 'required' : '')" [editable]="!propertiesFormGroup?.get(field.id).get('textValue').disabled">
</rich-text-editor-component> </rich-text-editor-component>
</div> </div>
@ -148,7 +148,7 @@
</div> </div>
</ng-container> </ng-container>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.BOOLEAN_DECISION" class="col-12"> <div *ngSwitchCase="descriptionTemplateFieldTypeEnum.BOOLEAN_DECISION" class="col-12">
<mat-radio-group [formControl]="propertiesFormGroup?.get(field.id).get('booleanValue')" [required]="isRequired"> <mat-radio-group [formControl]="propertiesFormGroup?.get(field.id).get('booleanValue')">
<mat-radio-button class="radio-button-item" [value]="true">{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.ACTIONS.YES" | translate }}</mat-radio-button> <mat-radio-button class="radio-button-item" [value]="true">{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.ACTIONS.YES" | translate }}</mat-radio-button>
<mat-radio-button class="radio-button-item" [value]="false">{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.ACTIONS.NO" | translate }}</mat-radio-button> <mat-radio-button class="radio-button-item" [value]="false">{{ "TYPES.DATASET-PROFILE-COMBO-BOX-TYPE.ACTIONS.NO" | translate }}</mat-radio-button>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('booleanValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('booleanValue').getError('backendError').message}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('booleanValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('booleanValue').getError('backendError').message}}</mat-error>
@ -159,7 +159,7 @@
</div> </div>
<div *ngSwitchCase="descriptionTemplateFieldTypeEnum.RADIO_BOX" class="col-12"> <div *ngSwitchCase="descriptionTemplateFieldTypeEnum.RADIO_BOX" class="col-12">
<mat-radio-group [formControl]="propertiesFormGroup?.get(field.id).get('textValue')" [required]="isRequired"> <mat-radio-group [formControl]="propertiesFormGroup?.get(field.id).get('textValue')">
<mat-radio-button *ngFor="let option of field.data.options let index = index" class="radio-button-item" [value]="option.value">{{option.label}}</mat-radio-button> <mat-radio-button *ngFor="let option of field.data.options let index = index" class="radio-button-item" [value]="option.value">{{option.label}}</mat-radio-button>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textValue').getError('backendError').message}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textValue').getError('backendError').message}}</mat-error>
</mat-radio-group> </mat-radio-group>
@ -170,7 +170,7 @@
<mat-form-field *ngSwitchCase="descriptionTemplateFieldTypeEnum.DATE_PICKER" class="col-12"> <mat-form-field *ngSwitchCase="descriptionTemplateFieldTypeEnum.DATE_PICKER" class="col-12">
<mat-label>{{ field.data.label }}</mat-label> <mat-label>{{ field.data.label }}</mat-label>
<input matInput placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" class="table-input" [matDatepicker]="date" [required]="isRequired" [formControl]="propertiesFormGroup?.get(field.id).get('dateValue')"> <input matInput placeholder="{{ (field.data.label | translate) + (isRequired? ' *': '') }}" class="table-input" [matDatepicker]="date" [formControl]="propertiesFormGroup?.get(field.id).get('dateValue')">
<mat-datepicker-toggle matSuffix [for]="date"></mat-datepicker-toggle> <mat-datepicker-toggle matSuffix [for]="date"></mat-datepicker-toggle>
<mat-datepicker #date></mat-datepicker> <mat-datepicker #date></mat-datepicker>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('dateValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('dateValue').getError('backendError').message}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('dateValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('dateValue').getError('backendError').message}}</mat-error>
@ -185,13 +185,13 @@
<div class="row" *ngIf="datasetIdInitialized"> <div class="row" *ngIf="datasetIdInitialized">
<mat-form-field class="col-md-12"> <mat-form-field class="col-md-12">
<mat-label>{{ field.data.label }}</mat-label> <mat-label>{{ field.data.label }}</mat-label>
<input matInput class="col-md-12" [formControl]="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier')" placeholder="{{(field.data.label) + (isRequired? ' *': '')}}" [required]="isRequired" [disabled]="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').disabled"> <input matInput class="col-md-12" [formControl]="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier')" placeholder="{{(field.data.label) + (isRequired? ' *': '')}}" [disabled]="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').disabled">
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').getError('backendError').message}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').getError('backendError').message}}</mat-error>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field class="col-md-12"> <mat-form-field class="col-md-12">
<mat-label>{{ field.data.label }}</mat-label> <mat-label>{{ field.data.label }}</mat-label>
<mat-select class="col-md-12" [formControl]="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('type')" [placeholder]="('TYPES.DATASET-PROFILE-IDENTIFIER.IDENTIFIER-TYPE' | translate) + (isRequired? ' *': '')" [required]="isRequired" [disabled]="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('type').disabled"> <mat-select class="col-md-12" [formControl]="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('type')" [placeholder]="('TYPES.DATASET-PROFILE-IDENTIFIER.IDENTIFIER-TYPE' | translate) + (isRequired? ' *': '')" [disabled]="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('type').disabled">
<mat-option *ngFor="let type of datasetIdTypes" [value]="type.value"> <mat-option *ngFor="let type of datasetIdTypes" [value]="type.value">
{{ type.name }} {{ type.name }}
</mat-option> </mat-option>
@ -206,7 +206,7 @@
<div class="row align-items-baseline"> <div class="row align-items-baseline">
<mat-form-field class="col-md-4"> <mat-form-field class="col-md-4">
<mat-label>{{ field.data.label }}</mat-label> <mat-label>{{ field.data.label }}</mat-label>
<input matInput class="col-md-12" [formControl]="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier')" placeholder="{{(field.data.label) + (isRequired? ' *': '')}}" [required]="isRequired"> <input matInput class="col-md-12" [formControl]="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier')" placeholder="{{(field.data.label) + (isRequired? ' *': '')}}">
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').getError('backendError').message}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').getError('backendError').message}}</mat-error>
<mat-error *ngIf="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="propertiesFormGroup?.get(field.id).get('externalIdentifier')?.get('identifier').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field> </mat-form-field>
@ -231,4 +231,4 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -80,11 +80,12 @@ export class FormProgressIndicationComponent extends BaseComponent implements On
countRequiredFieldsByFieldset(ordinal: number, fieldsFormGroup: UntypedFormGroup, filterValid: boolean = false): number { countRequiredFieldsByFieldset(ordinal: number, fieldsFormGroup: UntypedFormGroup, filterValid: boolean = false): number {
let fieldsCount: number = 0; let fieldsCount: number = 0;
const fieldNames = Object.keys(fieldsFormGroup.controls); const fieldSetNames = Object.keys(fieldsFormGroup.controls);
for(let item of fieldNames) { for(let item of fieldSetNames) {
if (!this.checkVisibility || this.visibilityRulesService.isVisible(item, ordinal)) { if (!this.checkVisibility || this.visibilityRulesService.isVisible(item, ordinal)) {
const fieldControl = fieldsFormGroup.get(item); const fieldControl = fieldsFormGroup.get(item);
for (let fieldType of this.fieldTypes) { const fieldNames = Object.keys((fieldControl as UntypedFormGroup).controls);
for (let fieldType of fieldNames) {
const typedControl = fieldControl.get(fieldType); const typedControl = fieldControl.get(fieldType);
let controlFilter: boolean = this.controlRequired(typedControl) && this.controlEnabled(typedControl); let controlFilter: boolean = this.controlRequired(typedControl) && this.controlEnabled(typedControl);
if (filterValid) controlFilter = controlFilter && typedControl.valid; if (filterValid) controlFilter = controlFilter && typedControl.valid;

View File

@ -1,6 +1,6 @@
<div class="main-content pl-5 pr-5"> <div class="main-content pl-5 pr-5">
<div class="container-fluid pl-0 pr-0"> <div class="container-fluid pl-0 pr-0">
<div *ngIf="description"> <div *ngIf="description && userName">
<div class="row"> <div class="row">
<div class="col-12 pl-2 mb-3"> <div class="col-12 pl-2 mb-3">
<app-navigation-breadcrumb /> <app-navigation-breadcrumb />
@ -197,12 +197,20 @@
</button> </button>
</div> </div>
<div class="col pl-0" style="min-width: 0;"> <div class="col pl-0" style="min-width: 0;">
<p class="authors-label">{{ dmpUser.user?.name }} <span *ngIf="isUserAuthor(dmpUser.user?.id)">({{ 'DESCRIPTION-OVERVIEW.YOU' | translate }})</span></p> <ng-container *ngIf="!isUserAuthor(dmpUser.user?.id); else you">
<p class="authors-role"> <p class="authors-label">{{ dmpUser.user?.name }}</p>
<span>{{ enumUtils.toDmpUserRoleString(dmpUser.role) }} - </span> </ng-container>
<span *ngIf="!dmpUser.sectionId">{{ 'DESCRIPTION-OVERVIEW.ROLES.ALL-SECTIONS' | translate}}</span> <ng-template #you>
<span *ngIf="dmpUser.sectionId">{{ getSectionNameById(dmpUser.sectionId) }}</span> <p class="authors-label">{{ userName }}
<span>({{ 'DESCRIPTION-OVERVIEW.YOU' | translate }})</span>
</p> </p>
</ng-template>
<p class="authors-role">
<span>{{ enumUtils.toDmpUserRoleString(dmpUser.role) }} - </span>
<span *ngIf="!dmpUser.sectionId">{{ 'DESCRIPTION-OVERVIEW.ROLES.ALL-SECTIONS' | translate}}</span>
<span *ngIf="dmpUser.sectionId">{{ getSectionNameById(dmpUser.sectionId) }}</span>
</p>
</div> </div>
<div class="col-auto" *ngIf="canInviteDmpUsers && description.dmp?.status === dmpStatusEnum.Draft && dmpUser.role != dmpUserRoleEnum.Owner"> <div class="col-auto" *ngIf="canInviteDmpUsers && description.dmp?.status === dmpStatusEnum.Draft && dmpUser.role != dmpUserRoleEnum.Owner">
<button (click)="removeUserFromDmp(dmpUser)" mat-mini-fab matTooltip="{{ 'DESCRIPTION-OVERVIEW.ACTIONS.REMOVE-AUTHOR' | translate}}" matTooltipPosition="above"> <button (click)="removeUserFromDmp(dmpUser)" mat-mini-fab matTooltip="{{ 'DESCRIPTION-OVERVIEW.ACTIONS.REMOVE-AUTHOR' | translate}}" matTooltipPosition="above">

View File

@ -36,11 +36,14 @@ import { DmpInvitationDialogComponent } from '@app/ui/dmp/invitation/dialog/dmp-
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { takeUntil } from 'rxjs/operators'; import { map, takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof'; import { nameof } from 'ts-simple-nameof';
import { DescriptionCopyDialogComponent } from '../description-copy-dialog/description-copy-dialog.component'; import { DescriptionCopyDialogComponent } from '../description-copy-dialog/description-copy-dialog.component';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { Observable, of } from 'rxjs';
import { UserService } from '@app/core/services/user/user.service';
import { User } from '@app/core/model/user/user';
@Component({ @Component({
@ -72,6 +75,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
canInviteDmpUsers = false; canInviteDmpUsers = false;
authorFocus: string; authorFocus: string;
userName: string;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@ -94,7 +98,8 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
private lockService: LockService, private lockService: LockService,
private analyticsService: AnalyticsService, private analyticsService: AnalyticsService,
private breadcrumbService: BreadcrumbService, private breadcrumbService: BreadcrumbService,
private httpErrorHandlingService: HttpErrorHandlingService private httpErrorHandlingService: HttpErrorHandlingService,
private userService: UserService,
) { ) {
super(); super();
} }
@ -183,6 +188,15 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
}); });
} }
}); });
if (this.isAuthenticated()) {
this.userService.getSingle(this.authentication.userId(), [
nameof<User>(x => x.id),
nameof<User>(x => x.name)])
.pipe(map(u => u.name)).subscribe(name => this.userName = name);
} else {
this.userName = '';
}
} }
get unauthorizedTootipText(): string { get unauthorizedTootipText(): string {
@ -229,7 +243,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
isUserAuthor(userId: Guid): boolean { isUserAuthor(userId: Guid): boolean {
if (this.isAuthenticated()) { if (this.isAuthenticated()) {
const principalId: Guid = this.authentication.userId(); const principalId: Guid = this.authentication.userId();
return userId === principalId; return this.userName && (userId === principalId);
} else return false; } else return false;
} }

View File

@ -96,9 +96,9 @@ export class DmpEditorComponent extends BaseEditor<DmpEditorModel, Dmp> implemen
getDescriptionTemplateMultipleAutoCompleteConfiguration(sectionId: Guid): MultipleAutoCompleteConfiguration { getDescriptionTemplateMultipleAutoCompleteConfiguration(sectionId: Guid): MultipleAutoCompleteConfiguration {
return { return {
initialItems: (excludedItems: any[], data?: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup(null, excludedItems ? excludedItems : null)).pipe(map(x => x.items)), initialItems: (excludedItems: any[], data?: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active], null, excludedItems ? excludedItems : null)).pipe(map(x => x.items)),
filterFn: (searchQuery: string, excludedItems: any[]) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup(searchQuery, excludedItems)).pipe(map(x => x.items)), filterFn: (searchQuery: string, excludedItems: any[]) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active], searchQuery, excludedItems)).pipe(map(x => x.items)),
getSelectedItems: (selectedItems: any[]) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup(null, null, selectedItems)).pipe(map(x => x.items)), getSelectedItems: (selectedItems: any[]) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active, IsActive.Inactive], null, null, selectedItems)).pipe(map(x => x.items)),
displayFn: (item: DescriptionTemplate) => item.label, displayFn: (item: DescriptionTemplate) => item.label,
titleFn: (item: DescriptionTemplate) => item.label, titleFn: (item: DescriptionTemplate) => item.label,
subtitleFn: (item: DescriptionTemplate) => item.description, subtitleFn: (item: DescriptionTemplate) => item.description,
@ -358,7 +358,8 @@ export class DmpEditorComponent extends BaseEditor<DmpEditorModel, Dmp> implemen
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) { if (result) {
setTimeout(x => { setTimeout(x => {
this.step = this.step > 0 ? this.step - 1 : 0; if (this.isNew) this.step = 0;
else this.step = this.step > 0 ? this.step - 1 : 0;
this.ngOnInit(); this.ngOnInit();
}); });
} }

View File

@ -1,6 +1,6 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormattingModule } from '@app/core/formatting.module'; import { FormattingModule } from '@app/core/formatting.module';
import { DmpRoutingModule } from '@app/ui/dmp/dmp.routing'; import { DmpRoutingModule, PublicDmpRoutingModule } from '@app/ui/dmp/dmp.routing';
import { CommonFormsModule } from '@common/forms/common-forms.module'; import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module'; import { CommonUiModule } from '@common/ui/common-ui.module';
@ -17,3 +17,17 @@ import { CommonUiModule } from '@common/ui/common-ui.module';
] ]
}) })
export class DmpModule { } export class DmpModule { }
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
FormattingModule,
PublicDmpRoutingModule,
],
declarations: [
],
exports: [
]
})
export class PublicDmpModule { }

View File

@ -1,11 +1,13 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { BreadcrumbService } from '../misc/breadcrumb/breadcrumb.service'; import { BreadcrumbService } from '../misc/breadcrumb/breadcrumb.service';
import { AuthGuard } from '@app/core/auth-guard.service';
const routes: Routes = [ const routes: Routes = [
{ {
path: 'overview', path: 'overview',
loadChildren: () => import('./overview/dmp-overview.module').then(m => m.DmpOverviewModule), loadChildren: () => import('./overview/dmp-overview.module').then(m => m.DmpOverviewModule),
canActivate:[AuthGuard],
data: { data: {
breadcrumb: true, breadcrumb: true,
...BreadcrumbService.generateRouteDataConfiguration({ ...BreadcrumbService.generateRouteDataConfiguration({
@ -16,6 +18,7 @@ const routes: Routes = [
{ {
path: 'new', path: 'new',
loadChildren: () => import('./dmp-editor-blueprint/dmp-editor.module').then(m => m.DmpEditorModule), loadChildren: () => import('./dmp-editor-blueprint/dmp-editor.module').then(m => m.DmpEditorModule),
canActivate:[AuthGuard],
data: { data: {
breadcrumb: true, breadcrumb: true,
...BreadcrumbService.generateRouteDataConfiguration({ ...BreadcrumbService.generateRouteDataConfiguration({
@ -27,6 +30,7 @@ const routes: Routes = [
{ {
path: 'edit', path: 'edit',
loadChildren: () => import('./dmp-editor-blueprint/dmp-editor.module').then(m => m.DmpEditorModule), loadChildren: () => import('./dmp-editor-blueprint/dmp-editor.module').then(m => m.DmpEditorModule),
canActivate:[AuthGuard],
data: { data: {
breadcrumb: true, breadcrumb: true,
...BreadcrumbService.generateRouteDataConfiguration({ ...BreadcrumbService.generateRouteDataConfiguration({
@ -37,80 +41,32 @@ const routes: Routes = [
}, },
{ {
path: '', path: '',
canActivate:[AuthGuard],
loadChildren: () => import('./listing/dmp-listing.module').then(m => m.DmpListingModule), loadChildren: () => import('./listing/dmp-listing.module').then(m => m.DmpListingModule),
data: { data: {
breadcrumb: true breadcrumb: true
}, },
}, },
];
const publicRoutes: Routes = [
{
// { path: 'overview',
// path: 'publicEdit/:publicId', loadChildren: () => import('./overview/dmp-overview.module').then(m => m.DmpOverviewModule),
// component: DmpEditorComponent, data: {
// data: { breadcrumb: true,
// breadcrumb: true, ...BreadcrumbService.generateRouteDataConfiguration({
// title: 'GENERAL.TITLES.DMP-PUBLIC-EDIT' hideNavigationItem: true
// }, }),
// canDeactivate: [CanDeactivateGuard] }
// }, },
{
// { path: '',
// path: 'publicOverview/:publicId', loadChildren: () => import('./listing/dmp-listing.module').then(m => m.DmpListingModule),
// component: DmpOverviewComponent, data: {
// data: { breadcrumb: true
// breadcrumb: true, },
// title: 'GENERAL.TITLES.DMP-OVERVIEW' },
// },
// },
// {
// path: 'new/dataset',
// component: DmpEditorComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumbs: 'new/dataset',
// title: 'GENERAL.TITLES.DATASET-NEW'
// }
// },
// {
// path: 'new/dataset/:dmpId',
// component: DmpEditorComponent,
// canActivate: [AuthGuard],
// data: {
// breadcrumbs: 'new/dataset',
// title: 'GENERAL.TITLES.DATASET-NEW'
// }
// },
// {
// path: 'new_version/:id',
// // component: DmpWizardComponent,
// component: DmpCloneComponent,
// data: {
// clone: false,
// breadcrumb: true,
// title: 'GENERAL.TITLES.DMP-NEW-VERSION'
// },
// },
// {
// path: 'clone/:id',
// component: DmpCloneComponent,
// data: {
// clone: false,
// breadcrumb: true,
// title: 'GENERAL.TITLES.DMP-CLONE'
// },
// },
// {
// path: 'invitation/:id',
// component: InvitationAcceptedComponent,
// data: {
// breadcrumb: true
// },
// }
]; ];
@NgModule({ @NgModule({
@ -118,3 +74,9 @@ const routes: Routes = [
exports: [RouterModule] exports: [RouterModule]
}) })
export class DmpRoutingModule { } export class DmpRoutingModule { }
@NgModule({
imports: [RouterModule.forChild(publicRoutes)],
exports: [RouterModule]
})
export class PublicDmpRoutingModule { }

View File

@ -12,7 +12,7 @@
<app-dmp-user-field-component [form]="formGroup" [validationErrorModel]="editorModel.validationErrorModel" [sections]="selectedBlueprint.definition.sections" [viewOnly]="false" [initializeUsers]="true" [enableSorting]="false"></app-dmp-user-field-component> <app-dmp-user-field-component [form]="formGroup" [validationErrorModel]="editorModel.validationErrorModel" [sections]="selectedBlueprint.definition.sections" [viewOnly]="false" [initializeUsers]="true" [enableSorting]="false"></app-dmp-user-field-component>
</div> </div>
<div class="col mt-2"> <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> <button mat-raised-button *ngIf="hasValue()" [disabled]="inProgressSendButton" (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('backendError')">{{formGroup.get('users').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('users').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="formGroup.get('users').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</div> </div>

View File

@ -1,29 +1,31 @@
import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { HttpErrorResponse } from '@angular/common/http';
import { Component, Inject, OnInit } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms'; import { UntypedFormGroup } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { DmpUserRole } from '@app/core/common/enum/dmp-user-role'; import { DmpUserRole } from '@app/core/common/enum/dmp-user-role';
import { DmpBlueprint } from '@app/core/model/dmp-blueprint/dmp-blueprint';
import { DmpUserPersist } from '@app/core/model/dmp/dmp'; import { DmpUserPersist } from '@app/core/model/dmp/dmp';
import { DmpService } from '@app/core/services/dmp/dmp.service'; import { DmpService } from '@app/core/services/dmp/dmp.service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-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 { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { BaseComponent } from '@common/base/base.component'; import { BaseComponent } from '@common/base/base.component';
import { FilterService } from '@common/modules/text-filter/filter-service'; import { FormService } from '@common/forms/form-service';
import { HttpError, HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DmpEditorModel } from '../../dmp-editor-blueprint/dmp-editor.model';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { DmpBlueprint } from '@app/core/model/dmp-blueprint/dmp-blueprint'; import { DmpEditorModel } from '../../dmp-editor-blueprint/dmp-editor.model';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; import { DmpEditorService } from '../../dmp-editor-blueprint/dmp-editor.service';
import { HttpErrorResponse } from '@angular/common/http'; import { ResponseErrorCode } from '@app/core/common/enum/respone-error-code';
@Component({ @Component({
selector: 'app-invitation-dialog-component', selector: 'app-invitation-dialog-component',
templateUrl: 'dmp-invitation-dialog.component.html', templateUrl: 'dmp-invitation-dialog.component.html',
styleUrls: ['./dmp-invitation-dialog.component.scss'], styleUrls: ['./dmp-invitation-dialog.component.scss'],
providers: [DmpEditorService]
}) })
export class DmpInvitationDialogComponent extends BaseComponent implements OnInit { export class DmpInvitationDialogComponent extends BaseComponent implements OnInit {
@ -31,7 +33,8 @@ export class DmpInvitationDialogComponent extends BaseComponent implements OnIni
editorModel: DmpEditorModel; editorModel: DmpEditorModel;
formGroup: UntypedFormGroup; formGroup: UntypedFormGroup;
dmpUserRoleEnum = DmpUserRole; dmpUserRoleEnum = DmpUserRole;
selectedBlueprint: DmpBlueprint; selectedBlueprint: DmpBlueprint;
inProgressSendButton = false;
readonly separatorKeysCodes: number[] = [ENTER, COMMA]; readonly separatorKeysCodes: number[] = [ENTER, COMMA];
constructor( constructor(
@ -43,8 +46,8 @@ export class DmpInvitationDialogComponent extends BaseComponent implements OnIni
private uiNotificationService: UiNotificationService, private uiNotificationService: UiNotificationService,
private httpErrorHandlingService: HttpErrorHandlingService, private httpErrorHandlingService: HttpErrorHandlingService,
private dmpService: DmpService, private dmpService: DmpService,
private userService: UserService, private formService: FormService,
private filterService: FilterService, private dmpEditorService: DmpEditorService,
@Inject(MAT_DIALOG_DATA) public data: any @Inject(MAT_DIALOG_DATA) public data: any
) { ) {
super(); super();
@ -58,10 +61,14 @@ export class DmpInvitationDialogComponent extends BaseComponent implements OnIni
} }
send() { send() {
this.formService.removeAllBackEndErrors(this.formGroup.get("users"));
this.formService.touchAllFormFields(this.formGroup.get("users"));
if (!this.formGroup.get("users").valid) { return; } if (!this.formGroup.get("users").valid) { return; }
this.inProgressSendButton = true;
const userFormData = this.formGroup.get("users").value as DmpUserPersist[]; const userFormData = this.formGroup.get("users").value as DmpUserPersist[];
this.dmpService.inviteUsers(this.dmpId, {users: userFormData}) this.dmpService.inviteUsers(this.dmpId, { users: userFormData })
.pipe(takeUntil(this._destroyed)) .pipe(takeUntil(this._destroyed))
.subscribe( .subscribe(
complete => { complete => {
@ -84,9 +91,21 @@ export class DmpInvitationDialogComponent extends BaseComponent implements OnIni
this.uiNotificationService.snackBarNotification(this.language.instant('DMP-USER-INVITATION-DIALOG.SUCCESS'), SnackBarNotificationLevel.Success); this.uiNotificationService.snackBarNotification(this.language.instant('DMP-USER-INVITATION-DIALOG.SUCCESS'), SnackBarNotificationLevel.Success);
} }
// onCallbackError(errorResponse: HttpErrorResponse) {
// this.inProgressSendButton = false;
// let errorOverrides = new Map<number, string>();
// errorOverrides.set(-1, this.language.instant('DMP-USER-INVITATION-DIALOG.ERROR'));
// this.httpErrorHandlingService.handleBackedRequestError(errorResponse, errorOverrides, SnackBarNotificationLevel.Error);
// }
onCallbackError(errorResponse: HttpErrorResponse) { onCallbackError(errorResponse: HttpErrorResponse) {
let errorOverrides = new Map<number, string>(); this.inProgressSendButton = false;
errorOverrides.set(-1, this.language.instant('DMP-USER-INVITATION-DIALOG.ERROR')); this.httpErrorHandlingService.handleBackedRequestError(errorResponse);
this.httpErrorHandlingService.handleBackedRequestError(errorResponse, errorOverrides, SnackBarNotificationLevel.Error);
const error: HttpError = this.httpErrorHandlingService.getError(errorResponse);
if (error.statusCode === 400) {
this.editorModel.validationErrorModel.fromJSONObject(errorResponse.error);
this.formService.validateAllFormFields(this.formGroup);
}
} }
} }

View File

@ -1,6 +1,6 @@
<div class="main-content dmp-overview pl-5 pr-5"> <div class="main-content dmp-overview pl-5 pr-5">
<div class="container-fluid pl-0 pr-0"> <div class="container-fluid pl-0 pr-0">
<div *ngIf="dmp"> <div *ngIf="dmp && userName">
<div class="row"> <div class="row">
<div class="col-12 pl-2 mb-3"> <div class="col-12 pl-2 mb-3">
<app-navigation-breadcrumb /> <app-navigation-breadcrumb />
@ -258,11 +258,15 @@
</button> </button>
</div> </div>
<div class="col pl-0" style="min-width: 0;"> <div class="col pl-0" style="min-width: 0;">
<!-- <div class="mytext">{{ dmpUser.user?.name }}</div> --> <ng-container *ngIf="!isUserAuthor(dmpUser.user?.id); else you">
<p class="authors-label">{{ dmpUser.user?.name }} <p class="authors-label">{{ dmpUser.user?.name }}</p>
<span *ngIf="isUserAuthor(dmpUser.user?.id)"> </ng-container>
({{ 'DMP-OVERVIEW.YOU' | translate }})</span> <ng-template #you>
</p> <p class="authors-label"> {{ userName }}
<span >
({{ 'DMP-OVERVIEW.YOU' | translate }})</span>
</p>
</ng-template>
<p class="authors-role"> <p class="authors-role">
<span>{{ enumUtils.toDmpUserRoleString(dmpUser.role) }} - </span> <span>{{ enumUtils.toDmpUserRoleString(dmpUser.role) }} - </span>
<span *ngIf="!dmpUser.sectionId">{{ 'DMP-OVERVIEW.ROLES.ALL-SECTIONS' | translate}}</span> <span *ngIf="!dmpUser.sectionId">{{ 'DMP-OVERVIEW.ROLES.ALL-SECTIONS' | translate}}</span>

View File

@ -41,7 +41,7 @@ import { PopupNotificationDialogComponent } from '@app/library/notification/popu
import { BaseComponent } from '@common/base/base.component'; import { BaseComponent } from '@common/base/base.component';
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { takeUntil } from 'rxjs/operators'; import { map, takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof'; import { nameof } from 'ts-simple-nameof';
import { CloneDmpDialogComponent } from '../clone-dialog/dmp-clone-dialog.component'; import { CloneDmpDialogComponent } from '../clone-dialog/dmp-clone-dialog.component';
import { DmpDeleteDialogComponent } from '../dmp-delete-dialog/dmp-delete-dialog.component'; import { DmpDeleteDialogComponent } from '../dmp-delete-dialog/dmp-delete-dialog.component';
@ -51,6 +51,9 @@ import { DmpInvitationDialogComponent } from '../invitation/dialog/dmp-invitatio
import { NewVersionDmpDialogComponent } from '../new-version-dialog/dmp-new-version-dialog.component'; import { NewVersionDmpDialogComponent } from '../new-version-dialog/dmp-new-version-dialog.component';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { User } from '@app/core/model/user/user';
import { UserService } from '@app/core/services/user/user.service';
import { Observable, of } from 'rxjs';
@Component({ @Component({
selector: 'app-dmp-overview', selector: 'app-dmp-overview',
@ -85,6 +88,7 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
dmpUserRoleEnum = DmpUserRole; dmpUserRoleEnum = DmpUserRole;
authorFocus: string; authorFocus: string;
userName: string;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@ -108,6 +112,7 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
private analyticsService: AnalyticsService, private analyticsService: AnalyticsService,
private breadcrumbService: BreadcrumbService, private breadcrumbService: BreadcrumbService,
private httpErrorHandlingService: HttpErrorHandlingService, private httpErrorHandlingService: HttpErrorHandlingService,
private userService: UserService,
) { ) {
super(); super();
} }
@ -205,6 +210,13 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
this.depositRepos = repos; this.depositRepos = repos;
}, },
error => this.depositRepos = []); error => this.depositRepos = []);
this.userService.getSingle(this.authentication.userId(), [
nameof<User>(x => x.id),
nameof<User>(x => x.name)])
.pipe(map(u => u.name)).subscribe(name => this.userName = name);
} else {
this.userName = '';
} }
} }
@ -223,7 +235,7 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
isUserAuthor(userId: Guid): boolean { isUserAuthor(userId: Guid): boolean {
if (this.isAuthenticated()) { if (this.isAuthenticated()) {
const principalId: Guid = this.authentication.userId(); const principalId: Guid = this.authentication.userId();
return userId === principalId; return this.userName && userId === principalId;
} else return false; } else return false;
} }

View File

@ -90,14 +90,20 @@ export class NavbarComponent extends BaseComponent implements OnInit {
this.authentication.getAuthenticationStateObservable().subscribe(authenticationState => { this.authentication.getAuthenticationStateObservable().subscribe(authenticationState => {
if (authenticationState.loginStatus === LoginStatus.LoggedIn) { if (authenticationState.loginStatus === LoginStatus.LoggedIn) {
this.loadLogo(); this.loadLogo();
this.loadUser();
} }
}); });
this.loadLogo(); this.loadLogo();
this.loadUser();
}
this.userService.getSingle(this.authentication.userId(), [ private loadUser() {
nameof<User>(x => x.id), if (this.authentication.currentAccountIsAuthenticated() && this.authentication.userId()) {
nameof<User>(x => x.name) this.userService.getSingle(this.authentication.userId(), [
]).subscribe(u => this.userName = u.name); //TODO HANDLE-ERRORS nameof<User>(x => x.id),
nameof<User>(x => x.name)
]).subscribe(u => this.userName = u.name); //TODO HANDLE-ERRORS
}
} }
private loadLogo() { private loadLogo() {

View File

@ -4,7 +4,6 @@
<div *ngIf="showItem(groupMenuItem);"> <div *ngIf="showItem(groupMenuItem);">
<hr *ngIf="!firstGroup"> <hr *ngIf="!firstGroup">
<mat-list-item routerLinkActive="active" [isActiveMatchOptions]="{ paths: 'exact', queryParams: 'ignored' }" *ngFor="let groupMenuRoute of groupMenuItem.routes; let first = first" class="nav-item" [ngClass]="{'mt-4': first && firstGroup}"> <mat-list-item routerLinkActive="active" [isActiveMatchOptions]="{ paths: 'exact', queryParams: 'ignored' }" *ngFor="let groupMenuRoute of groupMenuItem.routes; let first = first" class="nav-item" [ngClass]="{'mt-4': first && firstGroup}">
<!-- {{ groupMenuRoute |json }} -->
<a class="new-dmp nav-link nav-row" *ngIf="groupMenuRoute.path !== '/contact-support' && groupMenuRoute.path !== '/co-branding' && groupMenuRoute.path !== '/feedback' && groupMenuRoute.path !== '/descriptions'" [routerLink]="[groupMenuRoute.path]" [ngClass]="{'dmp-tour': groupMenuRoute.path == '/plans'}"> <a class="new-dmp nav-link nav-row" *ngIf="groupMenuRoute.path !== '/contact-support' && groupMenuRoute.path !== '/co-branding' && groupMenuRoute.path !== '/feedback' && groupMenuRoute.path !== '/descriptions'" [routerLink]="[groupMenuRoute.path]" [ngClass]="{'dmp-tour': groupMenuRoute.path == '/plans'}">
<i class="material-symbols-outlined icon">{{ groupMenuRoute.icon }}</i> <i class="material-symbols-outlined icon">{{ groupMenuRoute.icon }}</i>
<i *ngIf="groupMenuRoute.path == '/plans'" class="material-symbols-outlined icon-mask">person</i> <i *ngIf="groupMenuRoute.path == '/plans'" class="material-symbols-outlined icon-mask">person</i>

View File

@ -1,15 +1,13 @@
import { Component, EventEmitter, OnInit, Output } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { MatButtonToggleChange } from "@angular/material/button-toggle"; import { MatButtonToggleChange } from "@angular/material/button-toggle";
import { Router } from "@angular/router";
import { Tenant } from "@app/core/model/tenant/tenant"; import { Tenant } from "@app/core/model/tenant/tenant";
import { AuthService } from "@app/core/services/auth/auth.service"; import { AuthService } from "@app/core/services/auth/auth.service";
import { PrincipalService } from "@app/core/services/http/principal.service"; import { PrincipalService } from "@app/core/services/http/principal.service";
import { TenantHandlingService } from "@app/core/services/tenant/tenant-handling.service";
import { BaseComponent } from "@common/base/base.component"; import { BaseComponent } from "@common/base/base.component";
import { BaseHttpParams } from "@common/http/base-http-params"; import { BaseHttpParams } from "@common/http/base-http-params";
import { InterceptorType } from "@common/http/interceptors/interceptor-type"; import { InterceptorType } from "@common/http/interceptors/interceptor-type";
import { KeycloakService } from "keycloak-angular"; import { Observable } from "rxjs";
import { Observable, from } from "rxjs";
import { takeUntil } from "rxjs/operators";
@Component({ @Component({
selector: 'app-tenant-switch', selector: 'app-tenant-switch',
@ -20,10 +18,9 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
tenants: Observable<Array<Tenant>>; tenants: Observable<Array<Tenant>>;
constructor( constructor(
private router: Router,
private keycloakService: KeycloakService,
private principalService: PrincipalService, private principalService: PrincipalService,
private authService: AuthService, private authService: AuthService,
private tenantHandlingService: TenantHandlingService
) { ) {
super(); super();
} }
@ -34,7 +31,6 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.tenants = this.loadUserTenants(); //TODO this.tenants = this.loadUserTenants(); //TODO
//this.tenantChange.emit(this.getCurrentLanguage())
} }
loadUserTenants(): Observable<Array<Tenant>> { loadUserTenants(): Observable<Array<Tenant>> {
@ -48,22 +44,7 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
onTenantSelected(selectedTenant: MatButtonToggleChange) { onTenantSelected(selectedTenant: MatButtonToggleChange) {
if (selectedTenant.value === undefined || selectedTenant.value === '') return; if (selectedTenant.value === undefined || selectedTenant.value === '') return;
this.formSubmit(selectedTenant.value); this.authService.selectedTenant(selectedTenant.value);
this.loadUser(); window.location.href = this.tenantHandlingService.getCurrentUrlEnrichedWithTenantCode(selectedTenant.value, true);
}
formSubmit(selectedTenant: string): void {
this.authService.selectedTenant(selectedTenant);
}
loadUser(): void {
this.authService.prepareAuthRequest(from(this.keycloakService.getToken()), {})
.pipe(takeUntil(this._destroyed))
.subscribe(
() => {
this.authService.onAuthenticateSuccessReload();
},
(error) => this.authService.onAuthenticateError(error)
);
} }
} }

View File

@ -1,5 +1,3 @@
<!-- {{ userCredentials | async | json }} -->
<div class="profile"> <div class="profile">
<div class="container-fluid"> <div class="container-fluid">
<div *ngIf="user | async as userProfile; else loading" class="user-profile"> <div *ngIf="user | async as userProfile; else loading" class="user-profile">

View File

@ -1,4 +1,3 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -18,6 +17,7 @@ import { AnalyticsService } from '@app/core/services/matomo/analytics-service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.service'; import { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.service';
import { ReferenceService } from '@app/core/services/reference/reference.service'; import { ReferenceService } from '@app/core/services/reference/reference.service';
import { TenantHandlingService } from '@app/core/services/tenant/tenant-handling.service';
import { UserService } from '@app/core/services/user/user.service'; import { UserService } from '@app/core/services/user/user.service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration'; import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
@ -28,16 +28,15 @@ import { FormValidationErrorsDialogComponent } from '@common/forms/form-validati
import { BaseHttpParams } from '@common/http/base-http-params'; import { BaseHttpParams } from '@common/http/base-http-params';
import { InterceptorType } from '@common/http/interceptors/interceptor-type'; import { InterceptorType } from '@common/http/interceptors/interceptor-type';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { KeycloakService } from 'keycloak-angular';
import * as moment from 'moment-timezone'; import * as moment from 'moment-timezone';
import { Observable, from, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators'; import { map, takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof'; import { nameof } from 'ts-simple-nameof';
import { AddAccountDialogComponent } from './add-account/add-account-dialog.component'; import { AddAccountDialogComponent } from './add-account/add-account-dialog.component';
import { UserProfileEditorModel } from './user-profile-editor.model'; import { UserProfileEditorModel } from './user-profile-editor.model';
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
@Component({ @Component({
selector: 'app-user-profile', selector: 'app-user-profile',
@ -81,7 +80,7 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
private dialog: MatDialog, private dialog: MatDialog,
public enumUtils: EnumUtils, public enumUtils: EnumUtils,
private formBuilder: UntypedFormBuilder, private formBuilder: UntypedFormBuilder,
private keycloakService: KeycloakService, private tenantHandlingService: TenantHandlingService,
private principalService: PrincipalService, private principalService: PrincipalService,
private formService: FormService, private formService: FormService,
private referenceService: ReferenceService, private referenceService: ReferenceService,
@ -281,7 +280,7 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
}, maxWidth: '30em' }, maxWidth: '30em'
}); });
}, },
error => this.httpErrorHandlingService.handleBackedRequestError(error)); error => this.httpErrorHandlingService.handleBackedRequestError(error));
} }
}); });
} }
@ -309,7 +308,7 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
}); });
} }
}, },
error => this.httpErrorHandlingService.handleBackedRequestError(error)); //TODO how to handle this error => this.httpErrorHandlingService.handleBackedRequestError(error)); //TODO how to handle this
} }
}); });
} }
@ -367,17 +366,9 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
if (this.tenantFormGroup.valid === false) return; if (this.tenantFormGroup.valid === false) return;
const selectedTenant = this.tenantFormGroup.get('tenantCode').value; const selectedTenant = this.tenantFormGroup.get('tenantCode').value;
this.formSubmit(selectedTenant);
this.loadUser();
}
formSubmit(selectedTenant: string): void { this.authService.selectedTenant(selectedTenant.value);
this.authService.selectedTenant(selectedTenant); window.location.href = this.tenantHandlingService.getCurrentUrlEnrichedWithTenantCode(selectedTenant.value, true);
}
loadUser(): void {
const returnUrl = '/profile';
this.authService.prepareAuthRequest(from(this.keycloakService.getToken()), {}).pipe(takeUntil(this._destroyed)).subscribe(() => this.authService.onAuthenticateSuccess(returnUrl), (error) => this.authService.onAuthenticateError(error));
} }
//Preferences //Preferences

View File

@ -69,7 +69,8 @@
"DESCRIPTION-TEMPLATE-INACTIVE-USER": "This description template contains users that are not exist", "DESCRIPTION-TEMPLATE-INACTIVE-USER": "This description template contains users that are not exist",
"DESCRIPTION-TEMPLATE-MISSING-USER-CONTACT-INFO": "This description template contains users that don't have contact info", "DESCRIPTION-TEMPLATE-MISSING-USER-CONTACT-INFO": "This description template contains users that don't have contact info",
"DMP-INACTIVE-USER": "This plan contains users that are not exist", "DMP-INACTIVE-USER": "This plan contains users that are not exist",
"DMP-MISSING-USER-CONTACT-INFO": "This plan contains users that don't have contact info" "DMP-MISSING-USER-CONTACT-INFO": "This plan contains users that don't have contact info",
"DUPLICATE-DMP-USER": "You can't invite authors with same role and plan section more than once"
}, },
"FORM-VALIDATION-DISPLAY-DIALOG": { "FORM-VALIDATION-DISPLAY-DIALOG": {
"WARNING": "Warning!", "WARNING": "Warning!",
@ -257,7 +258,7 @@
"POLICY": "Cookies Policy" "POLICY": "Cookies Policy"
}, },
"EMAIL-CONFIRMATION": { "EMAIL-CONFIRMATION": {
"EXPIRED-EMAIL": "Mail invitation expired", "EXPIRED-EMAIL": "Your mail invitation has expired, or you are not logged in with the correct account.",
"EMAIL-FOUND": "Email is already confirmed" "EMAIL-FOUND": "Email is already confirmed"
}, },
"HOME": { "HOME": {
@ -2221,7 +2222,8 @@
"MERGE-ACCOUNT": { "MERGE-ACCOUNT": {
"TITLE": "Merge Your Account", "TITLE": "Merge Your Account",
"MESSAGES": { "MESSAGES": {
"CONFIRMATION": "Are you sure that you want to merge this account?" "CONFIRMATION": "Are you sure that you want to merge this account?",
"INVALID-TOKEN": "Looks like your mail invitation has expired, or you are not logged in with the correct account. Please try logging in again or have the invitation re-sent."
}, },
"ACTIONS": { "ACTIONS": {
"CONFIRM": "Confirm" "CONFIRM": "Confirm"

View File

@ -2,6 +2,7 @@ import { AbstractControl, UntypedFormArray, UntypedFormGroup, ValidatorFn, Valid
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model'; import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
import { isNullOrUndefined } from '@app/utilities/enhancers/utils'; import { isNullOrUndefined } from '@app/utilities/enhancers/utils';
import { DmpBlueprintSystemFieldType } from '@app/core/common/enum/dmp-blueprint-system-field-type'; import { DmpBlueprintSystemFieldType } from '@app/core/common/enum/dmp-blueprint-system-field-type';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
export function BackendErrorValidator(errorModel: ValidationErrorModel, propertyName: string): ValidatorFn { export function BackendErrorValidator(errorModel: ValidationErrorModel, propertyName: string): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => { return (control: AbstractControl): { [key: string]: any } => {
@ -29,6 +30,34 @@ export function CustomErrorValidator(errorModel: ValidationErrorModel, propertyN
}; };
} }
export function RequiredWithVisibilityRulesValidator(visibilityRulesService: VisibilityRulesService, visibilityRulesKey: string) {
return (control: AbstractControl): { [key: string]: any } => {
if (visibilityRulesService.isVisibleMap[visibilityRulesKey] ?? true) {
return Validators.required(control);
}
control.setErrors(null);
return null;
};
}
export function UrlValidator() {
return (control: AbstractControl): { [key: string]: any } => {
const urlRegex = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/;
return Validators.pattern(urlRegex);
};
}
export function MinMaxValidator(min: number, max: number) {
return (control: AbstractControl): { [key: string]: any } => {
return null;
};
}
export function DateValidator(): ValidatorFn { export function DateValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => { return (control: AbstractControl): { [key: string]: any } => {
if (control.value) { if (control.value) {
@ -105,28 +134,28 @@ export function DmpBlueprintSystemFieldRequiredValidator(): ValidatorFn {
let foundTitle = false; let foundTitle = false;
let foundDescription = false; let foundDescription = false;
let foundLanguage = false; let foundLanguage = false;
let foundAccess = false; let foundAccess = false;
const sectionsFormArray = (control as UntypedFormArray); const sectionsFormArray = (control as UntypedFormArray);
if (sectionsFormArray.controls != null && sectionsFormArray.controls.length > 0 ){ if (sectionsFormArray.controls != null && sectionsFormArray.controls.length > 0) {
sectionsFormArray.controls.forEach((section, index) => { sectionsFormArray.controls.forEach((section, index) => {
const fieldsFormArray = section.get('fields') as UntypedFormArray; const fieldsFormArray = section.get('fields') as UntypedFormArray;
if (fieldsFormArray && fieldsFormArray.length > 0){ if (fieldsFormArray && fieldsFormArray.length > 0) {
if (fieldsFormArray.controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === DmpBlueprintSystemFieldType.Title)){ if (fieldsFormArray.controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === DmpBlueprintSystemFieldType.Title)) {
foundTitle = true; foundTitle = true;
} }
if (fieldsFormArray.controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === DmpBlueprintSystemFieldType.Description)){ if (fieldsFormArray.controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === DmpBlueprintSystemFieldType.Description)) {
foundDescription = true; foundDescription = true;
} }
if (fieldsFormArray.controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === DmpBlueprintSystemFieldType.Language)){ if (fieldsFormArray.controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === DmpBlueprintSystemFieldType.Language)) {
foundLanguage = true; foundLanguage = true;
} }
if (fieldsFormArray.controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === DmpBlueprintSystemFieldType.AccessRights)){ if (fieldsFormArray.controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === DmpBlueprintSystemFieldType.AccessRights)) {
foundAccess = true; foundAccess = true;
} }
} }
}); });
} }
return foundTitle && foundDescription && foundAccess && foundLanguage ? null : { 'dmpBlueprintSystemFieldRequired': true }; return foundTitle && foundDescription && foundAccess && foundLanguage ? null : { 'dmpBlueprintSystemFieldRequired': true };
}; };

View File

@ -43,13 +43,13 @@ export class UnauthorizedResponseInterceptor extends BaseInterceptor {
this.authService.refreshToken().then((isRefreshed) => { this.authService.refreshToken().then((isRefreshed) => {
this.accountRefresh$ = null; this.accountRefresh$ = null;
if (!isRefreshed) { if (!isRefreshed) {
this.logoutUser(); this.handleUnauthorized();
return false; return false;
} }
return true; return true;
}).catch(x => { }).catch(x => {
this.logoutUser(); this.handleUnauthorized();
return false; return false;
}) })
).pipe(filter((x) => x)); ).pipe(filter((x) => x));
@ -67,10 +67,10 @@ export class UnauthorizedResponseInterceptor extends BaseInterceptor {
return next.handle(newRequest); return next.handle(newRequest);
} }
private logoutUser() { private handleUnauthorized() {
if (!this.isLoginRoute() && !this.isSignupRoute()) { if (!this.isLoginRoute() && !this.isSignupRoute()) {
this.authService.clear(); this.authService.clear();
this.router.navigate(['/unauthorized']); this.router.navigate(['/unauthorized', { queryParams: { returnUrl: this.router.url } }]);
} }
} }

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
</head> </head>
<body class=""> <body class="">
{description} {description}

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
<style> <style>
/* ------------------------------------- /* -------------------------------------
GLOBAL RESETS GLOBAL RESETS

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
<style> <style>
/* ------------------------------------- /* -------------------------------------
GLOBAL RESETS GLOBAL RESETS

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
<style> <style>
/* ------------------------------------- /* -------------------------------------
GLOBAL RESETS GLOBAL RESETS

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
<style> <style>
/* ------------------------------------- /* -------------------------------------
GLOBAL RESETS GLOBAL RESETS

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
<style> <style>
/* ------------------------------------- /* -------------------------------------
GLOBAL RESETS GLOBAL RESETS

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
<style> <style>
/* ------------------------------------- /* -------------------------------------
GLOBAL RESETS GLOBAL RESETS

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
<style> <style>
/* ------------------------------------- /* -------------------------------------
GLOBAL RESETS GLOBAL RESETS

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
</head> </head>
<body class=""> <body class="">
<p>Dear {recipient},</p> <p>Dear {recipient},</p>

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
<style> <style>
/* ------------------------------------- /* -------------------------------------
GLOBAL RESETS GLOBAL RESETS

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
<style> <style>
/* ------------------------------------- /* -------------------------------------
GLOBAL RESETS GLOBAL RESETS

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
</head> </head>
<body class=""> <body class="">
{description} {description}

View File

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title> <title>OpenCDMP Notification</title>
<style> <style>
/* ------------------------------------- /* -------------------------------------
GLOBAL RESETS GLOBAL RESETS