diff --git a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/api/WorkflowDBAdaptor.java b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/api/WorkflowDBAdaptor.java index a50d88c409..45e4e52af0 100644 --- a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/api/WorkflowDBAdaptor.java +++ b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/api/WorkflowDBAdaptor.java @@ -22,6 +22,7 @@ enum QueryParams implements QueryParam { ID("id", TEXT, ""), UID("uid", LONG, ""), UUID("uuid", TEXT, ""), + NAME("name", TEXT, ""), DESCRIPTION("description", TEXT, ""), DRAFT("draft", BOOLEAN, ""), TYPE("type", TEXT, ""), diff --git a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/FamilyMongoDBAdaptor.java b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/FamilyMongoDBAdaptor.java index 9e8c35d00a..163e3ad811 100644 --- a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/FamilyMongoDBAdaptor.java +++ b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/FamilyMongoDBAdaptor.java @@ -228,7 +228,7 @@ private Family insert(ClientSession clientSession, long studyUid, Family family, familyDocument.put(PERMISSION_RULES_APPLIED, Collections.emptyList()); logger.debug("Inserting family '{}' ({})...", family.getId(), family.getUid()); - versionedMongoDBAdaptor.insert(clientSession, familyDocument); + versionedMongoDBAdaptor.insert(clientSession, familyDocument, family.getRelease()); logger.debug("Family '{}' successfully inserted", family.getId()); // Add family reference to the members diff --git a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/IndividualMongoDBAdaptor.java b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/IndividualMongoDBAdaptor.java index 13dbb40741..2823f04ef9 100644 --- a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/IndividualMongoDBAdaptor.java +++ b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/IndividualMongoDBAdaptor.java @@ -197,7 +197,7 @@ Individual insert(ClientSession clientSession, long studyId, Individual individu individualDocument.put(PERMISSION_RULES_APPLIED, Collections.emptyList()); logger.debug("Inserting individual '{}' ({})...", individual.getId(), individual.getUid()); - versionedMongoDBAdaptor.insert(clientSession, individualDocument); + versionedMongoDBAdaptor.insert(clientSession, individualDocument, individual.getRelease()); logger.debug("Individual '{}' successfully inserted", individual.getId()); if (individual.getSamples() != null && !individual.getSamples().isEmpty()) { diff --git a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/InterpretationMongoDBAdaptor.java b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/InterpretationMongoDBAdaptor.java index 3d78516dd5..090c10d0e5 100644 --- a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/InterpretationMongoDBAdaptor.java +++ b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/InterpretationMongoDBAdaptor.java @@ -265,7 +265,7 @@ Interpretation insert(ClientSession clientSession, long studyId, Interpretation interpretationObject.put(LAST_OF_VERSION, true); interpretationObject.put(LAST_OF_RELEASE, true); - versionedMongoDBAdaptor.insert(clientSession, interpretationObject); + versionedMongoDBAdaptor.insert(clientSession, interpretationObject, interpretation.getRelease()); return interpretation; } diff --git a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/PanelMongoDBAdaptor.java b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/PanelMongoDBAdaptor.java index d82535b935..0f2efa3a9a 100644 --- a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/PanelMongoDBAdaptor.java +++ b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/PanelMongoDBAdaptor.java @@ -135,7 +135,7 @@ void insert(ClientSession clientSession, long studyUid, Panel panel) throws Cata logger.debug("Inserting panel '{}' ({})", panel.getId(), panel.getUid()); Document panelDocument = getPanelDocumentForInsertion(clientSession, panel, studyUid); - versionedMongoDBAdaptor.insert(clientSession, panelDocument); + versionedMongoDBAdaptor.insert(clientSession, panelDocument, panel.getRelease()); logger.info("Panel '" + panel.getId() + "(" + panel.getUid() + ")' successfully created"); } diff --git a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/SampleMongoDBAdaptor.java b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/SampleMongoDBAdaptor.java index 198c9fce45..63dec1c6c5 100644 --- a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/SampleMongoDBAdaptor.java +++ b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/SampleMongoDBAdaptor.java @@ -187,7 +187,7 @@ Sample insert(ClientSession clientSession, long studyUid, Sample sample, List 0) { diff --git a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/SnapshotVersionedMongoDBAdaptor.java b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/SnapshotVersionedMongoDBAdaptor.java index 8a4ac1ed73..32f08943d4 100644 --- a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/SnapshotVersionedMongoDBAdaptor.java +++ b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/SnapshotVersionedMongoDBAdaptor.java @@ -136,11 +136,12 @@ DBIterator iterator(ClientSession session, Query query, QueryOptions options) throws CatalogDBException, CatalogParameterException, CatalogAuthorizationException; } - protected void insert(ClientSession session, Document document) { + protected void insert(ClientSession session, Document document, int release) { String uuid = getClientSessionUuid(session); document.put(VERSION, 1); document.put(LAST_OF_VERSION, true); document.put(LAST_OF_RELEASE, true); + document.put(RELEASE_FROM_VERSION, Collections.singletonList(release)); document.put(PRIVATE_TRANSACTION_ID, uuid); collection.insert(session, document, QueryOptions.empty()); archiveCollection.insert(session, document, QueryOptions.empty()); diff --git a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/WorkflowMongoDBAdaptor.java b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/WorkflowMongoDBAdaptor.java index 0030434104..fda0f04617 100644 --- a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/WorkflowMongoDBAdaptor.java +++ b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/db/mongodb/WorkflowMongoDBAdaptor.java @@ -83,6 +83,7 @@ Workflow insert(ClientSession clientSession, long studyUid, Workflow workflow) t long uid = getNewUid(clientSession); workflow.setUid(uid); workflow.setStudyUid(studyUid); + workflow.setRelease(dbAdaptorFactory.getCatalogStudyDBAdaptor().getCurrentRelease(clientSession, studyUid)); Document workflowObject = workflowConverter.convertToStorageType(workflow); @@ -92,7 +93,7 @@ Workflow insert(ClientSession clientSession, long studyUid, Workflow workflow) t ? TimeUtils.toDate(workflow.getModificationDate()) : TimeUtils.getDate()); logger.debug("Inserting workflow '{}' ({})...", workflow.getId(), workflow.getUid()); - versionedMongoDBAdaptor.insert(clientSession, workflowObject); + versionedMongoDBAdaptor.insert(clientSession, workflowObject, workflow.getRelease()); logger.debug("Workflow '{}' successfully inserted", workflow.getId()); return workflow; @@ -323,7 +324,7 @@ private UpdateDocument parseAndValidateUpdateParams(ObjectMap parameters, QueryO final String[] acceptedBooleanParams = {QueryParams.DRAFT.key()}; filterBooleanParams(parameters, document.getSet(), acceptedBooleanParams); - final String[] acceptedParams = {QueryParams.DESCRIPTION.key(), QueryParams.COMMAND_LINE.key()}; + final String[] acceptedParams = {QueryParams.NAME.key(), QueryParams.DESCRIPTION.key(), QueryParams.COMMAND_LINE.key()}; filterStringParams(parameters, document.getSet(), acceptedParams); final String[] acceptedMapParams = {QueryParams.ATTRIBUTES.key()}; @@ -513,6 +514,7 @@ protected Bson parseQuery(Query query, Document extraQuery, String user) case RELEASE: case VERSION: case TYPE: + case DRAFT: addAutoOrQuery(queryParam.key(), queryParam.key(), queryCopy, queryParam.type(), andBsonList); break; default: diff --git a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/managers/WorkflowManager.java b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/managers/WorkflowManager.java index 85bb74105f..7f36e4597a 100644 --- a/opencga-catalog/src/main/java/org/opencb/opencga/catalog/managers/WorkflowManager.java +++ b/opencga-catalog/src/main/java/org/opencb/opencga/catalog/managers/WorkflowManager.java @@ -175,7 +175,7 @@ public OpenCGAResult update(String studyStr, String workflowId, Workfl ObjectMap updateMap = null; try { if (updateParams != null) { - updateMap = new ObjectMap(getUpdateObjectMapper().writeValueAsString(this)); + updateMap = new ObjectMap(getUpdateObjectMapper().writeValueAsString(updateParams)); } } catch (JsonProcessingException e) { throw new CatalogException("Could not parse WorkflowUpdateParams object: " + e.getMessage(), e); @@ -548,7 +548,13 @@ private WorkflowDBAdaptor.QueryParams getFieldFilter(List idList) throws private void validateNewWorkflow(Workflow workflow) throws CatalogParameterException { ParamUtils.checkIdentifier(workflow.getId(), ID.key()); - ParamUtils.checkObj(workflow.getType(), TYPE.key()); + if (Workflow.Type.values().length > 1) { + ParamUtils.checkObj(workflow.getType(), TYPE.key()); + } else if (workflow.getType() == null) { + // TODO: Remove this condition in the future once we know we support more than one type. + // If there is only one valid type, we set it + workflow.setType(Workflow.Type.NEXTFLOW); + } workflow.setScripts(workflow.getScripts() != null ? workflow.getScripts() : Collections.emptyList()); boolean main = false; for (WorkflowScript script : workflow.getScripts()) { @@ -566,7 +572,11 @@ private void validateNewWorkflow(Workflow workflow) throws CatalogParameterExcep } workflow.setRepository(workflow.getRepository() != null ? workflow.getRepository() : new WorkflowRepository("")); if (StringUtils.isEmpty(workflow.getRepository().getImage()) && CollectionUtils.isEmpty(workflow.getScripts())) { - throw new CatalogParameterException("No docker image or scripts found."); + throw new CatalogParameterException("No repository image or scripts found."); + } + if (StringUtils.isNotEmpty(workflow.getRepository().getImage()) && CollectionUtils.isNotEmpty(workflow.getScripts())) { + throw new CatalogParameterException("Both repository image and scripts found. Please, either add scripts or a repository" + + " image."); } workflow.setName(ParamUtils.defaultString(workflow.getName(), workflow.getId())); diff --git a/opencga-catalog/src/test/java/org/opencb/opencga/catalog/managers/WorkflowManagerTest.java b/opencga-catalog/src/test/java/org/opencb/opencga/catalog/managers/WorkflowManagerTest.java new file mode 100644 index 0000000000..8f5f58e7ef --- /dev/null +++ b/opencga-catalog/src/test/java/org/opencb/opencga/catalog/managers/WorkflowManagerTest.java @@ -0,0 +1,130 @@ +package org.opencb.opencga.catalog.managers; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.opencb.commons.datastore.core.Query; +import org.opencb.commons.datastore.core.QueryOptions; +import org.opencb.opencga.catalog.db.api.WorkflowDBAdaptor; +import org.opencb.opencga.catalog.exceptions.CatalogException; +import org.opencb.opencga.core.models.workflow.Workflow; +import org.opencb.opencga.core.models.workflow.WorkflowRepository; +import org.opencb.opencga.core.models.workflow.WorkflowScript; +import org.opencb.opencga.core.models.workflow.WorkflowUpdateParams; +import org.opencb.opencga.core.response.OpenCGAResult; +import org.opencb.opencga.core.testclassification.duration.MediumTests; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.*; + +@Category(MediumTests.class) +public class WorkflowManagerTest extends AbstractManagerTest { + + private WorkflowManager workflowManager; + + @Before + public void setUp() throws Exception { + super.setUp(); + workflowManager = catalogManager.getWorkflowManager(); + } + + @Test + public void createWorkflowTest() throws CatalogException { + Workflow workflow = new Workflow() + .setId("workflow") + .setScripts(Collections.singletonList(new WorkflowScript("pipeline.nf", "echo 'Hello world!'", true))); + OpenCGAResult result = workflowManager.create(studyFqn, workflow, INCLUDE_RESULT, ownerToken); + assertEquals(1, result.getNumResults()); + assertNotNull(result.first()); + assertEquals(workflow.getId(), result.first().getId()); + + // Add repository to workflow + workflow.setId("workflow2"); + workflow.setRepository(new WorkflowRepository("blabla")); + CatalogException catalogException = assertThrows(CatalogException.class, + () -> workflowManager.create(studyFqn, workflow, INCLUDE_RESULT, ownerToken)); + assertTrue(catalogException.getMessage().contains("script") && catalogException.getMessage().contains("repository")); + + // Remove script from workflow + workflow.setScripts(Collections.emptyList()); + result = workflowManager.create(studyFqn, workflow, INCLUDE_RESULT, ownerToken); + assertEquals(1, result.getNumResults()); + assertNotNull(result.first()); + assertEquals(workflow.getId(), result.first().getId()); + + // Remove script and add two scripts with 2 mains + workflow.setId("workflow3"); + workflow.setRepository(null); + workflow.setScripts(Arrays.asList( + new WorkflowScript("script1", "echo 'Hello'", true), + new WorkflowScript("script2", "echo 'World'", true) + )); + catalogException = assertThrows(CatalogException.class, + () -> workflowManager.create(studyFqn, workflow, INCLUDE_RESULT, ownerToken)); + assertTrue(catalogException.getMessage().contains("script") && catalogException.getMessage().contains("main")); + + // Add one single script without main + workflow.setScripts(Collections.singletonList( + new WorkflowScript("script1", "echo 'Hello'", false) + )); + catalogException = assertThrows(CatalogException.class, + () -> workflowManager.create(studyFqn, workflow, INCLUDE_RESULT, ownerToken)); + assertTrue(catalogException.getMessage().contains("script") && catalogException.getMessage().contains("main")); + } + + @Test + public void workflowSearchTest() throws CatalogException { + Workflow workflow = new Workflow() + .setId("workflow") + .setScripts(Collections.singletonList(new WorkflowScript("pipeline.nf", "echo 'Hello world!'", true))); + workflowManager.create(studyFqn, workflow, INCLUDE_RESULT, ownerToken); + + workflow = new Workflow() + .setId("workflow2") + .setDraft(true) + .setScripts(Collections.singletonList(new WorkflowScript("pipeline.nf", "echo 'Hello world!'", true))); + workflowManager.create(studyFqn, workflow, INCLUDE_RESULT, ownerToken); + + OpenCGAResult search = workflowManager.search(studyFqn, new Query(), QueryOptions.empty(), ownerToken); + assertEquals(2, search.getNumResults()); + + Query query = new Query(WorkflowDBAdaptor.QueryParams.DRAFT.key(), true); + search = workflowManager.search(studyFqn, query, QueryOptions.empty(), ownerToken); + assertEquals(1, search.getNumResults()); + assertEquals("workflow2", search.first().getId()); + assertTrue(search.first().isDraft()); + + query = new Query(WorkflowDBAdaptor.QueryParams.DRAFT.key(), false); + search = workflowManager.search(studyFqn, query, QueryOptions.empty(), ownerToken); + assertEquals(1, search.getNumResults()); + assertEquals("workflow", search.first().getId()); + assertFalse(search.first().isDraft()); + } + + @Test + public void updateWorkflowTest() throws CatalogException { + Workflow workflow = new Workflow() + .setId("workflow") + .setScripts(Collections.singletonList(new WorkflowScript("pipeline.nf", "echo 'Hello world!'", true))); + workflowManager.create(studyFqn, workflow, INCLUDE_RESULT, ownerToken); + + WorkflowUpdateParams updateParams = new WorkflowUpdateParams() + .setName("newName") + .setDraft(true) + .setCreationDate("20240101000000") + .setModificationDate("20240201000000") + .setDescription("description"); + + OpenCGAResult update = workflowManager.update(studyFqn, workflow.getId(), updateParams, INCLUDE_RESULT, ownerToken); + assertEquals(1, update.getNumUpdated()); + Workflow updatedWorkflow = update.first(); + assertEquals(updateParams.getName(), updatedWorkflow.getName()); + assertEquals(updateParams.isDraft(), updatedWorkflow.isDraft()); + assertEquals(updateParams.getCreationDate(), updatedWorkflow.getCreationDate()); + assertEquals(updateParams.getModificationDate(), updatedWorkflow.getModificationDate()); + assertEquals(updateParams.getDescription(), updatedWorkflow.getDescription()); + } + +} diff --git a/opencga-core/src/main/java/org/opencb/opencga/core/common/JacksonUtils.java b/opencga-core/src/main/java/org/opencb/opencga/core/common/JacksonUtils.java index e5f542454e..6b8ca6ee2b 100644 --- a/opencga-core/src/main/java/org/opencb/opencga/core/common/JacksonUtils.java +++ b/opencga-core/src/main/java/org/opencb/opencga/core/common/JacksonUtils.java @@ -45,6 +45,7 @@ import org.opencb.opencga.core.models.sample.Sample; import org.opencb.opencga.core.models.study.Study; import org.opencb.opencga.core.models.study.VariableSet; +import org.opencb.opencga.core.models.workflow.Workflow; import javax.ws.rs.ext.ContextResolver; import java.io.IOException; @@ -109,6 +110,7 @@ private static ObjectMapper generateUpdateObjectMapper(JsonFactory jf) { objectMapper.addMixIn(VariableSet.class, PrivateUidMixin.class); objectMapper.addMixIn(ClinicalAnalysis.class, PrivateUidMixin.class); objectMapper.addMixIn(Interpretation.class, PrivateUidMixin.class); + objectMapper.addMixIn(Workflow.class, PrivateUidMixin.class); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); @@ -143,6 +145,7 @@ private static ObjectMapper generateOpenCGAObjectMapper(JsonFactory jf) { objectMapper.addMixIn(VariableSet.class, PrivateUidMixin.class); objectMapper.addMixIn(ClinicalAnalysis.class, PrivateUidMixin.class); objectMapper.addMixIn(Interpretation.class, PrivateUidMixin.class); + objectMapper.addMixIn(Workflow.class, PrivateUidMixin.class); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return objectMapper; diff --git a/opencga-core/src/main/java/org/opencb/opencga/core/models/workflow/Workflow.java b/opencga-core/src/main/java/org/opencb/opencga/core/models/workflow/Workflow.java index 6374b55ebc..759255bc3a 100644 --- a/opencga-core/src/main/java/org/opencb/opencga/core/models/workflow/Workflow.java +++ b/opencga-core/src/main/java/org/opencb/opencga/core/models/workflow/Workflow.java @@ -30,6 +30,9 @@ public class Workflow extends PrivateStudyUid { @DataField(id = "version", managed = true, indexed = true, description = FieldConstants.GENERIC_VERSION_DESCRIPTION) private int version; + @DataField(id = "release", managed = true, indexed = true, description = FieldConstants.GENERIC_RELEASE_DESCRIPTION) + private int release; + @DataField(id = "type", description = FieldConstants.WORKFLOW_TYPE_DESCRIPTION) private Type type; @@ -83,6 +86,7 @@ public String toString() { sb.append(", description='").append(description).append('\''); sb.append(", draft=").append(draft); sb.append(", version=").append(version); + sb.append(", release=").append(release); sb.append(", type=").append(type); sb.append(", repository=").append(repository); sb.append(", scripts=").append(scripts); @@ -139,6 +143,15 @@ public Workflow setVersion(int version) { return this; } + public int getRelease() { + return release; + } + + public Workflow setRelease(int release) { + this.release = release; + return this; + } + public Type getType() { return type; }