diff --git a/.circleci/config.yml b/.circleci/config.yml index f8446b1a0..c573fef52 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,7 +33,8 @@ jobs: docker create -v /etc/newman --name mms_test_configs alpine:3.4 /bin/true docker cp example/. mms_test_configs:/etc/newman docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run crud.postman_collection.json -e test-env.json --delay-request 500 - docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run getAtCommits.postman_collection.json -e test-env.json --delay-request 500 + docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run getAtCommits.postman_collection.json -e test-env.json --delay-request 500 + docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run makeBranchFromCommit.postman_collection.json -e test-env.json --delay-request 500 docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run cameo.postman_collection.json -e test-env.json --delay-request 1000 docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run jupyter.postman_collection.json -e test-env.json --delay-request 500 docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run localauth.postman_collection.json -e test-env.json --delay-request 500 diff --git a/core/src/main/java/org/openmbee/mms/core/services/BranchService.java b/core/src/main/java/org/openmbee/mms/core/services/BranchService.java index 83831dad8..bc61f413d 100644 --- a/core/src/main/java/org/openmbee/mms/core/services/BranchService.java +++ b/core/src/main/java/org/openmbee/mms/core/services/BranchService.java @@ -10,6 +10,7 @@ public interface BranchService { RefsResponse getBranch(String projectId, String id); RefJson createBranch(String projectId, RefJson branch); + RefJson createBranchfromCommit(String projectId, RefJson branch, NodeService nodeService); RefsResponse deleteBranch(String projectId, String id); } diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java index 851701b83..598821731 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java @@ -96,9 +96,7 @@ public RefsResponse createRefs( if (branch.getParentCommitId() == null || branch.getParentCommitId().isEmpty()) { res = branchService.createBranch(projectId, branch); } else { - //TODO implement branching from historical commit - response.addRejection(new Rejection(branch, 400, "Branching from historical commits is not implemented.")); - continue; + res = branchService.createBranchfromCommit(projectId, branch, getNodeService(projectId)); } permissionService.initBranchPerms(projectId, branch.getId(), true, auth.getName()); diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultBranchService.java b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultBranchService.java index 365bc3379..7abc363f1 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultBranchService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultBranchService.java @@ -5,11 +5,7 @@ import org.openmbee.mms.core.config.Constants; import org.openmbee.mms.core.config.ContextHolder; import org.openmbee.mms.core.config.Formats; -import org.openmbee.mms.core.dao.BranchDAO; -import org.openmbee.mms.core.dao.BranchIndexDAO; -import org.openmbee.mms.core.dao.CommitDAO; -import org.openmbee.mms.core.dao.NodeDAO; -import org.openmbee.mms.core.dao.NodeIndexDAO; +import org.openmbee.mms.core.dao.*; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.DeletedException; import org.openmbee.mms.core.exceptions.InternalErrorException; @@ -18,9 +14,11 @@ import org.openmbee.mms.core.objects.RefsResponse; import org.openmbee.mms.core.services.BranchService; import org.openmbee.mms.core.services.EventService; +import org.openmbee.mms.core.services.NodeService; import org.openmbee.mms.data.domains.scoped.Branch; import org.openmbee.mms.data.domains.scoped.Commit; import org.openmbee.mms.data.domains.scoped.Node; +import org.openmbee.mms.json.ElementJson; import org.openmbee.mms.json.RefJson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,6 +43,15 @@ public class DefaultBranchService implements BranchService { private NodeIndexDAO nodeIndex; protected Collection eventPublisher; + protected NodeGetHelper nodeGetHelper; + + + + @Autowired + public void setNodeGetHelper(NodeGetHelper nodeGetHelper) { + this.nodeGetHelper = nodeGetHelper; + } + @Autowired public void setBranchRepository(BranchDAO branchRepository) { @@ -130,6 +137,7 @@ public RefJson createBranch(String projectId, RefJson branch) { branch.setDeleted(false); branch.setProjectId(projectId); branch.setStatus("created"); + boolean fromCommit = branch.getParentCommitId() == null ? false : true; if (branch.getDocId() == null || branch.getDocId().isEmpty()) { String docId = branchIndex.createDocId(branch); @@ -145,18 +153,26 @@ public RefJson createBranch(String projectId, RefJson branch) { b.setParentRefId(Constants.MASTER_BRANCH); } - //This service cannot create branches from historic versions - if (branch.getParentCommitId() != null) { - throw new BadRequestException("Internal Error: Invalid branch creation logic."); - } - Optional refOption = branchRepository.findByBranchId(b.getParentRefId()); if (refOption.isPresent()) { - Optional parentCommit = commitRepository.findLatestByRef(refOption.get()); - parentCommit.ifPresent(parent -> { - b.setParentCommit(parent.getId()); - branch.setParentCommitId(parent.getCommitId()); //commit id is same as its docId - }); + if(branch.getParentCommitId() != null){ + Optional commitRequestId = commitRepository.findByCommitId(branch.getParentCommitId()); + commitRequestId.ifPresentOrElse(commit -> { + Optional parentCommit = commitRepository.findByRefAndTimestamp(refOption.get(), commit.getTimestamp()); + parentCommit.ifPresent(parent -> { + b.setParentCommit(parent.getId()); + branch.setParentCommitId(parent.getCommitId()); + }); + }, + () -> { throw new BadRequestException(new RefsResponse().addMessage("parentCommitId not found " + now.toString())); + }); + } else { + Optional parentCommit = commitRepository.findLatestByRef(refOption.get()); + parentCommit.ifPresent(parent -> { + b.setParentCommit(parent.getId()); + branch.setParentCommitId(parent.getCommitId()); //commit id is same as its docId + }); + } } if (b.getParentCommit() == null) { @@ -167,11 +183,14 @@ public RefJson createBranch(String projectId, RefJson branch) { try { branchIndex.update(branch); branchRepository.save(b); - Set docIds = new HashSet<>(); - for (Node n: nodeRepository.findAllByDeleted(false)) { - docIds.add(n.getDocId()); + if(!fromCommit) { + Set docIds = new HashSet<>(); + for (Node n: nodeRepository.findAllByDeleted(false)) { + docIds.add(n.getDocId()); + } + + try { nodeIndex.addToRef(docIds); } catch(Exception e) {} } - try { nodeIndex.addToRef(docIds); } catch(Exception e) {} eventPublisher.forEach((pub) -> pub.publish( EventObject.create(projectId, branch.getId(), "branch_created", branch))); return branch; @@ -181,6 +200,60 @@ public RefJson createBranch(String projectId, RefJson branch) { throw new InternalErrorException(e); } } + public RefJson createBranchfromCommit(String projectId, RefJson parentCommitIdRef, NodeService nodeService) { + Instant now = Instant.now(); + + if(parentCommitIdRef.getParentCommitId().isEmpty()){ + throw new BadRequestException(new RefsResponse().addMessage("parentCommitId not provided " + now.toString())); + } + + ContextHolder.setContext(projectId); + Optional parentCommit = commitRepository.findByCommitId(parentCommitIdRef.getParentCommitId()); + + // Get Commit object + String parentCommitID = parentCommit.map(Commit::getCommitId).orElseThrow(() -> + new BadRequestException(new RefsResponse().addMessage("parentCommitId not found " + now.toString()))); + + // Get Commit parentRef, the branch will be created from this + String parentRef = parentCommit.map(Commit::getBranchId).orElseThrow(() -> + new BadRequestException(new RefsResponse().addMessage("Ref from parentCommitId not found" + now.toString()))); + + parentCommitIdRef.setParentRefId(parentRef); + ContextHolder.setContext(projectId, parentRef); + + RefJson branchFromCommit = this.createBranch(projectId, parentCommitIdRef); + + ContextHolder.setContext(projectId, branchFromCommit.getId()); + + // Get current nodes from database + List nodes = nodeRepository.findAll(); + // Get elements from index + Collection result = nodeGetHelper.processGetJsonFromNodes(nodes, parentCommitID, nodeService) + .getActiveElementMap().values(); + + Map nodeCommitData = new HashMap<>(); + for (ElementJson element : result) { + nodeCommitData.put(element.getId(), element); + } + + // Update database table to match index + Set docIds = new HashSet<>(); + for (Node node : nodes) { + if(nodeCommitData.containsKey(node.getNodeId())){ + node.setDocId(nodeCommitData.get(node.getNodeId()).getDocId()); + node.setLastCommit(nodeCommitData.get(node.getNodeId()).getCommitId()); + node.setDeleted(false); + docIds.add(node.getDocId()); + } else { + node.setDeleted(true); + } + } + nodeRepository.updateAll(nodes); + + try { nodeIndex.addToRef(docIds); } catch(Exception e) {} + + return branchFromCommit; + } public RefsResponse deleteBranch(String projectId, String id) { ContextHolder.setContext(projectId); diff --git a/example/makeBranchFromCommit.postman_collection.json b/example/makeBranchFromCommit.postman_collection.json new file mode 100644 index 000000000..817a46cf2 --- /dev/null +++ b/example/makeBranchFromCommit.postman_collection.json @@ -0,0 +1,652 @@ +{ + "info": { + "_postman_id": "1ddbdc4a-79cc-4bec-8118-7e600e908b38", + "name": "MakeBranchFromCommit", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "3693486" + }, + "item": [ + { + "name": "login using admin", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + " ", + "});", + "", + "pm.test(\"response has token\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.token).to.be.a('string');", + " pm.environment.set(\"token\", jsonData.token);", + "", + "});", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"username\": \"{{adminUsername}}\",\n\t\"password\": \"{{adminPassword}}\"\n}" + }, + "url": { + "raw": "{{host}}/authentication", + "host": [ + "{{host}}" + ], + "path": [ + "authentication" + ] + } + }, + "response": [] + }, + { + "name": "add org", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has org branch_from_commit\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.orgs[0].id).to.eql('branch_from_commit_org');", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"orgs\": [\n\t\t{\n\t\t\t\"id\": \"branch_from_commit_org\",\n\t\t\t\"name\": \"branch_from_commit_org\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/orgs", + "host": [ + "{{host}}" + ], + "path": [ + "orgs" + ] + } + }, + "response": [] + }, + { + "name": "add project", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has project branch_from_commit\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.projects[0].id).to.eql('branch_from_commit');", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"projects\": [\n\t\t{\n\t\t\t\"id\": \"branch_from_commit\", \n\t\t\t\"name\": \"branch_from_commit\",\n\t\t\t\"orgId\": \"branch_from_commit_org\",\n\t\t\t\"schema\": \"default\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects", + "host": [ + "{{host}}" + ], + "path": [ + "projects" + ] + } + }, + "response": [] + }, + { + "name": "add a and b to master", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has elements\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.elements.length).to.eql(2);", + "});", + "", + "pm.environment.set(\"addABCommitId\", pm.response.json().commitId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"elements\": [\n\t\t{\n\t\t\t\"id\": \"a\",\n\t\t\t\"name\": \"a\"\n\t\t}, {\n\t\t\t\"id\": \"b\", \n\t\t\t\"name\": \"b\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/master/elements", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "master", + "elements" + ] + } + }, + "response": [] + }, + { + "name": "add c to master", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has elements\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.elements.length).to.eql(1);", + "});", + "", + "pm.environment.set(\"addCCommitId\", pm.response.json().commitId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"elements\": [\n\t\t{\n\t\t\t\"id\": \"c\",\n\t\t\t\"name\": \"c\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/master/elements", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "master", + "elements" + ] + } + }, + "response": [] + }, + { + "name": "add d to master", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has elements\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.elements.length).to.eql(1);", + "});", + "", + "pm.environment.set(\"addDCommitId\", pm.response.json().commitId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"elements\": [\n\t\t{\n\t\t\t\"id\": \"d\",\n\t\t\t\"name\": \"d\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/master/elements", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "master", + "elements" + ] + } + }, + "response": [] + }, + { + "name": "delete a in refa", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has elements\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.elements.length).to.eql(1);", + "});", + "", + "pm.environment.set(\"deleteACommitId\", pm.response.json().commitId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/master/elements/a", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "master", + "elements", + "a" + ] + } + }, + "response": [] + }, + { + "name": "update b in refa", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has elements\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.elements.length).to.eql(1);", + "});", + "", + "pm.environment.set(\"updateBCommitId\", pm.response.json().commitId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"elements\": [\n {\n\t\t\t\"id\": \"b\", \n\t\t\t\"name\": \"b updated\"\n\t\t}\n\t]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/master/elements", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "master", + "elements" + ] + } + }, + "response": [] + }, + { + "name": "delete c in refa", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has elements\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.elements.length).to.eql(1);", + "});", + "", + "pm.environment.set(\"deleteCCommitId\", pm.response.json().commitId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"elements\": [\n {\n\t\t\t\"id\": \"c\"\n\t\t}\n\t]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/master/elements", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "master", + "elements" + ] + } + }, + "response": [] + }, + { + "name": "add e to refa", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has elements\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.elements.length).to.eql(1);", + "});", + "", + "pm.environment.set(\"addECommitId\", pm.response.json().commitId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"elements\": [\n\t\t{\n\t\t\t\"id\": \"e\",\n\t\t\t\"name\": \"e\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/master/elements", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "master", + "elements" + ] + } + }, + "response": [] + }, + { + "name": "recurrect c in refa", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has elements\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.elements.length).to.eql(1);", + "});", + "", + "pm.environment.set(\"resurrectCCommitId\", pm.response.json().commitId);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"elements\": [\n\t\t{\n\t\t\t\"id\": \"c\",\n\t\t\t\"name\": \"c\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/master/elements", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "master", + "elements" + ] + } + }, + "response": [] + }, + { + "name": "create branch from \"add c to master\"", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"branch created with right parentRef and commit id\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.refs[0].id).to.eql('addCCommitId_branch');", + " pm.expect(jsonData.refs[0].parentRefId).to.eql('master');", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"refs\": [\n\t\t{\n\t\t\t\"id\": \"addCCommitId_branch\",\n\t\t\t\"name\": \"addCCommitId_branch\",\n\t\t\t\"type\": \"Branch\",\n\t\t\t\"parentCommitId\": \"{{addCCommitId}}\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs" + ] + } + }, + "response": [] + }, + { + "name": "get elements from branch from \"add c to master\"", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has a,b,c\", function () {", + " var jsonData = pm.response.json();", + "", + " pm.expect(jsonData.elements.length).to.eql(3);", + " var result = jsonData.elements.map(e => ({id: e.id}));", + " pm.expect(result).to.deep.have.members([{id: 'a'}, {id: 'b'}, {id: 'c'}]);", + " pm.expect(jsonData.commitId).to.eql(pm.environment.get('addCCommitId'));", + " ", + "})", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/addCCommitId_branch/elements", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "addCCommitId_branch", + "elements" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{token}}", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] +} \ No newline at end of file