Merge branch 'dmp-refactoring' of code-repo.d4science.org:MaDgiK-CITE/argos into dmp-refactoring
This commit is contained in:
commit
e322b6adbc
|
@ -308,4 +308,14 @@ public class ErrorThesaurusProperties {
|
|||
public void setImportDescriptionWithoutDmpDescriptionTemplate(ErrorDescription importDescriptionWithoutDmpDescriptionTemplate) {
|
||||
this.importDescriptionWithoutDmpDescriptionTemplate = importDescriptionWithoutDmpDescriptionTemplate;
|
||||
}
|
||||
|
||||
private ErrorDescription duplicateDmpUser;
|
||||
|
||||
public ErrorDescription getDuplicateDmpUser() {
|
||||
return duplicateDmpUser;
|
||||
}
|
||||
|
||||
public void setDuplicateDmpUser(ErrorDescription duplicateDmpUser) {
|
||||
this.duplicateDmpUser = duplicateDmpUser;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,12 +81,6 @@ public class DescriptionTemplateDeleter implements Deleter {
|
|||
UserDescriptionTemplateDeleter deleter = this.deleterFactory.deleter(UserDescriptionTemplateDeleter.class);
|
||||
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
|
||||
|
||||
|
@ -94,7 +88,6 @@ public class DescriptionTemplateDeleter implements Deleter {
|
|||
|
||||
for (DescriptionTemplateEntity item : data) {
|
||||
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.setUpdatedAt(now);
|
||||
logger.trace("updating item");
|
||||
|
|
|
@ -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();
|
||||
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) {
|
||||
xml.setDescriptionTemplate(this.descriptionTemplateService.exportXmlEntity(descriptionTemplateEntity.getId(), true));
|
||||
}
|
||||
|
|
|
@ -572,8 +572,8 @@ public class DescriptionTemplateServiceImpl implements DescriptionTemplateServic
|
|||
if (previousFinalized != null){
|
||||
previousFinalized.setVersionStatus(DescriptionTemplateVersionStatus.Current);
|
||||
this.entityManager.merge(previousFinalized);
|
||||
}
|
||||
data.setVersionStatus(DescriptionTemplateVersionStatus.NotFinalized);
|
||||
}
|
||||
this.entityManager.merge(data);
|
||||
this.entityManager.flush();
|
||||
}
|
||||
|
@ -908,7 +908,7 @@ public class DescriptionTemplateServiceImpl implements DescriptionTemplateServic
|
|||
logger.debug(new MapLogEntry("exportXml").And("id", id));
|
||||
|
||||
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()));
|
||||
|
||||
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));
|
||||
|
||||
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()));
|
||||
|
||||
String xml = this.xmlHandlingService.toXml(this.exportXmlEntity(id, false));
|
||||
|
|
|
@ -769,6 +769,8 @@ public class DmpServiceImpl implements DmpService {
|
|||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
public Dmp removeUser(DmpUserRemovePersist model, FieldSet fields) throws InvalidApplicationException, IOException {
|
||||
this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(model.getDmpId())), Permission.AssignDmpUsers);
|
||||
|
|
|
@ -37,6 +37,8 @@ public interface UserService {
|
|||
|
||||
void sendRemoveCredentialConfirmation(RemoveCredentialRequestPersist model) throws InvalidApplicationException, JAXBException;
|
||||
|
||||
boolean doesTokenBelongToLoggedInUser(String token) throws InvalidApplicationException, IOException;
|
||||
|
||||
void confirmMergeAccount(String token) throws InvalidApplicationException, IOException;
|
||||
|
||||
void confirmRemoveCredential(String token) throws InvalidApplicationException;
|
||||
|
|
|
@ -601,6 +601,12 @@ public class UserServiceImpl implements UserService {
|
|||
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 {
|
||||
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()));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ import org.opencdmp.authorization.AuthorizationFlags;
|
|||
import org.opencdmp.commons.enums.DmpAccessType;
|
||||
import org.opencdmp.commons.enums.DmpStatus;
|
||||
import org.opencdmp.commons.enums.IsActive;
|
||||
import org.opencdmp.controllers.swagger.SwaggerHelpers;
|
||||
import org.opencdmp.convention.ConventionService;
|
||||
import org.opencdmp.data.StorageFileEntity;
|
||||
import org.opencdmp.model.DescriptionValidationResult;
|
||||
|
@ -151,16 +152,16 @@ public class DescriptionController {
|
|||
@PostMapping("query")
|
||||
@Operation(
|
||||
summary = "Query all descriptions",
|
||||
description = SwaggerHelpers.endpoint_query,
|
||||
description = SwaggerHelpers.Description.endpoint_query,
|
||||
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = SwaggerHelpers.endpoint_query_request_body,
|
||||
description = SwaggerHelpers.Description.endpoint_query_request_body,
|
||||
content = {
|
||||
@Content(
|
||||
examples = {
|
||||
@ExampleObject(
|
||||
name = "Pagination and projection",
|
||||
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")
|
||||
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 = "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)
|
||||
throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||
@Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet
|
||||
) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||
logger.debug(new MapLogEntry("retrieving" + Description.class.getSimpleName()).And("id", id).And("fields", fieldSet));
|
||||
fieldSet = this.fieldSetExpanderService.expand(fieldSet);
|
||||
|
||||
|
@ -208,7 +209,10 @@ public class DescriptionController {
|
|||
@Operation(summary = "Create a new or update an existing description")
|
||||
@Transactional
|
||||
@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));
|
||||
fieldSet = this.fieldSetExpanderService.expand(fieldSet);
|
||||
|
||||
|
@ -226,7 +230,10 @@ public class DescriptionController {
|
|||
@Operation(summary = "Update the status of an existing description")
|
||||
@Transactional
|
||||
@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));
|
||||
fieldSet = this.fieldSetExpanderService.expand(fieldSet);
|
||||
|
||||
|
@ -275,7 +282,9 @@ public class DescriptionController {
|
|||
@DeleteMapping("{id}")
|
||||
@Operation(summary = "Delete a description by id")
|
||||
@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));
|
||||
|
||||
this.descriptionService.deleteAndSave(id);
|
||||
|
@ -285,7 +294,10 @@ public class DescriptionController {
|
|||
|
||||
@GetMapping("{id}/export/{type}")
|
||||
@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"));
|
||||
|
||||
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")
|
||||
@Transactional
|
||||
@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));
|
||||
fieldSet = this.fieldSetExpanderService.expand(fieldSet);
|
||||
|
||||
|
@ -311,7 +327,10 @@ public class DescriptionController {
|
|||
|
||||
@GetMapping("{id}/field-file/{fileId}")
|
||||
@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));
|
||||
|
||||
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")
|
||||
@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));
|
||||
|
||||
ResponseEntity<byte[]> response = this.descriptionService.exportXml(id);
|
||||
|
@ -360,181 +381,4 @@ public class DescriptionController {
|
|||
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 =
|
||||
"""
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,9 +11,12 @@ import gr.cite.tools.fieldset.FieldSet;
|
|||
import gr.cite.tools.logging.LoggerService;
|
||||
import gr.cite.tools.logging.MapLogEntry;
|
||||
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.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
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 jakarta.xml.bind.JAXBException;
|
||||
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.DmpStatus;
|
||||
import org.opencdmp.commons.enums.IsActive;
|
||||
import org.opencdmp.controllers.swagger.SwaggerHelpers;
|
||||
import org.opencdmp.model.DescriptionsToBeFinalized;
|
||||
import org.opencdmp.model.DmpUser;
|
||||
import org.opencdmp.model.DmpValidationResult;
|
||||
|
@ -99,6 +103,7 @@ public class DmpController {
|
|||
|
||||
@PostMapping("public/query")
|
||||
@Operation(summary = "Query public published plans")
|
||||
@Hidden
|
||||
public QueryResult<PublicDmp> publicQuery(@RequestBody DmpLookup lookup) throws MyApplicationException, MyForbiddenException {
|
||||
logger.debug("querying {}", Dmp.class.getSimpleName());
|
||||
|
||||
|
@ -114,6 +119,7 @@ public class DmpController {
|
|||
|
||||
@GetMapping("public/{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 {
|
||||
logger.debug(new MapLogEntry("retrieving" + Dmp.class.getSimpleName()).And("id", id).And("fields", fieldSet));
|
||||
|
||||
|
@ -136,21 +142,38 @@ public class DmpController {
|
|||
@PostMapping("query")
|
||||
@Operation(
|
||||
summary = "Query all plans",
|
||||
description = SwaggerHelpers.endpoint_query,
|
||||
description = SwaggerHelpers.Dmp.endpoint_query,
|
||||
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = SwaggerHelpers.endpoint_query_request_body,
|
||||
description = SwaggerHelpers.Dmp.endpoint_query_request_body,
|
||||
content = {
|
||||
@Content(
|
||||
examples = {
|
||||
@ExampleObject(
|
||||
name = "Pagination and projection",
|
||||
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 {
|
||||
logger.debug("querying {}", Dmp.class.getSimpleName());
|
||||
|
@ -166,7 +189,11 @@ public class DmpController {
|
|||
|
||||
@GetMapping("{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));
|
||||
|
||||
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")
|
||||
@Transactional
|
||||
@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));
|
||||
|
||||
Dmp persisted = this.dmpService.persist(model, fieldSet);
|
||||
|
@ -204,7 +234,9 @@ public class DmpController {
|
|||
@DeleteMapping("{id}")
|
||||
@Operation(summary = "Delete a plan by id")
|
||||
@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));
|
||||
|
||||
this.dmpService.deleteAndSave(id);
|
||||
|
@ -215,7 +247,10 @@ public class DmpController {
|
|||
@PostMapping("finalize/{id}")
|
||||
@Operation(summary = "Finalize a plan by id")
|
||||
@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()));
|
||||
|
||||
this.dmpService.finalize(id, descriptions.getDescriptionIds());
|
||||
|
@ -231,7 +266,10 @@ public class DmpController {
|
|||
@GetMapping("undo-finalize/{id}")
|
||||
@Operation(summary = "Undo the finalization of a plan by id (only possible if it is not already deposited)")
|
||||
@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));
|
||||
|
||||
this.censorFactory.censor(DmpCensor.class).censor(fieldSet, null);
|
||||
|
@ -247,6 +285,7 @@ public class DmpController {
|
|||
|
||||
@GetMapping("validate/{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 {
|
||||
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")
|
||||
@Transactional
|
||||
@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));
|
||||
|
||||
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")
|
||||
@Transactional
|
||||
@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));
|
||||
|
||||
Dmp persisted = this.dmpService.createNewVersion(model, fieldSet);
|
||||
|
@ -301,6 +346,7 @@ public class DmpController {
|
|||
@Operation(summary = "Assign users to the plan by id")
|
||||
@Transactional
|
||||
@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 {
|
||||
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")
|
||||
@Transactional
|
||||
@ValidationFilterAnnotation(validator = DmpUserRemovePersist.DmpUserRemovePersistValidator.ValidatorName, argumentName = "model")
|
||||
@Hidden
|
||||
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));
|
||||
|
||||
|
@ -333,7 +380,11 @@ public class DmpController {
|
|||
|
||||
@GetMapping("{id}/export/{transformerId}/{type}")
|
||||
@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));
|
||||
|
||||
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")
|
||||
@Transactional
|
||||
@ValidationFilterAnnotation(validator = DmpUserInvitePersist.DmpUserInvitePersistValidator.ValidatorName, argumentName = "model")
|
||||
@Hidden
|
||||
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));
|
||||
|
||||
|
@ -364,6 +416,7 @@ public class DmpController {
|
|||
@GetMapping("{id}/token/{token}/invite-accept")
|
||||
@Operation(summary = "Accept an invitation token for a plan by id")
|
||||
@Transactional
|
||||
@Hidden
|
||||
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));
|
||||
|
||||
|
@ -378,7 +431,9 @@ public class DmpController {
|
|||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml")
|
||||
@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));
|
||||
|
||||
ResponseEntity<byte[]> response = this.dmpService.exportXml(id);
|
||||
|
@ -392,7 +447,11 @@ public class DmpController {
|
|||
@RequestMapping(method = RequestMethod.POST, value = "/xml/import")
|
||||
@Operation(summary = "Import a plan from an xml file")
|
||||
@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));
|
||||
|
||||
Dmp model = this.dmpService.importXml(file.getBytes(), label, fields);
|
||||
|
@ -407,7 +466,13 @@ public class DmpController {
|
|||
@PostMapping("json/import")
|
||||
@Operation(summary = "Import a plan from an json file")
|
||||
@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));
|
||||
|
||||
Dmp model = this.dmpService.importJson(file, label, repositoryId, format, fields);
|
||||
|
@ -421,171 +486,4 @@ public class DmpController {
|
|||
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_ =
|
||||
"""
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -297,6 +297,14 @@ public class UserController {
|
|||
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")
|
||||
@Transactional
|
||||
@ValidationFilterAnnotation(validator = RemoveCredentialRequestPersist.RemoveCredentialRequestPersistValidator.ValidatorName, argumentName = "model")
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -98,3 +98,6 @@ error-thesaurus:
|
|||
import-description-without-dmp-description-template:
|
||||
code: 136
|
||||
message: Error creating description without dmp description template
|
||||
duplicate-dmp-user:
|
||||
code: 137
|
||||
message: Duplicate Dmp User not allowed
|
|
@ -958,6 +958,7 @@ permissions:
|
|||
DeleteLock:
|
||||
roles:
|
||||
- Admin
|
||||
- TenantAdmin
|
||||
dmp:
|
||||
roles:
|
||||
- Owner
|
||||
|
|
|
@ -3,12 +3,11 @@ import { RouterModule, Routes } from '@angular/router';
|
|||
import { AppPermission } from './core/common/enum/permission.enum';
|
||||
import { BreadcrumbService } from './ui/misc/breadcrumb/breadcrumb.service';
|
||||
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 = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo:'home',
|
||||
redirectTo: 'home',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
|
@ -35,7 +34,7 @@ const appRoutes: Routes = [
|
|||
},
|
||||
{
|
||||
path: 'explore-descriptions',
|
||||
loadChildren: () => import('./ui/description/description.module').then(m => m.DescriptionModule),
|
||||
loadChildren: () => import('./ui/description/description.module').then(m => m.PublicDescriptionModule),
|
||||
data: {
|
||||
breadcrumb: true,
|
||||
...BreadcrumbService.generateRouteDataConfiguration({
|
||||
|
@ -60,7 +59,7 @@ const appRoutes: Routes = [
|
|||
},
|
||||
{
|
||||
path: 'explore-plans',
|
||||
loadChildren: () => import('./ui/dmp/dmp.module').then(m => m.DmpModule),
|
||||
loadChildren: () => import('./ui/dmp/dmp.module').then(m => m.PublicDmpModule),
|
||||
data: {
|
||||
breadcrumb: true,
|
||||
...BreadcrumbService.generateRouteDataConfiguration({
|
||||
|
@ -183,14 +182,6 @@ const appRoutes: Routes = [
|
|||
title: 'GENERAL.TITLES.COOKIES-POLICY'
|
||||
}
|
||||
},
|
||||
|
||||
// {
|
||||
// path: 'splash',
|
||||
// loadChildren: () => import('./ui/splash/splash.module').then(m => m.SplashModule),
|
||||
// data: {
|
||||
// breadcrumb: true
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: 'unauthorized',
|
||||
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({
|
||||
imports: [RouterModule.forRoot(appRoutes, {})],
|
||||
imports: [RouterModule.forRoot(tenantEnrichedRoutes, {})],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
|
|
|
@ -3,7 +3,7 @@ import { of as observableOf, Subscription } from 'rxjs';
|
|||
|
||||
import { switchMap, filter, map, takeUntil } from 'rxjs/operators';
|
||||
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 { AuthService, LoginStatus } from './core/services/auth/auth.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 { CssColorsTenantConfiguration, TenantConfiguration } from './core/model/tenant-configuaration/tenant-configuration';
|
||||
import { nameof } from 'ts-simple-nameof';
|
||||
import { TenantHandlingService } from './core/services/tenant/tenant-handling.service';
|
||||
|
||||
|
||||
declare const gapi: any;
|
||||
|
@ -43,7 +44,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
onlySplash = true;
|
||||
showOnlyRouterOutlet = false;
|
||||
|
||||
@ViewChild('sidenav') sidenav:MatSidenav;
|
||||
@ViewChild('sidenav') sidenav: MatSidenav;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
|
@ -61,7 +62,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
private location: Location,
|
||||
private matomoService: MatomoService,
|
||||
private tenantConfigurationService: TenantConfigurationService,
|
||||
private sidenavService: SideNavService
|
||||
private sidenavService: SideNavService,
|
||||
private tenantHandlingService: TenantHandlingService
|
||||
) {
|
||||
this.initializeServices();
|
||||
this.matomoService.init();
|
||||
|
@ -69,30 +71,30 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
ngAfterViewInit(): void {
|
||||
setTimeout(() => {
|
||||
this.sideNavSubscription = this.sidenavService.status().subscribe(isopen=>{
|
||||
this.sideNavSubscription = this.sidenavService.status().subscribe(isopen => {
|
||||
const hamburger = document.getElementById('hamburger');
|
||||
if(isopen){
|
||||
if (isopen) {
|
||||
//update value of hamburfer
|
||||
if(!hamburger){//try later
|
||||
if (!hamburger) {//try later
|
||||
setTimeout(() => {
|
||||
const hamburger = document.getElementById('hamburger');
|
||||
if(hamburger){
|
||||
if (hamburger) {
|
||||
hamburger.classList.add('change');
|
||||
}
|
||||
}, 300);
|
||||
}else{
|
||||
} else {
|
||||
hamburger.classList.add('change');
|
||||
}
|
||||
this.sidenav.open()
|
||||
}else{//closed
|
||||
if(!hamburger){//try later
|
||||
} else {//closed
|
||||
if (!hamburger) {//try later
|
||||
setTimeout(() => {
|
||||
const hamburger = document.getElementById('hamburger');
|
||||
if(hamburger){
|
||||
if (hamburger) {
|
||||
hamburger.classList.remove('change');
|
||||
}
|
||||
}, 300);
|
||||
}else{
|
||||
} else {
|
||||
hamburger.classList.remove('change');
|
||||
}
|
||||
this.sidenav.close();
|
||||
|
@ -114,7 +116,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
if (this.location.path() === '') {
|
||||
if (!this.configurationService.useSplash) {
|
||||
this.onlySplash = false;
|
||||
this.router.navigate(['/reload']).then(() => this.router.navigate(['/home']));
|
||||
this.router.navigate(['/home']);
|
||||
} else {
|
||||
this.onlySplash = true;
|
||||
this.router.navigate(['/reload']).then(() => this.router.navigate(['/splash']));
|
||||
|
@ -126,7 +128,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
if (!this.cookieService.check("cookiesConsent")) {
|
||||
// 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: 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.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) => {
|
||||
if (event.status == "dismiss") {
|
||||
// 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');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -226,7 +240,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
|
||||
ngOnDestroy() {
|
||||
this.statusChangeSubscription.unsubscribe();
|
||||
if(this.sideNavSubscription){
|
||||
if (this.sideNavSubscription) {
|
||||
this.sideNavSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import { CoreAnnotationServiceModule } from 'annotation-service/services/core-se
|
|||
import { CoreNotificationServiceModule } from '@notification-service/services/core-service.module';
|
||||
import { DepositOauth2DialogModule } from './ui/misc/deposit-oauth2-dialog/deposit-oauth2-dialog.module';
|
||||
import { AnalyticsService } from './core/services/matomo/analytics-service';
|
||||
import { TenantHandlingService } from './core/services/tenant/tenant-handling.service';
|
||||
|
||||
// AoT requires an exported function for factories
|
||||
export function HttpLoaderFactory(languageHttpService: LanguageHttpService) {
|
||||
|
@ -82,7 +83,7 @@ const appearance: MatFormFieldDefaultOptions = {
|
|||
// 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 languageService.loadAvailableLanguages().toPromise();
|
||||
}).then(x => keycloak.init({
|
||||
|
@ -109,13 +110,16 @@ export function InstallationConfigurationFactory(appConfig: ConfigurationService
|
|||
InterceptorType.UnauthorizedResponse,
|
||||
]
|
||||
};
|
||||
|
||||
const tenantCode = tenantHandlingService.extractTenantCodeFromUrlPath(window.location.pathname) ?? authService.selectedTenant() ?? 'default';
|
||||
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({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ReloadHelperComponent
|
||||
],
|
||||
|
@ -157,7 +161,7 @@ export function InstallationConfigurationFactory(appConfig: ConfigurationService
|
|||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: InstallationConfigurationFactory,
|
||||
deps: [ConfigurationService, KeycloakService, AuthService, LanguageService],
|
||||
deps: [ConfigurationService, KeycloakService, AuthService, LanguageService, TenantHandlingService],
|
||||
multi: true
|
||||
},
|
||||
{
|
||||
|
@ -181,5 +185,6 @@ export function InstallationConfigurationFactory(appConfig: ConfigurationService
|
|||
MatomoService,
|
||||
AnalyticsService,
|
||||
provideHttpClient(withInterceptorsFromDi())
|
||||
] })
|
||||
]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
|
|
@ -34,6 +34,7 @@ export enum ResponseErrorCode {
|
|||
DmpInactiveUser = 134,
|
||||
DmpMissingUserContactInfo = 135,
|
||||
ImportDescriptionWithoutDmpDescriptionTemplate = 136,
|
||||
DuplicateDmpUser = 137,
|
||||
|
||||
// Notification & Annotation Errors
|
||||
InvalidApiKey = 200,
|
||||
|
@ -116,6 +117,8 @@ export class ResponseErrorCodeHelper {
|
|||
return language.instant("GENERAL.BACKEND-ERRORS.IMPORT-DESCRIPTION-WITHOUT-DMP-DESCRIPTION-TEMPLATE");
|
||||
case ResponseErrorCode.InvalidApiKey:
|
||||
return language.instant("GENERAL.BACKEND-ERRORS.INVALID-API-KEY");
|
||||
case ResponseErrorCode.DuplicateDmpUser:
|
||||
return language.instant("GENERAL.BACKEND-ERRORS.DUPLICATE-DMP-USER");
|
||||
case ResponseErrorCode.StaleApiKey:
|
||||
return language.instant("GENERAL.BACKEND-ERRORS.STALE-API-KEY");
|
||||
case ResponseErrorCode.SensitiveInfo:
|
||||
|
|
|
@ -45,6 +45,7 @@ import { VisibilityRulesService } from '@app/ui/description/editor/description-f
|
|||
import { StorageFileService } from './services/storage-file/storage-file.service';
|
||||
import { TenantConfigurationService } from './services/tenant-configuration/tenant-configuration.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.
|
||||
|
@ -109,7 +110,8 @@ export class CoreServiceModule {
|
|||
PrefillingSourceService,
|
||||
VisibilityRulesService,
|
||||
TenantConfigurationService,
|
||||
StorageFileService
|
||||
StorageFileService,
|
||||
TenantHandlingService
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -154,11 +154,11 @@ export class AuthService extends BaseService {
|
|||
public isLoggedIn(): boolean {
|
||||
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(
|
||||
map((x) => this.currentAuthenticationToken(x)),
|
||||
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),
|
||||
])),
|
||||
map((item) => {
|
||||
|
@ -176,10 +176,7 @@ export class AuthService extends BaseService {
|
|||
);
|
||||
}
|
||||
|
||||
public ensureTenant(): Observable<string> {
|
||||
if (!this.selectedTenant()) {
|
||||
this.selectedTenant('default');
|
||||
}
|
||||
private ensureTenant(tenantCode: string): Observable<string> {
|
||||
const params = new BaseHttpParams();
|
||||
params.interceptorContext = {
|
||||
excludedInterceptors: [InterceptorType.TenantHeaderInterceptor]
|
||||
|
@ -188,11 +185,11 @@ export class AuthService extends BaseService {
|
|||
map(
|
||||
(myTenants) => {
|
||||
if (myTenants) {
|
||||
if (this.selectedTenant()) {
|
||||
if (myTenants.findIndex(x => x.code.toLocaleLowerCase() == this.selectedTenant().toLocaleLowerCase()) < 0) {
|
||||
if (myTenants.some(x => x.code.toLocaleLowerCase() == tenantCode.toLocaleLowerCase())) {
|
||||
this.selectedTenant(tenantCode);
|
||||
} else {
|
||||
this.selectedTenant(null);
|
||||
}
|
||||
}
|
||||
if (!this.selectedTenant()) {
|
||||
if (myTenants.length > 0) {
|
||||
this.selectedTenant(myTenants[0]?.code);
|
||||
|
@ -326,6 +323,7 @@ export class AuthService extends BaseService {
|
|||
|
||||
return this.prepareAuthRequest(
|
||||
from(this.keycloakService.getToken()),
|
||||
this.selectedTenant(),
|
||||
httpParams
|
||||
)
|
||||
.pipe(takeUntil(this._destroyed))
|
||||
|
|
|
@ -57,21 +57,34 @@ export class CultureService {
|
|||
// 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
|
||||
// use the culture with the specialization (ex en-US), and if not exists we import the base culture (first part).
|
||||
let locale = newCulture.name;
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
// let locale = newCulture.name;
|
||||
// const base = import(
|
||||
// /* webpackExclude: /\.d\.ts$/ */
|
||||
// /* webpackMode: "lazy-once" */
|
||||
// /* webpackChunkName: "i18n-base" */
|
||||
// `@angular/common/locales/${locale}.mjs`)//.then(m => m[basePkg]);
|
||||
|
||||
// const extra = import(
|
||||
// /* webpackExclude: /\.d\.ts$/ */
|
||||
// /* webpackMode: "lazy-once" */
|
||||
// /* webpackChunkName: "i18n-extra" */
|
||||
// `@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> {
|
||||
|
|
|
@ -148,9 +148,9 @@ export class DescriptionTemplateService {
|
|||
//
|
||||
// tslint:disable-next-line: member-ordering
|
||||
descriptionTempalteGroupSingleAutocompleteConfiguration: SingleAutoCompleteConfiguration = {
|
||||
initialItems: (data?: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup()).pipe(map(x => x.items)),
|
||||
filterFn: (searchQuery: string, data?: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup(searchQuery)).pipe(map(x => x.items)),
|
||||
getSelectedItem: (selectedItem: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup(null, null, [selectedItem])).pipe(map(x => x.items[0])),
|
||||
initialItems: (data?: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup([IsActive.Active])).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([IsActive.Active, IsActive.Inactive], null, null, [selectedItem])).pipe(map(x => x.items[0])),
|
||||
displayFn: (item: DescriptionTemplate) => item.label,
|
||||
titleFn: (item: DescriptionTemplate) => item.label,
|
||||
subtitleFn: (item: DescriptionTemplate) => item.description,
|
||||
|
@ -160,9 +160,9 @@ export class DescriptionTemplateService {
|
|||
|
||||
// tslint:disable-next-line: member-ordering
|
||||
descriptionTempalteGroupMultipleAutocompleteConfiguration: MultipleAutoCompleteConfiguration = {
|
||||
initialItems: (excludedItems: any[], data?: any) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup(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)),
|
||||
getSelectedItems: (selectedItems: any[]) => this.query(this.buildDescriptionTempalteGroupAutocompleteLookup(null, null, selectedItems)).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([IsActive.Active], searchQuery, excludedItems)).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,
|
||||
titleFn: (item: DescriptionTemplate) => item.label,
|
||||
subtitleFn: (item: DescriptionTemplate) => item.description,
|
||||
|
@ -170,14 +170,14 @@ export class DescriptionTemplateService {
|
|||
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();
|
||||
lookup.page = { size: 100, offset: 0 };
|
||||
if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; }
|
||||
if (groupIds && groupIds.length > 0) { lookup.groupIds = groupIds; }
|
||||
if (excludedGroupIds && excludedGroupIds.length > 0) { lookup.excludedGroupIds = excludedGroupIds; }
|
||||
|
||||
lookup.isActive = [IsActive.Active];
|
||||
lookup.isActive = isActive;
|
||||
lookup.versionStatuses = [DescriptionTemplateVersionStatus.Current, DescriptionTemplateVersionStatus.NotFinalized];
|
||||
lookup.statuses = [DescriptionTemplateStatus.Finalized];
|
||||
lookup.project = {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -117,6 +117,14 @@ export class UserService {
|
|||
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> {
|
||||
const url = `${this.apiBase}/mine/confirm-merge-account/token/${token}`;
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ export class DescriptionTemplatePreviewDialogComponent extends BaseComponent imp
|
|||
}
|
||||
|
||||
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.visibilityRulesService.setContext(this.descriptionTemplate.definition, this.previewPropertiesFormGroup);
|
||||
}
|
||||
|
|
|
@ -31,13 +31,13 @@
|
|||
<div style="position: relative;" class="col-12" *ngIf="hasFocus" [@fade-in]>
|
||||
<div *ngIf="showDescription" class="mb-4">
|
||||
<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>
|
||||
<mat-error *ngIf="this.form.get('description').hasError('backendError')">{{form.get('description').getError('backendError').message}}</mat-error>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
<mat-error *ngIf="this.form.get('extendedDescription').hasError('backendError')">{{form.get('extendedDescription').getError('backendError').message}}</mat-error>
|
||||
</div>
|
||||
|
|
|
@ -72,9 +72,9 @@ export class DmpBlueprintEditorComponent extends BaseEditor<DmpBlueprintEditorMo
|
|||
public dmpBlueprintExtraFieldDataTypeEnum = this.enumUtils.getEnumValues<DmpBlueprintExtraFieldDataType>(DmpBlueprintExtraFieldDataType);
|
||||
public dmpBlueprintFieldCategoryEnum = this.enumUtils.getEnumValues<DmpBlueprintFieldCategory>(DmpBlueprintFieldCategory);
|
||||
descriptionTempalteGroupSingleAutocompleteConfiguration: SingleAutoCompleteConfiguration = {
|
||||
initialItems: (data?: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup(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)),
|
||||
getSelectedItem: (selectedItem: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup(null, null, [selectedItem])).pipe(map(x => x.items[0])),
|
||||
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([IsActive.Active], searchQuery, null, null, this.getUsedDescriptionTemplateGroupIds() ? this.getUsedDescriptionTemplateGroupIds() : null)).pipe(map(x => x.items)),
|
||||
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,
|
||||
titleFn: (item: DescriptionTemplate) => item.label,
|
||||
subtitleFn: (item: DescriptionTemplate) => item.description,
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<mat-icon>more_horiz</mat-icon>
|
||||
</button>
|
||||
<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}}
|
||||
</button>
|
||||
<button *ngIf="row.belongsToCurrentTenant != false && (row.status === dmpBlueprintStatuses.Finalized || row.status == null)" mat-menu-item [routerLink]="['/dmp-blueprints/new-version' , row.id]">
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
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 { 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 { BaseHttpParams } from '@common/http/base-http-params';
|
||||
import { InterceptorType } from '@common/http/interceptors/interceptor-type';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { from } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
@ -27,7 +25,7 @@ export class LoginComponent extends BaseComponent implements OnInit {
|
|||
private router: Router,
|
||||
private authService: AuthService,
|
||||
private route: ActivatedRoute,
|
||||
private principalService: PrincipalService,
|
||||
private tenantHandlingService: TenantHandlingService,
|
||||
private keycloakService: KeycloakService
|
||||
) { super(); }
|
||||
|
||||
|
@ -36,7 +34,8 @@ export class LoginComponent extends BaseComponent implements OnInit {
|
|||
if (!this.keycloakService.isLoggedIn()) {
|
||||
this.authService.authenticate(this.returnUrl);
|
||||
} 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;
|
||||
this.zone.run(() => this.router.navigateByUrl(returnUrL));
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="col merge-account-title">{{'MERGE-ACCOUNT.TITLE' | translate}}</div>
|
||||
</div>
|
||||
<div *ngIf="showForm" class="row merge-account-content">
|
||||
<div class="col">
|
||||
<div *ngIf="isTokenValid" class="col">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
|
@ -20,6 +20,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!isTokenValid" class="col">
|
||||
<span>{{'MERGE-ACCOUNT.MESSAGES.INVALID-TOKEN' | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #loading>
|
||||
</ng-template>
|
||||
|
|
|
@ -16,6 +16,8 @@ import { takeUntil } from "rxjs/operators";
|
|||
})
|
||||
export class MergeEmailConfirmation extends BaseComponent implements OnInit {
|
||||
|
||||
isTokenValid: boolean = false;
|
||||
|
||||
private token: Guid;
|
||||
|
||||
get showForm(): boolean {
|
||||
|
@ -37,10 +39,20 @@ export class MergeEmailConfirmation extends BaseComponent implements OnInit {
|
|||
.subscribe(params => {
|
||||
const token = params['token']
|
||||
if (token != null) {
|
||||
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 {
|
||||
if (this.showForm === false) return;
|
||||
|
@ -64,7 +76,7 @@ export class MergeEmailConfirmation extends BaseComponent implements OnInit {
|
|||
onCallbackError(errorResponse: HttpErrorResponse) {
|
||||
const errorOverrides = new Map<number, string>();
|
||||
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)
|
||||
|
||||
const error: HttpError = this.httpErrorHandlingService.getError(errorResponse);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthService } from '@app/core/services/auth/auth.service';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
|
||||
|
@ -11,7 +10,7 @@ export class LogoutComponent implements OnInit {
|
|||
constructor(
|
||||
private keycloak: KeycloakService,
|
||||
private authService: AuthService,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.authService.clear();
|
||||
|
@ -19,9 +18,5 @@ export class LogoutComponent implements OnInit {
|
|||
localStorage.clear();
|
||||
// this.router.navigate(['./'], { replaceUrl: true });
|
||||
});
|
||||
// this.tokenService.logout(() => {
|
||||
// localStorage.clear();
|
||||
// this.router.navigate(["./"], { replaceUrl: true });
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
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 { CommonUiModule } from '@common/ui/common-ui.module';
|
||||
|
||||
|
@ -17,3 +17,17 @@ import { CommonUiModule } from '@common/ui/common-ui.module';
|
|||
]
|
||||
})
|
||||
export class DescriptionModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonUiModule,
|
||||
CommonFormsModule,
|
||||
FormattingModule,
|
||||
PublicDescriptionRoutingModule,
|
||||
],
|
||||
declarations: [
|
||||
],
|
||||
exports: [
|
||||
]
|
||||
})
|
||||
export class PublicDescriptionModule { }
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthGuard } from '@app/core/auth-guard.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 = [
|
||||
{
|
||||
path: 'overview',
|
||||
loadChildren: () => import('./overview/description-overview.module').then(m => m.DescriptionOverviewModule),
|
||||
canActivate: [AuthGuard],
|
||||
data: {
|
||||
breadcrumb: true,
|
||||
...BreadcrumbService.generateRouteDataConfiguration({
|
||||
|
@ -18,6 +18,28 @@ const routes: Routes = [
|
|||
{
|
||||
path: 'edit',
|
||||
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: {
|
||||
breadcrumb: true,
|
||||
...BreadcrumbService.generateRouteDataConfiguration({
|
||||
|
@ -40,3 +62,10 @@ const routes: Routes = [
|
|||
providers: []
|
||||
})
|
||||
export class DescriptionRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(publicRoutes)],
|
||||
exports: [RouterModule],
|
||||
providers: []
|
||||
})
|
||||
export class PublicDescriptionRoutingModule { }
|
||||
|
|
|
@ -220,7 +220,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
|
|||
permissionPerSection => {
|
||||
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.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.pageToFieldSetMap = this.mapPageToFieldSet(this.item.descriptionTemplate);;
|
||||
|
||||
|
@ -681,7 +681,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
|
|||
|
||||
this.initialTemplateId = descriptionTemplateId.toString();
|
||||
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;
|
||||
|
||||
const sectionId = this.item.dmpDescriptionTemplate.sectionId;
|
||||
|
@ -755,6 +755,8 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
|
|||
finalize() {
|
||||
this.formService.removeAllBackEndErrors(this.formGroup);
|
||||
this.formService.touchAllFormFields(this.formGroup);
|
||||
this.formService.validateAllFormFields(this.formGroup);
|
||||
|
||||
this.tocValidationService.validateForm();
|
||||
if (!this.isFormValid()) {
|
||||
this.dialog.open(FormValidationErrorsDialogComponent, {
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import { FormControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
|
||||
import { DescriptionStatus } from "@app/core/common/enum/description-status";
|
||||
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 { 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 { ReferencePersist } from "@app/core/model/reference/reference";
|
||||
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 { Validation, ValidationContext } from '@common/forms/validation/validation-context';
|
||||
import { Guid } from "@common/types/guid";
|
||||
import { VisibilityRulesService } from "./description-form/visibility-rules/visibility-rules.service";
|
||||
|
||||
export class DescriptionEditorModel extends BaseEditorModel implements DescriptionPersist {
|
||||
label: string;
|
||||
|
@ -42,7 +44,7 @@ export class DescriptionEditorModel extends BaseEditorModel implements Descripti
|
|||
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(); }
|
||||
|
||||
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],
|
||||
description: [{ value: this.description, disabled: disabled }, context.getValidation('description').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]
|
||||
});
|
||||
}
|
||||
|
||||
buildProperties() {
|
||||
buildProperties(visibilityRulesService: VisibilityRulesService) {
|
||||
return this.properties.buildForm({
|
||||
rootPath: `properties.`
|
||||
rootPath: `properties.`,
|
||||
visibilityRulesService: visibilityRulesService
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -82,18 +85,37 @@ export class DescriptionEditorModel extends BaseEditorModel implements Descripti
|
|||
return baseContext;
|
||||
}
|
||||
|
||||
static reApplyPropertiesValidators(params: {
|
||||
formGroup: UntypedFormGroup,
|
||||
validationErrorModel: ValidationErrorModel,
|
||||
}): void {
|
||||
|
||||
const { formGroup, validationErrorModel } = params;
|
||||
const control = formGroup?.get('properties');
|
||||
DescriptionPropertyDefinitionEditorModel.reapplyValidators({
|
||||
formGroup: control.get('fieldSets') as UntypedFormGroup,
|
||||
rootPath: `properties.`,
|
||||
validationErrorModel: validationErrorModel
|
||||
});
|
||||
static getFieldValueControlName(fieldType: DescriptionTemplateFieldType, multipleSelect: boolean): string {
|
||||
switch (fieldType) {
|
||||
case DescriptionTemplateFieldType.FREE_TEXT:
|
||||
case DescriptionTemplateFieldType.TEXT_AREA:
|
||||
case DescriptionTemplateFieldType.UPLOAD:
|
||||
case DescriptionTemplateFieldType.RICH_TEXT_AREA:
|
||||
case DescriptionTemplateFieldType.RADIO_BOX:
|
||||
return 'textValue';
|
||||
case DescriptionTemplateFieldType.DATASET_IDENTIFIER:
|
||||
case DescriptionTemplateFieldType.VALIDATION:
|
||||
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?: {
|
||||
context?: ValidationContext,
|
||||
disabled?: boolean,
|
||||
rootPath?: string
|
||||
rootPath?: string,
|
||||
visibilityRulesService: VisibilityRulesService
|
||||
}): UntypedFormGroup {
|
||||
let { context = null, disabled = false, rootPath } = params ?? {}
|
||||
if (context == null) {
|
||||
|
@ -128,7 +151,8 @@ export class DescriptionPropertyDefinitionEditorModel implements DescriptionProp
|
|||
const fieldSetsFormGroup = this.formBuilder.group({});
|
||||
if (this.fieldSets.size > 0) {
|
||||
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'));
|
||||
formGroup.addControl('fieldSets', fieldSetsFormGroup);
|
||||
}
|
||||
|
@ -149,25 +173,6 @@ export class DescriptionPropertyDefinitionEditorModel implements DescriptionProp
|
|||
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> {
|
||||
let result: Map<string, DescriptionPropertyDefinitionFieldSetEditorModel> = new Map<string, DescriptionPropertyDefinitionFieldSetEditorModel>();
|
||||
if (descriptionTemplate) (
|
||||
|
@ -265,11 +270,14 @@ export class DescriptionPropertyDefinitionFieldSetEditorModel implements Descrip
|
|||
items?: DescriptionPropertyDefinitionFieldSetItemEditorModel[] = [];
|
||||
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
|
||||
|
||||
fieldSetDefinition: DescriptionTemplateFieldSet;
|
||||
|
||||
constructor(
|
||||
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
|
||||
) { }
|
||||
|
||||
public fromModel(item: DescriptionPropertyDefinitionFieldSet, descriptionReferences: DescriptionReference[], definitionFieldSet: DescriptionTemplateFieldSet): DescriptionPropertyDefinitionFieldSetEditorModel {
|
||||
this.fieldSetDefinition = definitionFieldSet;
|
||||
if (item) {
|
||||
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?: {
|
||||
context?: ValidationContext,
|
||||
disabled?: boolean,
|
||||
rootPath?: string
|
||||
rootPath?: string,
|
||||
visibilityRulesService: VisibilityRulesService
|
||||
}): UntypedFormGroup {
|
||||
let { context = null, disabled = false, rootPath } = params ?? {}
|
||||
if (context == null) {
|
||||
context = DescriptionPropertyDefinitionFieldSetEditorModel.createValidationContext({
|
||||
validationErrorModel: this.validationErrorModel,
|
||||
rootPath
|
||||
rootPath,
|
||||
fieldSetDefinition: this.fieldSetDefinition
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -293,7 +303,8 @@ export class DescriptionPropertyDefinitionFieldSetEditorModel implements Descrip
|
|||
items: this.formBuilder.array(
|
||||
(this.items ?? []).map(
|
||||
(item, index) => item.buildForm({
|
||||
rootPath: `${rootPath}items[${index}].`
|
||||
rootPath: `${rootPath}items[${index}].`,
|
||||
visibilityRulesService: params.visibilityRulesService
|
||||
})
|
||||
), context.getValidation('items').validators
|
||||
)
|
||||
|
@ -302,14 +313,25 @@ export class DescriptionPropertyDefinitionFieldSetEditorModel implements Descrip
|
|||
|
||||
static createValidationContext(params: {
|
||||
rootPath?: string,
|
||||
validationErrorModel: ValidationErrorModel
|
||||
validationErrorModel: ValidationErrorModel,
|
||||
fieldSetDefinition: DescriptionTemplateFieldSet
|
||||
}): ValidationContext {
|
||||
const { rootPath = '', validationErrorModel } = params;
|
||||
|
||||
const baseContext: ValidationContext = new ValidationContext();
|
||||
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;
|
||||
return baseContext;
|
||||
}
|
||||
|
@ -317,14 +339,18 @@ export class DescriptionPropertyDefinitionFieldSetEditorModel implements Descrip
|
|||
static reapplyValidators(params: {
|
||||
formArray: UntypedFormArray,
|
||||
validationErrorModel: ValidationErrorModel,
|
||||
rootPath: string
|
||||
rootPath: string,
|
||||
fieldSetDefinition: DescriptionTemplateFieldSet,
|
||||
visibilityRulesService: VisibilityRulesService
|
||||
}): void {
|
||||
const { validationErrorModel, rootPath, formArray } = params;
|
||||
const { validationErrorModel, rootPath, formArray, fieldSetDefinition } = params;
|
||||
formArray?.controls?.forEach(
|
||||
(control, index) => DescriptionPropertyDefinitionFieldSetItemEditorModel.reapplyValidators({
|
||||
formGroup: control as UntypedFormGroup,
|
||||
rootPath: `${rootPath}items[${index}].`,
|
||||
validationErrorModel: validationErrorModel
|
||||
validationErrorModel: validationErrorModel,
|
||||
fieldSetDefinition: fieldSetDefinition,
|
||||
visibilityRulesService: params.visibilityRulesService
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -359,7 +385,8 @@ export class DescriptionPropertyDefinitionFieldSetItemEditorModel implements Des
|
|||
buildForm(params?: {
|
||||
context?: ValidationContext,
|
||||
disabled?: boolean,
|
||||
rootPath?: string
|
||||
rootPath?: string,
|
||||
visibilityRulesService: VisibilityRulesService
|
||||
}): UntypedFormGroup {
|
||||
let { context = null, disabled = false, rootPath } = params ?? {}
|
||||
if (context == null) {
|
||||
|
@ -376,7 +403,9 @@ export class DescriptionPropertyDefinitionFieldSetItemEditorModel implements Des
|
|||
|
||||
const fieldsFormGroup = this.formBuilder.group({});
|
||||
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')
|
||||
)
|
||||
formGroup.addControl('fields', fieldsFormGroup);
|
||||
|
@ -403,10 +432,12 @@ export class DescriptionPropertyDefinitionFieldSetItemEditorModel implements Des
|
|||
static reapplyValidators(params: {
|
||||
formGroup: UntypedFormGroup,
|
||||
validationErrorModel: ValidationErrorModel,
|
||||
rootPath: string
|
||||
rootPath: string,
|
||||
fieldSetDefinition: DescriptionTemplateFieldSet,
|
||||
visibilityRulesService: VisibilityRulesService
|
||||
}): void {
|
||||
|
||||
const { formGroup, rootPath, validationErrorModel } = params;
|
||||
const { formGroup, rootPath, validationErrorModel, fieldSetDefinition } = params;
|
||||
const context = DescriptionPropertyDefinitionFieldSetItemEditorModel.createValidationContext({
|
||||
rootPath,
|
||||
validationErrorModel
|
||||
|
@ -419,7 +450,10 @@ export class DescriptionPropertyDefinitionFieldSetItemEditorModel implements Des
|
|||
DescriptionFieldEditorModel.reapplyValidators({
|
||||
formGroup: control as UntypedFormGroup,
|
||||
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[] = [];
|
||||
externalIdentifier?: DescriptionExternalIdentifierEditorModel = new DescriptionExternalIdentifierEditorModel(this.validationErrorModel);
|
||||
|
||||
fieldDefinition: DescriptionTemplateField;
|
||||
|
||||
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
|
||||
|
||||
constructor(
|
||||
|
@ -449,6 +485,7 @@ export class DescriptionFieldEditorModel implements DescriptionFieldPersist {
|
|||
) { }
|
||||
|
||||
public fromModel(item: DescriptionField, descriptionTemplateField: DescriptionTemplateField, descriptionReferences: DescriptionReference[]): DescriptionFieldEditorModel {
|
||||
this.fieldDefinition = descriptionTemplateField;
|
||||
if (item) {
|
||||
this.textValue = item.textValue;
|
||||
this.textListValue = item.textListValue;
|
||||
|
@ -488,46 +525,89 @@ export class DescriptionFieldEditorModel implements DescriptionFieldPersist {
|
|||
buildForm(params?: {
|
||||
context?: ValidationContext,
|
||||
disabled?: boolean,
|
||||
rootPath?: string
|
||||
rootPath?: string,
|
||||
visibilityRulesService: VisibilityRulesService,
|
||||
visibilityRulesKey: string
|
||||
}): UntypedFormGroup {
|
||||
let { context = null, disabled = false, rootPath } = params ?? {}
|
||||
if (context == null) {
|
||||
context = DescriptionFieldEditorModel.createValidationContext({
|
||||
validationErrorModel: this.validationErrorModel,
|
||||
rootPath
|
||||
rootPath,
|
||||
fieldDefinition: this.fieldDefinition,
|
||||
visibilityRulesService: params.visibilityRulesService,
|
||||
visibilityRulesKey: params.visibilityRulesKey
|
||||
});
|
||||
}
|
||||
|
||||
return this.formBuilder.group({
|
||||
textValue: [{ value: this.textValue, disabled: disabled }, context.getValidation('textValue').validators],
|
||||
textListValue: [{ value: this.textListValue, disabled: disabled }, context.getValidation('textListValue').validators],
|
||||
dateValue: [{ value: this.dateValue, disabled: disabled }, context.getValidation('dateValue').validators],
|
||||
booleanValue: [{ value: this.booleanValue, disabled: disabled }, context.getValidation('booleanValue').validators],
|
||||
references: [{ value: this.references, disabled: disabled }, context.getValidation('references').validators],
|
||||
reference: [{ value: this.reference, disabled: disabled }, context.getValidation('reference').validators],
|
||||
tags: [{ value: this.tags, disabled: disabled }, context.getValidation('tags').validators],
|
||||
externalIdentifier: this.externalIdentifier.buildForm({
|
||||
const fieldType = this.fieldDefinition.data.fieldType;
|
||||
const multipleSelect = this.fieldDefinition.data.multipleSelect;
|
||||
const fieldValueControlName = DescriptionEditorModel.getFieldValueControlName(fieldType, multipleSelect);
|
||||
const formGroup = this.formBuilder.group({});
|
||||
switch (fieldType) {
|
||||
case DescriptionTemplateFieldType.FREE_TEXT:
|
||||
case DescriptionTemplateFieldType.TEXT_AREA:
|
||||
case DescriptionTemplateFieldType.UPLOAD:
|
||||
case DescriptionTemplateFieldType.RICH_TEXT_AREA:
|
||||
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: {
|
||||
rootPath?: string,
|
||||
validationErrorModel: ValidationErrorModel
|
||||
validationErrorModel: ValidationErrorModel,
|
||||
fieldDefinition: DescriptionTemplateField,
|
||||
visibilityRulesService: VisibilityRulesService,
|
||||
visibilityRulesKey: string
|
||||
}): ValidationContext {
|
||||
const { rootPath = '', validationErrorModel } = params;
|
||||
|
||||
const baseContext: ValidationContext = new ValidationContext();
|
||||
const baseValidationArray: Validation[] = new Array<Validation>();
|
||||
baseValidationArray.push({ key: 'textValue', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}textValue`)] });
|
||||
baseValidationArray.push({ key: 'textListValue', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}textListValue`)] });
|
||||
baseValidationArray.push({ key: 'dateValue', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}dateValue`)] });
|
||||
baseValidationArray.push({ key: 'booleanValue', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}booleanValue`)] });
|
||||
baseValidationArray.push({ key: 'references', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}references`)] });
|
||||
baseValidationArray.push({ key: 'reference', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}references`)] });
|
||||
baseValidationArray.push({ key: 'tags', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}tags`)] });
|
||||
baseValidationArray.push({ key: 'externalIdentifier', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}externalIdentifier`)] });
|
||||
|
||||
const fieldValueControlName = DescriptionEditorModel.getFieldValueControlName(params.fieldDefinition.data.fieldType, params.fieldDefinition.data.multipleSelect);
|
||||
const validators = [];
|
||||
validators.push(BackendErrorValidator(validationErrorModel, `${rootPath}${fieldValueControlName}`));
|
||||
|
||||
params.fieldDefinition.validations.forEach(validation => {
|
||||
switch (validation) {
|
||||
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;
|
||||
return baseContext;
|
||||
}
|
||||
|
@ -535,13 +615,20 @@ export class DescriptionFieldEditorModel implements DescriptionFieldPersist {
|
|||
static reapplyValidators(params: {
|
||||
formGroup: UntypedFormGroup,
|
||||
validationErrorModel: ValidationErrorModel,
|
||||
rootPath: string
|
||||
rootPath: string,
|
||||
fieldDefinition: DescriptionTemplateField,
|
||||
visibilityRulesService: VisibilityRulesService,
|
||||
visibilityRulesKey: string
|
||||
}): void {
|
||||
|
||||
const { formGroup, rootPath, validationErrorModel } = params;
|
||||
const { formGroup, rootPath, validationErrorModel, fieldDefinition } = params;
|
||||
const context = DescriptionFieldEditorModel.createValidationContext({
|
||||
rootPath,
|
||||
validationErrorModel
|
||||
validationErrorModel,
|
||||
fieldDefinition: fieldDefinition,
|
||||
visibilityRulesService: params.visibilityRulesService,
|
||||
visibilityRulesKey: params.visibilityRulesKey
|
||||
|
||||
});
|
||||
|
||||
['textValue', 'textListValue', 'dateValue', 'booleanValue'].forEach(keyField => {
|
||||
|
@ -717,45 +804,6 @@ export class DescriptionFieldIndicator {
|
|||
this.sectionIds = sectionIds;
|
||||
this.fieldSetId = fieldSetId;
|
||||
this.fieldId = fieldId;
|
||||
|
||||
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;
|
||||
}
|
||||
this.type = DescriptionEditorModel.getFieldValueControlName(type, multipleSelect);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
|
|||
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);
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,9 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
|
|||
{
|
||||
formArray: formArray,
|
||||
validationErrorModel: this.validationErrorModel,
|
||||
rootPath: `properties.fieldSets[${this.fieldSet.id}].`
|
||||
rootPath: `properties.fieldSets[${this.fieldSet.id}].`,
|
||||
fieldSetDefinition: this.fieldSet,
|
||||
visibilityRulesService: this.visibilityRulesService
|
||||
}
|
||||
);
|
||||
formArray.markAsDirty();
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div class="col-12">
|
||||
<mat-form-field class="w-100">
|
||||
<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('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>
|
||||
|
@ -31,14 +31,14 @@
|
|||
<div class="row">
|
||||
<mat-form-field class="col-md-12">
|
||||
<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-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('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
|
||||
</ng-container>
|
||||
<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>
|
||||
</mat-select>
|
||||
|
@ -63,7 +63,7 @@
|
|||
<ng-container *ngIf="!(field.data.multipleSelect)">
|
||||
<mat-form-field class="col-md-12">
|
||||
<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>
|
||||
<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>
|
||||
|
@ -87,7 +87,7 @@
|
|||
<ng-container *ngIf="!(field.data.multipleSelect)">
|
||||
<mat-form-field class="col-md-12">
|
||||
<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>
|
||||
<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>
|
||||
|
@ -105,7 +105,7 @@
|
|||
<div class="col-12">
|
||||
<mat-form-field *ngSwitchCase="descriptionTemplateFieldTypeEnum.TEXT_AREA" class="w-100">
|
||||
<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('')">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
|
@ -116,7 +116,7 @@
|
|||
|
||||
<ng-container *ngSwitchCase="descriptionTemplateFieldTypeEnum.RICH_TEXT_AREA">
|
||||
<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">
|
||||
</rich-text-editor-component>
|
||||
</div>
|
||||
|
@ -148,7 +148,7 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
<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]="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>
|
||||
|
@ -159,7 +159,7 @@
|
|||
</div>
|
||||
|
||||
<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-error *ngIf="propertiesFormGroup?.get(field.id).get('textValue').hasError('backendError')">{{propertiesFormGroup?.get(field.id).get('textValue').getError('backendError').message}}</mat-error>
|
||||
</mat-radio-group>
|
||||
|
@ -170,7 +170,7 @@
|
|||
|
||||
<mat-form-field *ngSwitchCase="descriptionTemplateFieldTypeEnum.DATE_PICKER" class="col-12">
|
||||
<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 #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>
|
||||
|
@ -185,13 +185,13 @@
|
|||
<div class="row" *ngIf="datasetIdInitialized">
|
||||
<mat-form-field class="col-md-12">
|
||||
<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('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="col-md-12">
|
||||
<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">
|
||||
{{ type.name }}
|
||||
</mat-option>
|
||||
|
@ -206,7 +206,7 @@
|
|||
<div class="row align-items-baseline">
|
||||
<mat-form-field class="col-md-4">
|
||||
<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('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
|
||||
</mat-form-field>
|
||||
|
|
|
@ -80,11 +80,12 @@ export class FormProgressIndicationComponent extends BaseComponent implements On
|
|||
|
||||
countRequiredFieldsByFieldset(ordinal: number, fieldsFormGroup: UntypedFormGroup, filterValid: boolean = false): number {
|
||||
let fieldsCount: number = 0;
|
||||
const fieldNames = Object.keys(fieldsFormGroup.controls);
|
||||
for(let item of fieldNames) {
|
||||
const fieldSetNames = Object.keys(fieldsFormGroup.controls);
|
||||
for(let item of fieldSetNames) {
|
||||
if (!this.checkVisibility || this.visibilityRulesService.isVisible(item, ordinal)) {
|
||||
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);
|
||||
let controlFilter: boolean = this.controlRequired(typedControl) && this.controlEnabled(typedControl);
|
||||
if (filterValid) controlFilter = controlFilter && typedControl.valid;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="main-content pl-5 pr-5">
|
||||
<div class="container-fluid pl-0 pr-0">
|
||||
<div *ngIf="description">
|
||||
<div *ngIf="description && userName">
|
||||
<div class="row">
|
||||
<div class="col-12 pl-2 mb-3">
|
||||
<app-navigation-breadcrumb />
|
||||
|
@ -197,7 +197,15 @@
|
|||
</button>
|
||||
</div>
|
||||
<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-label">{{ dmpUser.user?.name }}</p>
|
||||
</ng-container>
|
||||
<ng-template #you>
|
||||
<p class="authors-label">{{ userName }}
|
||||
<span>({{ 'DESCRIPTION-OVERVIEW.YOU' | translate }})</span>
|
||||
</p>
|
||||
</ng-template>
|
||||
|
||||
<p class="authors-role">
|
||||
<span>{{ enumUtils.toDmpUserRoleString(dmpUser.role) }} - </span>
|
||||
<span *ngIf="!dmpUser.sectionId">{{ 'DESCRIPTION-OVERVIEW.ROLES.ALL-SECTIONS' | translate}}</span>
|
||||
|
|
|
@ -36,11 +36,14 @@ import { DmpInvitationDialogComponent } from '@app/ui/dmp/invitation/dialog/dmp-
|
|||
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
|
||||
import { Guid } from '@common/types/guid';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { nameof } from 'ts-simple-nameof';
|
||||
import { DescriptionCopyDialogComponent } from '../description-copy-dialog/description-copy-dialog.component';
|
||||
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.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({
|
||||
|
@ -72,6 +75,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
|
|||
canInviteDmpUsers = false;
|
||||
|
||||
authorFocus: string;
|
||||
userName: string;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
|
@ -94,7 +98,8 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
|
|||
private lockService: LockService,
|
||||
private analyticsService: AnalyticsService,
|
||||
private breadcrumbService: BreadcrumbService,
|
||||
private httpErrorHandlingService: HttpErrorHandlingService
|
||||
private httpErrorHandlingService: HttpErrorHandlingService,
|
||||
private userService: UserService,
|
||||
) {
|
||||
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 {
|
||||
|
@ -229,7 +243,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
|
|||
isUserAuthor(userId: Guid): boolean {
|
||||
if (this.isAuthenticated()) {
|
||||
const principalId: Guid = this.authentication.userId();
|
||||
return userId === principalId;
|
||||
return this.userName && (userId === principalId);
|
||||
} else return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -96,9 +96,9 @@ export class DmpEditorComponent extends BaseEditor<DmpEditorModel, Dmp> implemen
|
|||
|
||||
getDescriptionTemplateMultipleAutoCompleteConfiguration(sectionId: Guid): MultipleAutoCompleteConfiguration {
|
||||
return {
|
||||
initialItems: (excludedItems: any[], data?: any) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup(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)),
|
||||
getSelectedItems: (selectedItems: any[]) => this.descriptionTemplateService.query(this.descriptionTemplateService.buildDescriptionTempalteGroupAutocompleteLookup(null, null, selectedItems)).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([IsActive.Active], searchQuery, excludedItems)).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,
|
||||
titleFn: (item: DescriptionTemplate) => item.label,
|
||||
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 => {
|
||||
if (result) {
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
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 { CommonUiModule } from '@common/ui/common-ui.module';
|
||||
|
||||
|
@ -17,3 +17,17 @@ import { CommonUiModule } from '@common/ui/common-ui.module';
|
|||
]
|
||||
})
|
||||
export class DmpModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonUiModule,
|
||||
CommonFormsModule,
|
||||
FormattingModule,
|
||||
PublicDmpRoutingModule,
|
||||
],
|
||||
declarations: [
|
||||
],
|
||||
exports: [
|
||||
]
|
||||
})
|
||||
export class PublicDmpModule { }
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { BreadcrumbService } from '../misc/breadcrumb/breadcrumb.service';
|
||||
import { AuthGuard } from '@app/core/auth-guard.service';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'overview',
|
||||
loadChildren: () => import('./overview/dmp-overview.module').then(m => m.DmpOverviewModule),
|
||||
canActivate:[AuthGuard],
|
||||
data: {
|
||||
breadcrumb: true,
|
||||
...BreadcrumbService.generateRouteDataConfiguration({
|
||||
|
@ -16,6 +18,7 @@ const routes: Routes = [
|
|||
{
|
||||
path: 'new',
|
||||
loadChildren: () => import('./dmp-editor-blueprint/dmp-editor.module').then(m => m.DmpEditorModule),
|
||||
canActivate:[AuthGuard],
|
||||
data: {
|
||||
breadcrumb: true,
|
||||
...BreadcrumbService.generateRouteDataConfiguration({
|
||||
|
@ -27,6 +30,7 @@ const routes: Routes = [
|
|||
{
|
||||
path: 'edit',
|
||||
loadChildren: () => import('./dmp-editor-blueprint/dmp-editor.module').then(m => m.DmpEditorModule),
|
||||
canActivate:[AuthGuard],
|
||||
data: {
|
||||
breadcrumb: true,
|
||||
...BreadcrumbService.generateRouteDataConfiguration({
|
||||
|
@ -37,80 +41,32 @@ const routes: Routes = [
|
|||
},
|
||||
{
|
||||
path: '',
|
||||
canActivate:[AuthGuard],
|
||||
loadChildren: () => import('./listing/dmp-listing.module').then(m => m.DmpListingModule),
|
||||
data: {
|
||||
breadcrumb: true
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
// {
|
||||
// path: 'publicEdit/:publicId',
|
||||
// component: DmpEditorComponent,
|
||||
// data: {
|
||||
// breadcrumb: true,
|
||||
// title: 'GENERAL.TITLES.DMP-PUBLIC-EDIT'
|
||||
// },
|
||||
// canDeactivate: [CanDeactivateGuard]
|
||||
// },
|
||||
|
||||
// {
|
||||
// path: 'publicOverview/:publicId',
|
||||
// component: DmpOverviewComponent,
|
||||
// data: {
|
||||
// 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
|
||||
// },
|
||||
// }
|
||||
const publicRoutes: Routes = [
|
||||
{
|
||||
path: 'overview',
|
||||
loadChildren: () => import('./overview/dmp-overview.module').then(m => m.DmpOverviewModule),
|
||||
data: {
|
||||
breadcrumb: true,
|
||||
...BreadcrumbService.generateRouteDataConfiguration({
|
||||
hideNavigationItem: true
|
||||
}),
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./listing/dmp-listing.module').then(m => m.DmpListingModule),
|
||||
data: {
|
||||
breadcrumb: true
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -118,3 +74,9 @@ const routes: Routes = [
|
|||
exports: [RouterModule]
|
||||
})
|
||||
export class DmpRoutingModule { }
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(publicRoutes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class PublicDmpRoutingModule { }
|
||||
|
|
|
@ -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>
|
||||
</div>
|
||||
<div class="col mt-2">
|
||||
<button mat-raised-button *ngIf="hasValue()" (click)="send()" type="button" class="invite-btn">{{'DMP-USER-INVITATION-DIALOG.ACTIONS.INVITE' | translate}}</button>
|
||||
<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('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
|
||||
</div>
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
|
||||
import { COMMA, ENTER } from '@angular/cdk/keycodes';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { UntypedFormGroup } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { DmpUserRole } from '@app/core/common/enum/dmp-user-role';
|
||||
import { DmpBlueprint } from '@app/core/model/dmp-blueprint/dmp-blueprint';
|
||||
import { DmpUserPersist } from '@app/core/model/dmp/dmp';
|
||||
import { DmpService } from '@app/core/services/dmp/dmp.service';
|
||||
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
|
||||
import { UserService } from '@app/core/services/user/user.service';
|
||||
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
|
||||
import { 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 { TranslateService } from '@ngx-translate/core';
|
||||
import { DmpEditorModel } from '../../dmp-editor-blueprint/dmp-editor.model';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { DmpBlueprint } from '@app/core/model/dmp-blueprint/dmp-blueprint';
|
||||
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { DmpEditorModel } from '../../dmp-editor-blueprint/dmp-editor.model';
|
||||
import { DmpEditorService } from '../../dmp-editor-blueprint/dmp-editor.service';
|
||||
import { ResponseErrorCode } from '@app/core/common/enum/respone-error-code';
|
||||
|
||||
@Component({
|
||||
selector: 'app-invitation-dialog-component',
|
||||
templateUrl: 'dmp-invitation-dialog.component.html',
|
||||
styleUrls: ['./dmp-invitation-dialog.component.scss'],
|
||||
providers: [DmpEditorService]
|
||||
})
|
||||
export class DmpInvitationDialogComponent extends BaseComponent implements OnInit {
|
||||
|
||||
|
@ -32,6 +34,7 @@ export class DmpInvitationDialogComponent extends BaseComponent implements OnIni
|
|||
formGroup: UntypedFormGroup;
|
||||
dmpUserRoleEnum = DmpUserRole;
|
||||
selectedBlueprint: DmpBlueprint;
|
||||
inProgressSendButton = false;
|
||||
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
|
||||
|
||||
constructor(
|
||||
|
@ -43,8 +46,8 @@ export class DmpInvitationDialogComponent extends BaseComponent implements OnIni
|
|||
private uiNotificationService: UiNotificationService,
|
||||
private httpErrorHandlingService: HttpErrorHandlingService,
|
||||
private dmpService: DmpService,
|
||||
private userService: UserService,
|
||||
private filterService: FilterService,
|
||||
private formService: FormService,
|
||||
private dmpEditorService: DmpEditorService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any
|
||||
) {
|
||||
super();
|
||||
|
@ -58,10 +61,14 @@ export class DmpInvitationDialogComponent extends BaseComponent implements OnIni
|
|||
}
|
||||
|
||||
send() {
|
||||
this.formService.removeAllBackEndErrors(this.formGroup.get("users"));
|
||||
this.formService.touchAllFormFields(this.formGroup.get("users"));
|
||||
|
||||
if (!this.formGroup.get("users").valid) { return; }
|
||||
this.inProgressSendButton = true;
|
||||
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))
|
||||
.subscribe(
|
||||
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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
let errorOverrides = new Map<number, string>();
|
||||
errorOverrides.set(-1, this.language.instant('DMP-USER-INVITATION-DIALOG.ERROR'));
|
||||
this.httpErrorHandlingService.handleBackedRequestError(errorResponse, errorOverrides, SnackBarNotificationLevel.Error);
|
||||
this.inProgressSendButton = false;
|
||||
this.httpErrorHandlingService.handleBackedRequestError(errorResponse);
|
||||
|
||||
const error: HttpError = this.httpErrorHandlingService.getError(errorResponse);
|
||||
if (error.statusCode === 400) {
|
||||
this.editorModel.validationErrorModel.fromJSONObject(errorResponse.error);
|
||||
this.formService.validateAllFormFields(this.formGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="main-content dmp-overview pl-5 pr-5">
|
||||
<div class="container-fluid pl-0 pr-0">
|
||||
<div *ngIf="dmp">
|
||||
<div *ngIf="dmp && userName">
|
||||
<div class="row">
|
||||
<div class="col-12 pl-2 mb-3">
|
||||
<app-navigation-breadcrumb />
|
||||
|
@ -258,11 +258,15 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="col pl-0" style="min-width: 0;">
|
||||
<!-- <div class="mytext">{{ dmpUser.user?.name }}</div> -->
|
||||
<p class="authors-label">{{ dmpUser.user?.name }}
|
||||
<span *ngIf="isUserAuthor(dmpUser.user?.id)">
|
||||
<ng-container *ngIf="!isUserAuthor(dmpUser.user?.id); else you">
|
||||
<p class="authors-label">{{ dmpUser.user?.name }}</p>
|
||||
</ng-container>
|
||||
<ng-template #you>
|
||||
<p class="authors-label"> {{ userName }}
|
||||
<span >
|
||||
({{ 'DMP-OVERVIEW.YOU' | translate }})</span>
|
||||
</p>
|
||||
</ng-template>
|
||||
<p class="authors-role">
|
||||
<span>{{ enumUtils.toDmpUserRoleString(dmpUser.role) }} - </span>
|
||||
<span *ngIf="!dmpUser.sectionId">{{ 'DMP-OVERVIEW.ROLES.ALL-SECTIONS' | translate}}</span>
|
||||
|
|
|
@ -41,7 +41,7 @@ import { PopupNotificationDialogComponent } from '@app/library/notification/popu
|
|||
import { BaseComponent } from '@common/base/base.component';
|
||||
import { Guid } from '@common/types/guid';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { nameof } from 'ts-simple-nameof';
|
||||
import { CloneDmpDialogComponent } from '../clone-dialog/dmp-clone-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 { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.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({
|
||||
selector: 'app-dmp-overview',
|
||||
|
@ -85,6 +88,7 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
|
|||
dmpUserRoleEnum = DmpUserRole;
|
||||
|
||||
authorFocus: string;
|
||||
userName: string;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
|
@ -108,6 +112,7 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
|
|||
private analyticsService: AnalyticsService,
|
||||
private breadcrumbService: BreadcrumbService,
|
||||
private httpErrorHandlingService: HttpErrorHandlingService,
|
||||
private userService: UserService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
@ -205,6 +210,13 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit {
|
|||
this.depositRepos = repos;
|
||||
},
|
||||
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 {
|
||||
if (this.isAuthenticated()) {
|
||||
const principalId: Guid = this.authentication.userId();
|
||||
return userId === principalId;
|
||||
return this.userName && userId === principalId;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -90,15 +90,21 @@ export class NavbarComponent extends BaseComponent implements OnInit {
|
|||
this.authentication.getAuthenticationStateObservable().subscribe(authenticationState => {
|
||||
if (authenticationState.loginStatus === LoginStatus.LoggedIn) {
|
||||
this.loadLogo();
|
||||
this.loadUser();
|
||||
}
|
||||
});
|
||||
this.loadLogo();
|
||||
this.loadUser();
|
||||
}
|
||||
|
||||
private loadUser() {
|
||||
if (this.authentication.currentAccountIsAuthenticated() && this.authentication.userId()) {
|
||||
this.userService.getSingle(this.authentication.userId(), [
|
||||
nameof<User>(x => x.id),
|
||||
nameof<User>(x => x.name)
|
||||
]).subscribe(u => this.userName = u.name); //TODO HANDLE-ERRORS
|
||||
}
|
||||
}
|
||||
|
||||
private loadLogo() {
|
||||
if (this.authentication.currentAccountIsAuthenticated() && this.authentication.selectedTenant()) {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
<div *ngIf="showItem(groupMenuItem);">
|
||||
<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}">
|
||||
<!-- {{ 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'}">
|
||||
<i class="material-symbols-outlined icon">{{ groupMenuRoute.icon }}</i>
|
||||
<i *ngIf="groupMenuRoute.path == '/plans'" class="material-symbols-outlined icon-mask">person</i>
|
||||
|
|
|
@ -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 { Router } from "@angular/router";
|
||||
import { Tenant } from "@app/core/model/tenant/tenant";
|
||||
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 { BaseHttpParams } from "@common/http/base-http-params";
|
||||
import { InterceptorType } from "@common/http/interceptors/interceptor-type";
|
||||
import { KeycloakService } from "keycloak-angular";
|
||||
import { Observable, from } from "rxjs";
|
||||
import { takeUntil } from "rxjs/operators";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: 'app-tenant-switch',
|
||||
|
@ -20,10 +18,9 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
|
|||
tenants: Observable<Array<Tenant>>;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private keycloakService: KeycloakService,
|
||||
private principalService: PrincipalService,
|
||||
private authService: AuthService,
|
||||
private tenantHandlingService: TenantHandlingService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
@ -34,7 +31,6 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
|
|||
|
||||
ngOnInit() {
|
||||
this.tenants = this.loadUserTenants(); //TODO
|
||||
//this.tenantChange.emit(this.getCurrentLanguage())
|
||||
}
|
||||
|
||||
loadUserTenants(): Observable<Array<Tenant>> {
|
||||
|
@ -48,22 +44,7 @@ export class TenantSwitchComponent extends BaseComponent implements OnInit {
|
|||
onTenantSelected(selectedTenant: MatButtonToggleChange) {
|
||||
if (selectedTenant.value === undefined || selectedTenant.value === '') return;
|
||||
|
||||
this.formSubmit(selectedTenant.value);
|
||||
this.loadUser();
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
this.authService.selectedTenant(selectedTenant.value);
|
||||
window.location.href = this.tenantHandlingService.getCurrentUrlEnrichedWithTenantCode(selectedTenant.value, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
<!-- {{ userCredentials | async | json }} -->
|
||||
|
||||
<div class="profile">
|
||||
<div class="container-fluid">
|
||||
<div *ngIf="user | async as userProfile; else loading" class="user-profile">
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
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 { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.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 { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
|
||||
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 { InterceptorType } from '@common/http/interceptors/interceptor-type';
|
||||
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 { TranslateService } from '@ngx-translate/core';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import * as moment from 'moment-timezone';
|
||||
import { Observable, from, of } from 'rxjs';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { nameof } from 'ts-simple-nameof';
|
||||
import { AddAccountDialogComponent } from './add-account/add-account-dialog.component';
|
||||
import { UserProfileEditorModel } from './user-profile-editor.model';
|
||||
import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-profile',
|
||||
|
@ -81,7 +80,7 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
|
|||
private dialog: MatDialog,
|
||||
public enumUtils: EnumUtils,
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private keycloakService: KeycloakService,
|
||||
private tenantHandlingService: TenantHandlingService,
|
||||
private principalService: PrincipalService,
|
||||
private formService: FormService,
|
||||
private referenceService: ReferenceService,
|
||||
|
@ -367,17 +366,9 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
|
|||
if (this.tenantFormGroup.valid === false) return;
|
||||
|
||||
const selectedTenant = this.tenantFormGroup.get('tenantCode').value;
|
||||
this.formSubmit(selectedTenant);
|
||||
this.loadUser();
|
||||
}
|
||||
|
||||
formSubmit(selectedTenant: string): void {
|
||||
this.authService.selectedTenant(selectedTenant);
|
||||
}
|
||||
|
||||
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));
|
||||
this.authService.selectedTenant(selectedTenant.value);
|
||||
window.location.href = this.tenantHandlingService.getCurrentUrlEnrichedWithTenantCode(selectedTenant.value, true);
|
||||
}
|
||||
|
||||
//Preferences
|
||||
|
|
|
@ -69,7 +69,8 @@
|
|||
"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",
|
||||
"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": {
|
||||
"WARNING": "Warning!",
|
||||
|
@ -257,7 +258,7 @@
|
|||
"POLICY": "Cookies Policy"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"HOME": {
|
||||
|
@ -2221,7 +2222,8 @@
|
|||
"MERGE-ACCOUNT": {
|
||||
"TITLE": "Merge Your Account",
|
||||
"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": {
|
||||
"CONFIRM": "Confirm"
|
||||
|
|
|
@ -2,6 +2,7 @@ import { AbstractControl, UntypedFormArray, UntypedFormGroup, ValidatorFn, Valid
|
|||
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
|
||||
import { isNullOrUndefined } from '@app/utilities/enhancers/utils';
|
||||
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 {
|
||||
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 {
|
||||
return (control: AbstractControl): { [key: string]: any } => {
|
||||
if (control.value) {
|
||||
|
@ -108,20 +137,20 @@ export function DmpBlueprintSystemFieldRequiredValidator(): ValidatorFn {
|
|||
let foundAccess = false;
|
||||
|
||||
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) => {
|
||||
const fieldsFormArray = section.get('fields') as UntypedFormArray;
|
||||
if (fieldsFormArray && fieldsFormArray.length > 0){
|
||||
if (fieldsFormArray.controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === DmpBlueprintSystemFieldType.Title)){
|
||||
if (fieldsFormArray && fieldsFormArray.length > 0) {
|
||||
if (fieldsFormArray.controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === DmpBlueprintSystemFieldType.Title)) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,13 +43,13 @@ export class UnauthorizedResponseInterceptor extends BaseInterceptor {
|
|||
this.authService.refreshToken().then((isRefreshed) => {
|
||||
this.accountRefresh$ = null;
|
||||
if (!isRefreshed) {
|
||||
this.logoutUser();
|
||||
this.handleUnauthorized();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).catch(x => {
|
||||
this.logoutUser();
|
||||
this.handleUnauthorized();
|
||||
return false;
|
||||
})
|
||||
).pipe(filter((x) => x));
|
||||
|
@ -67,10 +67,10 @@ export class UnauthorizedResponseInterceptor extends BaseInterceptor {
|
|||
return next.handle(newRequest);
|
||||
}
|
||||
|
||||
private logoutUser() {
|
||||
private handleUnauthorized() {
|
||||
if (!this.isLoginRoute() && !this.isSignupRoute()) {
|
||||
this.authService.clear();
|
||||
this.router.navigate(['/unauthorized']);
|
||||
this.router.navigate(['/unauthorized', { queryParams: { returnUrl: this.router.url } }]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
</head>
|
||||
<body class="">
|
||||
{description}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
</head>
|
||||
<body class="">
|
||||
<p>Dear {recipient},</p>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
</head>
|
||||
<body class="">
|
||||
{description}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Simple Transactional Email</title>
|
||||
<title>OpenCDMP Notification</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
|
Loading…
Reference in New Issue