From 0945b8300e5870b032ceb02e02e60e60acb6ff6a Mon Sep 17 00:00:00 2001 From: Thomas Georgios Giannos Date: Thu, 16 May 2024 14:27:55 +0300 Subject: [PATCH 1/3] Fixes on existing plans docs --- .../application/plans/create-a-plan.md | 4 +++- .../application/plans/edit-a-plan.md | 4 ++-- .../application/plans/exports.md | 6 +++--- .../application/plans/imports.md | 2 +- .../application/plans/invite-collaborators.md | 19 ++++++++++--------- .../application/plans/plan-lifecycle.md | 12 +++++++++++- .../application/plans/versions.md | 14 +++++++------- .../supplementary-services/index.md | 2 +- 8 files changed, 38 insertions(+), 25 deletions(-) diff --git a/docs/docs/documentation/application/plans/create-a-plan.md b/docs/docs/documentation/application/plans/create-a-plan.md index f4ece939b..e3c07725a 100644 --- a/docs/docs/documentation/application/plans/create-a-plan.md +++ b/docs/docs/documentation/application/plans/create-a-plan.md @@ -7,7 +7,9 @@ description: Discover how to create a new plan -img- start a new plan -img- -To create a new Plan in OpenCDMP you can either [import](/docs/documentation/application/plans/imports) an existing plan from other source or writting a Plan from start. It provides an editor that you must fill **Title**, **Description** and select the [**Blueprint**](/docs/documentation/administration/blueprints/) of this plan that describes the structure. Also there is a Default Plan Blueprint for use. After save, the plan is automatically in draft status. +To create a new Plan in OpenCDMP you can either [import](/docs/documentation/application/plans/imports) an existing plan from a file or fill in a Plan from the start. + +To do this, there is a form available where you must fill a **Title**, a **Description** and select the **[Blueprint](/docs/documentation/administration/blueprints/)** of this plan that describes its structure. Also, there is a Default Plan Blueprint for use. When ready, after you press `save`, the plan is automatically saved having the `draft` status. More information about the lifecycle of a plan, can be found [here](/docs/documentation/application/plans/plan-lifecycle). -img- plan editor first step -img- diff --git a/docs/docs/documentation/application/plans/edit-a-plan.md b/docs/docs/documentation/application/plans/edit-a-plan.md index 0a48b949d..d43b73dd9 100644 --- a/docs/docs/documentation/application/plans/edit-a-plan.md +++ b/docs/docs/documentation/application/plans/edit-a-plan.md @@ -5,10 +5,10 @@ description: Discover how to edit a plan # Edit a Plan -You can view a plan by either from `Home Page` or from `My Plans`. By selecting the plan you can view the overview and all actions for this. +You can view a plan either from `Home Page` or from `My Plans`. By selecting the plan you can view the overview and all the actions available for it. -img- plan overview -img- -In edit state you can make and revert changes but also add descriptions to specific section that contains description templates field. +While in the edit view, you can make and revert changes but also add descriptions to specific sections that contain description template fields. -img- plan editor in edit state -img- \ No newline at end of file diff --git a/docs/docs/documentation/application/plans/exports.md b/docs/docs/documentation/application/plans/exports.md index d006ba741..6ba7f9926 100644 --- a/docs/docs/documentation/application/plans/exports.md +++ b/docs/docs/documentation/application/plans/exports.md @@ -7,7 +7,7 @@ description: Discover export options for a plan -img- export plan-img- -Application supports download the structure of the plan in the following formats: +You can export the structure of a plan in the following formats: - **PDF** - **DOCX** - **RDA JSON** @@ -15,7 +15,7 @@ Application supports download the structure of the plan in the following formats :::tip -- You can use the export feature in anytime in plans' lifecycle -- Export is also available in Public Plans +- You can use the export feature anytime in a plans' lifecycle +- Export is also available for the Public Plans ::: diff --git a/docs/docs/documentation/application/plans/imports.md b/docs/docs/documentation/application/plans/imports.md index d415ddc0e..8642a33f0 100644 --- a/docs/docs/documentation/application/plans/imports.md +++ b/docs/docs/documentation/application/plans/imports.md @@ -5,6 +5,6 @@ description: Discover import options for a plan # Imports -You can import an existing plan to continue work in appliation. Import supports upload of `.json` files that are produced according to [RDA specifications](https://github.com/RDA-DMP-Common/RDA-DMP-Common-Standard) for machine-actionable plans. +You can import an existing plan so that you don't have to work on a new one from scratch. Import supports the upload of `.json` files that are produced according to [RDA specifications](https://github.com/RDA-DMP-Common/RDA-DMP-Common-Standard) for machine-actionable plans. -img- import plan pop up dialog -img- diff --git a/docs/docs/documentation/application/plans/invite-collaborators.md b/docs/docs/documentation/application/plans/invite-collaborators.md index b880b11be..8d2354a82 100644 --- a/docs/docs/documentation/application/plans/invite-collaborators.md +++ b/docs/docs/documentation/application/plans/invite-collaborators.md @@ -7,18 +7,19 @@ description: Discover how you invite people to a plan -img- invite users -img- -Before finalization, the plan can be further edited by inviting users collaborate on completing this. There are two methods to invite users: -- **External**: If user is registered in application then a notification (email or in App) sends to him. If not, a registration email sends. -- **Internal**: It's a quick way, that we can invite users that is already associated to other plans we work with as well. +Before finalization, the plan can be further edited by inviting users in order to collaborate on it and complete it. There are two methods to invite users: +- **External**: If the user is registered in the application, then a notification (email or in App) gets sent to him. If not, a registration email is being sent instead. +- **Internal**: It's a quick way, that we can invite users already associated to other plans we work with as well. +For each member, a role on the plan must be defined. There are three different roles: -For each member, must be defined the role for the plan. There are three different roles: - -- **Owner**: manage and edit all plan features. -- **Description Contributor**: only edit descriptions of this plan. -- **Reviewer**: view the plan but no rights to edit. +- **Owner**: Manage and edit all plan features. +- **Description Contributor**: Edit only the descriptions of this plan. +- **Reviewer**: View the plan without being able to edit it. :::note -the roles' features can be apply to entire plan or specific section + +The collaboration roles we discussed above, can be applied to an entire plan or only to a specific section of it. + ::: \ No newline at end of file diff --git a/docs/docs/documentation/application/plans/plan-lifecycle.md b/docs/docs/documentation/application/plans/plan-lifecycle.md index 2b3d69393..7b6aaaa0b 100644 --- a/docs/docs/documentation/application/plans/plan-lifecycle.md +++ b/docs/docs/documentation/application/plans/plan-lifecycle.md @@ -5,4 +5,14 @@ description: Discover the lifecycle of a plan # Plan lifecycle -After a plan is created automatically get the draft status. In `My Plans` they appears all the plans that user has or collaborate. \ No newline at end of file +After a plan is created, it gets saved automatically in `draft` mode. While in this mode, the user can make any changes he wants and save it as many times as he wants. When ready, the user can then `finalize` it which means no more changes will be possible. If there comes a need for further editing after the plan is already finalized, the user has the option to undo the finalization and bring it back into `draft` mode. + +A finalized plan can then be [deposited](/docs/documentation/application/plans/doi-assignment.md). + +:::warning + +When a plan gets deposited it can not be edited anymore. This action is irreversible. In this case, the only option is to either clone it or make a new [version](/docs/documentation/application/plans/versions) of it. + +::: + +In the `My Plans` page, the user can view all the plans he has created or the ones he has been invited as a [collaborator](/docs/documentation/application/plans/invite-collaborators). \ No newline at end of file diff --git a/docs/docs/documentation/application/plans/versions.md b/docs/docs/documentation/application/plans/versions.md index 5ddee2d40..c6a37ea12 100644 --- a/docs/docs/documentation/application/plans/versions.md +++ b/docs/docs/documentation/application/plans/versions.md @@ -5,15 +5,15 @@ description: Discover plan versioning options # Versions +A plan can have many versions. There is always one version that is marked as `current`, it is the most recent version and it is the one that gets displayed on the listing views. All the other ones are marked as `previous` and can be viewed when the `All Plan Versions` option is pressed. + :::info -If the plan is finalized you can create a new version for it. New Version don't delete the previous version of this plan, just creates a new with draft status. + +If the plan is finalized you can create a new version for it, which will have all the properties of the last one and it will be in `draft` mode ready for changes. Making a new version doesn't delete the previous ones. + ::: -`New Version` button exists in `My Plans` or `Plan Overview` actions. In new version form can change the title, description, plan blueprint and optional select which descriptions include in the new version. After the process is completed, plan and descriptions are available to edit. +The `New Version` option cab be found in the `My Plans` or `Plan Overview` pages. In the new version form, the user can change titles, descriptions, plan blueprints and optionally select which descriptions to include in the new version. After the process is completed, the new plan and descriptions are available for editing. -img- new version overview -img- --img- new version listing -img- - -:::tip -`All Plan Versions` - shows the history of the different versions of the existing plan -::: \ No newline at end of file +-img- new version listing -img- \ No newline at end of file diff --git a/docs/docs/documentation/supplementary-services/index.md b/docs/docs/documentation/supplementary-services/index.md index be7c69868..522baa3b5 100644 --- a/docs/docs/documentation/supplementary-services/index.md +++ b/docs/docs/documentation/supplementary-services/index.md @@ -1,6 +1,6 @@ # Supplementary Services -There are four supplementary services directly integrated into this platform. They are used to extend it's functionality and improve the user experience. In this section of the docs, we will dive into each one of them, and see how they contribute to the overall usage of the platform. +There are four supplementary services directly integrated into this platform. They are used to extend its functionality and improve the user experience. In this section of the docs, we will dive into each one of them, and see how they contribute to the overall usage of the platform. Follow the links below, to find out more information about them respectively. From 066d8c8d24d680fb67e616d1dde01cb738cbcf9c Mon Sep 17 00:00:00 2001 From: amentis Date: Thu, 16 May 2024 14:32:56 +0300 Subject: [PATCH 2/3] fix new version flow, sort other dmp versions --- .../model/builder/PublicDmpBuilder.java | 8 +++++-- .../model/builder/dmp/DmpBuilder.java | 8 +++++-- .../descriptionproperties/FieldPersist.java | 2 +- .../opencdmp/service/dmp/DmpServiceImpl.java | 24 ++++++++++++++++--- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/PublicDmpBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/PublicDmpBuilder.java index 7ad8a3586..b9ed5f07a 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/PublicDmpBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/PublicDmpBuilder.java @@ -13,6 +13,7 @@ import org.opencdmp.commons.enums.IsActive; import org.opencdmp.convention.ConventionService; import org.opencdmp.data.DmpEntity; import org.opencdmp.model.*; +import org.opencdmp.model.dmp.Dmp; import org.opencdmp.query.*; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -88,7 +89,10 @@ public class PublicDmpBuilder extends BaseBuilder { if (dmpUsersMap != null && !dmpUsersMap.isEmpty() && dmpUsersMap.containsKey(d.getId())) m.setDmpUsers(dmpUsersMap.get(d.getId())); if (descriptionsMap != null && !descriptionsMap.isEmpty() && descriptionsMap.containsKey(d.getId())) m.setDescriptions(descriptionsMap.get(d.getId())); if (entityDoisMap != null && !entityDoisMap.isEmpty() && entityDoisMap.containsKey(d.getId())) m.setEntityDois(entityDoisMap.get(d.getId())); - if (otherDmpVersionsMap != null && !otherDmpVersionsMap.isEmpty() && otherDmpVersionsMap.containsKey(d.getGroupId())) m.setOtherDmpVersions(otherDmpVersionsMap.get(d.getGroupId())); + if (otherDmpVersionsMap != null && !otherDmpVersionsMap.isEmpty() && otherDmpVersionsMap.containsKey(d.getGroupId())){ + m.setOtherDmpVersions(otherDmpVersionsMap.get(d.getGroupId())); + m.getOtherDmpVersions().sort(Comparator.comparing(PublicDmp::getVersion)); + } models.add(m); } @@ -178,7 +182,7 @@ public class PublicDmpBuilder extends BaseBuilder { DmpQuery query = this.queryFactory.query(DmpQuery.class).disableTracking().authorize(this.authorize).groupIds(data.stream().map(DmpEntity::getGroupId).distinct().collect(Collectors.toList())).isActive(IsActive.Active); itemMap = this.builderFactory.builder(PublicDmpBuilder.class).authorize(this.authorize).asMasterKey(query, clone, PublicDmp::getGroupId); - if (!fields.hasField(this.asIndexer(PublicDmp._otherDmpVersions, PublicDmp._id))) { + if (!fields.hasField(PublicDmp._id)) { itemMap.values().stream().flatMap(List::stream).filter(Objects::nonNull).forEach(x -> { x.setId(null); }); diff --git a/backend/core/src/main/java/org/opencdmp/model/builder/dmp/DmpBuilder.java b/backend/core/src/main/java/org/opencdmp/model/builder/dmp/DmpBuilder.java index 621ee47c4..0c06bd05c 100644 --- a/backend/core/src/main/java/org/opencdmp/model/builder/dmp/DmpBuilder.java +++ b/backend/core/src/main/java/org/opencdmp/model/builder/dmp/DmpBuilder.java @@ -32,6 +32,7 @@ import org.opencdmp.model.dmpblueprint.DmpBlueprint; import org.opencdmp.model.dmpreference.DmpReference; import org.opencdmp.model.user.User; import org.opencdmp.query.*; +import org.opencdmp.service.externalfetcher.config.entities.SourceBaseConfiguration; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @@ -137,7 +138,10 @@ public class DmpBuilder extends BaseBuilder { if (dmpUsersMap != null && !dmpUsersMap.isEmpty() && dmpUsersMap.containsKey(d.getId())) m.setDmpUsers(dmpUsersMap.get(d.getId())); if (descriptionsMap != null && !descriptionsMap.isEmpty() && descriptionsMap.containsKey(d.getId())) m.setDescriptions(descriptionsMap.get(d.getId())); if (dmpDescriptionTemplatesMap != null && !dmpDescriptionTemplatesMap.isEmpty() && dmpDescriptionTemplatesMap.containsKey(d.getId())) m.setDmpDescriptionTemplates(dmpDescriptionTemplatesMap.get(d.getId())); - if (otherDmpVersionsMap != null && !otherDmpVersionsMap.isEmpty() && otherDmpVersionsMap.containsKey(d.getGroupId())) m.setOtherDmpVersions(otherDmpVersionsMap.get(d.getGroupId())); + if (otherDmpVersionsMap != null && !otherDmpVersionsMap.isEmpty() && otherDmpVersionsMap.containsKey(d.getGroupId())) { + m.setOtherDmpVersions(otherDmpVersionsMap.get(d.getGroupId())); + m.getOtherDmpVersions().sort(Comparator.comparing(Dmp::getVersion)); + } if (!propertiesFields.isEmpty() && d.getProperties() != null){ DmpPropertiesEntity propertyDefinition = this.jsonHandlingService.fromJsonSafe(DmpPropertiesEntity.class, d.getProperties()); m.setProperties(this.builderFactory.builder(DmpPropertiesBuilder.class).authorize(this.authorize).build(propertiesFields, propertyDefinition)); @@ -310,7 +314,7 @@ public class DmpBuilder extends BaseBuilder { DmpQuery query = this.queryFactory.query(DmpQuery.class).disableTracking().authorize(this.authorize).groupIds(data.stream().map(DmpEntity::getGroupId).distinct().collect(Collectors.toList())); itemMap = this.builderFactory.builder(DmpBuilder.class).authorize(this.authorize).asMasterKey(query, clone, Dmp::getGroupId); - if (!fields.hasField(this.asIndexer(Dmp._otherDmpVersions, Dmp._id))) { + if (!fields.hasField(Dmp._id)) { itemMap.values().stream().flatMap(List::stream).filter(Objects::nonNull).forEach(x -> { x.setId(null); }); diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/descriptionproperties/FieldPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/descriptionproperties/FieldPersist.java index aaa3a2fa0..d87ecc736 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/descriptionproperties/FieldPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/descriptionproperties/FieldPersist.java @@ -136,7 +136,7 @@ public class FieldPersist { .must(() -> !this.isEmpty(item.getTextValue())) .failOn(FieldPersist._textValue).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{FieldPersist._textValue}, LocaleContextHolder.getLocale())), this.spec() - .iff(()-> fieldType.equals(FieldType.FREE_TEXT) && DescriptionStatus.Finalized.equals(this.status) && isVisible && isUrlRequired) + .iff(()-> fieldType.equals(FieldType.FREE_TEXT) && DescriptionStatus.Finalized.equals(this.status) && isVisible && isUrlRequired && (required || !this.isEmpty(item.getTextValue()))) .must(() -> this.isValidURL(item.getTextValue())) .failOn(FieldPersist._textValue).failWith(this.messageSource.getMessage("Validation_UrlRequired", new Object[]{FieldPersist._textValue}, LocaleContextHolder.getLocale())), this.spec() diff --git a/backend/core/src/main/java/org/opencdmp/service/dmp/DmpServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/dmp/DmpServiceImpl.java index 4cfb4c263..7b0c48759 100644 --- a/backend/core/src/main/java/org/opencdmp/service/dmp/DmpServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/dmp/DmpServiceImpl.java @@ -343,7 +343,7 @@ public class DmpServiceImpl implements DmpService { .groupIds(data.getGroupId()); dmpQuery.setOrder(new Ordering().addDescending(Dmp._version)); - previousDmp = dmpQuery.first(); + previousDmp = dmpQuery.collect().get(0); if (previousDmp != null){ if (previousDmp.getStatus().equals(DmpStatus.Finalized)) previousDmp.setVersionStatus(DmpVersionStatus.Current); else previousDmp.setVersionStatus(DmpVersionStatus.NotFinalized); @@ -566,7 +566,8 @@ public class DmpServiceImpl implements DmpService { if (newStatus.equals(DmpStatus.Finalized)) { List latestVersionDmps = this.queryFactory.query(DmpQuery.class) - .versionStatuses(DmpVersionStatus.Current).isActive(IsActive.Active).groupIds(data.getGroupId()).collect(); + .versionStatuses(DmpVersionStatus.Current).excludedIds(data.getId()) + .isActive(IsActive.Active).groupIds(data.getGroupId()).collect(); if (latestVersionDmps.size() > 1) throw new MyValidationException("Multiple previous template found"); DmpEntity oldDmpEntity = latestVersionDmps.stream().findFirst().orElse(null); @@ -1086,11 +1087,28 @@ public class DmpServiceImpl implements DmpService { dmp.setStatus(DmpStatus.Draft); dmp.setUpdatedAt(Instant.now()); - dmp.setVersionStatus(DmpVersionStatus.NotFinalized); this.entityManager.merge(dmp); this.entityManager.flush(); + this.updateVersionStatusAndSave(dmp, DmpStatus.Finalized, dmp.getStatus()); + this.entityManager.flush(); + + DmpQuery dmpQuery = this.queryFactory.query(DmpQuery.class).disableTracking() + .versionStatuses(DmpVersionStatus.Previous) + .excludedIds(dmp.getId()) + .isActive(IsActive.Active) + .groupIds(dmp.getGroupId()); + + dmpQuery.setOrder(new Ordering().addDescending(Dmp._version)); + DmpEntity previousDmp = dmpQuery.collect().get(0); + if (previousDmp != null){ + if (previousDmp.getStatus().equals(DmpStatus.Finalized)) previousDmp.setVersionStatus(DmpVersionStatus.Current); + else previousDmp.setVersionStatus(DmpVersionStatus.NotFinalized); + this.entityManager.merge(previousDmp); + } + this.entityManager.flush(); + this.annotationEntityTouchedIntegrationEventHandler.handleDmp(dmp.getId()); this.sendNotification(dmp); } From 7646fd8daedc0505fdbcba4c8863a00bfc4912ea Mon Sep 17 00:00:00 2001 From: amentis Date: Thu, 16 May 2024 14:46:37 +0300 Subject: [PATCH 3/3] remove valid url backend validator --- .../persist/descriptionproperties/FieldPersist.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/core/src/main/java/org/opencdmp/model/persist/descriptionproperties/FieldPersist.java b/backend/core/src/main/java/org/opencdmp/model/persist/descriptionproperties/FieldPersist.java index d87ecc736..7b2f76dc6 100644 --- a/backend/core/src/main/java/org/opencdmp/model/persist/descriptionproperties/FieldPersist.java +++ b/backend/core/src/main/java/org/opencdmp/model/persist/descriptionproperties/FieldPersist.java @@ -128,17 +128,18 @@ public class FieldPersist { protected List specifications(FieldPersist item) { FieldType fieldType = this.fieldEntity != null && this.fieldEntity.getData() != null ? this.fieldEntity.getData().getFieldType() : FieldType.FREE_TEXT; boolean required = this.fieldEntity != null && this.fieldEntity.getValidations() != null ? this.fieldEntity.getValidations().contains(FieldValidationType.Required) : false; - boolean isUrlRequired = this.fieldEntity != null && this.fieldEntity.getValidations() != null ? this.fieldEntity.getValidations().contains(FieldValidationType.Url) : false; +// boolean isUrlRequired = this.fieldEntity != null && this.fieldEntity.getValidations() != null ? this.fieldEntity.getValidations().contains(FieldValidationType.Url) : false; boolean isVisible = this.fieldEntity != null ? this.visibilityService.isVisible(this.fieldEntity.getId(), this.ordinal) : true; return Arrays.asList( this.spec() .iff(()-> FieldType.isTextType(fieldType) && DescriptionStatus.Finalized.equals(this.status) && isVisible && required) .must(() -> !this.isEmpty(item.getTextValue())) .failOn(FieldPersist._textValue).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{FieldPersist._textValue}, LocaleContextHolder.getLocale())), - this.spec() - .iff(()-> fieldType.equals(FieldType.FREE_TEXT) && DescriptionStatus.Finalized.equals(this.status) && isVisible && isUrlRequired && (required || !this.isEmpty(item.getTextValue()))) - .must(() -> this.isValidURL(item.getTextValue())) - .failOn(FieldPersist._textValue).failWith(this.messageSource.getMessage("Validation_UrlRequired", new Object[]{FieldPersist._textValue}, LocaleContextHolder.getLocale())), + //TODO +// this.spec() +// .iff(()-> fieldType.equals(FieldType.FREE_TEXT) && DescriptionStatus.Finalized.equals(this.status) && isVisible && isUrlRequired && (required || !this.isEmpty(item.getTextValue()))) +// .must(() -> this.isValidURL(item.getTextValue())) +// .failOn(FieldPersist._textValue).failWith(this.messageSource.getMessage("Validation_UrlRequired", new Object[]{FieldPersist._textValue}, LocaleContextHolder.getLocale())), this.spec() .iff(()-> FieldType.isDateType(fieldType) && DescriptionStatus.Finalized.equals(this.status) && isVisible && required) .must(() -> !this.isNull(item.getDateValue()))