diff --git a/backend/core/src/main/java/org/opencdmp/commons/notification/NotificationProperties.java b/backend/core/src/main/java/org/opencdmp/commons/notification/NotificationProperties.java index 28f7d489b..ba05e75af 100644 --- a/backend/core/src/main/java/org/opencdmp/commons/notification/NotificationProperties.java +++ b/backend/core/src/main/java/org/opencdmp/commons/notification/NotificationProperties.java @@ -15,6 +15,7 @@ public class NotificationProperties { private UUID descriptionModifiedType; private UUID descriptionFinalisedType; private UUID descriptionAnnotationCreated; + private UUID descriptionAnnotationStatusChanged; private UUID mergeAccountConfirmationType; private UUID removeCredentialConfirmationType; private UUID dmpDepositType; @@ -154,6 +155,14 @@ public class NotificationProperties { this.descriptionAnnotationCreated = descriptionAnnotationCreated; } + public UUID getDescriptionAnnotationStatusChanged() { + return descriptionAnnotationStatusChanged; + } + + public void setDescriptionAnnotationStatusChanged(UUID descriptionAnnotationStatusChanged) { + this.descriptionAnnotationStatusChanged = descriptionAnnotationStatusChanged; + } + public UUID getTenantSpecificInvitationExternalUserType() { return tenantSpecificInvitationExternalUserType; } diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/AppRabbitConfigurer.java b/backend/core/src/main/java/org/opencdmp/integrationevent/AppRabbitConfigurer.java index 7c714e905..89ebb7afe 100644 --- a/backend/core/src/main/java/org/opencdmp/integrationevent/AppRabbitConfigurer.java +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/AppRabbitConfigurer.java @@ -33,6 +33,7 @@ public class AppRabbitConfigurer extends RabbitConfigurer { public InboxBindings inboxBindingsCreator() { List bindingItems = new ArrayList<>(); if (this.inboxProperties.getAnnotationCreatedTopic() != null) bindingItems.addAll(this.inboxProperties.getAnnotationCreatedTopic()); + if (this.inboxProperties.getAnnotationStatusChangedTopic() != null) bindingItems.addAll(this.inboxProperties.getAnnotationStatusChangedTopic()); return new InboxBindings(bindingItems); } diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/InboxProperties.java b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/InboxProperties.java index db18d9bb5..99b95e5ae 100644 --- a/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/InboxProperties.java +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/InboxProperties.java @@ -16,10 +16,13 @@ public class InboxProperties { private final List annotationCreatedTopic; + private final List annotationStatusChangedTopic; + public InboxProperties( - String exchange, List annotationCreatedTopic) { + String exchange, List annotationCreatedTopic, List annotationStatusChangedTopic) { this.exchange = exchange; this.annotationCreatedTopic = annotationCreatedTopic; + this.annotationStatusChangedTopic = annotationStatusChangedTopic; } public String getExchange() { @@ -29,4 +32,8 @@ public class InboxProperties { public List getAnnotationCreatedTopic() { return annotationCreatedTopic; } + + public List getAnnotationStatusChangedTopic() { + return annotationStatusChangedTopic; + } } 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 85d45608b..92e360632 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 @@ -16,6 +16,7 @@ import org.opencdmp.commons.fake.FakeRequestScope; import org.opencdmp.data.QueueInboxEntity; import org.opencdmp.data.TenantEntityManager; import org.opencdmp.integrationevent.inbox.annotationentitycreated.AnnotationEntityCreatedIntegrationEventHandler; +import org.opencdmp.integrationevent.inbox.annotationstatusentitychanged.AnnotationStatusEntityChangedIntegrationEventHandler; import org.opencdmp.query.QueueInboxQuery; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; @@ -393,6 +394,8 @@ public class InboxRepositoryImpl implements InboxRepository { logger.debug("Processing message with routing key '{}'", queueInboxMessage.getRoute()); if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getAnnotationCreatedTopic())) handler = this.applicationContext.getBean(AnnotationEntityCreatedIntegrationEventHandler.class); + else if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getAnnotationStatusChangedTopic())) + handler = this.applicationContext.getBean(AnnotationStatusEntityChangedIntegrationEventHandler.class); else { logger.error("No handler found for message routing key '{}'. Discarding.", queueInboxMessage.getRoute()); handler = null; diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationstatusentitychanged/AnnotationStatusEntityChangedIntegrationEvent.java b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationstatusentitychanged/AnnotationStatusEntityChangedIntegrationEvent.java new file mode 100644 index 000000000..7e5e4bf9b --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationstatusentitychanged/AnnotationStatusEntityChangedIntegrationEvent.java @@ -0,0 +1,160 @@ +package org.opencdmp.integrationevent.inbox.annotationstatusentitychanged; + +import gr.cite.tools.validation.ValidatorFactory; +import gr.cite.tools.validation.specification.Specification; +import org.opencdmp.commons.validation.BaseValidator; +import org.opencdmp.convention.ConventionService; +import org.opencdmp.errorcode.ErrorThesaurusProperties; +import org.opencdmp.integrationevent.TrackedEvent; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class AnnotationStatusEntityChangedIntegrationEvent extends TrackedEvent { + + private UUID id; + public static final String _id = "id"; + + private UUID annotationId; + public static final String _annotationId = "annotationId"; + + private UUID statusId; + public static final String _statusId = "statusId"; + + private UUID subjectId; + public static final String _subjectId = "subjectId"; + + private UUID entityId; + public static final String _entityId = "entityId"; + + private String entityType; + public static final String _entityType = "entityType"; + + private String anchor; + public static final String _anchor = "anchor"; + + private String statusLabel; + public static final String _statusLabel = "statusLabel"; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getAnnotationId() { + return annotationId; + } + + public void setAnnotationId(UUID annotationId) { + this.annotationId = annotationId; + } + + public UUID getStatusId() { + return statusId; + } + + public void setStatusId(UUID statusId) { + this.statusId = statusId; + } + + public UUID getSubjectId() { + return subjectId; + } + + public void setSubjectId(UUID subjectId) { + this.subjectId = subjectId; + } + + public UUID getEntityId() { + return entityId; + } + + public void setEntityId(UUID entityId) { + this.entityId = entityId; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public String getAnchor() { + return anchor; + } + + public void setAnchor(String anchor) { + this.anchor = anchor; + } + + public String getStatusLabel() { + return statusLabel; + } + + public void setStatusLabel(String statusLabel) { + this.statusLabel = statusLabel; + } + + @Component(AnnotationStatusEntityChangedIntegrationEventValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class AnnotationStatusEntityChangedIntegrationEventValidator extends BaseValidator { + + public static final String ValidatorName = "AnnotationStatusEntityChangedIntegrationEventValidator"; + + private final MessageSource messageSource; + + private final ValidatorFactory validatorFactory; + + protected AnnotationStatusEntityChangedIntegrationEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource, ValidatorFactory validatorFactory) { + super(conventionService, errors); + this.messageSource = messageSource; + this.validatorFactory = validatorFactory; + } + + @Override + protected Class modelClass() { + return AnnotationStatusEntityChangedIntegrationEvent.class; + } + + @Override + protected List specifications(AnnotationStatusEntityChangedIntegrationEvent item) { + return Arrays.asList( + this.spec() + .must(() -> this.isValidGuid(item.getId())) + .failOn(AnnotationStatusEntityChangedIntegrationEvent._id).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationStatusEntityChangedIntegrationEvent._id}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> this.isValidGuid(item.getAnnotationId())) + .failOn(AnnotationStatusEntityChangedIntegrationEvent._annotationId).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationStatusEntityChangedIntegrationEvent._annotationId}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> this.isValidGuid(item.getStatusId())) + .failOn(AnnotationStatusEntityChangedIntegrationEvent._statusId).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationStatusEntityChangedIntegrationEvent._statusId}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> this.isValidGuid(item.getSubjectId())) + .failOn(AnnotationStatusEntityChangedIntegrationEvent._subjectId).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationStatusEntityChangedIntegrationEvent._subjectId}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> this.isValidGuid(item.getEntityId())) + .failOn(AnnotationStatusEntityChangedIntegrationEvent._entityId).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationStatusEntityChangedIntegrationEvent._entityId}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getEntityType())) + .failOn(AnnotationStatusEntityChangedIntegrationEvent._entityType).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationStatusEntityChangedIntegrationEvent._entityType}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getAnchor())) + .failOn(AnnotationStatusEntityChangedIntegrationEvent._anchor).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationStatusEntityChangedIntegrationEvent._anchor}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getStatusLabel())) + .failOn(AnnotationStatusEntityChangedIntegrationEvent._statusLabel).failWith(messageSource.getMessage("Validation_Required", new Object[]{AnnotationStatusEntityChangedIntegrationEvent._statusLabel}, LocaleContextHolder.getLocale())) + ); + } + } +} diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationstatusentitychanged/AnnotationStatusEntityChangedIntegrationEventHandler.java b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationstatusentitychanged/AnnotationStatusEntityChangedIntegrationEventHandler.java new file mode 100644 index 000000000..37c137726 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationstatusentitychanged/AnnotationStatusEntityChangedIntegrationEventHandler.java @@ -0,0 +1,6 @@ +package org.opencdmp.integrationevent.inbox.annotationstatusentitychanged; + +import org.opencdmp.integrationevent.inbox.IntegrationEventHandler; + +public interface AnnotationStatusEntityChangedIntegrationEventHandler extends IntegrationEventHandler { +} diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationstatusentitychanged/AnnotationStatusEntityChangedIntegrationEventHandlerImpl.java b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationstatusentitychanged/AnnotationStatusEntityChangedIntegrationEventHandlerImpl.java new file mode 100644 index 000000000..7969f9afd --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/inbox/annotationstatusentitychanged/AnnotationStatusEntityChangedIntegrationEventHandlerImpl.java @@ -0,0 +1,181 @@ +package org.opencdmp.integrationevent.inbox.annotationstatusentitychanged; + +import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; +import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties; +import gr.cite.tools.auditing.AuditService; +import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyApplicationException; +import gr.cite.tools.exception.MyNotFoundException; +import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.fieldset.BaseFieldSet; +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.validation.ValidatorFactory; +import org.opencdmp.audit.AuditableAction; +import org.opencdmp.commons.JsonHandlingService; +import org.opencdmp.commons.enums.IsActive; +import org.opencdmp.commons.notification.NotificationProperties; +import org.opencdmp.commons.scope.tenant.TenantScope; +import org.opencdmp.commons.scope.user.UserScope; +import org.opencdmp.commons.types.notification.DataType; +import org.opencdmp.commons.types.notification.FieldInfo; +import org.opencdmp.commons.types.notification.NotificationFieldData; +import org.opencdmp.data.*; +import org.opencdmp.errorcode.ErrorThesaurusProperties; +import org.opencdmp.integrationevent.inbox.EventProcessingStatus; +import org.opencdmp.integrationevent.inbox.InboxPrincipal; +import org.opencdmp.integrationevent.inbox.IntegrationEventProperties; +import org.opencdmp.integrationevent.outbox.notification.NotifyIntegrationEvent; +import org.opencdmp.integrationevent.outbox.notification.NotifyIntegrationEventHandler; +import org.opencdmp.model.Tenant; +import org.opencdmp.model.description.Description; +import org.opencdmp.query.DescriptionQuery; +import org.opencdmp.query.DmpUserQuery; +import org.opencdmp.query.TenantQuery; +import org.opencdmp.query.UserQuery; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import javax.management.InvalidApplicationException; +import java.util.*; + + +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class AnnotationStatusEntityChangedIntegrationEventHandlerImpl implements AnnotationStatusEntityChangedIntegrationEventHandler { + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(AnnotationStatusEntityChangedIntegrationEventHandlerImpl.class)); + + private final QueryFactory queryFactory; + private final JsonHandlingService jsonHandlingService; + private final NotificationProperties notificationProperties; + private final TenantScope tenantScope; + private final NotifyIntegrationEventHandler notifyIntegrationEventHandler; + private final CurrentPrincipalResolver currentPrincipalResolver; + private final ClaimExtractorProperties claimExtractorProperties; + private final MessageSource messageSource; + private final UserScope userScope; + private final ErrorThesaurusProperties errors; + private final TenantEntityManager tenantEntityManager; + private final ValidatorFactory validatorFactory; + private final AuditService auditService; + + public AnnotationStatusEntityChangedIntegrationEventHandlerImpl(QueryFactory queryFactory, JsonHandlingService jsonHandlingService, NotificationProperties notificationProperties, TenantScope tenantScope, NotifyIntegrationEventHandler notifyIntegrationEventHandler, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, MessageSource messageSource, UserScope userScope, ErrorThesaurusProperties errors, TenantEntityManager tenantEntityManager, ValidatorFactory validatorFactory, AuditService auditService) { + this.queryFactory = queryFactory; + this.jsonHandlingService = jsonHandlingService; + this.notificationProperties = notificationProperties; + this.tenantScope = tenantScope; + this.notifyIntegrationEventHandler = notifyIntegrationEventHandler; + this.currentPrincipalResolver = currentPrincipalResolver; + this.claimExtractorProperties = claimExtractorProperties; + this.messageSource = messageSource; + this.userScope = userScope; + this.errors = errors; + this.tenantEntityManager = tenantEntityManager; + this.validatorFactory = validatorFactory; + this.auditService = auditService; + } + + @Override + public EventProcessingStatus handle(IntegrationEventProperties properties, String message) throws InvalidApplicationException { + AnnotationStatusEntityChangedIntegrationEvent event = this.jsonHandlingService.fromJsonSafe(AnnotationStatusEntityChangedIntegrationEvent.class, message); + if (event == null) + return EventProcessingStatus.Error; + + logger.debug("Handling {}", AnnotationStatusEntityChangedIntegrationEvent.class.getSimpleName()); + this.validatorFactory.validator(AnnotationStatusEntityChangedIntegrationEvent.AnnotationStatusEntityChangedIntegrationEventValidator.class).validateForce(event); + + EventProcessingStatus status = EventProcessingStatus.Success; + try { + + if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) { + 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(this.tenantEntityManager, properties.getTenantId(), tenant.getCode()); + } else if (this.tenantScope.isMultitenant()) { + this.tenantScope.setTempTenant(this.tenantEntityManager, null, this.tenantScope.getDefaultTenantCode()); + } + + this.currentPrincipalResolver.push(InboxPrincipal.build(properties, this.claimExtractorProperties)); + this.sendNotification(event); + this.auditService.track(AuditableAction.Annotation_Created_Notify, Map.ofEntries( + new AbstractMap.SimpleEntry("model", event) + )); + + } catch (Exception ex) { + status = EventProcessingStatus.Error; + logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); + } finally { + this.currentPrincipalResolver.pop(); + try { + this.tenantScope.removeTempTenant(this.tenantEntityManager); + this.tenantEntityManager.reloadTenantFilters(); + } catch (InvalidApplicationException e) { + } + } + + return status; + } + + private void sendNotification(AnnotationStatusEntityChangedIntegrationEvent event) throws InvalidApplicationException { + + DescriptionEntity descriptionEntity = this.queryFactory.query(DescriptionQuery.class).disableTracking().ids(event.getEntityId()).first(); + + if (descriptionEntity == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{event.getEntityId(), Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + List existingUsers = this.queryFactory.query(DmpUserQuery.class).disableTracking() + .dmpIds(descriptionEntity.getDmpId()) + .isActives(IsActive.Active) + .collect(); + + if (existingUsers == null || existingUsers.size() <= 1){ + return; + } + + UserEntity sender = this.queryFactory.query(UserQuery.class).disableTracking().ids(event.getSubjectId()).first(); + + if (sender == null) throw new MyApplicationException("Sender user not found"); + + List existingUSerIDs = existingUsers.stream() + .map(DmpUserEntity::getUserId) + .filter(dmpUserId -> !dmpUserId.equals(event.getSubjectId())) + .distinct().toList(); + for (UUID dmpUserId : existingUSerIDs) { + UserEntity user = this.queryFactory.query(UserQuery.class).disableTracking().ids(dmpUserId).first(); + if (user == null || user.getIsActive().equals(IsActive.Inactive)) + throw new MyValidationException(this.errors.getDmpInactiveUser().getCode(), this.errors.getDmpInactiveUser().getMessage()); + this.createAnnotationStatusChangedNotificationEvent(user, descriptionEntity, sender.getName(), event.getAnchor(), event.getStatusLabel()); + } + + } + + private void createAnnotationStatusChangedNotificationEvent(UserEntity user, DescriptionEntity description, String reasonName, String anchor, String statusLabel) throws InvalidApplicationException, InvalidApplicationException { + NotifyIntegrationEvent notifyIntegrationEvent = new NotifyIntegrationEvent(); + notifyIntegrationEvent.setUserId(user.getId()); + + notifyIntegrationEvent.setNotificationType(this.notificationProperties.getDescriptionAnnotationStatusChanged()); + + NotificationFieldData data = new NotificationFieldData(); + List fieldInfoList = new ArrayList<>(); + fieldInfoList.add(new FieldInfo("{recipient}", DataType.String, user.getName())); + fieldInfoList.add(new FieldInfo("{reasonName}", DataType.String, reasonName)); + fieldInfoList.add(new FieldInfo("{name}", DataType.String, description.getLabel())); + fieldInfoList.add(new FieldInfo("{id}", DataType.String, description.getId().toString())); + fieldInfoList.add(new FieldInfo("{status}", DataType.String, statusLabel)); + String anchorUrl = "f/"+anchor+"/annotation"; + fieldInfoList.add(new FieldInfo("{anchor}", DataType.String, anchorUrl)); + if(this.tenantScope.getTenantCode() != null && !this.tenantScope.getTenantCode().equals(this.tenantScope.getDefaultTenantCode())){ + fieldInfoList.add(new FieldInfo("{tenant-url-path}", DataType.String, String.format("/t/%s", this.tenantScope.getTenantCode()))); + } + data.setFields(fieldInfoList); + notifyIntegrationEvent.setData(this.jsonHandlingService.toJsonSafe(data)); + + this.notifyIntegrationEventHandler.handle(notifyIntegrationEvent); + } +} diff --git a/backend/web/src/main/resources/config/notification-devel.yml b/backend/web/src/main/resources/config/notification-devel.yml index 2ef74e6f8..1c8bab032 100644 --- a/backend/web/src/main/resources/config/notification-devel.yml +++ b/backend/web/src/main/resources/config/notification-devel.yml @@ -7,6 +7,7 @@ notification: descriptionModifiedType: 4FDBFA80-7A71-4A69-B854-67CBB70648F1 descriptionFinalisedType: 33790bad-94d4-488a-8ee2-7f6295ca18ea descriptionAnnotationCreated: db1e99d2-a240-4e75-9bb2-ef25b234c1f0 + descriptionAnnotationStatusChanged: 3189e3a6-91e6-40c6-8ff8-275a68445aec mergeAccountConfirmationType: BFE68845-CB05-4C5A-A03D-29161A7C9660 removeCredentialConfirmationType: C9BC3F16-057E-4BBA-8A5F-36BD835E5604 dmpDepositType: 55736F7A-83AB-4190-AF43-9D031A6F9612 diff --git a/backend/web/src/main/resources/config/queue.yml b/backend/web/src/main/resources/config/queue.yml index f8eb40e76..bc1135fab 100644 --- a/backend/web/src/main/resources/config/queue.yml +++ b/backend/web/src/main/resources/config/queue.yml @@ -59,6 +59,7 @@ queue: options: exchange: null annotation-created-topic: annotation.created + annotation-status-changed-topic: annotationStatus.changed rabbitmq: enable: true interval-seconds: 3 diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/radio-box/description-template-editor-radio-box-field.component.html b/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/radio-box/description-template-editor-radio-box-field.component.html index 3edda54bb..7a83a3f66 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/radio-box/description-template-editor-radio-box-field.component.html +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/radio-box/description-template-editor-radio-box-field.component.html @@ -25,7 +25,7 @@ {{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-RADIO-BOX-VALUE' | translate}} - + {{option.get('value').getError('backendError').message}} {{'GENERAL.VALIDATION.REQUIRED' | translate}} diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/radio-box/description-template-editor-radio-box-field.component.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/radio-box/description-template-editor-radio-box-field.component.ts index 1af01042f..0f2e873cd 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/radio-box/description-template-editor-radio-box-field.component.ts +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/radio-box/description-template-editor-radio-box-field.component.ts @@ -1,20 +1,57 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, SimpleChanges } from '@angular/core'; import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { DescriptionTemplateRadioBoxDataEditorModel, DescriptionTemplateRadioBoxOptionEditorModel } from '../../../description-template-editor.model'; import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model'; +import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs'; +import { BaseComponent } from '@common/base/base.component'; @Component({ selector: 'app-description-template-editor-radio-box-field-component', styleUrls: ['./description-template-editor-radio-box-field.component.scss'], templateUrl: './description-template-editor-radio-box-field.component.html' }) -export class DescriptionTemplateEditorRadioBoxFieldComponent implements OnInit { +export class DescriptionTemplateEditorRadioBoxFieldComponent extends BaseComponent implements OnInit { @Input() form: UntypedFormGroup; @Input() validationErrorModel: ValidationErrorModel; @Input() validationRootPath: string; ngOnInit() { + this.applyNewVisibilityValueListener(); + } + + ngOnChanges(changes: SimpleChanges) { + this.applyNewVisibilityValueListener(); + } + + applyNewVisibilityValueListener() { + (this.form.get('data').get('options') as UntypedFormArray).controls?.forEach( + (option, index) => { + let oldValue = option.get('value').value || null; + option.get('value').valueChanges + .pipe(takeUntil(this._destroyed), debounceTime(200), distinctUntilChanged()) + .subscribe(newValue => { + if (newValue) { + (this.form.get('visibilityRules') as UntypedFormArray).controls?.forEach( + (visibilityRule, index) => { + if (visibilityRule.get('textValue').value == oldValue){ + setTimeout( () => { + if (newValue == null){ + visibilityRule.get('textValue').setValue(null); + } else { + visibilityRule.get('textValue').setValue(newValue); + } + visibilityRule.updateValueAndValidity(); + oldValue = newValue; + }, 100); + } + } + + ) + } + }); + } + ) } addNewRow() { @@ -23,10 +60,25 @@ export class DescriptionTemplateEditorRadioBoxFieldComponent implements OnInit { if (!selectOptionsArray) { (this.form.get('data')).addControl('options', new UntypedFormBuilder().array([])); } selectOptionsArray.push(radioListOptions.buildForm({rootPath: this.validationRootPath + 'data.options[' + selectOptionsArray.length + '].'})); + this.applyNewVisibilityValueListener(); } deleteRow(intex: number) { if (this.form.get('data').get('options')) { + const control = (this.form.get('data').get('options') as UntypedFormArray).at(intex) as UntypedFormGroup; + if (control && control.get('value').value != null){ + (this.form.get('visibilityRules') as UntypedFormArray).controls?.forEach( + (visibilityRule, index) => { + if (visibilityRule.get('textValue').value == control.get('value').value){ + setTimeout( () => { + visibilityRule.get('textValue').setValue(null); + visibilityRule.updateValueAndValidity(); + }, 100); + } + } + ); + } + (this.form.get('data').get('options')).removeAt(intex); DescriptionTemplateRadioBoxDataEditorModel.reapplyRadioBoxValidators({ diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/select/description-template-editor-select-field.component.html b/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/select/description-template-editor-select-field.component.html index 1c297f479..d54eea9b9 100644 --- a/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/select/description-template-editor-select-field.component.html +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/components/field-type/select/description-template-editor-select-field.component.html @@ -28,7 +28,7 @@ {{'DESCRIPTION-TEMPLATE-EDITOR.STEPS.FORM.FIELD.FIELDS.FIELD-SELECT-VALUE' | translate}} - + {{form.get('data').get('options').get(''+i).get('value').getError('backendError').message}} +
+ + {{'ANNOTATION-DIALOG.ANNOTATION-STATUS.TITLE' | translate}} + + {{status.label}} + + {{getAnnotationStatusFormControl(annotationStatusFormGroup.get('annotationsStatusArray'), getParentAnnotation(thread).id).get('statusId').getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + + +
diff --git a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts index 80fb51011..967e691d7 100644 --- a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts +++ b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts @@ -1,9 +1,12 @@ import { Component, Inject } from '@angular/core'; -import { FormBuilder, FormControl, UntypedFormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormControl, UntypedFormArray, UntypedFormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { Router } from '@angular/router'; +import { AnnotationStatus, AnnotationStatusPersist } from '@annotation-service/core/model/annotation-status.model'; import { Annotation, AnnotationPersist } from '@annotation-service/core/model/annotation.model'; +import { Status } from '@annotation-service/core/model/status.model'; import { AnnotationLookup } from '@annotation-service/core/query/annotation.lookup'; +import { StatusLookup } from '@annotation-service/core/query/status.lookup'; import { AnnotationService } from '@annotation-service/services/http/annotation.service'; import { AnnotationProtectionType } from '@app/core/common/enum/annotation-protection-type.enum'; import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; @@ -14,8 +17,12 @@ import { BaseComponent } from '@common/base/base.component'; import { FormService } from '@common/forms/form-service'; import { Guid } from '@common/types/guid'; import { TranslateService } from '@ngx-translate/core'; +import { IsActive } from '@notification-service/core/enum/is-active.enum'; import { takeUntil } from 'rxjs/operators'; import { nameof } from 'ts-simple-nameof'; +import { AnnotationStatusArrayEditorModel } from './annotation-status-editor.model'; +import { StatusService } from '@annotation-service/services/http/status.service'; +import { InternalStatus } from '@annotation-service/core/enum/internal-status.enum'; @Component({ selector: 'app-annotation-dialog', @@ -43,6 +50,9 @@ export class AnnotationDialogComponent extends BaseComponent { threadReplyTextsFG: Array; threadFormGroup: UntypedFormGroup; private formBuilder: FormBuilder = new FormBuilder(); + public annotationStatusFormGroup: UntypedFormGroup; + public listingStatuses: Status[] = []; + public dmpUsersMentionNames: string[] = []; constructor( @@ -55,12 +65,14 @@ export class AnnotationDialogComponent extends BaseComponent { private annotationService: AnnotationService, private formService: FormService, public enumUtils: EnumUtils, + private statusService: StatusService, protected routerUtils: RouterUtilsService, ) { super(); this.entityId = data.entityId; this.anchor = data.anchor; this.entityType = data.entityType; + this.dmpUsersMentionNames = data.dmpUsers.map(x => x.user.name); dialogRef.backdropClick().pipe(takeUntil(this._destroyed)).subscribe(() => dialogRef.close(this.changesMade)); } @@ -72,6 +84,7 @@ export class AnnotationDialogComponent extends BaseComponent { if (this.entityId != null) { this.loadThreads(); } + this.getStatuses(); } createThread() { @@ -139,6 +152,13 @@ export class AnnotationDialogComponent extends BaseComponent { nameof(x => x.author.name), nameof(x => x.payload), nameof(x => x.protectionType), + [nameof(x => x.annotationStatuses), nameof(x => x.id)].join('.'), + [nameof(x => x.annotationStatuses), nameof(x => x.annotation), nameof(x => x.id)].join('.'), + [nameof(x => x.annotationStatuses), nameof(x => x.status), nameof(x => x.id)].join('.'), + [nameof(x => x.annotationStatuses), nameof(x => x.createdAt)].join('.'), + [nameof(x => x.annotationStatuses), nameof(x => x.updatedAt)].join('.'), + [nameof(x => x.annotationStatuses), nameof(x => x.isActive)].join('.'), + [nameof(x => x.annotationStatuses), nameof(x => x.hash)].join('.'), ] }; @@ -160,6 +180,8 @@ export class AnnotationDialogComponent extends BaseComponent { this.parentAnnotationsPerThread[element.threadId.toString()] = data.items.filter(x => x.threadId === element.threadId && x.id === element.id)[0]; this.threads.add(element.threadId); }); + // create annotation status array to handle each annotation + this.annotationStatusFormGroup = new AnnotationStatusArrayEditorModel().fromModel(this.comments, this.listingStatuses).buildForm(); }, error => this.onCallbackError(error), ); @@ -236,4 +258,76 @@ export class AnnotationDialogComponent extends BaseComponent { SnackBarNotificationLevel.Success ); } + + // status + + private getStatuses(){ + const lookup: StatusLookup = new StatusLookup(); + lookup.metadata = { countAll: true }; + lookup.page = { size: 100, offset: 0 }; + lookup.isActive = [IsActive.Active]; + lookup.project = { + fields: [ + nameof(x => x.id), + nameof(x => x.label), + nameof(x => x.internalStatus) + ] + }; + + lookup.order = { items: [nameof(x => x.label)] }; + + return this.statusService.query(lookup).pipe(takeUntil(this._destroyed)) + .subscribe(result => { + if (result && result.items?.length > 0) this.listingStatuses.push(...result.items); + }); + } + + private getAnnotationStatusControl(formArray: UntypedFormArray, annotationId: Guid){ + const index = formArray.controls.findIndex(x => x.get('annotationId')?.value == annotationId); + if (index < 0) return null; + return formArray.at(index); + } + + getAnnotationStatusFormControl(formArray: UntypedFormArray, annotationId: Guid) { + const control = this.getAnnotationStatusControl(formArray, annotationId); + if (control == null) return; + return control; + } + + persistAnnotationStatus(formArray: UntypedFormArray, annotationId: Guid){ + const control = this.getAnnotationStatusControl(formArray, annotationId); + if (control && control.valid){ + const formData = this.formService.getValue(control.value) as AnnotationStatusPersist; + this.annotationService.persistStatus(formData) + .pipe(takeUntil(this._destroyed)).subscribe( + complete => this.onCallbackAnnotationStatusSuccess(), + error => this.onCallbackError(error) + ); + } + } + + changeIcon(formArray: UntypedFormArray, annotationId: Guid){ + const control = this.getAnnotationStatusControl(formArray, annotationId); + if (control == null) return; + const status = this.listingStatuses.find(x => x.id === control.get('statusId').value) || null; + if (status && status.internalStatus != null && status.internalStatus == InternalStatus.Resolved){ + control.get('isCheckIcon').patchValue(true); + } else { + control.get('isCheckIcon').patchValue(false); + } + } + + canSaveAnnotationStatus(formArray: UntypedFormArray, annotationId: Guid): boolean{ + const control = this.getAnnotationStatusControl(formArray, annotationId); + if (control == null) return false; + return control.valid; + } + + private onCallbackAnnotationStatusSuccess() { + this.uiNotificationService.snackBarNotification(this.language.instant('ANNOTATION-DIALOG.ANNOTATION-STATUS.SUCCESS'), SnackBarNotificationLevel.Success); + this.refreshAnnotations(); + } + + + } diff --git a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.module.ts b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.module.ts index 92b80ffb6..b2e256cde 100644 --- a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.module.ts +++ b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.module.ts @@ -3,12 +3,14 @@ import { FormattingModule } from '@app/core/formatting.module'; import { CommonFormsModule } from '@common/forms/common-forms.module'; import { CommonUiModule } from '@common/ui/common-ui.module'; import { AnnotationDialogComponent } from './annotation-dialog.component'; +import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.module'; @NgModule({ imports: [ CommonUiModule, CommonFormsModule, FormattingModule, + AutoCompleteModule, ], declarations: [ AnnotationDialogComponent, diff --git a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-status-editor.model.ts b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-status-editor.model.ts new file mode 100644 index 000000000..86ba12190 --- /dev/null +++ b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-status-editor.model.ts @@ -0,0 +1,109 @@ +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { InternalStatus } from '@annotation-service/core/enum/internal-status.enum'; +import { AnnotationStatus, AnnotationStatusPersist } from '@annotation-service/core/model/annotation-status.model'; +import { Annotation } from '@annotation-service/core/model/annotation.model'; +import { Status } from '@annotation-service/core/model/status.model'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +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'; +import { Guid } from '@common/types/guid'; + +export class AnnotationStatusArrayEditorModel { + annotationsStatusArray: AnnotationStatusEditorModel [] = []; + + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel(); + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor() { } + + public fromModel(item: Annotation[], listingStatuses: Status[]): AnnotationStatusArrayEditorModel { + if (item) { + item.forEach(annotation => { + if (annotation.annotationStatuses){ + annotation.annotationStatuses = annotation.annotationStatuses.filter(x => x.isActive === IsActive.Active); + annotation.annotationStatuses.forEach( x => { + this.annotationsStatusArray.push(new AnnotationStatusEditorModel(this.validationErrorModel).fromModel(x, listingStatuses)) + }) + } else { + const tempAnnotationStatus = { + annotation: annotation, + status: null + } as AnnotationStatus; + this.annotationsStatusArray.push(new AnnotationStatusEditorModel(this.validationErrorModel).fromModel(tempAnnotationStatus, listingStatuses)) + } + + }) + } + return this; + } + + buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup { + if (context == null) { context = this.createValidationContext(); } + + return this.formBuilder.group({ + annotationsStatusArray: this.formBuilder.array( + (this.annotationsStatusArray ?? []).map( + (item, index) => item.buildForm() + ), context.getValidation('annotationsStatusArray').validators + ), + }); + } + + createValidationContext(): ValidationContext { + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'annotationsStatusArray', validators: [BackendErrorValidator(this.validationErrorModel, 'annotationsStatusArray')] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} + +export class AnnotationStatusEditorModel implements AnnotationStatusPersist { + annotationId: Guid; + statusId: Guid; + isCheckIcon: boolean = false; + + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor(public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()) {} + + public fromModel(item: AnnotationStatus, listingStatuses: Status[]): AnnotationStatusEditorModel { + if (item) { + this.annotationId = item.annotation?.id; + this.statusId = item.status?.id; + if (this.statusId) { + const status = listingStatuses.find(x => x.id === this.statusId) || null; + if (status && status.internalStatus != null && status.internalStatus == InternalStatus.Resolved){ + this.isCheckIcon = true; + } else { + this.isCheckIcon = false; + } + } + } + return this; + } + + buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup { + if (context == null) { context = this.createValidationContext(); } + + return this.formBuilder.group({ + annotationId: [{ value: this.annotationId, disabled: disabled }, context.getValidation('annotationId').validators], + statusId: [{ value: this.statusId, disabled: disabled }, context.getValidation('statusId').validators], + isCheckIcon: [{ value: this.isCheckIcon, disabled: disabled }, context.getValidation('isCheckIcon').validators], + }); + } + + createValidationContext(): ValidationContext { + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'annotationId', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'label')] }); + baseValidationArray.push({ key: 'statusId', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'internalStatus')] }); + baseValidationArray.push({ key: 'isCheckIcon', validators: [] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} diff --git a/dmp-frontend/src/app/ui/description/editor/description-editor.component.html b/dmp-frontend/src/app/ui/description/editor/description-editor.component.html index a072f3dbd..4f05f9a68 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-editor.component.html +++ b/dmp-frontend/src/app/ui/description/editor/description-editor.component.html @@ -178,6 +178,7 @@ [validationErrorModel]="editorModel.validationErrorModel" [isNew]="isNew || isCopy" [canReview]="canReview" + [dmpUsers]="item?.dmp?.dmpUsers ?? []" >
diff --git a/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts b/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts index 4fc6c4da9..f0acdfcb8 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts +++ b/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts @@ -210,6 +210,7 @@ export class DescriptionEditorComponent extends BaseEditor x.isActive === IsActive.Active); this.item = data; this.initialTemplateId = data?.descriptionTemplate?.id?.toString(); if (data && data.dmpDescriptionTemplate?.sectionId && data.dmp?.blueprint?.definition?.sections?.length > 0 && data.dmp?.descriptions?.length > 0) { diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.ts b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.ts index a4822ec3b..c48cb95a2 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.ts +++ b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.ts @@ -20,6 +20,7 @@ import { DescriptionTemplateFieldType } from '@app/core/common/enum/description- import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; import { TranslateService } from '@ngx-translate/core'; +import { DmpUser } from '@app/core/model/dmp/dmp'; @Component({ selector: 'app-description-form-field-set', @@ -34,6 +35,7 @@ export class DescriptionFormFieldSetComponent extends BaseComponent { @Input() hideAnnotations: boolean = false; @Input() canReview: boolean = false; @Input() numbering: string; + @Input() dmpUsers: DmpUser[] = []; get isMultiplicityEnabled() { return this.fieldSet.hasMultiplicity && this.fieldSet.multiplicity != null; @@ -183,7 +185,8 @@ export class DescriptionFormFieldSetComponent extends BaseComponent { data: { entityId: this.descriptionId, anchor: fieldSetId, - entityType: AnnotationEntityType.Description + entityType: AnnotationEntityType.Description, + dmpUsers: this.dmpUsers } }); dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(changesMade => { diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.html b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.html index 2360e1ad2..a22461f6c 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.html +++ b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.html @@ -25,6 +25,7 @@ [isChild]="false" [hideAnnotations]="isNew" [canReview]="canReview" + [dmpUsers]="dmpUsers" > diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts index a88fb4631..e0814b3a1 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts +++ b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts @@ -8,6 +8,7 @@ import { ToCEntryType } from '../../../table-of-contents/models/toc-entry-type.e import { LinkToScroll } from '../../../table-of-contents/table-of-contents.component'; import { VisibilityRulesService } from '../../visibility-rules/visibility-rules.service'; import { Guid } from '@common/types/guid'; +import { DmpUser } from '@app/core/model/dmp/dmp'; @Component({ @@ -25,6 +26,7 @@ export class DescriptionFormSectionComponent extends BaseComponent implements On @Input() visibilityRulesService: VisibilityRulesService; @Input() path: string; @Input() descriptionId: Guid; + @Input() dmpUsers: DmpUser[] = []; // @Input() descriptionTemplateId: String; diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.html b/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.html index 6dc8589bc..f29f4c18e 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.html +++ b/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.html @@ -15,7 +15,7 @@
- +
diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.ts b/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.ts index cb4399975..828647825 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.ts +++ b/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.ts @@ -8,6 +8,7 @@ import { Guid } from '@common/types/guid'; import { LinkToScroll } from '../table-of-contents/table-of-contents.component'; import { DescriptionFormAnnotationService } from './description-form-annotation.service'; import { VisibilityRulesService } from './visibility-rules/visibility-rules.service'; +import { DmpUser } from '@app/core/model/dmp/dmp'; @Component({ selector: 'app-description-form', @@ -26,6 +27,7 @@ export class DescriptionFormComponent extends BaseComponent implements OnInit, O @Input() datasetDescription: String; @Input() linkToScroll: LinkToScroll; @Input() validationErrorModel: ValidationErrorModel; + @Input() dmpUsers: DmpUser[] = []; @Output() formChanged: EventEmitter = new EventEmitter(); diff --git a/dmp-frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts b/dmp-frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts index 95096a3be..e50bb0028 100644 --- a/dmp-frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts +++ b/dmp-frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts @@ -5,7 +5,7 @@ import { AppPermission } from '@app/core/common/enum/permission.enum'; import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; import { Description, DescriptionExternalIdentifier, DescriptionField, DescriptionPropertyDefinition, DescriptionPropertyDefinitionFieldSet, DescriptionPropertyDefinitionFieldSetItem, DescriptionReference, DescriptionReferenceData, DescriptionTag } from '@app/core/model/description/description'; import { DescriptionTemplatesInSection, DmpBlueprint, DmpBlueprintDefinition, DmpBlueprintDefinitionSection } from '@app/core/model/dmp-blueprint/dmp-blueprint'; -import { Dmp, DmpDescriptionTemplate } from '@app/core/model/dmp/dmp'; +import { Dmp, DmpDescriptionTemplate, DmpUser } from '@app/core/model/dmp/dmp'; import { PrefillingSource } from '@app/core/model/prefilling-source/prefilling-source'; import { ReferenceType } from '@app/core/model/reference-type/reference-type'; import { Reference } from '@app/core/model/reference/reference'; @@ -149,6 +149,12 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver { (prefix ? prefix + '.' : '') + [nameof(x => x.descriptions), nameof(x => x.dmpDescriptionTemplate), nameof(x => x.descriptionTemplateGroupId)].join('.'), (prefix ? prefix + '.' : '') + [nameof(x => x.descriptions), nameof(x => x.dmpDescriptionTemplate), nameof(x => x.sectionId)].join('.'), (prefix ? prefix + '.' : '') + [nameof(x => x.descriptions), nameof(x => x.dmpDescriptionTemplate), nameof(x => x.isActive)].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.id)].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.sectionId)].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.user.id)].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.user.name)].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.role)].join('.'), + (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.isActive)].join('.'), ] } diff --git a/docs/docs/documentation/administration/blueprints/index.md b/docs/docs/documentation/administration/blueprints/index.md index 5268ecdfe..4bf0b9cf1 100644 --- a/docs/docs/documentation/administration/blueprints/index.md +++ b/docs/docs/documentation/administration/blueprints/index.md @@ -31,7 +31,7 @@ There are also many more actions available for plan blueprints. When you clink o ## Authorization -Only users that have the **Admin** role can access this page. +Only users that have the global **Admin** role or the tenant specific **TenantAdmin** role or the tenant specific **TenantPlanManager** role can access this page. ## Navigation diff --git a/docs/docs/documentation/administration/blueprints/prefilling-configurations.md b/docs/docs/documentation/administration/blueprints/prefilling-configurations.md index db915480c..b9b436b5b 100644 --- a/docs/docs/documentation/administration/blueprints/prefilling-configurations.md +++ b/docs/docs/documentation/administration/blueprints/prefilling-configurations.md @@ -19,7 +19,7 @@ You can also create new or edit / remove sources by clicking to the `+ Create Pr ## Authorization -Only users that have the **Admin** role can access this page. +Only users that have the global **Admin** role or the tenant specific **TenantAdmin** role can access this page. ## Navigation diff --git a/docs/docs/documentation/administration/entity-locks.md b/docs/docs/documentation/administration/entity-locks.md new file mode 100644 index 000000000..e2efcdbd6 --- /dev/null +++ b/docs/docs/documentation/administration/entity-locks.md @@ -0,0 +1,49 @@ +--- +sidebar_position: 13 +description: Manage all entity locks +--- + +# Entity Locks + +In this page, there is a listing where you can view details about all the actively locked entites. + +:::info + +When a user edits a [plan](/docs/documentation/application/plans) or a [description](/docs/documentation/application/descriptions), no other user can make changes on them at the same time. This is forced by an implemented locking mechanism. All the locks are eventually removed after some period of inactivity on the entities or when the user is done editing them. + +::: + +The information displayed by default is: the `target` which is the id of the entity, the `type` of the entity and timestamps for the `locking` and `updating` of the locks. At the top right corner of the listing you can also select which columns to display. + +:::tip + +For entity locks, all the columns are visible by default. + +::: + +You can manually remove a lock by clicking on the three dots on the far right corner of the records and then select `Delete`. + +## Authorization + +Only users that have the global **Admin** role can access this page. + +## Navigation + +This view is available when the user presses the `Entity Locks` link from the side navigation menu. + +## Pagination + +Not all the records are being displayed at once. By default, there is a pagination of 10 records applied to them. + +You can control how many records are being displayed at any time, by adjusting the `items per page` control at the bottom left corner of the table. + +## Filtering + +There are some filtering options available for users. + +- **Users**: You can filter locks by their user. You can select one or more users.
*By default, no user is selected.* +- **Types**: You can filter locks by the entity type they act uppon. You can select one or more types.
*By default, no type is selected.* + +In order for the filters to apply, you have to click the `Apply filters` button. + +You can also clear any filters already applied, by pressing the `clear all filters` option, located at the top of the popup. \ No newline at end of file diff --git a/docs/docs/documentation/administration/languages.md b/docs/docs/documentation/administration/languages.md index 006fcc104..4030d528d 100644 --- a/docs/docs/documentation/administration/languages.md +++ b/docs/docs/documentation/administration/languages.md @@ -21,7 +21,7 @@ Editing and removing existing languages is also possible by clicking on the tree ## Authorization -Only users that have the **Admin** role can access this page. +Only users that have the global **Admin** role or the tenant specific **TenantAdmin** role or the tenant specific **TenantConfigManager** role can access this page. ## Navigation diff --git a/docs/docs/documentation/administration/notification-templates.md b/docs/docs/documentation/administration/notification-templates.md index 003070492..1c5d5cc62 100644 --- a/docs/docs/documentation/administration/notification-templates.md +++ b/docs/docs/documentation/administration/notification-templates.md @@ -19,7 +19,7 @@ You can also create new or edit / remove notification templates by clicking to t ## Authorization -Only users that have the **Admin** role can access this page. +Only users that have the global **Admin** role or the tenant specific **TenantAdmin** role or the tenant specific **TenantConfigManager** role can access this page. ## Navigation diff --git a/docs/docs/documentation/administration/reference-types.md b/docs/docs/documentation/administration/reference-types.md index 85e0b76ad..cf801634f 100644 --- a/docs/docs/documentation/administration/reference-types.md +++ b/docs/docs/documentation/administration/reference-types.md @@ -25,7 +25,7 @@ You can also create new or edit / remove reference types by clicking to the `+ C ## Authorization -Only users that have the **Admin** role can access this page. +Only users that have the global **Admin** role or the tenant specific **TenantAdmin** role can access this page. ## Navigation diff --git a/docs/docs/documentation/administration/supportive-material.md b/docs/docs/documentation/administration/supportive-material.md index 0bbf5dd04..534c3c7c1 100644 --- a/docs/docs/documentation/administration/supportive-material.md +++ b/docs/docs/documentation/administration/supportive-material.md @@ -16,7 +16,7 @@ When you select both options above, the **supportive material editor** is openin ## Authorization -Only users that have the **Admin** role can access this page. +Only users that have the global **Admin** role or the tenant specific **TenantAdmin** role or the tenant specific **TenantConfigManager** role can access this page. ## Navigation diff --git a/docs/docs/documentation/administration/template-types.md b/docs/docs/documentation/administration/template-types.md index 3f0756eac..6e1f629ae 100644 --- a/docs/docs/documentation/administration/template-types.md +++ b/docs/docs/documentation/administration/template-types.md @@ -19,7 +19,7 @@ You can also create new or remove template types by clicking to the `Create Desc ## Authorization -Only users that have the **Admin** role can access this page. +Only users that have the global **Admin** role or the tenant specific **TenantAdmin** role or the tenant specific **TenantPlanManager** role can access this page. ## Navigation diff --git a/docs/docs/documentation/administration/templates/index.md b/docs/docs/documentation/administration/templates/index.md index 52c6a69f5..70adf053f 100644 --- a/docs/docs/documentation/administration/templates/index.md +++ b/docs/docs/documentation/administration/templates/index.md @@ -33,7 +33,7 @@ There are also many more actions available for description templates. When you c ## Authorization -Only users that have the **Admin** role can access this page. +Only users that have the global **Admin** role or the tenant specific **TenantAdmin** role or the tenant specific **TenantPlanManager** role can access this page. ## Navigation @@ -162,6 +162,12 @@ For every question you configure, you can view a live preview on how it will loo ::: +:::info + +You can invite users to the templates so that they can also edit them. + +::: + --- \ No newline at end of file diff --git a/docs/docs/documentation/administration/tenant-configuration.md b/docs/docs/documentation/administration/tenant-configuration.md index 718c297da..77226a654 100644 --- a/docs/docs/documentation/administration/tenant-configuration.md +++ b/docs/docs/documentation/administration/tenant-configuration.md @@ -1,5 +1,5 @@ --- -sidebar_position: 11 +sidebar_position: 12 description: Configure the tenant you are logged in with --- @@ -7,12 +7,6 @@ description: Configure the tenant you are logged in with On this page, we can configure the tenant we are logged in with. The configuration options are separated in the following sections. -:::info - -Only tenant administrators can access this page. - -::: -
Default Tenant Locale @@ -72,4 +66,8 @@ Here we can upload an extra logo, which will be displayed next to the platform l Notification Preferences Here we specify the notification preferences, which will be applied by default to the users of this tenant. For every notification case, we can specify the channels which will be used and their priority. -
\ No newline at end of file + + +## Authorization + +Only users that have the global **Admin** role or the tenant specific **TenantAdmin** role can access this page. \ No newline at end of file diff --git a/docs/docs/documentation/administration/tenant-users.md b/docs/docs/documentation/administration/tenant-users.md new file mode 100644 index 000000000..1f4a1b89b --- /dev/null +++ b/docs/docs/documentation/administration/tenant-users.md @@ -0,0 +1,53 @@ +--- +sidebar_position: 11 +description: Manage all tenant based user roles +--- + +# Tenant Users + +In this page, there is a listing where you can view details about all the tenant registered users. + +:::info + +Users that are not assigned to a specific tenant are always assigned to the *default* tenant. If you are an administrator on a tenant, you can view the assigned users and their roles in this page by selecting your tenant from the respective top navigation menu dropdown. If you do not belong to a tenant, that dropdown won't be available for you. + +::: + +The information displayed by default is: the `name` of the users, the `status` of their accounts and timestamps for the `creation` and `updates` of the records. At the top right corner of the listing you can also select which columns to display. + +:::tip + +For tenant users, there is a column for `emails`, hidden by default. + +::: + +You can assign new or remove assigned tenant roles from users by clicking to the icon next to the roles column. When you click it, a multiselect dropdown is made available in the roles column. + +You can also invite users by clicking the `Invite users` button at the top of the listing, as well as export the users list in **.csv** format by clicking the `Export Users` button respectively. + +For the user invitation, you will have to provide an email and a list of the roles you want the user to be assigned with, on the currently selected tenant. + +## Authorization + +Only users that have the global **Admin** role or the tenant specific **TenantAdmin** role can access this page. + +## Navigation + +This view is available when the user presses the `Tenant Users` link from the side navigation menu. + +## Pagination + +Not all the records are being displayed at once. By default, there is a pagination of 10 records applied to them. + +You can control how many records are being displayed at any time, by adjusting the `items per page` control at the bottom left corner of the table. + +## Filtering + +There are some filtering options available for users. + +- **Is Active**: By toggling this control you can view only the active or only the disabled users.
*By default, this option is set to true.* +- **Roles**: You can filter users by their assigned roles. You can select one or more roles.
*By default, no role is selected.* + +In order for the filters to apply, you have to click the `Apply filters` button. + +You can also clear any filters already applied, by pressing the `clear all filters` option, located at the top of the popup. \ No newline at end of file diff --git a/docs/docs/documentation/administration/tenants.md b/docs/docs/documentation/administration/tenants.md index 231bedacd..3376c81f5 100644 --- a/docs/docs/documentation/administration/tenants.md +++ b/docs/docs/documentation/administration/tenants.md @@ -19,7 +19,7 @@ You can also create new or edit / remove tenants by clicking to the `Create Tena ## Authorization -Only users that have the **Admin** role can access this page. +Only users that have the global **Admin** role or the global **InstallationAdmin** role can access this page. ## Navigation diff --git a/docs/docs/documentation/administration/users.md b/docs/docs/documentation/administration/users.md index 71b92de33..f139e2d87 100644 --- a/docs/docs/documentation/administration/users.md +++ b/docs/docs/documentation/administration/users.md @@ -19,7 +19,7 @@ You can also assign new or remove assigned roles from users by clicking to the i ## Authorization -Only users that have the **Admin** role can access this page. +Only users that have the global **Admin** role or the global **InstallationAdmin** role can access this page. ## Navigation diff --git a/docs/docs/documentation/for-devs/apis/swagger.md b/docs/docs/documentation/for-devs/apis/swagger.md index 87667b7fb..aa0d809b3 100644 --- a/docs/docs/documentation/for-devs/apis/swagger.md +++ b/docs/docs/documentation/for-devs/apis/swagger.md @@ -12,7 +12,7 @@ import Admonition from '@theme/Admonition'; The swagger UI is available at the `/swagger-ui/index.html` url. It contains documentation for the following API endpoints. - + - **/api/public/dmps/\*\*** - **/api/public/descriptions/\*\*** @@ -22,10 +22,16 @@ The swagger UI is available at the `/swagger-ui/index.html` url. It contains doc - + - **/api/dmp/\*\*** - **/api/description/\*\*** + - **/api/description-template/\*\*** + - **/api/description-template-type/\*\*** + - **/api/dmp-blueprint/\*\*** + - **/api/entity-doi/\*\*** + - **/api/deposit/\*\*** + - **/api/file-transformer/\*\***

These endpoints require authentication.

diff --git a/docs/docs/tutorials/_category_.json b/docs/docs/tutorials/_category_.json deleted file mode 100644 index ae410ff82..000000000 --- a/docs/docs/tutorials/_category_.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "label": "Tutorials", - "position": 1, - "link": { - "type": "generated-index" - } -} \ No newline at end of file diff --git a/docs/docs/tutorials/intro.md b/docs/docs/tutorials/intro.md deleted file mode 100644 index 45e8604c8..000000000 --- a/docs/docs/tutorials/intro.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Tutorial Intro - -Let's discover **Docusaurus in less than 5 minutes**. - -## Getting Started - -Get started by **creating a new site**. - -Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**. - -### What you'll need - -- [Node.js](https://nodejs.org/en/download/) version 18.0 or above: - - When installing Node.js, you are recommended to check all checkboxes related to dependencies. - -## Generate a new site - -Generate a new Docusaurus site using the **classic template**. - -The classic template will automatically be added to your project after you run the command: - -```bash -npm init docusaurus@latest my-website classic -``` - -You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor. - -The command also installs all necessary dependencies you need to run Docusaurus. - -## Start your site - -Run the development server: - -```bash -cd my-website -npm run start -``` - -The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there. - -The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/. - -Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes. diff --git a/docs/docs/tutorials/tutorial-basics/_category_.json b/docs/docs/tutorials/tutorial-basics/_category_.json deleted file mode 100644 index 2e6db55b1..000000000 --- a/docs/docs/tutorials/tutorial-basics/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Tutorial - Basics", - "position": 2, - "link": { - "type": "generated-index", - "description": "5 minutes to learn the most important Docusaurus concepts." - } -} diff --git a/docs/docs/tutorials/tutorial-basics/congratulations.md b/docs/docs/tutorials/tutorial-basics/congratulations.md deleted file mode 100644 index 04771a00b..000000000 --- a/docs/docs/tutorials/tutorial-basics/congratulations.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Congratulations! - -You have just learned the **basics of Docusaurus** and made some changes to the **initial template**. - -Docusaurus has **much more to offer**! - -Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**. - -Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610) - -## What's next? - -- Read the [official documentation](https://docusaurus.io/) -- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config) -- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration) -- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout) -- Add a [search bar](https://docusaurus.io/docs/search) -- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase) -- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support) diff --git a/docs/docs/tutorials/tutorial-basics/create-a-blog-post.md b/docs/docs/tutorials/tutorial-basics/create-a-blog-post.md deleted file mode 100644 index 550ae17ee..000000000 --- a/docs/docs/tutorials/tutorial-basics/create-a-blog-post.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Create a Blog Post - -Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed... - -## Create your first Post - -Create a file at `blog/2021-02-28-greetings.md`: - -```md title="blog/2021-02-28-greetings.md" ---- -slug: greetings -title: Greetings! -authors: - - name: Joel Marcey - title: Co-creator of Docusaurus 1 - url: https://github.com/JoelMarcey - image_url: https://github.com/JoelMarcey.png - - name: Sébastien Lorber - title: Docusaurus maintainer - url: https://sebastienlorber.com - image_url: https://github.com/slorber.png -tags: [greetings] ---- - -Congratulations, you have made your first post! - -Feel free to play around and edit this post as much as you like. -``` - -A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings). diff --git a/docs/docs/tutorials/tutorial-basics/create-a-document.md b/docs/docs/tutorials/tutorial-basics/create-a-document.md deleted file mode 100644 index c22fe2944..000000000 --- a/docs/docs/tutorials/tutorial-basics/create-a-document.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Create a Document - -Documents are **groups of pages** connected through: - -- a **sidebar** -- **previous/next navigation** -- **versioning** - -## Create your first Doc - -Create a Markdown file at `docs/hello.md`: - -```md title="docs/hello.md" -# Hello - -This is my **first Docusaurus document**! -``` - -A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello). - -## Configure the Sidebar - -Docusaurus automatically **creates a sidebar** from the `docs` folder. - -Add metadata to customize the sidebar label and position: - -```md title="docs/hello.md" {1-4} ---- -sidebar_label: 'Hi!' -sidebar_position: 3 ---- - -# Hello - -This is my **first Docusaurus document**! -``` - -It is also possible to create your sidebar explicitly in `sidebars.js`: - -```js title="sidebars.js" -export default { - tutorialSidebar: [ - 'intro', - // highlight-next-line - 'hello', - { - type: 'category', - label: 'Tutorial', - items: ['tutorial-basics/create-a-document'], - }, - ], -}; -``` diff --git a/docs/docs/tutorials/tutorial-basics/create-a-page.md b/docs/docs/tutorials/tutorial-basics/create-a-page.md deleted file mode 100644 index 20e2ac300..000000000 --- a/docs/docs/tutorials/tutorial-basics/create-a-page.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Create a Page - -Add **Markdown or React** files to `src/pages` to create a **standalone page**: - -- `src/pages/index.js` → `localhost:3000/` -- `src/pages/foo.md` → `localhost:3000/foo` -- `src/pages/foo/bar.js` → `localhost:3000/foo/bar` - -## Create your first React Page - -Create a file at `src/pages/my-react-page.js`: - -```jsx title="src/pages/my-react-page.js" -import React from 'react'; -import Layout from '@theme/Layout'; - -export default function MyReactPage() { - return ( - -

My React page

-

This is a React page

-
- ); -} -``` - -A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page). - -## Create your first Markdown Page - -Create a file at `src/pages/my-markdown-page.md`: - -```mdx title="src/pages/my-markdown-page.md" -# My Markdown page - -This is a Markdown page -``` - -A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page). diff --git a/docs/docs/tutorials/tutorial-basics/deploy-your-site.md b/docs/docs/tutorials/tutorial-basics/deploy-your-site.md deleted file mode 100644 index 1c50ee063..000000000 --- a/docs/docs/tutorials/tutorial-basics/deploy-your-site.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Deploy your site - -Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**). - -It builds your site as simple **static HTML, JavaScript and CSS files**. - -## Build your site - -Build your site **for production**: - -```bash -npm run build -``` - -The static files are generated in the `build` folder. - -## Deploy your site - -Test your production build locally: - -```bash -npm run serve -``` - -The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/). - -You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**). diff --git a/docs/docs/tutorials/tutorial-basics/markdown-features.mdx b/docs/docs/tutorials/tutorial-basics/markdown-features.mdx deleted file mode 100644 index 35e00825e..000000000 --- a/docs/docs/tutorials/tutorial-basics/markdown-features.mdx +++ /dev/null @@ -1,152 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Markdown Features - -Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**. - -## Front Matter - -Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/): - -```text title="my-doc.md" -// highlight-start ---- -id: my-doc-id -title: My document title -description: My document description -slug: /my-custom-url ---- -// highlight-end - -## Markdown heading - -Markdown text with [links](./hello.md) -``` - -## Links - -Regular Markdown links are supported, using url paths or relative file paths. - -```md -Let's see how to [Create a page](/create-a-page). -``` - -```md -Let's see how to [Create a page](./create-a-page.md). -``` - -**Result:** Let's see how to [Create a page](./create-a-page.md). - -## Images - -Regular Markdown images are supported. - -You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`): - -```md -![Docusaurus logo](/img/docusaurus.png) -``` - -![Docusaurus logo](/img/docusaurus.png) - -You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them: - -```md -![Docusaurus logo](./img/docusaurus.png) -``` - -## Code Blocks - -Markdown code blocks are supported with Syntax highlighting. - -````md -```jsx title="src/components/HelloDocusaurus.js" -function HelloDocusaurus() { - return

Hello, Docusaurus!

; -} -``` -```` - -```jsx title="src/components/HelloDocusaurus.js" -function HelloDocusaurus() { - return

Hello, Docusaurus!

; -} -``` - -## Admonitions - -Docusaurus has a special syntax to create admonitions and callouts: - -```md -:::tip My tip - -Use this awesome feature option - -::: - -:::danger Take care - -This action is dangerous - -::: -``` - -:::tip My tip - -Use this awesome feature option - -::: - -:::danger Take care - -This action is dangerous - -::: - -## MDX and React Components - -[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**: - -```jsx -export const Highlight = ({children, color}) => ( - { - alert(`You clicked the color ${color} with label ${children}`) - }}> - {children} - -); - -This is Docusaurus green ! - -This is Facebook blue ! -``` - -export const Highlight = ({children, color}) => ( - { - alert(`You clicked the color ${color} with label ${children}`); - }}> - {children} - -); - -This is Docusaurus green ! - -This is Facebook blue ! diff --git a/docs/docs/tutorials/tutorial-extras/_category_.json b/docs/docs/tutorials/tutorial-extras/_category_.json deleted file mode 100644 index a8ffcc193..000000000 --- a/docs/docs/tutorials/tutorial-extras/_category_.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "label": "Tutorial - Extras", - "position": 3, - "link": { - "type": "generated-index" - } -} diff --git a/docs/docs/tutorials/tutorial-extras/img/docsVersionDropdown.png b/docs/docs/tutorials/tutorial-extras/img/docsVersionDropdown.png deleted file mode 100644 index 97e416461..000000000 Binary files a/docs/docs/tutorials/tutorial-extras/img/docsVersionDropdown.png and /dev/null differ diff --git a/docs/docs/tutorials/tutorial-extras/img/localeDropdown.png b/docs/docs/tutorials/tutorial-extras/img/localeDropdown.png deleted file mode 100644 index e257edc1f..000000000 Binary files a/docs/docs/tutorials/tutorial-extras/img/localeDropdown.png and /dev/null differ diff --git a/docs/docs/tutorials/tutorial-extras/manage-docs-versions.md b/docs/docs/tutorials/tutorial-extras/manage-docs-versions.md deleted file mode 100644 index ccda0b907..000000000 --- a/docs/docs/tutorials/tutorial-extras/manage-docs-versions.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Manage Docs Versions - -Docusaurus can manage multiple versions of your docs. - -## Create a docs version - -Release a version 1.0 of your project: - -```bash -npm run docusaurus docs:version 1.0 -``` - -The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created. - -Your docs now have 2 versions: - -- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs -- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs** - -## Add a Version Dropdown - -To navigate seamlessly across versions, add a version dropdown. - -Modify the `docusaurus.config.js` file: - -```js title="docusaurus.config.js" -export default { - themeConfig: { - navbar: { - items: [ - // highlight-start - { - type: 'docsVersionDropdown', - }, - // highlight-end - ], - }, - }, -}; -``` - -The docs version dropdown appears in your navbar: - -![Docs Version Dropdown](./img/docsVersionDropdown.png) - -## Update an existing version - -It is possible to edit versioned docs in their respective folder: - -- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello` -- `docs/hello.md` updates `http://localhost:3000/docs/next/hello` diff --git a/docs/docs/tutorials/tutorial-extras/translate-your-site.md b/docs/docs/tutorials/tutorial-extras/translate-your-site.md deleted file mode 100644 index b5a644abd..000000000 --- a/docs/docs/tutorials/tutorial-extras/translate-your-site.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Translate your site - -Let's translate `docs/intro.md` to French. - -## Configure i18n - -Modify `docusaurus.config.js` to add support for the `fr` locale: - -```js title="docusaurus.config.js" -export default { - i18n: { - defaultLocale: 'en', - locales: ['en', 'fr'], - }, -}; -``` - -## Translate a doc - -Copy the `docs/intro.md` file to the `i18n/fr` folder: - -```bash -mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/ - -cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md -``` - -Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French. - -## Start your localized site - -Start your site on the French locale: - -```bash -npm run start -- --locale fr -``` - -Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated. - -:::caution - -In development, you can only use one locale at a time. - -::: - -## Add a Locale Dropdown - -To navigate seamlessly across languages, add a locale dropdown. - -Modify the `docusaurus.config.js` file: - -```js title="docusaurus.config.js" -export default { - themeConfig: { - navbar: { - items: [ - // highlight-start - { - type: 'localeDropdown', - }, - // highlight-end - ], - }, - }, -}; -``` - -The locale dropdown now appears in your navbar: - -![Locale Dropdown](./img/localeDropdown.png) - -## Build your localized site - -Build your site for a specific locale: - -```bash -npm run build -- --locale fr -``` - -Or build your site to include all the locales at once: - -```bash -npm run build -``` diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index e99a9820d..7a8d3e8ea 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -74,12 +74,6 @@ const config: Config = { position: 'left', label: 'Documentation', }, - { - type: 'docSidebar', - sidebarId: 'tutorialSidebar', - position: 'left', - label: 'Tutorials', - }, { href: 'https://test4.opendmp.eu/splash/', label: 'Visit OpenCDMP', diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 8736fe8c7..fe03210f7 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -12,21 +12,7 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; */ const sidebars: SidebarsConfig = { // By default, Docusaurus generates a sidebar from the docs folder structure - documentationSidebar: [{type: 'autogenerated', dirName: 'documentation'}], - tutorialSidebar: [{type: 'autogenerated', dirName: 'tutorials'}] - - // But you can create a sidebar manually - /* - tutorialSidebar: [ - 'intro', - 'hello', - { - type: 'category', - label: 'Tutorial', - items: ['tutorial-basics/create-a-document'], - }, - ], - */ + documentationSidebar: [{type: 'autogenerated', dirName: 'documentation'}] }; export default sidebars;