diff --git a/backend/core/src/main/java/org/opencdmp/authorization/Permission.java b/backend/core/src/main/java/org/opencdmp/authorization/Permission.java index 4a08a8a74..6f5f98f49 100644 --- a/backend/core/src/main/java/org/opencdmp/authorization/Permission.java +++ b/backend/core/src/main/java/org/opencdmp/authorization/Permission.java @@ -200,6 +200,11 @@ public final class Permission { public static String EditPrefillingSource= "EditPrefillingSource"; public static String DeletePrefillingSource = "DeletePrefillingSource"; + //NotificationTemplate + public static String BrowseStatus = "BrowseStatus"; + public static String EditStatus = "EditStatus"; + public static String DeleteStatus = "DeleteStatus"; + // UI Pages public static String ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage"; @@ -224,5 +229,6 @@ public final class Permission { public static String ViewHomePage = "ViewHomePage"; public static String ViewMineInAppNotificationPage = "ViewMineInAppNotificationPage"; public static String ViewTenantConfigurationPage = "ViewTenantConfigurationPage"; + public static String ViewStatusPage = "ViewStatusPage"; } diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/InboxRepositoryImpl.java b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/InboxRepositoryImpl.java index 04828f1c1..85d45608b 100644 --- a/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/InboxRepositoryImpl.java +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/InboxRepositoryImpl.java @@ -48,8 +48,12 @@ public class InboxRepositoryImpl implements InboxRepository { EntityTransaction transaction = null; CandidateInfo candidate = null; try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try{ + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); + tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); @@ -92,7 +96,8 @@ public class InboxRepositoryImpl implements InboxRepository { transaction.rollback(); candidate = null; } finally { - tenantEntityManager.reloadTenantFilters(); + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); } } catch (Exception ex) { logger.error("Problem getting list of queue inbox. Skipping: {}", ex.getMessage(), ex); @@ -107,8 +112,12 @@ public class InboxRepositoryImpl implements InboxRepository { boolean success = false; try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try{ + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); + tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); @@ -138,7 +147,8 @@ public class InboxRepositoryImpl implements InboxRepository { transaction.rollback(); success = false; } finally { - tenantEntityManager.reloadTenantFilters(); + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); } } catch (Exception ex) { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); @@ -152,8 +162,12 @@ public class InboxRepositoryImpl implements InboxRepository { boolean success = false; try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try{ + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); + tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); @@ -184,7 +198,8 @@ public class InboxRepositoryImpl implements InboxRepository { transaction.rollback(); success = false; } finally { - tenantEntityManager.reloadTenantFilters(); + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); } } catch (Exception ex) { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); @@ -198,8 +213,12 @@ public class InboxRepositoryImpl implements InboxRepository { boolean success; QueueInboxEntity queueMessage = null; try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try{ + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); + tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); @@ -223,7 +242,8 @@ public class InboxRepositoryImpl implements InboxRepository { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); if (transaction != null) transaction.rollback(); } finally { - tenantEntityManager.reloadTenantFilters(); + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); } } catch (Exception ex) { success = false; @@ -272,8 +292,12 @@ public class InboxRepositoryImpl implements InboxRepository { } else { EventProcessingStatus status = this.emitQueueInboxEntity(queueInboxMessage); try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try{ + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); + tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); @@ -313,7 +337,8 @@ public class InboxRepositoryImpl implements InboxRepository { transaction.rollback(); success = false; } finally { - tenantEntityManager.reloadTenantFilters(); + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); } } catch (Exception ex) { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); @@ -327,8 +352,12 @@ public class InboxRepositoryImpl implements InboxRepository { EntityTransaction transaction = null; EventProcessingStatus status = EventProcessingStatus.Discard; try (FakeRequestScope ignored = new FakeRequestScope()) { - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try{ + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); + tenantEntityManager.setEntityManager(entityManager); transaction = entityManager.getTransaction(); @@ -350,6 +379,8 @@ public class InboxRepositoryImpl implements InboxRepository { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); if (transaction != null) transaction.rollback(); + } finally { + if (entityManager != null) entityManager.close(); } } catch (Exception ex) { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationentitycreated/AnnotationEntityCreatedIntegrationEventHandlerImpl.java b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationentitycreated/AnnotationEntityCreatedIntegrationEventHandlerImpl.java index 3841b0516..e9807ef05 100644 --- a/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationentitycreated/AnnotationEntityCreatedIntegrationEventHandlerImpl.java +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationentitycreated/AnnotationEntityCreatedIntegrationEventHandlerImpl.java @@ -41,7 +41,6 @@ import org.springframework.stereotype.Component; import javax.management.InvalidApplicationException; import java.util.*; -import java.util.stream.Collectors; @Component @@ -93,19 +92,19 @@ public class AnnotationEntityCreatedIntegrationEventHandlerImpl implements Annot try { if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) { - TenantEntity tenant = queryFactory.query(TenantQuery.class).disableTracking().ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); + TenantEntity tenant = this.queryFactory.query(TenantQuery.class).disableTracking().ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); if (tenant == null) { logger.error("missing tenant from event message"); return EventProcessingStatus.Error; } - this.tenantScope.setTempTenant(tenantEntityManager, properties.getTenantId(), tenant.getCode()); + this.tenantScope.setTempTenant(this.tenantEntityManager, properties.getTenantId(), tenant.getCode()); } else if (this.tenantScope.isMultitenant()) { - this.tenantScope.setTempTenant(tenantEntityManager, null, this.tenantScope.getDefaultTenantCode()); + this.tenantScope.setTempTenant(this.tenantEntityManager, null, this.tenantScope.getDefaultTenantCode()); } - currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties)); + this.currentPrincipalResolver.push(InboxPrincipal.build(properties, this.claimExtractorProperties)); this.sendNotification(event); - auditService.track(AuditableAction.Annotation_Created_Notify, Map.ofEntries( + this.auditService.track(AuditableAction.Annotation_Created_Notify, Map.ofEntries( new AbstractMap.SimpleEntry("model", event) )); @@ -113,9 +112,9 @@ public class AnnotationEntityCreatedIntegrationEventHandlerImpl implements Annot status = EventProcessingStatus.Error; logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); } finally { - currentPrincipalResolver.pop(); + this.currentPrincipalResolver.pop(); try { - tenantScope.removeTempTenant(this.tenantEntityManager); + this.tenantScope.removeTempTenant(this.tenantEntityManager); this.tenantEntityManager.reloadTenantFilters(); } catch (InvalidApplicationException e) { } @@ -160,7 +159,7 @@ public class AnnotationEntityCreatedIntegrationEventHandlerImpl implements Annot NotifyIntegrationEvent notifyIntegrationEvent = new NotifyIntegrationEvent(); notifyIntegrationEvent.setUserId(user.getId()); - notifyIntegrationEvent.setNotificationType(notificationProperties.getDescriptionAnnotationCreated()); + notifyIntegrationEvent.setNotificationType(this.notificationProperties.getDescriptionAnnotationCreated()); NotificationFieldData data = new NotificationFieldData(); List fieldInfoList = new ArrayList<>(); diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/OutboxProperties.java b/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/OutboxProperties.java index 884e51cb6..82e07e717 100644 --- a/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/OutboxProperties.java +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/OutboxProperties.java @@ -2,12 +2,14 @@ package org.opencdmp.integrationevent.outbox; import org.springframework.boot.context.properties.ConfigurationProperties; -import java.util.List; - @ConfigurationProperties(prefix = "queue.task.publisher.options") public class OutboxProperties { private final String exchange; + private final int handleAckRetries; + private final int handleNackRetries; + private final int handleAckWaitInMilliSeconds; + private final int handleNackWaitInMilliSeconds; private final String tenantDefaultLocaleRemovalTopic; private final String tenantDefaultLocaleTouchedTopic; @@ -38,8 +40,8 @@ public class OutboxProperties { private final String generateFileTopic; - public OutboxProperties(String exchange, - String tenantDefaultLocaleRemovalTopic, + public OutboxProperties(String exchange, int handleAckRetries, int handleNackRetries, int handleAckWaitInMilliSeconds, int handleNackWaitInMilliSeconds, + String tenantDefaultLocaleRemovalTopic, String tenantDefaultLocaleTouchedTopic, String tenantTouchTopic, String tenantRemovalTopic, @@ -57,6 +59,10 @@ public class OutboxProperties { String generateFileTopic ) { this.exchange = exchange; + this.handleAckRetries = handleAckRetries; + this.handleNackRetries = handleNackRetries; + this.handleAckWaitInMilliSeconds = handleAckWaitInMilliSeconds; + this.handleNackWaitInMilliSeconds = handleNackWaitInMilliSeconds; this.tenantDefaultLocaleRemovalTopic = tenantDefaultLocaleRemovalTopic; this.tenantDefaultLocaleTouchedTopic = tenantDefaultLocaleTouchedTopic; this.tenantTouchTopic = tenantTouchTopic; @@ -76,70 +82,82 @@ public class OutboxProperties { } public String getExchange() { - return exchange; + return this.exchange; + } + + public int getHandleAckRetries() { + return this.handleAckRetries; + } + + public int getHandleNackRetries() { + return this.handleNackRetries; + } + + public int getHandleAckWaitInMilliSeconds() { + return this.handleAckWaitInMilliSeconds; } public String getTenantDefaultLocaleRemovalTopic() { - return tenantDefaultLocaleRemovalTopic; + return this.tenantDefaultLocaleRemovalTopic; } public String getTenantDefaultLocaleTouchedTopic() { - return tenantDefaultLocaleTouchedTopic; + return this.tenantDefaultLocaleTouchedTopic; } public String getTenantTouchTopic() { - return tenantTouchTopic; + return this.tenantTouchTopic; } public String getTenantRemovalTopic() { - return tenantRemovalTopic; + return this.tenantRemovalTopic; } public String getTenantReactivationTopic() { - return tenantReactivationTopic; + return this.tenantReactivationTopic; } public String getTenantUserInviteTopic() { - return tenantUserInviteTopic; + return this.tenantUserInviteTopic; } public String getUserRemovalTopic() { - return userRemovalTopic; + return this.userRemovalTopic; } public String getUserTouchTopic() { - return userTouchTopic; + return this.userTouchTopic; } public String getDmpTouchTopic() { - return dmpTouchTopic; + return this.dmpTouchTopic; } public String getDescriptionTouchTopic() { - return descriptionTouchTopic; + return this.descriptionTouchTopic; } public String getAnnotationEntitiesTouchTopic() { - return annotationEntitiesTouchTopic; + return this.annotationEntitiesTouchTopic; } public String getAnnotationEntitiesRemovalTopic() { - return annotationEntitiesRemovalTopic; + return this.annotationEntitiesRemovalTopic; } public String getNotifyTopic() { - return notifyTopic; + return this.notifyTopic; } public String getForgetMeCompletedTopic() { - return forgetMeCompletedTopic; + return this.forgetMeCompletedTopic; } public String getWhatYouKnowAboutMeCompletedTopic() { - return whatYouKnowAboutMeCompletedTopic; + return this.whatYouKnowAboutMeCompletedTopic; } public String getGenerateFileTopic() { - return generateFileTopic; + return this.generateFileTopic; } } diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/OutboxRepositoryImpl.java b/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/OutboxRepositoryImpl.java index 72a6c0013..6f321889b 100644 --- a/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/OutboxRepositoryImpl.java +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/OutboxRepositoryImpl.java @@ -16,6 +16,7 @@ import org.opencdmp.commons.fake.FakeRequestScope; import org.opencdmp.data.QueueOutboxEntity; import org.opencdmp.data.TenantEntityManager; import org.opencdmp.query.QueueOutboxQuery; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; @@ -27,6 +28,7 @@ import java.util.stream.Collectors; public class OutboxRepositoryImpl implements OutboxRepository { + private static final Logger log = LoggerFactory.getLogger(OutboxRepositoryImpl.class); protected final ApplicationContext applicationContext; private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(OutboxRepositoryImpl.class)); @@ -50,8 +52,12 @@ public class OutboxRepositoryImpl implements OutboxRepository { EntityTransaction transaction = null; CandidateInfo candidate = null; try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try{ + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); + tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); @@ -96,7 +102,8 @@ public class OutboxRepositoryImpl implements OutboxRepository { transaction.rollback(); candidate = null; } finally { - tenantEntityManager.reloadTenantFilters(); + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); } } catch (Exception ex) { logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); @@ -111,8 +118,11 @@ public class OutboxRepositoryImpl implements OutboxRepository { boolean success = false; try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try{ + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); @@ -142,7 +152,8 @@ public class OutboxRepositoryImpl implements OutboxRepository { transaction.rollback(); success = false; } finally { - tenantEntityManager.reloadTenantFilters(); + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); } } catch (Exception ex) { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); @@ -156,8 +167,11 @@ public class OutboxRepositoryImpl implements OutboxRepository { boolean success = false; try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try{ + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); @@ -188,7 +202,8 @@ public class OutboxRepositoryImpl implements OutboxRepository { transaction.rollback(); success = false; } finally { - tenantEntityManager.reloadTenantFilters(); + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); } } catch (Exception ex) { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); @@ -202,8 +217,11 @@ public class OutboxRepositoryImpl implements OutboxRepository { Boolean success = false; try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try { + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); @@ -243,7 +261,8 @@ public class OutboxRepositoryImpl implements OutboxRepository { transaction.rollback(); success = false; } finally { - tenantEntityManager.reloadTenantFilters(); + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); } } catch (Exception ex) { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); @@ -253,14 +272,32 @@ public class OutboxRepositoryImpl implements OutboxRepository { @Override public void handleConfirm(List confirmedMessages) { - EntityTransaction transaction = null; - try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try { + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); + this.handleConfirmWithRetries(entityManager, confirmedMessages); + + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } finally { + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + } + + private void handleConfirmWithRetries(EntityManager entityManager, List confirmedMessages) throws InterruptedException { + EntityTransaction transaction = null; + for (int i = 0; i < this.outboxProperties.getHandleAckRetries() + 1; i++) { + try { transaction = entityManager.getTransaction(); transaction.begin(); @@ -272,37 +309,55 @@ public class OutboxRepositoryImpl implements OutboxRepository { } else { for (QueueOutboxEntity queueOutboxMessage : queueOutboxMessages) { + queueOutboxMessage = queryFactory.query(QueueOutboxQuery.class).ids(queueOutboxMessage.getId()).first(); queueOutboxMessage.setNotifyStatus(QueueOutboxNotifyStatus.CONFIRMED); queueOutboxMessage.setConfirmedAt(Instant.now()); entityManager.merge(queueOutboxMessage); + entityManager.flush(); } - - entityManager.flush(); } transaction.commit(); + return; + } catch (OptimisticLockException ex) { + logger.warn("Problem handle ack {}. Rolling back any message emit db changes and marking error. Retrying...", confirmedMessages.stream().map(UUID::toString).collect(Collectors.joining(","))); + if (transaction != null) transaction.rollback(); + Thread.sleep(this.outboxProperties.getHandleAckWaitInMilliSeconds()); } catch (Exception ex) { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); - if (transaction != null) - transaction.rollback(); - } finally { - tenantEntityManager.reloadTenantFilters(); + if (transaction != null) transaction.rollback(); + return; } - } catch (Exception ex) { - logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); } } @Override public void handleNack(List nackedMessages) { - EntityTransaction transaction = null; - try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try { + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); + this.handleNackWithRetries(entityManager, nackedMessages); + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } finally { + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); + } + } catch (Exception ex) { + logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); + } + } + + private void handleNackWithRetries(EntityManager entityManager, List nackedMessages) throws InterruptedException { + EntityTransaction transaction = null; + for (int i = 0; i < this.outboxProperties.getHandleNackRetries() + 1; i++) { + try { transaction = entityManager.getTransaction(); transaction.begin(); @@ -323,15 +378,16 @@ public class OutboxRepositoryImpl implements OutboxRepository { } transaction.commit(); + return; + } catch (OptimisticLockException ex) { + logger.warn("Problem handle nack {}. Rolling back any message emit db changes and marking error. Retrying...", nackedMessages.stream().map(UUID::toString).collect(Collectors.joining(","))); + if (transaction != null) transaction.rollback(); + Thread.sleep(this.outboxProperties.getHandleNackRetries()); } catch (Exception ex) { logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); - if (transaction != null) - transaction.rollback(); - } finally { - tenantEntityManager.reloadTenantFilters(); + if (transaction != null) transaction.rollback(); + return; } - } catch (Exception ex) { - logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); } } @@ -341,8 +397,11 @@ public class OutboxRepositoryImpl implements OutboxRepository { QueueOutboxEntity queueMessage = null; boolean success; try (FakeRequestScope ignored = new FakeRequestScope()) { - TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); - try (EntityManager entityManager = this.entityManagerFactory.createEntityManager()) { + TenantEntityManager tenantEntityManager = null; + EntityManager entityManager = null; + try { + tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class); + entityManager = this.entityManagerFactory.createEntityManager(); tenantEntityManager.setEntityManager(entityManager); tenantEntityManager.disableTenantFilters(); @@ -362,7 +421,8 @@ public class OutboxRepositoryImpl implements OutboxRepository { if (transaction != null) transaction.rollback(); } finally { - tenantEntityManager.reloadTenantFilters(); + if (entityManager != null) entityManager.close(); + if (tenantEntityManager != null) tenantEntityManager.reloadTenantFilters(); } } catch (Exception ex) { success = false; diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DepositController.java b/backend/web/src/main/java/org/opencdmp/controllers/DepositController.java index 2e2d20859..136e13ac2 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DepositController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DepositController.java @@ -7,7 +7,18 @@ 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.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import org.opencdmp.audit.AuditableAction; +import org.opencdmp.controllers.swagger.SwaggerHelpers; +import org.opencdmp.controllers.swagger.annotation.OperationWithTenantHeader; +import org.opencdmp.controllers.swagger.annotation.Swagger400; +import org.opencdmp.controllers.swagger.annotation.Swagger404; +import org.opencdmp.controllers.swagger.annotation.SwaggerCommonErrorResponses; import org.opencdmp.model.EntityDoi; import org.opencdmp.model.censorship.EntityDoiCensor; import org.opencdmp.model.censorship.deposit.DepositConfigurationCensor; @@ -35,11 +46,12 @@ import java.util.Map; @RestController @CrossOrigin @RequestMapping("/api/deposit/") +@Tag(name = "Deposit", description = "Manage deposit repositories, make deposits") +@SwaggerCommonErrorResponses public class DepositController { private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(DepositController.class)); - private final DepositService depositService; private final CensorFactory censorFactory; @@ -53,11 +65,18 @@ public class DepositController { this.depositService = depositService; this.censorFactory = censorFactory; this.auditService = auditService; - this.messageSource = messageSource; + this.messageSource = messageSource; } @GetMapping("/repositories/available") - public List getAvailableRepos(FieldSet fieldSet) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + @OperationWithTenantHeader(summary = "Fetch all repositories", description = SwaggerHelpers.Deposit.endpoint_get_available_repos, + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + array = @ArraySchema( + schema = @Schema( + implementation = DepositConfiguration.class + ))) + )) + public List getAvailableRepos(FieldSet fieldSet) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { logger.debug(new MapLogEntry("retrieving" + DepositConfiguration.class.getSimpleName()).And("fields", fieldSet)); this.censorFactory.censor(DepositConfigurationCensor.class).censor(fieldSet, null); @@ -71,6 +90,13 @@ public class DepositController { } @PostMapping("/deposit") + @OperationWithTenantHeader(summary = "Make a plan deposit request", description = SwaggerHelpers.Deposit.endpoint_deposit, + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = EntityDoi.class + )) + )) + @Swagger400 @Transactional @ValidationFilterAnnotation(validator = DepositRequest.DepositRequestValidator.ValidatorName, argumentName = "model") public EntityDoi deposit(@RequestBody DepositRequest model) throws Exception { @@ -86,15 +112,26 @@ public class DepositController { } @GetMapping("/repositories/{repositoryId}") - public org.opencdmp.model.deposit.DepositConfiguration getRepository(@PathVariable("repositoryId") String repositoryId, FieldSet fieldSet) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + @OperationWithTenantHeader(summary = "Fetch a specific deposit repository by id", description = SwaggerHelpers.Deposit.endpoint_get_repository, + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DepositConfiguration.class + )) + )) + @Swagger404 + public DepositConfiguration getRepository( + @Parameter(name = "repositoryId", description = "The id of a repository to fetch", example = "zenodo", required = true) @PathVariable("repositoryId") String repositoryId, + @Parameter(name = "fieldSet", description = SwaggerHelpers.Commons.fieldset_description, required = true) FieldSet fieldSet + ) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { logger.debug(new MapLogEntry("retrieving" + DepositConfiguration.class.getSimpleName()).And("fields", fieldSet).And("repositoryId", repositoryId)); this.censorFactory.censor(DepositConfigurationCensor.class).censor(fieldSet, null); List models = this.depositService.getAvailableConfigurations(fieldSet); - org.opencdmp.model.deposit.DepositConfiguration model = models.stream().filter(x-> x.getRepositoryId().equals(repositoryId)).findFirst().orElse(null); - if (model == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{repositoryId, org.opencdmp.model.deposit.DepositConfiguration.class.getSimpleName()}, LocaleContextHolder.getLocale())); - + org.opencdmp.model.deposit.DepositConfiguration model = models.stream().filter(x -> x.getRepositoryId().equals(repositoryId)).findFirst().orElse(null); + if (model == null) + throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{repositoryId, org.opencdmp.model.deposit.DepositConfiguration.class.getSimpleName()}, LocaleContextHolder.getLocale())); + this.auditService.track(AuditableAction.Deposit_GetRepository, Map.ofEntries( new AbstractMap.SimpleEntry("repositoryId", repositoryId), new AbstractMap.SimpleEntry("fields", fieldSet) @@ -104,7 +141,16 @@ public class DepositController { } @GetMapping("/repositories/{repositoryId}/logo") - public String getLogo(@PathVariable("repositoryId") String repositoryId) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + @OperationWithTenantHeader(summary = "Fetch a specific deposit repository logo by id", description = SwaggerHelpers.Deposit.endpoint_get_logo, + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = String.class + )) + )) + @Swagger404 + public String getLogo( + @Parameter(name = "repositoryId", description = "The id of a repository of which to fetch the logo", example = "zenodo", required = true) @PathVariable("repositoryId") String repositoryId + ) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { logger.debug(new MapLogEntry("get logo" + DepositConfiguration.class.getSimpleName()).And("repositoryId", repositoryId)); String logo = this.depositService.getLogo(repositoryId); diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java index 16ab7bbac..c6a25ae60 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java @@ -195,7 +195,12 @@ public class DescriptionController { } @GetMapping("{id}") - @OperationWithTenantHeader(summary = "Fetch a specific description by id") + @OperationWithTenantHeader(summary = "Fetch a specific description by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = Description.class + )) + )) @Swagger404 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, @@ -220,7 +225,12 @@ public class DescriptionController { } @PostMapping("persist") - @OperationWithTenantHeader(summary = "Create a new or update an existing description") + @OperationWithTenantHeader(summary = "Create a new or update an existing description", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = Description.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -243,7 +253,12 @@ public class DescriptionController { } @PostMapping("persist-status") - @OperationWithTenantHeader(summary = "Update the status of an existing description") + @OperationWithTenantHeader(summary = "Update the status of an existing description", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = Description.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -298,7 +313,8 @@ public class DescriptionController { } @DeleteMapping("{id}") - @OperationWithTenantHeader(summary = "Delete a description by id") + @OperationWithTenantHeader(summary = "Delete a description by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 @Transactional public void delete( @@ -312,7 +328,8 @@ public class DescriptionController { } @GetMapping("{id}/export/{type}") - @OperationWithTenantHeader(summary = "Export a description in various formats by id") + @OperationWithTenantHeader(summary = "Export a description in various formats by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 public ResponseEntity export( @Parameter(name = "id", description = "The id of a description to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, @@ -324,7 +341,12 @@ public class DescriptionController { } @PostMapping("field-file/upload") - @OperationWithTenantHeader(summary = "Upload a file attachment on a field that supports it") + @OperationWithTenantHeader(summary = "Upload a file attachment on a field that supports it", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = StorageFile.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -348,7 +370,8 @@ public class DescriptionController { } @GetMapping("{id}/field-file/{fileId}") - @OperationWithTenantHeader(summary = "Fetch a field file attachment as byte array") + @OperationWithTenantHeader(summary = "Fetch a field file attachment as byte array", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 public ResponseEntity getFieldFile( @Parameter(name = "id", description = "The id of a description", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, @@ -377,7 +400,8 @@ public class DescriptionController { } @PostMapping("update-description-template") - @OperationWithTenantHeader(summary = "Change the template of a description") + @OperationWithTenantHeader(summary = "Change the template of a description", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger400 @Swagger404 @Transactional @@ -394,7 +418,8 @@ public class DescriptionController { } @RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml") - @OperationWithTenantHeader(summary = "Export a description in xml format by id") + @OperationWithTenantHeader(summary = "Export a description in xml format by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 public @ResponseBody ResponseEntity getXml( @Parameter(name = "id", description = "The id of a description to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable UUID id diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateController.java b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateController.java index 591988ef4..0dcc5e3c6 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateController.java @@ -134,7 +134,12 @@ public class DescriptionTemplateController { } @GetMapping("{id}") - @OperationWithTenantHeader(summary = "Fetch a specific description template by id") + @OperationWithTenantHeader(summary = "Fetch a specific description template by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DescriptionTemplate.class + )) + )) @Swagger404 public DescriptionTemplate get( @Parameter(name = "id", description = "The id of a description template to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, @@ -159,7 +164,12 @@ public class DescriptionTemplateController { } @PostMapping("persist") - @OperationWithTenantHeader(summary = "Create a new or update an existing description template") + @OperationWithTenantHeader(summary = "Create a new or update an existing description template", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DescriptionTemplate.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -182,7 +192,8 @@ public class DescriptionTemplateController { } @DeleteMapping("{id}") - @OperationWithTenantHeader(summary = "Delete a description template by id") + @OperationWithTenantHeader(summary = "Delete a description template by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 @Transactional public void delete( @@ -196,7 +207,12 @@ public class DescriptionTemplateController { } @GetMapping("clone/{id}") - @OperationWithTenantHeader(summary = "Clone a description template by id") + @OperationWithTenantHeader(summary = "Clone a description template by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DescriptionTemplate.class + )) + )) @Swagger404 public DescriptionTemplate buildClone( @Parameter(name = "id", description = "The id of a description template to clone", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, @@ -218,7 +234,12 @@ public class DescriptionTemplateController { } @PostMapping("new-version") - @OperationWithTenantHeader(summary = "Create a new version of a description template") + @OperationWithTenantHeader(summary = "Create a new version of a description template", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DescriptionTemplate.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -240,7 +261,8 @@ public class DescriptionTemplateController { } @RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml") - @OperationWithTenantHeader(summary = "Export a description template in xml by id") + @OperationWithTenantHeader(summary = "Export a description template in xml by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 public @ResponseBody ResponseEntity getXml( @Parameter(name = "id", description = "The id of a description template to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable UUID id @@ -257,7 +279,12 @@ public class DescriptionTemplateController { } @RequestMapping(method = RequestMethod.POST, value = {"/xml/import/{groupId}", "/xml/import"}) - @OperationWithTenantHeader(summary = "Import a description template from an xml file") + @OperationWithTenantHeader(summary = "Import a description template from an xml file", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DescriptionTemplate.class + )) + )) @Transactional public DescriptionTemplate importXml( @RequestParam("file") MultipartFile file, diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateTypeController.java b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateTypeController.java index 49c404928..dabe332c1 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateTypeController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionTemplateTypeController.java @@ -116,7 +116,12 @@ public class DescriptionTemplateTypeController { } @GetMapping("{id}") - @OperationWithTenantHeader(summary = "Fetch a specific description template type by id") + @OperationWithTenantHeader(summary = "Fetch a specific description template type by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DescriptionTemplateType.class + )) + )) @Swagger404 public DescriptionTemplateType Get( @Parameter(name = "id", description = "The id of a description template type to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, @@ -141,7 +146,12 @@ public class DescriptionTemplateTypeController { } @PostMapping("persist") - @OperationWithTenantHeader(summary = "Create a new or update an existing description template type") + @OperationWithTenantHeader(summary = "Create a new or update an existing description template type", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DescriptionTemplateType.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -162,7 +172,8 @@ public class DescriptionTemplateTypeController { } @DeleteMapping("{id}") - @OperationWithTenantHeader(summary = "Delete a description template type by id") + @OperationWithTenantHeader(summary = "Delete a description template type by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 @Transactional public void Delete( diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DmpBlueprintController.java b/backend/web/src/main/java/org/opencdmp/controllers/DmpBlueprintController.java index c58fc4b0a..bc344b6e5 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DmpBlueprintController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DmpBlueprintController.java @@ -29,7 +29,6 @@ import org.opencdmp.controllers.swagger.annotation.Swagger400; import org.opencdmp.controllers.swagger.annotation.Swagger404; import org.opencdmp.controllers.swagger.annotation.SwaggerCommonErrorResponses; import org.opencdmp.data.DmpBlueprintEntity; -import org.opencdmp.model.DescriptionTemplateType; import org.opencdmp.model.builder.dmpblueprint.DmpBlueprintBuilder; import org.opencdmp.model.censorship.dmpblueprint.DmpBlueprintCensor; import org.opencdmp.model.dmpblueprint.DmpBlueprint; @@ -127,7 +126,12 @@ public class DmpBlueprintController { } @GetMapping("{id}") - @OperationWithTenantHeader(summary = "Fetch a specific plan blueprint by id") + @OperationWithTenantHeader(summary = "Fetch a specific plan blueprint by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DmpBlueprint.class + )) + )) @Swagger404 public DmpBlueprint get( @Parameter(name = "id", description = "The id of a plan blueprint to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, @@ -151,7 +155,12 @@ public class DmpBlueprintController { } @PostMapping("persist") - @OperationWithTenantHeader(summary = "Create a new or update an existing plan blueprint") + @OperationWithTenantHeader(summary = "Create a new or update an existing plan blueprint", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DmpBlueprint.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -174,7 +183,8 @@ public class DmpBlueprintController { } @DeleteMapping("{id}") - @OperationWithTenantHeader(summary = "Delete a plan blueprint by id") + @OperationWithTenantHeader(summary = "Delete a plan blueprint by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 @Transactional public void delete( @@ -188,7 +198,12 @@ public class DmpBlueprintController { } @GetMapping("clone/{id}") - @OperationWithTenantHeader(summary = "Clone a plan blueprint by id") + @OperationWithTenantHeader(summary = "Clone a plan blueprint by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DmpBlueprint.class + )) + )) @Swagger404 public DmpBlueprint buildClone( @Parameter(name = "id", description = "The id of a plan blueprint to clone", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, @@ -209,7 +224,12 @@ public class DmpBlueprintController { } @PostMapping("new-version") - @OperationWithTenantHeader(summary = "Create a new version of an existing plan blueprint") + @OperationWithTenantHeader(summary = "Create a new version of an existing plan blueprint", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DmpBlueprint.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -230,7 +250,8 @@ public class DmpBlueprintController { } @RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml") - @OperationWithTenantHeader(summary = "Export a plan blueprint in xml format by id") + @OperationWithTenantHeader(summary = "Export a plan blueprint in xml format by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 public @ResponseBody ResponseEntity getXml( @Parameter(name = "id", description = "The id of a plan blueprint to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable UUID id @@ -246,7 +267,12 @@ public class DmpBlueprintController { } @RequestMapping(method = RequestMethod.POST, value = {"/xml/import/{groupId}", "/xml/import"}) - @OperationWithTenantHeader(summary = "Import a plan blueprint from an xml file") + @OperationWithTenantHeader(summary = "Import a plan blueprint from an xml file", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = DmpBlueprint.class + )) + )) @Transactional public DmpBlueprint importXml( @RequestParam("file") MultipartFile file, diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DmpController.java b/backend/web/src/main/java/org/opencdmp/controllers/DmpController.java index ae1fef59a..51fde57a4 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DmpController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DmpController.java @@ -179,7 +179,12 @@ public class DmpController { } @GetMapping("{id}") - @OperationWithTenantHeader(summary = "Fetch a specific plan by id") + @OperationWithTenantHeader(summary = "Fetch a specific plan by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = Dmp.class + )) + )) @Swagger404 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, @@ -204,7 +209,12 @@ public class DmpController { } @PostMapping("persist") - @OperationWithTenantHeader(summary = "Create a new or update an existing plan") + @OperationWithTenantHeader(summary = "Create a new or update an existing plan", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = Dmp.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -226,7 +236,8 @@ public class DmpController { } @DeleteMapping("{id}") - @OperationWithTenantHeader(summary = "Delete a plan by id") + @OperationWithTenantHeader(summary = "Delete a plan by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 @Transactional public void Delete( @@ -240,7 +251,8 @@ public class DmpController { } @PostMapping("finalize/{id}") - @OperationWithTenantHeader(summary = "Finalize a plan by id") + @OperationWithTenantHeader(summary = "Finalize a plan by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 @Transactional public boolean finalize( @@ -260,7 +272,8 @@ public class DmpController { } @GetMapping("undo-finalize/{id}") - @OperationWithTenantHeader(summary = "Undo the finalization of a plan by id (only possible if it is not already deposited)") + @OperationWithTenantHeader(summary = "Undo the finalization of a plan by id (only possible if it is not already deposited)", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 @Transactional public boolean undoFinalize( @@ -298,7 +311,12 @@ public class DmpController { } @PostMapping("clone") - @OperationWithTenantHeader(summary = "Create a clone of an existing plan") + @OperationWithTenantHeader(summary = "Create a clone of an existing plan", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = Dmp.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -322,7 +340,12 @@ public class DmpController { } @PostMapping("new-version") - @OperationWithTenantHeader(summary = "Create a new version of an existing plan") + @OperationWithTenantHeader(summary = "Create a new version of an existing plan", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = Dmp.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -380,7 +403,8 @@ public class DmpController { } @GetMapping("{id}/export/{transformerId}/{type}") - @OperationWithTenantHeader(summary = "Export a plan in various formats by id") + @OperationWithTenantHeader(summary = "Export a plan in various formats by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 public ResponseEntity export( @Parameter(name = "id", description = "The id of a plan to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, @@ -432,7 +456,8 @@ public class DmpController { } @RequestMapping(method = RequestMethod.GET, value = "/xml/export/{id}", produces = "application/xml") - @OperationWithTenantHeader(summary = "Export a plan in xml format by id") + @OperationWithTenantHeader(summary = "Export a plan in xml format by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 public @ResponseBody ResponseEntity getXml( @Parameter(name = "id", description = "The id of a plan to export", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable UUID id @@ -448,7 +473,12 @@ public class DmpController { } @RequestMapping(method = RequestMethod.POST, value = "/xml/import") - @OperationWithTenantHeader(summary = "Import a plan from an xml file") + @OperationWithTenantHeader(summary = "Import a plan from an xml file", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = Dmp.class + )) + )) @Transactional public Dmp importXml( @RequestParam("file") MultipartFile file, @@ -467,7 +497,12 @@ public class DmpController { } @PostMapping("json/preprocessing") - @OperationWithTenantHeader(summary = "preprocessing a plan from an json file") + @OperationWithTenantHeader(summary = "Preprocess a plan from a json file", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = PreprocessingDmpModel.class + )) + )) @Transactional public PreprocessingDmpModel preprocessing( @RequestParam("fileId") UUID fileId, @@ -486,7 +521,12 @@ public class DmpController { } @PostMapping("json/import") - @OperationWithTenantHeader(summary = "Import a plan from an json file") + @OperationWithTenantHeader(summary = "Import a plan from an json file", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = Dmp.class + )) + )) @ValidationFilterAnnotation(validator = DmpCommonModelConfig.DmpCommonModelConfigValidator.ValidatorName, argumentName = "model") @Transactional public Dmp importJson( diff --git a/backend/web/src/main/java/org/opencdmp/controllers/EntityDoiController.java b/backend/web/src/main/java/org/opencdmp/controllers/EntityDoiController.java index 58ba122aa..742e091d5 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/EntityDoiController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/EntityDoiController.java @@ -30,7 +30,6 @@ import org.opencdmp.model.DescriptionTemplateType; import org.opencdmp.model.EntityDoi; import org.opencdmp.model.builder.EntityDoiBuilder; import org.opencdmp.model.censorship.EntityDoiCensor; -import org.opencdmp.model.dmpblueprint.DmpBlueprint; import org.opencdmp.model.persist.EntityDoiPersist; import org.opencdmp.model.result.QueryResult; import org.opencdmp.query.EntityDoiQuery; @@ -116,7 +115,12 @@ public class EntityDoiController { } @GetMapping("{id}") - @OperationWithTenantHeader(summary = "Fetch a specific entity doi by id") + @OperationWithTenantHeader(summary = "Fetch a specific entity doi by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = EntityDoi.class + )) + )) @Swagger404 public EntityDoi get( @Parameter(name = "id", description = "The id of an entity doi to fetch", example = "c0c163dc-2965-45a5-9608-f76030578609", required = true) @PathVariable("id") UUID id, @@ -141,7 +145,12 @@ public class EntityDoiController { } @PostMapping("persist") - @OperationWithTenantHeader(summary = "Create a new or update an existing entity doi") + @OperationWithTenantHeader(summary = "Create a new or update an existing entity doi", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + schema = @Schema( + implementation = EntityDoi.class + )) + )) @Swagger400 @Swagger404 @Transactional @@ -162,7 +171,8 @@ public class EntityDoiController { } @DeleteMapping("{id}") - @OperationWithTenantHeader(summary = "Delete an entity doi by id") + @OperationWithTenantHeader(summary = "Delete an entity doi by id", description = "", + responses = @ApiResponse(description = "OK", responseCode = "200")) @Swagger404 @Transactional public void delete( diff --git a/backend/web/src/main/java/org/opencdmp/controllers/FileTransformerController.java b/backend/web/src/main/java/org/opencdmp/controllers/FileTransformerController.java index 0846f2ea7..14fd21e11 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/FileTransformerController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/FileTransformerController.java @@ -1,6 +1,15 @@ package org.opencdmp.controllers; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import org.opencdmp.audit.AuditableAction; +import org.opencdmp.controllers.swagger.SwaggerHelpers; +import org.opencdmp.controllers.swagger.annotation.OperationWithTenantHeader; +import org.opencdmp.controllers.swagger.annotation.SwaggerCommonErrorResponses; +import org.opencdmp.model.deposit.DepositConfiguration; import org.opencdmp.model.file.ExportRequestModel; import org.opencdmp.model.file.FileEnvelope; import org.opencdmp.model.file.RepositoryFileFormat; @@ -29,6 +38,8 @@ import java.util.List; @RestController @CrossOrigin @RequestMapping(value = {"/api/file-transformer/"}) +@Tag(name = "File Transformers", description = "Manage file transformers, perform exports") +@SwaggerCommonErrorResponses public class FileTransformerController { private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(FileTransformerController.class)); @@ -43,6 +54,13 @@ public class FileTransformerController { } @GetMapping("/available") + @OperationWithTenantHeader(summary = "Fetch all transformers", description = SwaggerHelpers.FileTransformer.endpoint_get_available_transformers, + responses = @ApiResponse(description = "OK", responseCode = "200", content = @Content( + array = @ArraySchema( + schema = @Schema( + implementation = RepositoryFileFormat.class + ))) + )) public List getAvailableConfigurations() throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { logger.debug(new MapLogEntry("getAvailableConfigurations")); @@ -54,8 +72,10 @@ public class FileTransformerController { } @PostMapping("/export-dmp") - public ResponseEntity export(@RequestBody ExportRequestModel requestModel) throws InvalidApplicationException, IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { - logger.debug(new MapLogEntry("exporting dmp")); + @OperationWithTenantHeader(summary = "Export a plan", description = SwaggerHelpers.FileTransformer.endpoint_export_plans, + responses = @ApiResponse(description = "OK", responseCode = "200")) + public ResponseEntity exportPlan(@RequestBody ExportRequestModel requestModel) throws InvalidApplicationException, IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + logger.debug(new MapLogEntry("exporting plan")); HttpHeaders headers = new HttpHeaders(); FileEnvelope fileEnvelope = this.fileTransformerService.exportDmp(requestModel.getId(), requestModel.getRepositoryId(), requestModel.getFormat()); @@ -66,8 +86,10 @@ public class FileTransformerController { } @PostMapping("/export-description") + @OperationWithTenantHeader(summary = "Export a description", description = SwaggerHelpers.FileTransformer.endpoint_export_descriptions, + responses = @ApiResponse(description = "OK", responseCode = "200")) public ResponseEntity exportDescription(@RequestBody ExportRequestModel requestModel) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { - logger.debug(new MapLogEntry("exporting dmp")); + logger.debug(new MapLogEntry("exporting description")); HttpHeaders headers = new HttpHeaders(); FileEnvelope fileEnvelope = this.fileTransformerService.exportDescription(requestModel.getId(), requestModel.getRepositoryId(), requestModel.getFormat()); diff --git a/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java b/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java index c7114e0fa..ee20f7032 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/swagger/SwaggerHelpers.java @@ -2550,6 +2550,23 @@ public class SwaggerHelpers { public static class FileTransformer { + public static final String endpoint_get_available_transformers = + """ + This endpoint is used to fetch all the available file transformers.
+ """; + + public static final String endpoint_export_plans = + """ + This endpoint is used to export a plan using a specific file transformer.
+ Choosing the transformer will determine the format of the export. + """; + + public static final String endpoint_export_descriptions = + """ + This endpoint is used to export a plan using a specific file transformer. + Choosing the transformer will determine the format of the export. + """; + } public static class EntityDoi { @@ -2815,10 +2832,31 @@ public class SwaggerHelpers { "count":118 } """; + } public static class Deposit { + public static final String endpoint_get_available_repos = + """ + This endpoint is used to fetch all the available deposit repositories. + """; + + public static final String endpoint_deposit = + """ + This endpoint is used to deposit a plan in a repository. + """; + + public static final String endpoint_get_repository = + """ + This endpoint is used to get information about a specific repository. + """; + + public static final String endpoint_get_logo = + """ + This endpoint is used to fetch the logo url of a repository. + """; + } } diff --git a/backend/web/src/main/resources/config/permissions.yml b/backend/web/src/main/resources/config/permissions.yml index 2d9a29a9f..a0922bd7c 100644 --- a/backend/web/src/main/resources/config/permissions.yml +++ b/backend/web/src/main/resources/config/permissions.yml @@ -1060,6 +1060,29 @@ permissions: clients: [ ] allowAnonymous: false allowAuthenticated: false + # Status + BrowseStatus: + roles: + - Admin + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + EditStatus: + roles: + - Admin + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + DeleteStatus: + roles: + - Admin + - TenantAdmin + claims: [ ] + clients: [ ] + allowAnonymous: false + allowAuthenticated: false # ViewPage Permissions ViewDescriptionTemplateTypePage: @@ -1206,6 +1229,13 @@ permissions: allowAnonymous: false allowAuthenticated: true ViewTenantConfigurationPage: + roles: + - Admin + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewStatusPage: roles: - Admin - TenantAdmin diff --git a/backend/web/src/main/resources/config/queue.yml b/backend/web/src/main/resources/config/queue.yml index a3d217e24..f8eb40e76 100644 --- a/backend/web/src/main/resources/config/queue.yml +++ b/backend/web/src/main/resources/config/queue.yml @@ -25,6 +25,10 @@ queue: enable: true options: exchange: null + handleAckRetries: 3 + handleNackRetries: 3 + handleAckWaitInMilliSeconds: 50 + handleNackWaitInMilliSeconds: 50 tenant-default-locale-removal-topic: tenant_default_locale.remove tenant-default-locale-touched-topic: tenant_default_locale.touch forget-me-completed-topic: forgetme.completed diff --git a/backend/web/src/main/resources/config/swagger.yml b/backend/web/src/main/resources/config/swagger.yml index 0310255b0..72d70a5b0 100644 --- a/backend/web/src/main/resources/config/swagger.yml +++ b/backend/web/src/main/resources/config/swagger.yml @@ -11,7 +11,7 @@ springdoc: displayName: Current packagesToScan: org.opencdmp.controllers packagesToExclude: org.opencdmp.controllers.publicapi - pathsToMatch: "/api/dmp/**, /api/description/**, /api/description-template/**, /api/description-template-type/**, /api/dmp-blueprint/**, /api/entity-doi/**" + pathsToMatch: "/api/dmp/**, /api/description/**, /api/description-template/**, /api/description-template-type/**, /api/dmp-blueprint/**, /api/entity-doi/**, /api/deposit/**, /api/file-transformer/**" swaggerUi: enabled: true useRootPath: true diff --git a/dmp-frontend/src/annotation-service/core/enum/internal-status.enum.ts b/dmp-frontend/src/annotation-service/core/enum/internal-status.enum.ts new file mode 100644 index 000000000..37e9db80e --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/enum/internal-status.enum.ts @@ -0,0 +1,3 @@ +export enum InternalStatus { + Resolved = 0, +} diff --git a/dmp-frontend/src/annotation-service/core/enum/is-active.enum.ts b/dmp-frontend/src/annotation-service/core/enum/is-active.enum.ts new file mode 100644 index 000000000..cdadd23b4 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/enum/is-active.enum.ts @@ -0,0 +1,4 @@ +export enum IsActive { + Inactive = 0, + Active = 1 +} diff --git a/dmp-frontend/src/annotation-service/core/formatting/enum-utils.service.ts b/dmp-frontend/src/annotation-service/core/formatting/enum-utils.service.ts new file mode 100644 index 000000000..e43362098 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/formatting/enum-utils.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { BaseEnumUtilsService } from '@common/base/base-enum-utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { IsActive } from '../enum/is-active.enum'; +import { InternalStatus } from '../enum/internal-status.enum'; + +@Injectable() +export class AnnotationServiceEnumUtils extends BaseEnumUtilsService { + constructor(private language: TranslateService) { super(); } + + + toIsActiveString(value: IsActive): string { + switch (value) { + case IsActive.Active: return this.language.instant('ANNOTATION-SERVICE.TYPES.IS-ACTIVE.ACTIVE'); + case IsActive.Inactive: return this.language.instant('ANNOTATION-SERVICE.TYPES.IS-ACTIVE.INACTIVE'); + default: return ''; + } + } + + toInternalStatusString(status: InternalStatus): string { + switch (status) { + case InternalStatus.Resolved: return this.language.instant('ANNOTATION-SERVICE.TYPES.INTERNAL-STATUS.RESOLVED'); + } + } + +} diff --git a/dmp-frontend/src/annotation-service/core/formatting/formatting.module.ts b/dmp-frontend/src/annotation-service/core/formatting/formatting.module.ts new file mode 100644 index 000000000..e7f028491 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/formatting/formatting.module.ts @@ -0,0 +1,36 @@ + + +// +// +// This is shared module that provides all notification service formatting utils. Its imported only once. +// + +import { CommonFormattingModule } from "@common/formatting/common-formatting.module"; +import { IsActiveTypePipe } from "./pipes/is-active-type.pipe"; +import { InternalStatusTypePipe } from "./pipes/internal-status-type.pipe"; +import { PipeService } from "@common/formatting/pipe.service"; +import { AnnotationServiceEnumUtils } from "./enum-utils.service"; +import { NgModule } from "@angular/core"; + +// +@NgModule({ + imports: [ + CommonFormattingModule, + ], + declarations: [ + IsActiveTypePipe, + InternalStatusTypePipe + ], + exports: [ + CommonFormattingModule, + IsActiveTypePipe, + InternalStatusTypePipe, + ], + providers: [ + PipeService, + IsActiveTypePipe, + InternalStatusTypePipe, + AnnotationServiceEnumUtils + ] +}) +export class AnnotationServiceFormattingModule { } diff --git a/dmp-frontend/src/annotation-service/core/formatting/pipes/internal-status-type.pipe.ts b/dmp-frontend/src/annotation-service/core/formatting/pipes/internal-status-type.pipe.ts new file mode 100644 index 000000000..1a61c2ab1 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/formatting/pipes/internal-status-type.pipe.ts @@ -0,0 +1,11 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { AnnotationServiceEnumUtils } from '../enum-utils.service'; + +@Pipe({ name: 'InternalStatusTypeFormat' }) +export class InternalStatusTypePipe implements PipeTransform { + constructor(private enumUtils: AnnotationServiceEnumUtils) { } + + public transform(value): any { + return this.enumUtils.toInternalStatusString(value); + } +} diff --git a/dmp-frontend/src/annotation-service/core/formatting/pipes/is-active-type.pipe.ts b/dmp-frontend/src/annotation-service/core/formatting/pipes/is-active-type.pipe.ts new file mode 100644 index 000000000..458a8b5c6 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/formatting/pipes/is-active-type.pipe.ts @@ -0,0 +1,11 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { AnnotationServiceEnumUtils } from '../enum-utils.service'; + +@Pipe({ name: 'IsActiveTypeFormat' }) +export class IsActiveTypePipe implements PipeTransform { + constructor(private enumUtils: AnnotationServiceEnumUtils) { } + + public transform(value): any { + return this.enumUtils.toIsActiveString(value); + } +} diff --git a/dmp-frontend/src/annotation-service/core/model/annotation-status.model.ts b/dmp-frontend/src/annotation-service/core/model/annotation-status.model.ts new file mode 100644 index 000000000..7e2c651c4 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/model/annotation-status.model.ts @@ -0,0 +1,16 @@ +import { BaseEntity } from "@common/base/base-entity.model"; +import { Annotation } from "./annotation.model"; +import { Status } from "./status.model"; +import { Guid } from "@common/types/guid"; + +export interface AnnotationStatus extends BaseEntity { + annotation: Annotation; + status: Status; +} + +// persist + +export interface AnnotationStatusPersist extends BaseEntity { + annotationId: Guid; + statusId: Guid; +} \ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/core/model/annotation.model.ts b/dmp-frontend/src/annotation-service/core/model/annotation.model.ts index 8c868f66c..6a5cfa871 100644 --- a/dmp-frontend/src/annotation-service/core/model/annotation.model.ts +++ b/dmp-frontend/src/annotation-service/core/model/annotation.model.ts @@ -2,6 +2,7 @@ import { AnnotationProtectionType } from "@app/core/common/enum/annotation-prote import { User } from "@app/core/model/user/user"; import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model"; import { Guid } from "@common/types/guid"; +import { AnnotationStatus } from "./annotation-status.model"; export interface Annotation extends BaseEntity { entityId: Guid; @@ -13,6 +14,7 @@ export interface Annotation extends BaseEntity { threadId: Guid; parent: Annotation; protectionType: AnnotationProtectionType; + annotationStatuses: AnnotationStatus[]; } export interface AnnotationPersist extends BaseEntityPersist { diff --git a/dmp-frontend/src/annotation-service/core/model/status.model.ts b/dmp-frontend/src/annotation-service/core/model/status.model.ts new file mode 100644 index 000000000..aa374663f --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/model/status.model.ts @@ -0,0 +1,12 @@ +import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model"; +import { InternalStatus } from "../enum/internal-status.enum"; + +export interface Status extends BaseEntity { + label: string; + internalStatus: InternalStatus; +} + +export interface StatusPersist extends BaseEntityPersist { + label: string; + internalStatus: InternalStatus; +} \ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/core/query/status.lookup.ts b/dmp-frontend/src/annotation-service/core/query/status.lookup.ts new file mode 100644 index 000000000..3078f8703 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/query/status.lookup.ts @@ -0,0 +1,26 @@ +import { IsActive } from "@app/core/common/enum/is-active.enum"; +import { Lookup } from "@common/model/lookup"; +import { Guid } from "@common/types/guid"; +import { InternalStatus } from "../enum/internal-status.enum"; + +export class StatusLookup extends Lookup implements StatusFilter { + + like: string; + ids: Guid[]; + excludedIds: Guid[]; + isActive: IsActive[]; + internalStatuses: InternalStatus[]; + + constructor() { + super(); + } + +} + +export interface StatusFilter { + like: string; + ids: Guid[]; + excludedIds: Guid[]; + isActive: IsActive[]; + internalStatuses: InternalStatus[]; +} \ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/services/core-service.module.ts b/dmp-frontend/src/annotation-service/services/core-service.module.ts index 58e1e9189..579cd9ddb 100644 --- a/dmp-frontend/src/annotation-service/services/core-service.module.ts +++ b/dmp-frontend/src/annotation-service/services/core-service.module.ts @@ -11,6 +11,7 @@ import { FormService } from "@common/forms/form-service"; import { HttpErrorHandlingService } from "@common/modules/errors/error-handling/http-error-handling.service"; import { FilterService } from "@common/modules/text-filter/filter-service"; import { AnnotationService } from "@annotation-service/services/http/annotation.service"; +import { StatusService } from "./http/status.service"; @NgModule({}) export class CoreAnnotationServiceModule { @@ -30,7 +31,8 @@ export class CoreAnnotationServiceModule { FormService, LoggingService, PrincipalService, - AnnotationService + AnnotationService, + StatusService ], }; } diff --git a/dmp-frontend/src/annotation-service/services/http/annotation.service.ts b/dmp-frontend/src/annotation-service/services/http/annotation.service.ts index d89f5d059..44c683da1 100644 --- a/dmp-frontend/src/annotation-service/services/http/annotation.service.ts +++ b/dmp-frontend/src/annotation-service/services/http/annotation.service.ts @@ -1,4 +1,5 @@ import { Injectable } from "@angular/core"; +import { AnnotationStatus, AnnotationStatusPersist } from "@annotation-service/core/model/annotation-status.model"; import { Annotation, AnnotationPersist } from "@annotation-service/core/model/annotation.model"; import { AnnotationLookup } from "@annotation-service/core/query/annotation.lookup"; import { ConfigurationService } from "@app/core/services/configuration/configuration.service"; @@ -42,6 +43,14 @@ export class AnnotationService { catchError((error: any) => throwError(error))); } + persistStatus(item: AnnotationStatusPersist) { + const url = `${this.apiBase}/persist-status`; + + return this.http + .post(url, item).pipe( + catchError((error: any) => throwError(error))); + } + delete(id: Guid, ): Observable { const url = `${this.apiBase}/${id}`; return this.http diff --git a/dmp-frontend/src/annotation-service/services/http/status.service.ts b/dmp-frontend/src/annotation-service/services/http/status.service.ts new file mode 100644 index 000000000..49a05b1d5 --- /dev/null +++ b/dmp-frontend/src/annotation-service/services/http/status.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from "@angular/core"; +import { Status, StatusPersist } from "@annotation-service/core/model/status.model"; +import { StatusLookup } from "@annotation-service/core/query/status.lookup"; +import { ConfigurationService } from "@app/core/services/configuration/configuration.service"; +import { BaseHttpV2Service } from "@app/core/services/http/base-http-v2.service"; +import { QueryResult } from "@common/model/query-result"; +import { FilterService } from "@common/modules/text-filter/filter-service"; +import { Guid } from "@common/types/guid"; +import { Observable, throwError } from "rxjs"; +import { catchError } from "rxjs/operators"; + +@Injectable() +export class StatusService { + private get apiBase(): string { return `${this.installationConfiguration.annotationServiceAddress}api/status`; } + + constructor( + private installationConfiguration: ConfigurationService, + private http: BaseHttpV2Service, + private filterService: FilterService + ) { } + + query(q: StatusLookup): Observable> { + const url = `${this.apiBase}/query`; + + return this.http + .post>(url, q).pipe( + catchError((error: any) => throwError(error))); + } + + getSingle(id: Guid, reqFields: string[] = []): Observable { + const url = `${this.apiBase}/${id}`; + const options = { params: { f: reqFields } }; + + return this.http + .get(url, options).pipe( + catchError((error: any) => throwError(error))); + } + + persist(item: StatusPersist) { + const url = `${this.apiBase}/persist`; + + return this.http + .post(url, item).pipe( + catchError((error: any) => throwError(error))); + } + + delete(id: Guid, ): Observable { + const url = `${this.apiBase}/${id}`; + return this.http + .delete(url).pipe( + catchError((error: any) => throwError(error))); + } + +} \ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.html b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.html new file mode 100644 index 000000000..88c2d4216 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.html @@ -0,0 +1,71 @@ +
+
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ + + +
+
+
+
+
+
+ + {{'ANNOTATION-SERVICE.STATUS-EDITOR.FIELDS.LABEL' | translate}} + +
+
+
+
+
+
+
+
+ + + {{formGroup.get('label').getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'ANNOTATION-SERVICE.STATUS-EDITOR.FIELDS.INTERNAL-STATUS' | translate}} + + {{enumUtils.toInternalStatusString(internalStatus)}} + + {{formGroup.get('internalStatus').getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+
+
+
+
+
+
+
+
+
diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.scss b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.scss new file mode 100644 index 000000000..489faf25d --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.scss @@ -0,0 +1,26 @@ +.status-editor { + // padding-top: 1em; + + .editor-actions { + margin-top: 30px; + } +} + +.action-btn { + border-radius: 30px; + background-color: var(--secondary-color); + border: 1px solid transparent; + padding-left: 2em; + padding-right: 2em; + box-shadow: 0px 3px 6px #1E202029; + + transition-property: background-color, color; + transition-duration: 200ms; + transition-delay: 50ms; + transition-timing-function: ease-in-out; + &:disabled{ + background-color: #CBCBCB; + color: #FFF; + border: 0px; + } +} \ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.ts b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.ts new file mode 100644 index 000000000..e4d57f7fb --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.ts @@ -0,0 +1,177 @@ +import { Component, OnInit } from '@angular/core'; +import { UntypedFormGroup } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { Title } from '@angular/platform-browser'; +import { ActivatedRoute, Router } from '@angular/router'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; +import { LockService } from '@app/core/services/lock/lock.service'; +import { LoggingService } from '@app/core/services/logging/logging-service'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; +import { BaseEditor } from '@common/base/base-editor'; +import { FormService } from '@common/forms/form-service'; +import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; +import { FilterService } from '@common/modules/text-filter/filter-service'; +import { Guid } from '@common/types/guid'; +import { TranslateService } from '@ngx-translate/core'; +import { map, takeUntil } from 'rxjs/operators'; +import { StatusEditorModel } from './status-editor.model'; +import { StatusEditorService } from './status-editor.service'; +import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; +import { AnnotationServiceEnumUtils } from '@annotation-service/core/formatting/enum-utils.service'; +import { Status, StatusPersist } from '@annotation-service/core/model/status.model'; +import { StatusService } from '@annotation-service/services/http/status.service'; +import { StatusEditorResolver } from './status-editor.resolver'; +import { InternalStatus } from '@annotation-service/core/enum/internal-status.enum'; + +@Component({ + templateUrl: './status-editor.component.html', + styleUrls: ['./status-editor.component.scss'], + providers: [StatusEditorService] +}) +export class StatusEditorComponent extends BaseEditor implements OnInit { + + isNew = true; + isDeleted = false; + formGroup: UntypedFormGroup = null; + showInactiveDetails = false; + public internalStatusEnum = this.enumUtils.getEnumValues(InternalStatus); + + + protected get canDelete(): boolean { + return !this.isDeleted && !this.isNew && this.hasPermission(this.authService.permissionEnum.DeleteStatus) && this.editorModel.belongsToCurrentTenant != false; + } + + protected get canSave(): boolean { + return !this.isDeleted && this.hasPermission(this.authService.permissionEnum.EditStatus) && this.editorModel.belongsToCurrentTenant != false; + } + + private hasPermission(permission: AppPermission): boolean { + return this.authService.hasPermission(permission) || this.editorModel?.permissions?.includes(permission); + } + + constructor( + // BaseFormEditor injected dependencies + protected dialog: MatDialog, + protected language: TranslateService, + protected formService: FormService, + protected router: Router, + protected uiNotificationService: UiNotificationService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected filterService: FilterService, + protected route: ActivatedRoute, + protected queryParamsService: QueryParamsService, + protected lockService: LockService, + protected authService: AuthService, + protected configurationService: ConfigurationService, + // Rest dependencies. Inject any other needed deps here: + public enumUtils: AnnotationServiceEnumUtils, + private statusService: StatusService, + private logger: LoggingService, + private statusEditorService: StatusEditorService, + public titleService: Title, + protected routerUtils: RouterUtilsService + ) { + const statusLabel: string = route.snapshot.data['entity']?.name; + if (statusLabel) { + titleService.setTitle(statusLabel); + } else { + titleService.setTitle('ANNOTATION-SERVICE.STATUS-EDITOR.TITLE-EDIT-ANNOTATION-STATUS'); + } + super(dialog, language, formService, router, uiNotificationService, httpErrorHandlingService, filterService, route, queryParamsService, lockService, authService, configurationService); + } + + ngOnInit(): void { + super.ngOnInit(); + } + + getItem(itemId: Guid, successFunction: (item: Status) => void) { + this.statusService.getSingle(itemId, StatusEditorResolver.lookupFields()) + .pipe(map(data => data as Status), takeUntil(this._destroyed)) + .subscribe( + data => successFunction(data), + error => this.onCallbackError(error) + ); + } + + prepareForm(data: Status) { + try { + this.editorModel = data ? new StatusEditorModel().fromModel(data) : new StatusEditorModel(); + this.isDeleted = data ? data.isActive === IsActive.Inactive : false; + this.buildForm(); + } catch (error) { + this.logger.error('Could not parse Status item: ' + data + error); + this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error); + } + } + + buildForm() { + this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.authService.hasPermission(AppPermission.EditStatus)); + this.statusEditorService.setValidationErrorModel(this.editorModel.validationErrorModel); + } + + refreshData(): void { + this.getItem(this.editorModel.id, (data: Status) => this.prepareForm(data)); + } + + refreshOnNavigateToData(id?: Guid): void { + this.formGroup.markAsPristine(); + + this.router.navigate([this.routerUtils.generateUrl('/annotation-statuses')], { queryParams: { 'lookup': this.queryParamsService.serializeLookup(this.lookupParams), 'lv': ++this.lv }, replaceUrl: true, relativeTo: this.route }); + } + + persistEntity(onSuccess?: (response) => void): void { + const formData = this.formService.getValue(this.formGroup.value) as StatusPersist; + + this.statusService.persist(formData) + .pipe(takeUntil(this._destroyed)).subscribe( + // for each state navigate to listing page + complete => { + this.onCallbackSuccess(); + }, + error => this.onCallbackError(error) + ); + } + + formSubmit(): void { + this.formService.removeAllBackEndErrors(this.formGroup); + this.formService.touchAllFormFields(this.formGroup); + if (!this.isFormValid()) { + return; + } + + this.persistEntity(); + } + + public delete() { + const value = this.formGroup.value; + if (value.id) { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + maxWidth: '300px', + data: { + message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'), + confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'), + cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL') + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + if (result) { + this.statusService.delete(value.id).pipe(takeUntil(this._destroyed)) + .subscribe( + complete => this.onCallbackDeleteSuccess(), + error => this.onCallbackError(error) + ); + } + }); + } + } + + clearErrorModel() { + this.editorModel.validationErrorModel.clear(); + this.formService.validateAllFormFields(this.formGroup); + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.model.ts b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.model.ts new file mode 100644 index 000000000..d860dc2ea --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.model.ts @@ -0,0 +1,50 @@ +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { InternalStatus } from '@annotation-service/core/enum/internal-status.enum'; +import { Status, StatusPersist } from '@annotation-service/core/model/status.model'; +import { BaseEditorModel } from '@common/base/base-form-editor-model'; +import { BackendErrorValidator } from '@common/forms/validation/custom-validator'; +import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model'; +import { Validation, ValidationContext } from '@common/forms/validation/validation-context'; + +export class StatusEditorModel extends BaseEditorModel implements StatusPersist { + label: string; + internalStatus: InternalStatus; + permissions: string[]; + + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel(); + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor() { super(); } + + public fromModel(item: Status): StatusEditorModel { + if (item) { + super.fromModel(item); + this.label = item.label; + this.internalStatus = item.internalStatus; + } + return this; + } + + buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup { + if (context == null) { context = this.createValidationContext(); } + + return this.formBuilder.group({ + id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators], + label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators], + internalStatus: [{ value: this.internalStatus, disabled: disabled }, context.getValidation('internalStatus').validators], + hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators] + }); + } + + createValidationContext(): ValidationContext { + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] }); + baseValidationArray.push({ key: 'label', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'label')] }); + baseValidationArray.push({ key: 'internalStatus', validators: [BackendErrorValidator(this.validationErrorModel, 'internalStatus')] }); + baseValidationArray.push({ key: 'hash', validators: [] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.resolver.ts b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.resolver.ts new file mode 100644 index 000000000..54f9a1077 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.resolver.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { Status } from '@annotation-service/core/model/status.model'; +import { StatusService } from '@annotation-service/services/http/status.service'; +import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; +import { BaseEditorResolver } from '@common/base/base-editor.resolver'; +import { Guid } from '@common/types/guid'; +import { takeUntil, tap } from 'rxjs/operators'; +import { nameof } from 'ts-simple-nameof'; + +@Injectable() +export class StatusEditorResolver extends BaseEditorResolver { + + constructor(private statusService: StatusService, private breadcrumbService: BreadcrumbService) { + super(); + } + + public static lookupFields(): string[] { + return [ + ...BaseEditorResolver.lookupFields(), + nameof(x => x.id), + nameof(x => x.label), + nameof(x => x.internalStatus), + nameof(x => x.createdAt), + nameof(x => x.updatedAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ] + } + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + + const fields = [ + ...StatusEditorResolver.lookupFields() + ]; + const id = route.paramMap.get('id'); + + if (id != null) { + return this.statusService.getSingle(Guid.parse(id), fields).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.label)), takeUntil(this._destroyed)); + } + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.service.ts b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.service.ts new file mode 100644 index 000000000..2bef89bd8 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from "@angular/core"; +import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model"; + +@Injectable() +export class StatusEditorService { + private validationErrorModel: ValidationErrorModel; + + public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void { + this.validationErrorModel = validationErrorModel; + } + + public getValidationErrorModel(): ValidationErrorModel { + return this.validationErrorModel; + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.html b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.html new file mode 100644 index 000000000..bc96f462b --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.html @@ -0,0 +1,59 @@ +
+ + + + + +
+
+
+

{{'ANNOTATION-SERVICE.STATUS-LISTING.FILTER.TITLE' | translate}}

+
+
+ +
+
+ +
+
+
+ + {{'ANNOTATION-SERVICE.STATUS-LISTING.FILTER.IS-ACTIVE' | translate}} + +
+
+
+ +
+
+ + {{'ANNOTATION-SERVICE.STATUS-LISTING.FILTER.INTERNAL-STATUS' | translate}} + + {{enumUtils.toInternalStatusString(type)}} + + +
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.scss b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.scss new file mode 100644 index 000000000..8e2ed08bf --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.scss @@ -0,0 +1,26 @@ + +::ng-deep.mat-mdc-menu-panel { + max-width: 100% !important; + height: 100% !important; +} + +:host::ng-deep.mat-mdc-menu-content:not(:empty) { + padding-top: 0 !important; +} + + +.filter-button{ + padding-top: .6rem; + padding-bottom: .6rem; + // .mat-icon{ + // font-size: 1.5em; + // width: 1.2em; + // height: 1.2em; + // } +} + +::ng-deep .mdc-form-field { + label { + margin: 0; + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.ts b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.ts new file mode 100644 index 000000000..9868cab51 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.ts @@ -0,0 +1,100 @@ +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { BaseComponent } from '@common/base/base.component'; +import { IsActive } from '@notification-service/core/enum/is-active.enum'; +import { nameof } from 'ts-simple-nameof'; +import { StatusFilter } from '@annotation-service/core/query/status.lookup'; +import { InternalStatus } from '@annotation-service/core/enum/internal-status.enum'; +import { AnnotationServiceEnumUtils } from '@annotation-service/core/formatting/enum-utils.service'; + +@Component({ + selector: 'app-status-listing-filters', + templateUrl: './status-listing-filters.component.html', + styleUrls: ['./status-listing-filters.component.scss'] +}) +export class StatusListingFiltersComponent extends BaseComponent implements OnInit, OnChanges { + + @Input() readonly filter: StatusFilter; + @Output() filterChange = new EventEmitter(); + internalStatusEnumValues = this.enumUtils.getEnumValues(InternalStatus) + + // * State + internalFilters: StatusListingFilters = this._getEmptyFilters(); + + protected appliedFilterCount: number = 0; + constructor( + public enumUtils: AnnotationServiceEnumUtils, + ) { super(); } + + ngOnInit() { + } + + ngOnChanges(changes: SimpleChanges): void { + const filterChange = changes[nameof(x => x.filter)]?.currentValue as StatusFilter; + if (filterChange) { + this.updateFilters() + } + } + + + onSearchTermChange(searchTerm: string): void { + this.applyFilters() + } + + + protected updateFilters(): void { + this.internalFilters = this._parseToInternalFilters(this.filter); + this.appliedFilterCount = this._computeAppliedFilters(this.internalFilters); + } + + protected applyFilters(): void { + const { isActive, internalStatuses, like } = this.internalFilters ?? {} + this.filterChange.emit({ + ...this.filter, + like: like, + isActive: isActive ? [IsActive.Active] : [IsActive.Inactive], + internalStatuses + }) + } + + + private _parseToInternalFilters(inputFilter: StatusFilter): StatusListingFilters { + if (!inputFilter) { + return this._getEmptyFilters(); + } + + let { isActive, internalStatuses, like } = inputFilter; + + return { + isActive: (isActive ?? [])?.includes(IsActive.Active) || !isActive?.length, + internalStatuses: internalStatuses, + like: like + } + + } + + private _getEmptyFilters(): StatusListingFilters { + return { + isActive: true, + internalStatuses: null, + like: null, + } + } + + private _computeAppliedFilters(filters: StatusListingFilters): number { + let count = 0; + if (filters?.isActive) { + count++ + } + return count; + } + + clearFilters() { + this.internalFilters = this._getEmptyFilters(); + } +} + +interface StatusListingFilters { + like: string; + isActive: boolean; + internalStatuses: InternalStatus[]; +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.html b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.html new file mode 100644 index 000000000..adbed7be5 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.html @@ -0,0 +1,93 @@ +
+
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + +
+
+
+ + + + +
+
+ + {{item?.name | nullifyValue}} +
+
+ + + {{'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.INTERNAL-STATUS' | translate}}: + + {{enumUtils.toNotificationTypeString(item.notificationType) | nullifyValue}} + + +
+
+ + + {{'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.CREATED-AT' | translate}}: + + {{item?.createdAt | dateTimeFormatter : 'short' | nullifyValue}} + + + + + + {{'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.UPDATED-AT' | translate}}: + + {{item?.updatedAt | dateTimeFormatter : 'short' | nullifyValue}} + + + +
+
+
+ + +
+
+ + + + + +
+
+
\ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.scss b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.scss new file mode 100644 index 000000000..248f88bac --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.scss @@ -0,0 +1,60 @@ +.status-listing { + // margin-top: 1.3rem; + // margin-left: 1rem; + // margin-right: 2rem; + + .mat-header-row{ + background: #f3f5f8; + } + .mat-card { + margin: 16px 0; + padding: 0px; + } + + .mat-row { + cursor: pointer; + min-height: 4.5em; + } + + mat-row:hover { + background-color: #eef5f6; + } + .mat-fab-bottom-right { + float: right; + z-index: 5; + } +} +.create-btn { + border-radius: 30px; + background-color: var(--secondary-color); + padding-left: 2em; + padding-right: 2em; + // color: #000; + + .button-text{ + display: inline-block; + } +} + +.dlt-btn { + color: rgba(0, 0, 0, 0.54); +} + +.status-chip{ + + border-radius: 20px; + padding-left: 1em; + padding-right: 1em; + padding-top: 0.2em; + font-size: .8em; +} + +.status-chip-finalized{ + color: #568b5a; + background: #9dd1a1 0% 0% no-repeat padding-box; +} + +.status-chip-draft{ + color: #00c4ff; + background: #d3f5ff 0% 0% no-repeat padding-box; +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.ts b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.ts new file mode 100644 index 000000000..50211dc9a --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.ts @@ -0,0 +1,180 @@ +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; +import { BaseListingComponent } from '@common/base/base-listing-component'; +import { PipeService } from '@common/formatting/pipe.service'; +import { DataTableDateTimeFormatPipe } from '@app/core/pipes/date-time-format.pipe'; +import { QueryResult } from '@common/model/query-result'; +import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; +import { ColumnDefinition, ColumnsChangedEvent, HybridListingComponent, PageLoadEvent } from '@common/modules/hybrid-listing/hybrid-listing.component'; +import { Guid } from '@common/types/guid'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { nameof } from 'ts-simple-nameof'; +import { IsActive } from '@notification-service/core/enum/is-active.enum'; +import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; +import { Status } from '@annotation-service/core/model/status.model'; +import { StatusLookup } from '@annotation-service/core/query/status.lookup'; +import { StatusService } from '@annotation-service/services/http/status.service'; +import { AnnotationServiceEnumUtils } from '@annotation-service/core/formatting/enum-utils.service'; +import { InternalStatusTypePipe } from '@annotation-service/core/formatting/pipes/internal-status-type.pipe'; +import { IsActiveTypePipe } from '@annotation-service/core/formatting/pipes/is-active-type.pipe'; + +@Component({ + templateUrl: './status-listing.component.html', + styleUrls: ['./status-listing.component.scss'] +}) +export class StatusListingComponent extends BaseListingComponent implements OnInit { + publish = false; + userSettingsKey = { key: 'StatusListingUserSettings' }; + propertiesAvailableForOrder: ColumnDefinition[]; + + @ViewChild('actions', { static: true }) actions?: TemplateRef; + @ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent; + + private readonly lookupFields: string[] = [ + nameof(x => x.id), + nameof(x => x.label), + nameof(x => x.internalStatus), + nameof(x => x.updatedAt), + nameof(x => x.createdAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ]; + + rowIdentity = x => x.id; + + constructor( + public routerUtils: RouterUtilsService, + protected router: Router, + protected route: ActivatedRoute, + protected uiNotificationService: UiNotificationService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected queryParamsService: QueryParamsService, + private statusService: StatusService, + public authService: AuthService, + private pipeService: PipeService, + public enumUtils: AnnotationServiceEnumUtils, + private language: TranslateService, + private dialog: MatDialog + ) { + super(router, route, uiNotificationService, httpErrorHandlingService, queryParamsService); + // Lookup setup + // Default lookup values are defined in the user settings class. + this.lookup = this.initializeLookup(); + } + + ngOnInit() { + super.ngOnInit(); + } + + protected initializeLookup(): StatusLookup { + const lookup = new StatusLookup(); + lookup.metadata = { countAll: true }; + lookup.page = { offset: 0, size: this.ITEMS_PER_PAGE }; + lookup.isActive = [IsActive.Active]; + lookup.order = { items: [this.toDescSortField(nameof(x => x.createdAt))] }; + this.updateOrderUiFields(lookup.order); + + lookup.project = { + fields: this.lookupFields + }; + + return lookup; + } + + protected setupColumns() { + this.gridColumns.push(...[{ + prop: nameof(x => x.label), + sortable: true, + languageName: 'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.LABEL', + }, + { + prop: nameof(x => x.internalStatus), + sortable: true, + languageName: 'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.INTERNAL-STATUS', + pipe: this.pipeService.getPipe(InternalStatusTypePipe) + }, + { + prop: nameof(x => x.createdAt), + sortable: true, + languageName: 'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.CREATED-AT', + pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') + }, + { + prop: nameof(x => x.updatedAt), + sortable: true, + languageName: 'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.UPDATED-AT', + pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') + }, + { + prop: nameof(x => x.isActive), + sortable: true, + languageName: 'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.IS-ACTIVE', + pipe: this.pipeService.getPipe(IsActiveTypePipe) + }, + { + alwaysShown: true, + cellTemplate: this.actions, + maxWidth: 120 + } + ]); + this.propertiesAvailableForOrder = this.gridColumns.filter(x => x.sortable); + } + + // + // Listing Component functions + // + onColumnsChanged(event: ColumnsChangedEvent) { + super.onColumnsChanged(event); + this.onColumnsChangedInternal(event.properties.map(x => x.toString())); + } + + private onColumnsChangedInternal(columns: string[]) { + // Here are defined the projection fields that always requested from the api. + const fields = new Set(this.lookupFields); + this.gridColumns.map(x => x.prop) + .filter(x => !columns?.includes(x as string)) + .forEach(item => { + fields.delete(item as string) + }); + this.lookup.project = { fields: [...fields] }; + this.onPageLoad({ offset: 0 } as PageLoadEvent); + } + + protected loadListing(): Observable> { + return this.statusService.query(this.lookup); + } + + public deleteType(id: Guid) { + if (id) { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + data: { + isDeleteConfirmation: true, + message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'), + confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'), + cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL') + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + if (result) { + this.statusService.delete(id).pipe(takeUntil(this._destroyed)) + .subscribe( + complete => this.onCallbackSuccess(), + error => this.onCallbackError(error) + ); + } + }); + } + } + + onCallbackSuccess(): void { + this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-DELETE'), SnackBarNotificationLevel.Success); + this.refresh(); + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/status.module.ts b/dmp-frontend/src/annotation-service/ui/admin/status/status.module.ts new file mode 100644 index 000000000..e82942316 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/status.module.ts @@ -0,0 +1,45 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { NgModule } from "@angular/core"; +import { AutoCompleteModule } from "@app/library/auto-complete/auto-complete.module"; +import { CommonFormattingModule } from '@common/formatting/common-formatting.module'; +import { CommonFormsModule } from '@common/forms/common-forms.module'; +import { ConfirmationDialogModule } from '@common/modules/confirmation-dialog/confirmation-dialog.module'; +import { HybridListingModule } from "@common/modules/hybrid-listing/hybrid-listing.module"; +import { TextFilterModule } from "@common/modules/text-filter/text-filter.module"; +import { UserSettingsModule } from "@common/modules/user-settings/user-settings.module"; +import { CommonUiModule } from '@common/ui/common-ui.module'; +import { NgxDropzoneModule } from "ngx-dropzone"; +import { StatusListingComponent } from './listing/status-listing.component'; +import { RichTextEditorModule } from '@app/library/rich-text-editor/rich-text-editor.module'; +import { MatIconModule } from '@angular/material/icon'; +import { EditorModule } from '@tinymce/tinymce-angular'; +import { StatusRoutingModule } from './status.routing'; +import { AnnotationServiceFormattingModule } from '@annotation-service/core/formatting/formatting.module'; +import { StatusListingFiltersComponent } from './listing/filters/status-listing-filters.component'; +import { StatusEditorComponent } from './editor/status-editor.component'; + +@NgModule({ + imports: [ + CommonUiModule, + CommonFormsModule, + ConfirmationDialogModule, + StatusRoutingModule, + NgxDropzoneModule, + DragDropModule, + AutoCompleteModule, + HybridListingModule, + TextFilterModule, + UserSettingsModule, + CommonFormattingModule, + RichTextEditorModule, + MatIconModule, + EditorModule, + AnnotationServiceFormattingModule + ], + declarations: [ + StatusEditorComponent, + StatusListingComponent, + StatusListingFiltersComponent, + ] +}) +export class StatusModule { } diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/status.routing.ts b/dmp-frontend/src/annotation-service/ui/admin/status/status.routing.ts new file mode 100644 index 000000000..655503825 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/status.routing.ts @@ -0,0 +1,58 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { AuthGuard } from '@app/core/auth-guard.service'; +import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; +import { PendingChangesGuard } from '@common/forms/pending-form-changes/pending-form-changes-guard.service'; +import { StatusListingComponent } from './listing/status-listing.component'; +import { StatusEditorResolver } from './editor/status-editor.resolver'; +import { StatusEditorComponent } from './editor/status-editor.component'; + +const routes: Routes = [ + { + path: '', + component: StatusListingComponent, + canActivate: [AuthGuard] + }, + { + path: 'new', + canActivate: [AuthGuard], + component: StatusEditorComponent, + canDeactivate: [PendingChangesGuard], + data: { + authContext: { + permissions: [AppPermission.EditStatus] + }, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.NEW-TENANT' + }), + getFromTitleService: true, + usePrefix: false + } + }, + { + path: ':id', + canActivate: [AuthGuard], + component: StatusEditorComponent, + canDeactivate: [PendingChangesGuard], + resolve: { + 'entity': StatusEditorResolver + }, + data: { + authContext: { + permissions: [AppPermission.EditStatus] + }, + getFromTitleService: true, + usePrefix: false + } + + }, + { path: '**', loadChildren: () => import('@common/modules/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule) }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [StatusEditorResolver] +}) +export class StatusRoutingModule { } diff --git a/dmp-frontend/src/app/app-routing.module.ts b/dmp-frontend/src/app/app-routing.module.ts index b5231d0ac..db44db47f 100644 --- a/dmp-frontend/src/app/app-routing.module.ts +++ b/dmp-frontend/src/app/app-routing.module.ts @@ -353,6 +353,19 @@ const appRoutes: Routes = [ title: 'GENERAL.TITLES.ENTITY-LOCKS' }, }, + { + path: 'annotation-statuses', + loadChildren: () => import('@annotation-service/ui/admin/status/status.module').then(m => m.StatusModule), + data: { + authContext: { + permissions: [AppPermission.ViewStatusPage] + }, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.ANNOTATION-STATUSES' + }), + title: 'GENERAL.TITLES.ANNOTATION-STATUSES' + }, + }, { path: 'maintenance-tasks', loadChildren: () => import('./ui/admin/maintenance-tasks/maintenance-tasks.module').then(m => m.MaintenanceTasksModule), diff --git a/dmp-frontend/src/app/core/common/enum/permission.enum.ts b/dmp-frontend/src/app/core/common/enum/permission.enum.ts index 5c585f061..820fa5fc4 100644 --- a/dmp-frontend/src/app/core/common/enum/permission.enum.ts +++ b/dmp-frontend/src/app/core/common/enum/permission.enum.ts @@ -195,6 +195,11 @@ export enum AppPermission { EditPrefillingSource= "EditPrefillingSource", DeletePrefillingSource = "DeletePrefillingSource", + //Status + BrowseStatus = "BrowseStatus", + EditStatus = "EditStatus", + DeleteStatus = "DeleteStatus", + // UI Pages ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage", @@ -219,5 +224,6 @@ export enum AppPermission { ViewHomePage = "ViewHomePage", ViewMineInAppNotificationPage = "ViewMineInAppNotificationPage", ViewTenantConfigurationPage = "ViewTenantConfigurationPage", + ViewStatusPage = "ViewStatusPage", } diff --git a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts index 367d37d19..e4ef8af3b 100644 --- a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts +++ b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts @@ -119,6 +119,7 @@ export class SidebarComponent implements OnInit { if (this.authentication.hasPermission(AppPermission.ViewSupportiveMaterialPage)) this.adminItems.routes.push({ path: '/supportive-material', title: 'SIDE-BAR.SUPPORTIVE-MATERIAL', icon: 'help_center' }); if (this.authentication.hasPermission(AppPermission.ViewNotificationTemplatePage)) this.adminItems.routes.push({ path: '/notification-templates', title: 'SIDE-BAR.NOTIFICATION-TEMPLATES', icon: 'grid_guides' }); if (this.authentication.hasPermission(AppPermission.ViewNotificationPage)) this.adminItems.routes.push({ path: '/notifications', title: 'SIDE-BAR.NOTIFICATIONS', icon: 'notifications' }); + if (this.authentication.hasPermission(AppPermission.ViewStatusPage)) this.adminItems.routes.push({ path: '/annotation-statuses', title: 'SIDE-BAR.ANNOTATION-STATUSES', icon: 'notifications' }); if (this.authentication.hasPermission(AppPermission.ViewMaintenancePage)) this.adminItems.routes.push({ path: '/maintenance-tasks', title: 'SIDE-BAR.MAINTENANCE', icon: 'build' }); this.groupMenuItems.push(this.adminItems); diff --git a/dmp-frontend/src/assets/i18n/baq.json b/dmp-frontend/src/assets/i18n/baq.json index 5ff82ebc3..7a216f1d7 100644 --- a/dmp-frontend/src/assets/i18n/baq.json +++ b/dmp-frontend/src/assets/i18n/baq.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-frontend/src/assets/i18n/de.json b/dmp-frontend/src/assets/i18n/de.json index 4019935b3..82ec675fc 100644 --- a/dmp-frontend/src/assets/i18n/de.json +++ b/dmp-frontend/src/assets/i18n/de.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-frontend/src/assets/i18n/en.json b/dmp-frontend/src/assets/i18n/en.json index d6e5978c1..dc04b8196 100644 --- a/dmp-frontend/src/assets/i18n/en.json +++ b/dmp-frontend/src/assets/i18n/en.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-frontend/src/assets/i18n/es.json b/dmp-frontend/src/assets/i18n/es.json index 656d2c37f..0ea669ddc 100644 --- a/dmp-frontend/src/assets/i18n/es.json +++ b/dmp-frontend/src/assets/i18n/es.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-frontend/src/assets/i18n/gr.json b/dmp-frontend/src/assets/i18n/gr.json index 20ccef6df..55d129236 100644 --- a/dmp-frontend/src/assets/i18n/gr.json +++ b/dmp-frontend/src/assets/i18n/gr.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-frontend/src/assets/i18n/hr.json b/dmp-frontend/src/assets/i18n/hr.json index 5bbeebd43..d052320a5 100644 --- a/dmp-frontend/src/assets/i18n/hr.json +++ b/dmp-frontend/src/assets/i18n/hr.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-frontend/src/assets/i18n/pl.json b/dmp-frontend/src/assets/i18n/pl.json index 28ef34c03..0376cb063 100644 --- a/dmp-frontend/src/assets/i18n/pl.json +++ b/dmp-frontend/src/assets/i18n/pl.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-frontend/src/assets/i18n/pt.json b/dmp-frontend/src/assets/i18n/pt.json index 863f742a5..696432124 100644 --- a/dmp-frontend/src/assets/i18n/pt.json +++ b/dmp-frontend/src/assets/i18n/pt.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-frontend/src/assets/i18n/sk.json b/dmp-frontend/src/assets/i18n/sk.json index a01575da6..1f0df25a5 100644 --- a/dmp-frontend/src/assets/i18n/sk.json +++ b/dmp-frontend/src/assets/i18n/sk.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-frontend/src/assets/i18n/sr.json b/dmp-frontend/src/assets/i18n/sr.json index 968601ffc..3d1332c83 100644 --- a/dmp-frontend/src/assets/i18n/sr.json +++ b/dmp-frontend/src/assets/i18n/sr.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-frontend/src/assets/i18n/tr.json b/dmp-frontend/src/assets/i18n/tr.json index 8237ef6c7..147b9b309 100644 --- a/dmp-frontend/src/assets/i18n/tr.json +++ b/dmp-frontend/src/assets/i18n/tr.json @@ -330,6 +330,10 @@ "PROTECTION": { "ENTITY-ACCESSORS": "Visible", "PRIVATE": "Hidden" + }, + "ANNOTATION-STATUS": { + "TITLE": "Status", + "SUCCESS": "Comment Status successfully saved" } }, "MAINTENANCE-TASKS": { diff --git a/dmp-migration-tool/web/src/main/java/eu/old/eudat/migration/DmpMigrationService.java b/dmp-migration-tool/web/src/main/java/eu/old/eudat/migration/DmpMigrationService.java index 4c2e436b3..ad35b0b4d 100644 --- a/dmp-migration-tool/web/src/main/java/eu/old/eudat/migration/DmpMigrationService.java +++ b/dmp-migration-tool/web/src/main/java/eu/old/eudat/migration/DmpMigrationService.java @@ -134,6 +134,9 @@ public class DmpMigrationService { } } } + if (data.getCreatorId() == null && item.getCreator() != null && item.getCreator().getId() != null) { + data.setCreatorId(item.getCreator().getId()); + } data.setGroupId(model.getGroupId()); data.setVersion((short) model.getVersion()); if (currentVersionDmp != null && currentVersionDmp.getId().equals(item.getId())){ diff --git a/dmp-migration-tool/web/src/main/java/eu/old/eudat/migration/GrantMigrationService.java b/dmp-migration-tool/web/src/main/java/eu/old/eudat/migration/GrantMigrationService.java index b98497c3e..af58599e5 100644 --- a/dmp-migration-tool/web/src/main/java/eu/old/eudat/migration/GrantMigrationService.java +++ b/dmp-migration-tool/web/src/main/java/eu/old/eudat/migration/GrantMigrationService.java @@ -65,7 +65,7 @@ GrantMigrationService { ReferenceEntity data = new ReferenceEntity(); data.setId(item.getId()); - data.setLabel(item.getLabel()); + data.setLabel(item.getLabel() +" (" + referenceParts[1] + ")"); data.setDescription(item.getDescription()); data.setAbbreviation(item.getAbbreviation()); data.setIsActive(IsActive.Active);