diff --git a/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/QuestionSetActor.scala b/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/QuestionSetActor.scala index 59933cc4f..f5b5742d9 100644 --- a/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/QuestionSetActor.scala +++ b/assessment-api/assessment-actors/src/main/scala/org/sunbird/actors/QuestionSetActor.scala @@ -54,8 +54,8 @@ class QuestionSetActor @Inject()(implicit oec: OntologyEngineContext) extends Ba request.getRequest.put("mode", "edit") AssessmentManager.getValidatedNodeForReview(request, "ERR_QUESTION_SET_REVIEW").flatMap(node => { AssessmentManager.getQuestionSetHierarchy(request, node).flatMap(hierarchyString => { - AssessmentManager.validateQuestionSetHierarchy(hierarchyString.asInstanceOf[String]) - val (updatedHierarchy, nodeIds) = AssessmentManager.updateHierarchy(hierarchyString.asInstanceOf[String], "Review") + AssessmentManager.validateQuestionSetHierarchy(hierarchyString.asInstanceOf[String], node.getMetadata.getOrDefault("createdBy", "").asInstanceOf[String]) + val (updatedHierarchy, nodeIds) = AssessmentManager.updateHierarchy(hierarchyString.asInstanceOf[String], "Review", node.getMetadata.getOrDefault("createdBy", "").asInstanceOf[String]) val updateReq = new Request(request) val date = DateUtils.formatCurrentDate updateReq.putAll(Map("identifiers" -> nodeIds, "metadata" -> Map("status" -> "Review", "prevStatus" -> node.getMetadata.get("status"), "lastStatusChangedOn" -> date, "lastUpdatedOn" -> date).asJava).asJava) @@ -66,9 +66,10 @@ class QuestionSetActor @Inject()(implicit oec: OntologyEngineContext) extends Ba def publish(request: Request): Future[Response] = { request.getRequest.put("identifier", request.getContext.get("identifier")) + request.put("mode", "edit") AssessmentManager.getValidatedNodeForPublish(request, "ERR_QUESTION_SET_PUBLISH").flatMap(node => { AssessmentManager.getQuestionSetHierarchy(request, node).map(hierarchyString => { - AssessmentManager.validateQuestionSetHierarchy(hierarchyString.asInstanceOf[String]) + AssessmentManager.validateQuestionSetHierarchy(hierarchyString.asInstanceOf[String], node.getMetadata.getOrDefault("createdBy", "").asInstanceOf[String]) AssessmentManager.pushInstructionEvent(node.getIdentifier, node) ResponseHandler.OK.putAll(Map[String, AnyRef]("identifier" -> node.getIdentifier.replace(".img", ""), "message" -> "Question is successfully sent for Publish").asJava) }) @@ -93,8 +94,8 @@ class QuestionSetActor @Inject()(implicit oec: OntologyEngineContext) extends Ba request.getRequest.put("mode", "edit") AssessmentManager.getValidateNodeForReject(request, "ERR_QUESTION_SET_REJECT").flatMap(node => { AssessmentManager.getQuestionSetHierarchy(request, node).flatMap(hierarchyString => { - AssessmentManager.validateQuestionSetHierarchy(hierarchyString.asInstanceOf[String]) - val (updatedHierarchy, nodeIds) = AssessmentManager.updateHierarchy(hierarchyString.asInstanceOf[String], "Draft") + AssessmentManager.validateQuestionSetHierarchy(hierarchyString.asInstanceOf[String], node.getMetadata.getOrDefault("createdBy", "").asInstanceOf[String]) + val (updatedHierarchy, nodeIds) = AssessmentManager.updateHierarchy(hierarchyString.asInstanceOf[String], "Draft", node.getMetadata.getOrDefault("createdBy", "").asInstanceOf[String]) val updateReq = new Request(request) val date = DateUtils.formatCurrentDate updateReq.putAll(Map("identifiers" -> nodeIds, "metadata" -> Map("status" -> "Draft", "prevStatus" -> node.getMetadata.get("status"), "lastStatusChangedOn" -> date, "lastUpdatedOn" -> date).asJava).asJava) diff --git a/assessment-api/assessment-actors/src/main/scala/org/sunbird/managers/AssessmentManager.scala b/assessment-api/assessment-actors/src/main/scala/org/sunbird/managers/AssessmentManager.scala index 5a07b0638..f05d14307 100644 --- a/assessment-api/assessment-actors/src/main/scala/org/sunbird/managers/AssessmentManager.scala +++ b/assessment-api/assessment-actors/src/main/scala/org/sunbird/managers/AssessmentManager.scala @@ -20,7 +20,8 @@ import scala.collection.JavaConverters._ object AssessmentManager { - val skipValidation: Boolean = Platform.getBoolean("assessment.skip.validation", true) + val skipValidation: Boolean = Platform.getBoolean("assessment.skip.validation", false) + val validStatus = List("Draft", "Review") def create(request: Request, errCode: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = { val visibility: String = request.getRequest.getOrDefault("visibility", "").asInstanceOf[String] @@ -128,36 +129,34 @@ object AssessmentManager { }) } - def validateQuestionSetHierarchy(hierarchyString: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Unit = { + def validateQuestionSetHierarchy(hierarchyString: String, rootUserId: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Unit = { if (!skipValidation) { val hierarchy = if (!hierarchyString.asInstanceOf[String].isEmpty) { JsonUtils.deserialize(hierarchyString.asInstanceOf[String], classOf[java.util.Map[String, AnyRef]]) } else new java.util.HashMap[String, AnyRef]() val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.List[java.util.Map[String, AnyRef]]] - validateChildrenRecursive(children) + validateChildrenRecursive(children, rootUserId) } } def getQuestionSetHierarchy(request: Request, rootNode: Node)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Any] = { - oec.graphService.readExternalProps(request, List("hierarchy")).flatMap(response => { - if (ResponseHandler.checkError(response) && ResponseHandler.isResponseNotFoundError(response)) { - if (StringUtils.equalsIgnoreCase("Live", rootNode.getMetadata.get("status").asInstanceOf[String])) - throw new ServerException("ERR_QUESTION_SET_REVIEW", "No hierarchy is present in cassandra for identifier:" + rootNode.getIdentifier) - request.put("identifier", if (!rootNode.getIdentifier.endsWith(".img")) rootNode.getIdentifier + ".img" else rootNode.getIdentifier) - oec.graphService.readExternalProps(request, List("hierarchy")).map(resp => { - resp.getResult.toMap.getOrElse("hierarchy", "{}").asInstanceOf[String] - }) recover { case e: ResourceNotFoundException => TelemetryManager.log("No hierarchy is present in cassandra for identifier:" + request.get("identifier")) } - } else Future(response.getResult.toMap.getOrElse("hierarchy", "{}").asInstanceOf[String]) + request.put("rootId", request.get("identifier").asInstanceOf[String]) + HierarchyManager.getUnPublishedHierarchy(request).map(resp => { + if (!ResponseHandler.checkError(resp) && resp.getResponseCode.code() == 200) { + val hierarchy = resp.getResult.get("questionSet").asInstanceOf[util.Map[String, AnyRef]] + JsonUtils.serialize(hierarchy) + } else throw new ServerException("ERR_QUESTION_SET_HIERARCHY", "No hierarchy is present in cassandra for identifier:" + rootNode.getIdentifier) }) } - private def validateChildrenRecursive(children: util.List[util.Map[String, AnyRef]]): Unit = { + private def validateChildrenRecursive(children: util.List[util.Map[String, AnyRef]], rootUserId: String): Unit = { children.toList.foreach(content => { - if (!StringUtils.equalsAnyIgnoreCase(content.getOrDefault("visibility", "").asInstanceOf[String], "Parent") + if ((StringUtils.equalsAnyIgnoreCase(content.getOrDefault("visibility", "").asInstanceOf[String], "Default") + && !StringUtils.equals(rootUserId, content.getOrDefault("createdBy", "").asInstanceOf[String])) && !StringUtils.equalsIgnoreCase(content.getOrDefault("status", "").asInstanceOf[String], "Live")) - throw new ClientException("ERR_QUESTION_SET", "Content with identifier: " + content.get("identifier") + "is not Live. Please Publish it.") - validateChildrenRecursive(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]]) + throw new ClientException("ERR_QUESTION_SET", "Object with identifier: " + content.get("identifier") + " is not Live. Please Publish it.") + validateChildrenRecursive(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]], rootUserId) }) } @@ -169,28 +168,32 @@ object AssessmentManager { (visibilityIdMap.getOrDefault("Default", List()), visibilityIdMap.getOrDefault("Parent", List())) } - def updateHierarchy(hierarchyString: String, status: String): (java.util.Map[String, AnyRef], java.util.List[String]) = { - val hierarchy = if (!hierarchyString.asInstanceOf[String].isEmpty) { + def updateHierarchy(hierarchyString: String, status: String, rootUserId: String): (java.util.Map[String, AnyRef], java.util.List[String]) = { + val hierarchy: java.util.Map[String, AnyRef] = if (!hierarchyString.asInstanceOf[String].isEmpty) { JsonUtils.deserialize(hierarchyString.asInstanceOf[String], classOf[java.util.Map[String, AnyRef]]) } else new java.util.HashMap[String, AnyRef]() + val keys = List("identifier", "children").asJava + hierarchy.keySet().retainAll(keys) val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.List[java.util.Map[String, AnyRef]]] hierarchy.put("status", status) - val childrenToUpdate: List[String] = updateChildrenRecursive(children, status, List()) + val childrenToUpdate: List[String] = updateChildrenRecursive(children, status, List(), rootUserId) (hierarchy, childrenToUpdate.asJava) } - private def updateChildrenRecursive(children: util.List[util.Map[String, AnyRef]], status: String, idList: List[String]): List[String] = { + private def updateChildrenRecursive(children: util.List[util.Map[String, AnyRef]], status: String, idList: List[String], rootUserId: String): List[String] = { children.toList.flatMap(content => { + val objectType = content.getOrDefault("objectType", "").asInstanceOf[String] val updatedIdList: List[String] = - if (StringUtils.equalsAnyIgnoreCase(content.getOrDefault("visibility", "").asInstanceOf[String], "Parent")) { + if (StringUtils.equalsAnyIgnoreCase(content.getOrDefault("visibility", "").asInstanceOf[String], "Parent") || (StringUtils.equalsIgnoreCase( objectType, "Question") && StringUtils.equalsAnyIgnoreCase(content.getOrDefault("visibility", "").asInstanceOf[String], "Default") && validStatus.contains(content.getOrDefault("status", "").asInstanceOf[String]) && StringUtils.equals(rootUserId, content.getOrDefault("createdBy", "").asInstanceOf[String]))) { content.put("lastStatusChangedOn", DateUtils.formatCurrentDate) + content.put("prevStatus", content.getOrDefault("status", "Draft")) content.put("status", status) content.put("prevStatus", "Draft") content.put("lastUpdatedOn", DateUtils.formatCurrentDate) - content.get("identifier").asInstanceOf[String] :: idList + if(StringUtils.equalsAnyIgnoreCase(objectType, "Question")) content.get("identifier").asInstanceOf[String] :: idList else idList } else idList - val list = updateChildrenRecursive(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]], status, updatedIdList) + val list = updateChildrenRecursive(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]], status, updatedIdList, rootUserId) list ++ updatedIdList }) } diff --git a/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/BaseSpec.scala b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/BaseSpec.scala index 8fc9d3e99..b1abb5a29 100644 --- a/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/BaseSpec.scala +++ b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/BaseSpec.scala @@ -59,4 +59,20 @@ class BaseSpec extends FlatSpec with Matchers { }) util.Arrays.asList(node) } + + def getNode(identifier: String, objectType: String, metadata: Option[util.Map[String, AnyRef]]): Node = { + val node = new Node("domain", "DATA_NODE", objectType) + node.setGraphId("domain") + val nodeMetadata = metadata.getOrElse(new util.HashMap[String, AnyRef]() {{ + put("name", "Question 1") + put("code", "ques.1") + put("status", "Draft") + put("primaryCategory", "Multiple Choice Question") + }}) + node.setNodeType("DATA_NODE") + node.setMetadata(nodeMetadata) + node.setObjectType(objectType) + node.setIdentifier(identifier) + node + } } diff --git a/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/QuestionSetActorTest.scala b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/QuestionSetActorTest.scala index 8383b0c08..705b30481 100644 --- a/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/QuestionSetActorTest.scala +++ b/assessment-api/assessment-actors/src/test/scala/org/sunbird/actors/QuestionSetActorTest.scala @@ -255,7 +255,7 @@ class QuestionSetActorTest extends BaseSpec with MockFactory { val kfClient = mock[KafkaClient] (oec.kafkaClient _).expects().returns(kfClient).anyNumberOfTimes() (oec.graphService _).expects().returns(graphDB).anyNumberOfTimes() - val node = getNode("QuestionSet", None) + val node = getNode("do_11348469558523494411","QuestionSet", None) node.getMetadata.putAll(mapAsJavaMap(Map("name" -> "question_1", "visibility" -> "Default", "code" -> "finemanfine", @@ -268,9 +268,15 @@ class QuestionSetActorTest extends BaseSpec with MockFactory { "showHints" -> "Yes", "summaryType" -> "Complete", "mimeType" -> "application/vnd.sunbird.questionset", + "createdBy" -> "g-001", "primaryCategory" -> "Practice Question Set"))) + val nodeList = new util.ArrayList[Node]() {{ + add(getNode("do_11348469662446387212","Question", Some(Map("visibility"-> "Parent", "createdBy"-> "g-001")))) + add(getNode("do_11348469662607769614","Question", Some(Map("visibility"-> "Default", "createdBy"-> "g-002")))) + }} (graphDB.getNodeByUniqueId(_: String, _: String, _: Boolean, _: Request)).expects(*, *, *, *).returns(Future(node)).atLeastOnce() - (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(getCassandraHierarchy())).anyNumberOfTimes + (graphDB.getNodeByUniqueIds(_ :String, _: SearchCriteria)).expects(*, *).returns(Future(nodeList)).atLeastOnce() + (graphDB.readExternalProps(_: Request, _: List[String])).expects(*, *).returns(Future(getDraftCassandraHierarchy)).anyNumberOfTimes (kfClient.send(_: String, _: String)).expects(*, *).once() val request = getQuestionSetRequest() request.getContext.put("identifier", "do1234") @@ -600,6 +606,12 @@ class QuestionSetActorTest extends BaseSpec with MockFactory { response.put("hierarchy", hierarchyString) } + def getDraftCassandraHierarchy(): Response = { + val hierarchyString: String = """{"identifier":"do_11348469558523494411","children":[{"parent":"do_11348469558523494411","code":"Q1","description":"Q1","language":["English"],"mimeType":"application/vnd.sunbird.question","createdOn":"2022-03-01T02:15:30.917+0530","objectType":"Question","primaryCategory":"Multiple Choice question","contentDisposition":"inline","lastUpdatedOn":"2022-03-01T02:15:30.915+0530","contentEncoding":"gzip","showSolutions":"No","allowAnonymousAccess":"Yes","identifier":"do_11348469662446387212","lastStatusChangedOn":"2022-03-01T02:15:30.917+0530","visibility":"Parent","showTimer":"No","index":1,"languageCode":["en"],"version":1,"versionKey":"1646081131087","showFeedback":"No","license":"CC BY 4.0","depth":1,"createdBy":"g-001","compatibilityLevel":4,"name":"Q1","status":"Draft"},{"parent":"do_11348469558523494411","code":"Q2","description":"Q2","language":["English"],"mimeType":"application/vnd.sunbird.question","createdOn":"2022-03-01T02:15:31.113+0530","objectType":"Question","primaryCategory":"Multiple Choice question","contentDisposition":"inline","lastUpdatedOn":"2022-03-01T02:15:31.126+0530","contentEncoding":"gzip","showSolutions":"No","allowAnonymousAccess":"Yes","identifier":"do_11348469662607769614","lastStatusChangedOn":"2022-03-01T02:15:31.113+0530","visibility":"Default","showTimer":"No","index":2,"languageCode":["en"],"version":1,"versionKey":"1646081131126","showFeedback":"No","license":"CC BY 4.0","depth":1,"createdBy":"g-002","compatibilityLevel":4,"name":"Q2","status":"Draft"}]}""" + val response = new Response + response.put("hierarchy", hierarchyString) + } + def getEmptyCassandraHierarchy(): Response = { val response = new Response response.put("hierarchy", "{}") diff --git a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala index d0fc9ce10..6a2654a72 100644 --- a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala +++ b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala @@ -21,7 +21,8 @@ import com.mashape.unirest.http.HttpResponse import com.mashape.unirest.http.Unirest import org.apache.commons.collections4.{CollectionUtils, MapUtils} import org.sunbird.graph.OntologyEngineContext -import org.sunbird.utils.{HierarchyConstants} +import org.sunbird.telemetry.logger.TelemetryManager +import org.sunbird.utils.HierarchyConstants object HierarchyManager { @@ -41,7 +42,7 @@ object HierarchyManager { @throws[Exception] def addLeafNodesToHierarchy(request:Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = { - validateRequest(request) + validateRequest(request, "add") val rootNodeFuture = getRootNode(request) rootNodeFuture.map(rootNode => { val unitId = request.getRequest.getOrDefault("collectionId", "").asInstanceOf[String] @@ -56,20 +57,7 @@ object HierarchyManager { Future{ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), "hierarchy is empty")} } else { val leafNodesFuture = fetchLeafNodes(request) - leafNodesFuture.map(leafNodes => { - updateRootNode(rootNode, request, "add").map(node => { - val updateResponse = updateHierarchy(unitId, hierarchy, leafNodes, node, request, "add") - updateResponse.map(response => { - if(!ResponseHandler.checkError(response)) { - ResponseHandler.OK - .put("rootId", node.getIdentifier.replaceAll(imgSuffix, "")) - .put(unitId, Map("children" -> request.get("children")).asJava) - }else { - response - } - }) - }).flatMap(f => f) - }).flatMap(f => f) + leafNodesFuture.map(leafNodes => updateHierarchyData(unitId, hierarchy, leafNodes, rootNode, request, "add").map(node => ResponseHandler.OK.put("rootId", node.getIdentifier.replaceAll(imgSuffix, "")))).flatMap(f => f) } }).flatMap(f => f) } @@ -79,7 +67,7 @@ object HierarchyManager { @throws[Exception] def removeLeafNodesFromHierarchy(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = { - validateRequest(request) + validateRequest(request, "remove") val rootNodeFuture = getRootNode(request) rootNodeFuture.map(rootNode => { val unitId = request.getRequest.getOrDefault("collectionId", "").asInstanceOf[String] @@ -92,18 +80,7 @@ object HierarchyManager { hierarchyFuture.map(hierarchy => { if(hierarchy.isEmpty){ Future{ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), "hierarchy is empty")} - } else { - updateRootNode(rootNode, request, "remove").map(node =>{ - val updateResponse = updateHierarchy(unitId, hierarchy, null, node, request, "remove") - updateResponse.map(response => { - if(!ResponseHandler.checkError(response)) { - ResponseHandler.OK.put("rootId", node.getIdentifier.replaceAll(imgSuffix, "")) - } else { - response - } - }) - }).flatMap(f => f) - } + } else updateHierarchyData(unitId, hierarchy, null, rootNode, request, "remove").map(node => ResponseHandler.OK.put("rootId", node.getIdentifier.replaceAll(imgSuffix, ""))) }).flatMap(f => f) } } @@ -116,6 +93,23 @@ object HierarchyManager { Future(ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), "hierarchy is empty")) } else { fetchLeafNodes(request).map(leafNodes => { + val rootNodeMap = NodeUtil.serialize(rootNode, java.util.Arrays.asList(HierarchyConstants.BRANCHING_LOGIC, HierarchyConstants.ALLOW_BRANCHING, HierarchyConstants.CHILD_NODES), schemaName, schemaVersion) + if (isBranchingEnabled(rootNodeMap, request, operation)) { + val branchingLogic = rootNodeMap.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]] + TelemetryManager.info(s"Branching Found For ${rootNode.getIdentifier}. Branching Rules are : ${branchingLogic}") + leafNodes.foreach(leafNode => { + val updatedBranching = operation match { + case "remove" => removeBranching(leafNode.getIdentifier, branchingLogic) + case "add" => addBranching(leafNode.getIdentifier, branchingLogic, request, rootNode.getMetadata.getOrDefault("childNodes", Array[String]()).asInstanceOf[Array[String]].toList) + } + if (MapUtils.isNotEmpty(updatedBranching)) { + rootNode.getMetadata.put(HierarchyConstants.BRANCHING_LOGIC, updatedBranching) + request.put(HierarchyConstants.BRANCHING_LOGIC, updatedBranching) + } + }) + TelemetryManager.info("updated branchingLogic for node " + rootNode.getIdentifier + " is : " + rootNode.getMetadata.get(HierarchyConstants.BRANCHING_LOGIC)) + } else if (StringUtils.equalsIgnoreCase("add", operation) && request.getRequest.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new util.HashMap[String, AnyRef]()).asInstanceOf[util.Map[String, AnyRef]].nonEmpty) + throw new ClientException("ERR_BRANCHING_LOGIC", s"Branching Is Not Enabled For ${rootNode.getIdentifier}. Please Enable Branching Or Remove branchingLogic from Request.") updateRootNode(rootNode, request, operation).map(node => { updateRootHierarchy(hierarchy, leafNodes, node, request, operation).map(response => { if (!ResponseHandler.checkError(response)) { @@ -241,15 +235,20 @@ object HierarchyManager { }) } - def validateRequest(request: Request)(implicit ec: ExecutionContext) = { + def validateRequest(request: Request, operation: String)(implicit ec: ExecutionContext) = { val rootId = request.get("rootId").asInstanceOf[String] val children = request.get("children").asInstanceOf[java.util.List[String]] + val branchingLogic = request.getRequest.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]] if (StringUtils.isBlank(rootId)) { throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "rootId is mandatory") } if (null == children || children.isEmpty) { throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "children are mandatory") } + if(StringUtils.equalsAnyIgnoreCase(operation, "add") && MapUtils.isNotEmpty(branchingLogic)) { + if(!children.containsAll(branchingLogic.keySet())) + throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "Branch Rule Found For The Node Which Is Not A Children Having Identifier : "+branchingLogic.keySet().toList.diff(children.toList).asJava) + } } private def getRootNode(request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = { @@ -285,17 +284,29 @@ object HierarchyManager { }) } - def addChildrenToUnit(children: java.util.List[java.util.Map[String,AnyRef]], unitId:String, leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String]): Unit = { + def addChildrenToUnit(children: java.util.List[java.util.Map[String,AnyRef]], unitId:String, leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String], request: Request): Unit = { val childNodes = children.filter(child => ("Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String]))).toList if(null != childNodes && !childNodes.isEmpty){ val child = childNodes.get(0) + if (isBranchingEnabled(child, request, "add")) { + TelemetryManager.info(s"Branching Found for ${child.get("identifier")}. Branching Rules Are : ${child.get(HierarchyConstants.BRANCHING_LOGIC)}") + val childrenIds: List[String] = child.getOrDefault(HierarchyConstants.CHILDREN, new util.ArrayList[java.util.Map[String, AnyRef]]()).asInstanceOf[util.ArrayList[java.util.Map[String, AnyRef]]].toList.map(child => child.get("identifier").asInstanceOf[String]) + leafNodeIds.foreach(nodeId => { + val updatedBranching = addBranching(nodeId, child.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new util.HashMap()).asInstanceOf[java.util.Map[String, AnyRef]], request, childrenIds) + if (MapUtils.isNotEmpty(updatedBranching)) { + child.put(HierarchyConstants.BRANCHING_LOGIC, updatedBranching) + } + }) + TelemetryManager.info(s"Branching Updated for ${child.get("identifier")}. Updated Branching Rules Are : ${child.get(HierarchyConstants.BRANCHING_LOGIC)}") + } else if(request.getRequest.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new util.HashMap[String, AnyRef]()).asInstanceOf[util.Map[String, AnyRef]].nonEmpty) + throw new ClientException("ERR_BRANCHING_LOGIC", s"Branching Is Not Enabled For ${unitId}. Please Enable Branching Or Remove branchingLogic from Request.") val childList = child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]] val restructuredChildren: java.util.List[java.util.Map[String,AnyRef]] = restructureUnit(childList, leafNodes, leafNodeIds, (child.get("depth").asInstanceOf[Integer] + 1), unitId) child.put("children", restructuredChildren) } else { for(child <- children) { if(null !=child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty) - addChildrenToUnit(child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]], unitId, leafNodes, leafNodeIds) + addChildrenToUnit(child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]], unitId, leafNodes, leafNodeIds, request) } } } @@ -304,6 +315,16 @@ object HierarchyManager { val childNodes = children.filter(child => ("Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String]))).toList if(null != childNodes && !childNodes.isEmpty){ val child = childNodes.get(0) + if (isBranchingEnabled(child, new Request(), "remove")) { + TelemetryManager.info(s"Branching Found for ${child.get("identifier")}. Branching Rules Are : ${child.get(HierarchyConstants.BRANCHING_LOGIC)}") + leafNodeIds.foreach(nodeId => { + val updatedBranching = removeBranching(nodeId, child.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new util.HashMap()).asInstanceOf[java.util.Map[String, AnyRef]]) + if (MapUtils.isNotEmpty(updatedBranching)) { + child.put(HierarchyConstants.BRANCHING_LOGIC, updatedBranching) + } + }) + TelemetryManager.info(s"Branching Updated for ${child.get("identifier")}. Updated Branching Rules Are : ${child.get(HierarchyConstants.BRANCHING_LOGIC)}") + } if(null != child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty) { var filteredLeafNodes = child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].filter(existingLeafNode => { !leafNodeIds.contains(existingLeafNode.get("identifier").asInstanceOf[String]) @@ -332,6 +353,8 @@ object HierarchyManager { childNodes.addAll(leafNodes) if(operation.equalsIgnoreCase("remove")) childNodes.removeAll(leafNodes) + if(request.getRequest.containsKey(HierarchyConstants.BRANCHING_LOGIC)) + req.put(HierarchyConstants.BRANCHING_LOGIC, request.get(HierarchyConstants.BRANCHING_LOGIC).asInstanceOf[java.util.Map[String, AnyRef]]) req.put("childNodes", childNodes.distinct.toArray) req.getContext.put("identifier", rootNode.getIdentifier.replaceAll(imgSuffix, "")) req.getContext.put("skipValidation", java.lang.Boolean.TRUE) @@ -343,7 +366,7 @@ object HierarchyManager { val leafNodeIds = request.get("children").asInstanceOf[java.util.List[String]] if("add".equalsIgnoreCase(operation)){ val leafNodesMap:java.util.List[java.util.Map[String, AnyRef]] = convertNodeToMap(leafNodes) - addChildrenToUnit(children, unitId, leafNodesMap, leafNodeIds) + addChildrenToUnit(children, unitId, leafNodesMap, leafNodeIds, request) } if("remove".equalsIgnoreCase(operation)) { removeChildrenFromUnit(children,unitId, leafNodeIds) @@ -358,6 +381,32 @@ object HierarchyManager { oec.graphService.saveExternalProps(req) } + def updateHierarchyData(unitId: String, hierarchy: java.util.Map[String, AnyRef], leafNodes: List[Node], rootNode: Node, request: Request, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = { + val children = hierarchy.get("children").asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]] + val leafNodeIds = request.get("children").asInstanceOf[java.util.List[String]] + val childNodes = new java.util.ArrayList[String]() + childNodes.addAll(rootNode.getMetadata.getOrDefault("childNodes", Array[String]()).asInstanceOf[Array[String]].toList) + if("add".equalsIgnoreCase(operation)){ + val leafNodesMap:java.util.List[java.util.Map[String, AnyRef]] = convertNodeToMap(leafNodes) + addChildrenToUnit(children, unitId, leafNodesMap, leafNodeIds, request) + childNodes.addAll(leafNodeIds) + } + if("remove".equalsIgnoreCase(operation)) { + removeChildrenFromUnit(children, unitId, leafNodeIds) + childNodes.removeAll(leafNodeIds) + } + val rootId = rootNode.getIdentifier.replaceAll(imgSuffix, "") + val updatedHierarchy = new java.util.HashMap[String, AnyRef]() + updatedHierarchy.put(HierarchyConstants.IDENTIFIER, rootId) + updatedHierarchy.put(HierarchyConstants.CHILDREN, children) + val req = new Request() + req.setContext(request.getContext) + req.getContext.put(HierarchyConstants.IDENTIFIER, rootNode.getIdentifier) + req.put(HierarchyConstants.CHILD_NODES, childNodes.distinct.toArray) + req.put(HierarchyConstants.HIERARCHY, ScalaJsonUtils.serialize(updatedHierarchy)) + DataNode.update(req) + } + def restructureUnit(childList: java.util.List[java.util.Map[String, AnyRef]], leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String], depth: Integer, parent: String): java.util.List[java.util.Map[String, AnyRef]] = { var maxIndex:Integer = 0 var leafNodeMap: java.util.Map[String, java.util.Map[String, AnyRef]] = new util.HashMap[String, java.util.Map[String, AnyRef]]() @@ -598,4 +647,71 @@ object HierarchyManager { } } + def isBranchingEnabled(metadata: java.util.Map[String, AnyRef], request: Request, operation: String): Boolean = { + val isBranchingAvailable = operation match { + case "add" => MapUtils.isNotEmpty(request.getRequest.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new util.HashMap()).asInstanceOf[java.util.Map[String, AnyRef]]) + case "remove" => MapUtils.isNotEmpty(metadata.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new java.util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]]) + case _ => false + } + StringUtils.equalsIgnoreCase("Yes", metadata.getOrDefault(HierarchyConstants.ALLOW_BRANCHING, "No").asInstanceOf[String]) && isBranchingAvailable + } + + def removeBranching(identifier: String, branchingLogic: java.util.Map[String, AnyRef]): java.util.Map[String, AnyRef] = { + if (branchingLogic.keySet().contains(identifier)) { + val obj: java.util.Map[String, AnyRef] = branchingLogic.getOrDefault(identifier, new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]] + val source: java.util.List[String] = obj.getOrDefault(HierarchyConstants.SOURCE, new java.util.ArrayList[String]()).asInstanceOf[java.util.List[String]] + val target: java.util.List[String] = obj.getOrDefault(HierarchyConstants.TARGET, new java.util.ArrayList[String]()).asInstanceOf[java.util.List[String]] + val preCondition: java.util.Map[String, AnyRef] = obj.getOrDefault(HierarchyConstants.PRE_CONDITION, new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]] + if ((source.nonEmpty && preCondition.nonEmpty) && target.isEmpty) { + val parentObj: java.util.Map[String, AnyRef] = branchingLogic.getOrDefault(source.get(0), new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]] + val pTarget = parentObj.getOrDefault(HierarchyConstants.TARGET, new java.util.ArrayList[String]()).asInstanceOf[java.util.List[String]] + pTarget.remove(identifier) + parentObj.put(HierarchyConstants.TARGET, pTarget) + branchingLogic.put(source.get(0), parentObj) + branchingLogic.remove(identifier) + } else if (source.isEmpty && preCondition.isEmpty) { + if (target.nonEmpty) + throw new ClientException("ERR_BRANCHING_LOGIC", s"Dependent Children Found! Please Remove Children With Identifiers ${target} For Node : ${identifier}") + else branchingLogic.remove(identifier) + } + } + branchingLogic + } + + def addBranching(identifier: String, branchingLogic: java.util.Map[String, AnyRef], request: Request, childrenIds: List[String]): java.util.Map[String, AnyRef] = { + val reqBranching: util.Map[String, AnyRef] = request.getRequest.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new util.HashMap[String, AnyRef]()).asInstanceOf[util.Map[String, AnyRef]] + if (reqBranching.nonEmpty) { + val sourceIds: List[String] = reqBranching.flatMap(entry => entry._2.asInstanceOf[util.Map[String, AnyRef]].get(HierarchyConstants.SOURCE).asInstanceOf[util.ArrayList[String]]).toList + if (!childrenIds.containsAll(sourceIds)) + throw new ClientException("ERR_BRANCHING_LOGIC", s"Source With Identifiers ${sourceIds.diff(childrenIds).asJava} Not Found! Please Provide Valid Source Identifier.") + } + val updatedBranchingLogic = new util.HashMap[String, AnyRef]() + updatedBranchingLogic.putAll(branchingLogic) + reqBranching.map(entry => { + val obj = entry._2.asInstanceOf[java.util.Map[String, AnyRef]] + val source: java.util.List[String] = obj.getOrDefault(HierarchyConstants.SOURCE, new java.util.ArrayList[String]()).asInstanceOf[java.util.List[String]] + if (source.nonEmpty && source.size > 1) + throw new ClientException("ERR_BRANCHING_LOGIC", "An Object Can't Depend On More Than 1 Object") + if (branchingLogic.contains(source.get(0))) { + val parentObj = branchingLogic.getOrDefault(source.get(0), new util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]] + val pSource: java.util.List[String] = parentObj.getOrDefault(HierarchyConstants.SOURCE, new java.util.ArrayList[String]()).asInstanceOf[java.util.List[String]] + if (pSource.nonEmpty) + throw new ClientException("ERR_BRANCHING_LOGIC", s"${source.get(0)} Is Already Children Of ${pSource.get(0)}. So It Can't Be Parent For ${entry._1}") + val pTarget: java.util.List[String] = parentObj.getOrDefault(HierarchyConstants.TARGET, new java.util.ArrayList[String]()).asInstanceOf[java.util.List[String]] + pTarget.add(entry._1) + parentObj.put(HierarchyConstants.TARGET, pTarget) + updatedBranchingLogic.put(source.get(0), parentObj) + + } else { + val parentObj = Map("target" -> List(entry._1).asJava, "source" -> List().asJava, "preCondition" -> Map().asJava).asJava + updatedBranchingLogic.put(source.get(0), parentObj) + } + updatedBranchingLogic.put(entry._1, entry._2.asInstanceOf[java.util.Map[String, AnyRef]]) + + }) + TelemetryManager.info(s"updated BranchingLogic for ${identifier} : ${updatedBranchingLogic}") + updatedBranchingLogic + } + + } diff --git a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala index f166405d0..59cdd1810 100644 --- a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala +++ b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala @@ -42,7 +42,8 @@ object UpdateHierarchyManager { TelemetryManager.info("NodeList final size: " + nodes.size) val idMap: mutable.Map[String, String] = mutable.Map() idMap += (rootId -> rootId) - updateNodesModifiedInNodeList(nodes, nodesModified, request, idMap).map(modifiedNodeList => { + updateNodesModifiedInNodeList(nodes, nodesModified, request, idMap).map(updatedList => { + val modifiedNodeList = processBranching(updatedList, request, idMap) getChildrenHierarchy(modifiedNodeList, rootId, hierarchy, idMap, result._1).map(children => { TelemetryManager.log("Children for root id :" + rootId +" :: " + JsonUtils.serialize(children)) updateHierarchyData(rootId, children, modifiedNodeList, request).map(node => { @@ -193,15 +194,17 @@ object UpdateHierarchyManager { if(StringUtils.isBlank(objectType)) throw new ClientException("ERR_UPDATE_QS_HIERARCHY", s"Object Type is mandatory for creation of node with id: ${nodeModified._1}") val metadata = nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].getOrDefault(HierarchyConstants.METADATA, new java.util.HashMap()).asInstanceOf[java.util.HashMap[String, AnyRef]] - if(!StringUtils.equalsIgnoreCase(metadata.getOrDefault(HierarchyConstants.VISIBILITY, "Parent").asInstanceOf[String], "Parent")) + if(StringUtils.equalsAnyIgnoreCase("QuestionSet", objectType) && !StringUtils.equalsIgnoreCase(metadata.getOrDefault(HierarchyConstants.VISIBILITY, "Parent").asInstanceOf[String], "Parent")) throw new ClientException("ERR_UPDATE_QS_HIERARCHY", s"Visibility can be only of type Parent for identifier: ${nodeModified._1}") + if(StringUtils.equalsAnyIgnoreCase("Question", objectType) && !HierarchyConstants.QUESTION_VISIBILITY.contains(metadata.getOrDefault(HierarchyConstants.VISIBILITY, "Parent").asInstanceOf[String])) + throw new ClientException("ERR_UPDATE_QS_HIERARCHY", s"Visibility can be only of type ${HierarchyConstants.QUESTION_VISIBILITY.asJava} for identifier: ${nodeModified._1}") metadata.remove(HierarchyConstants.DIALCODES) metadata.put(HierarchyConstants.STATUS, "Draft") metadata.put(HierarchyConstants.LAST_UPDATED_ON, DateUtils.formatCurrentDate) metadata.put(HierarchyConstants.OBJECT_TYPE, objectType) if (nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].containsKey(HierarchyConstants.IS_NEW) && nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.IS_NEW).asInstanceOf[Boolean]) { - if (!nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.ROOT).asInstanceOf[Boolean]) + if (!nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.ROOT).asInstanceOf[Boolean] && nodeModified._2.asInstanceOf[java.util.Map[String, AnyRef]].getOrDefault(HierarchyConstants.METADATA, new util.HashMap()).asInstanceOf[java.util.Map[String, AnyRef]].getOrDefault(HierarchyConstants.VISIBILITY, "").asInstanceOf[String].isEmpty) metadata.put(HierarchyConstants.VISIBILITY, HierarchyConstants.PARENT) if (nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].contains(HierarchyConstants.SET_DEFAULT_VALUE)) createNewNode(nodeModified._1, idMap, metadata, nodeList, request, nodeModified._2.asInstanceOf[java.util.HashMap[String, AnyRef]].get(HierarchyConstants.SET_DEFAULT_VALUE).asInstanceOf[Boolean]) @@ -510,4 +513,50 @@ object UpdateHierarchyManager { ExternalPropsManager.deleteProps(req) } + private def processBranching(nodeList: List[Node], request: Request, idMap: mutable.Map[String, String])(implicit ec: ExecutionContext, oec: OntologyEngineContext): List[Node] = { + val hierarchy: java.util.HashMap[String, AnyRef] = request.getRequest.get(HierarchyConstants.HIERARCHY).asInstanceOf[java.util.HashMap[String, AnyRef]] + nodeList.distinct.map(node => { + if (StringUtils.equalsIgnoreCase("Yes", node.getMetadata.getOrDefault(HierarchyConstants.ALLOW_BRANCHING, "No").asInstanceOf[String]) && + MapUtils.isNotEmpty(node.getMetadata.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new java.util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]])) { + validateAndUpdateBranching(node, hierarchy, idMap) + } else if (StringUtils.equalsIgnoreCase("No", node.getMetadata.getOrDefault(HierarchyConstants.ALLOW_BRANCHING, "No").asInstanceOf[String]) && + MapUtils.isNotEmpty(node.getMetadata.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new java.util.HashMap[String, AnyRef]()).asInstanceOf[java.util.Map[String, AnyRef]])) { + throw new ClientException("ERR_BRANCHING_LOGIC", s"Branching Is Not Enabled For: ${node.getIdentifier}. Please Remove branchingLogic from ${node.getIdentifier}") + } else node + }) + } + + private def validateAndUpdateBranching(node: Node, hierarchy: java.util.Map[String, AnyRef], idMap: mutable.Map[String, String]): Node = { + TelemetryManager.info(s"validating branching logic for: ${node.getIdentifier}") + val branchingLogic = node.getMetadata.getOrDefault(HierarchyConstants.BRANCHING_LOGIC, new util.HashMap()).asInstanceOf[java.util.HashMap[String, AnyRef]].toMap + TelemetryManager.info(s"branchingLogic for node ${node.getIdentifier} : ${branchingLogic}") + val updatedBranchingLogic = branchingLogic.map(entry => { + val obj: java.util.HashMap[String, AnyRef] = entry._2.asInstanceOf[java.util.HashMap[String, AnyRef]] + val target = obj.getOrElse(HierarchyConstants.TARGET, new util.ArrayList[String]()).asInstanceOf[java.util.List[String]].toList + val source = obj.getOrElse(HierarchyConstants.SOURCE, new util.ArrayList[String]()).asInstanceOf[java.util.List[String]].toList + val preCondition = obj.getOrElse(HierarchyConstants.PRE_CONDITION, new util.HashMap()).asInstanceOf[java.util.Map[String, AnyRef]].toMap + val children = hierarchy.getOrDefault(node.getIdentifier, new util.HashMap()).asInstanceOf[java.util.HashMap[String, AnyRef]].getOrDefault(HierarchyConstants.CHILDREN, new util.ArrayList[String]()).asInstanceOf[java.util.List[String]].toList + // branching logic should be present for all dependent question + if (!branchingLogic.keySet.containsAll(target)) + throw new ClientException("ERR_BRANCHING_LOGIC", s"Please Provide Branching Rules for : ${branchingLogic.keySet.toList.diff(target).asJava}") + // all dependent object should belong to same parent object. + if (!children.containsAll(target ++ List(entry._1))) + throw new ClientException("ERR_BRANCHING_LOGIC", s"Please Provide Dependent Object Within Same Parent having identifier : ${node.getIdentifier}") + // A dependent object should not depend on more than 1 object + if (source.nonEmpty && source.size > 1) + throw new ClientException("ERR_BRANCHING_LOGIC", "An Object Can't Depend On More Than 1 Object") + // A dependent object should not have further dependency + if ((source.nonEmpty && preCondition.nonEmpty) && target.nonEmpty) + throw new ClientException("ERR_BRANCHING_LOGIC", "A Dependent Object Can't Have Further Dependent Object") + + TelemetryManager.info(s"updating branching Logic for ${entry._1}") + obj.put(HierarchyConstants.TARGET, target.map(id => idMap.getOrElse(id, id)).asJava) + obj.put(HierarchyConstants.SOURCE, source.map(id => idMap.getOrElse(id, id)).asJava) + (idMap.getOrElse(entry._1, entry._1), obj) + }) + TelemetryManager.info(s"branchingLogic after update for ${node.getIdentifier}: " + updatedBranchingLogic.asJava) + node.getMetadata.put(HierarchyConstants.BRANCHING_LOGIC, updatedBranchingLogic.asJava) + node + } + } diff --git a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala index 24b2f64d4..25408886c 100644 --- a/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala +++ b/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala @@ -51,4 +51,10 @@ object HierarchyConstants { val QUESTIONSET_MIME_TYPE: String = "application/vnd.sunbird.questionset" val QUESTIONSET_OBJECT_TYPE: String = "QuestionSet" val OPERATION_UPDATE_HIERARCHY: String = "updateHierarchy" + val BRANCHING_LOGIC: String = "branchingLogic" + val ALLOW_BRANCHING: String = "allowBranching" + val TARGET: String = "target" + val SOURCE: String = "source" + val PRE_CONDITION: String = "preCondition" + val QUESTION_VISIBILITY: List[String] = List("Default", "Parent") } diff --git a/assessment-api/qs-hierarchy-manager/src/test/resources/application.conf b/assessment-api/qs-hierarchy-manager/src/test/resources/application.conf index 046ae1a25..82fe873b5 100644 --- a/assessment-api/qs-hierarchy-manager/src/test/resources/application.conf +++ b/assessment-api/qs-hierarchy-manager/src/test/resources/application.conf @@ -1,7 +1,7 @@ # This is the main configuration file for the application. # https://www.playframework.com/documentation/latest/ConfigFile # ~~~~~ -# Play uses HOCON as its configuration file format. HOCON has a number +# Play uses HOCON as its configuration file format. HOCON has a number # of advantages over other config formats, but there are two things that # can be used when modifying settings. # @@ -33,6 +33,64 @@ akka { # And then uncomment this line to debug the configuration. # #log-config-on-start = true + default-dispatcher { + # This will be used if you have set "executor = "fork-join-executor"" + fork-join-executor { + # Min number of threads to cap factor-based parallelism number to + parallelism-min = 8 + + # The parallelism factor is used to determine thread pool size using the + # following formula: ceil(available processors * factor). Resulting size + # is then bounded by the parallelism-min and parallelism-max values. + parallelism-factor = 32.0 + + # Max number of threads to cap factor-based parallelism number to + parallelism-max = 64 + + # Setting to "FIFO" to use queue like peeking mode which "poll" or "LIFO" to use stack + # like peeking mode which "pop". + task-peeking-mode = "FIFO" + } + } + actors-dispatcher { + type = "Dispatcher" + executor = "fork-join-executor" + fork-join-executor { + parallelism-min = 8 + parallelism-factor = 32.0 + parallelism-max = 64 + } + # Throughput for default Dispatcher, set to 1 for as fair as possible + throughput = 1 + } + actor { + deployment { + /healthActor + { + router = smallest-mailbox-pool + nr-of-instances = 5 + dispatcher = actors-dispatcher + } + /itemSetActor + { + router = smallest-mailbox-pool + nr-of-instances = 2 + dispatcher = actors-dispatcher + } + /questionActor + { + router = smallest-mailbox-pool + nr-of-instances = 5 + dispatcher = actors-dispatcher + } + /questionSetActor + { + router = smallest-mailbox-pool + nr-of-instances = 5 + dispatcher = actors-dispatcher + } + } + } } ## Secret key @@ -40,12 +98,12 @@ akka { # ~~~~~ # The secret key is used to sign Play's session cookie. # This must be changed for production, but we don't recommend you change it in this file. -play.http.secret.key = a-long-secret-to-calm-the-rage-of-the-entropy-gods +play.http.secret.key= a-long-secret-to-calm-the-rage-of-the-entropy-gods ## Modules # https://www.playframework.com/documentation/latest/Modules # ~~~~~ -# Control which modules are loaded when Play starts. Note that modules are +# Control which modules are loaded when Play starts. Note that modules are # the replacement for "GlobalSettings", which are deprecated in 2.5.x. # Please see https://www.playframework.com/documentation/latest/GlobalSettings # for more information. @@ -61,6 +119,7 @@ play.modules { # If there are any built-in modules that you want to disable, you can list them here. #disabled += "" + enabled += modules.AssessmentModule } ## IDE @@ -146,6 +205,10 @@ play.http { } } +play.server.http.idleTimeout = 60s +play.http.parser.maxDiskBuffer = 10MB +parsers.anyContent.maxLength = 10MB + ## Netty Provider # https://www.playframework.com/documentation/latest/SettingsNetty # ~~~~~ @@ -161,7 +224,7 @@ play.server.netty { ## WS (HTTP Client) # https://www.playframework.com/documentation/latest/ScalaWS#Configuring-WS # ~~~~~ -# The HTTP client primarily used for REST APIs. The default client can be +# The HTTP client primarily used for REST APIs. The default client can be # configured directly, but you can also create different client instances # with customized settings. You must enable this by adding to build.sbt: # @@ -178,14 +241,14 @@ play.ws { # https://www.playframework.com/documentation/latest/WsSSL # ~~~~~ ssl { - # Configuring HTTPS with Play WS does not require programming. You can + # Configuring HTTPS with Play WS does not require programming. You can # set up both trustManager and keyManager for mutual authentication, and # turn on JSSE debugging in development with a reload. #debug.handshake = true #trustManager = { - # stores = [ - # { type = "JKS", path = "exampletrust.jks" } - # ] + # stores = [ + # { type = "JKS", path = "exampletrust.jks" } + # ] #} } } @@ -214,7 +277,7 @@ play.filters { # Enabled filters are run automatically against Play. # CSRFFilter, AllowedHostFilters, and SecurityHeadersFilters are enabled by default. - enabled = [] + enabled = [filters.AccessLogFilter] # Disabled filters remove elements from the enabled list. # disabled += filters.CSRFFilter @@ -271,72 +334,20 @@ play.filters { } } -# Learning-Service Configuration -content.metadata.visibility.parent=["textbookunit", "courseunit", "lessonplanunit"] +play.http.parser.maxMemoryBuffer = 50MB +akka.http.parsing.max-content-length = 50MB +schema.base_path="../../schemas/" # Cassandra Configuration -content.keyspace.name=content_store -content.keyspace.table=content_data -#TODO: Add Configuration for assessment. e.g: question_data -orchestrator.keyspace.name=script_store -orchestrator.keyspace.table=script_data -cassandra.lp.connection="127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042" -cassandra.lpa.connection="127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042" +cassandra.lp.connection="127.0.0.1:9042" +content.keyspace = "content_store" # Redis Configuration -redis.host=localhost +redis.host="localhost" redis.port=6379 redis.maxConnections=128 -#Condition to enable publish locally -content.publish_task.enabled=true - -#directory location where store unzip file -dist.directory=/data/tmp/dist/ -output.zipfile=/data/tmp/story.zip -source.folder=/data/tmp/temp2/ -save.directory=/data/tmp/temp/ - -# Content 2 vec analytics URL -CONTENT_TO_VEC_URL="http://172.31.27.233:9000/content-to-vec" - -# FOR CONTENT WORKFLOW PIPELINE (CWP) - -#--Content Workflow Pipeline Mode -OPERATION_MODE=TEST - -#--Maximum Content Package File Size Limit in Bytes (50 MB) -MAX_CONTENT_PACKAGE_FILE_SIZE_LIMIT=52428800 - -#--Maximum Asset File Size Limit in Bytes (20 MB) -MAX_ASSET_FILE_SIZE_LIMIT=20971520 - -#--No of Retry While File Download Fails -RETRY_ASSET_DOWNLOAD_COUNT=1 - -#Google-vision-API -google.vision.tagging.enabled = false - -#Orchestrator env properties -env="https://dev.ekstep.in/api/learning" - -#Current environment -cloud_storage.env=dev - - -#Folder configuration -cloud_storage.content.folder=content -cloud_storage.asset.folder=assets -cloud_storage.artefact.folder=artifact -cloud_storage.bundle.folder=bundle -cloud_storage.media.folder=media -cloud_storage.ecar.folder=ecar_files - -# Media download configuration -content.media.base.url="https://dev.open-sunbird.org" -plugin.media.base.url="https://dev.open-sunbird.org" - -# Configuration +# Graph Configuration graph.dir=/data/testingGraphDB akka.request_timeout=30 environment.id=10000000 @@ -355,182 +366,58 @@ shard.id=1 platform.auth.check.enabled=false platform.cache.ttl=3600000 -# Elasticsearch properties -search.es_conn_info="localhost:9200" -search.fields.query=["name^100","title^100","lemma^100","code^100","tags^100","domain","subject","description^10","keywords^25","ageGroup^10","filter^10","theme^10","genre^10","objects^25","contentType^100","language^200","teachingMode^25","skills^10","learningObjective^10","curriculum^100","gradeLevel^100","developer^100","attributions^10","owner^50","text","words","releaseNotes"] -search.fields.date=["lastUpdatedOn","createdOn","versionDate","lastSubmittedOn","lastPublishedOn"] -search.batch.size=500 -search.connection.timeout=30 -platform-api-url="http://localhost:8080/language-service" -MAX_ITERATION_COUNT_FOR_SAMZA_JOB=2 - - -# DIAL Code Configuration -dialcode.keyspace.name="dialcode_store" -dialcode.keyspace.table="dial_code" -dialcode.max_count=1000 - -# System Configuration -system.config.keyspace.name="dialcode_store" -system.config.table="system_config" - -#Publisher Configuration -publisher.keyspace.name="dialcode_store" -publisher.keyspace.table="publisher" - -#DIAL Code Generator Configuration -dialcode.strip.chars="0" -dialcode.length=6.0 -dialcode.large.prime_number=1679979167 - -#DIAL Code ElasticSearch Configuration -dialcode.index=true -dialcode.object_type="DialCode" - -framework.max_term_creation_limit=200 - -# Enable Suggested Framework in Get Channel API. -channel.fetch.suggested_frameworks=true - -# Kafka configuration details -kafka.topics.instruction="local.learning.job.request" -kafka.urls="localhost:9092" - -#Youtube Standard Licence Validation -learning.content.youtube.validate.license=true -learning.content.youtube.application.name=fetch-youtube-license -youtube.license.regex.pattern=["\\?vi?=([^&]*)", "watch\\?.*v=([^&]*)", "(?:embed|vi?)/([^/?]*)","^([A-Za-z0-9\\-\\_]*)"] - #Top N Config for Search Telemetry telemetry_env=dev -telemetry.search.topn=5 installation.id=ekstep -channel.default="in.ekstep" - -# DialCode Link API Config -learning.content.link_dialcode_validation=true -dialcode.api.search.url="http://localhost:8080/learning-service/v3/dialcode/search" -dialcode.api.authorization=auth_key - -# Language-Code Configuration -platform.language.codes=["as","bn","en","gu","hi","hoc","jun","ka","mai","mr","unx","or","san","sat","ta","te","urd", "pj"] - -# Kafka send event to topic enable -kafka.topic.send.enable=false - -learning.valid_license=["creativeCommon"] -learning.service_provider=["youtube"] - -stream.mime.type=video/mp4 -compositesearch.index.name="compositesearch" - -hierarchy.keyspace.name=hierarchy_store -content.hierarchy.table=content_hierarchy -framework.hierarchy.table=framework_hierarchy - -# Kafka topic for definition update event. -kafka.topic.system.command="dev.system.command" - -learning.reserve_dialcode.content_type=["TextBook"] -# restrict.metadata.objectTypes=["Content", "ContentImage", "AssessmentItem", "Channel", "Framework", "Category", "CategoryInstance", "Term"] - -#restrict.metadata.objectTypes="Content,ContentImage" - -publish.collection.fullecar.disable=true - -# Consistency Level for Multi Node Cassandra cluster -cassandra.lp.consistency.level=QUORUM - - - - -content.nested.fields="badgeAssertions,targets,badgeAssociations" - -content.cache.ttl=86400 -content.cache.enable=true -collection.cache.enable=true -content.discard.status=["Draft","FlagDraft"] - -framework.categories_cached=["subject", "medium", "gradeLevel", "board"] -framework.cache.ttl=86400 -framework.cache.read=true - - -# Max size(width/height) of thumbnail in pixels -max.thumbnail.size.pixels=150 - -schema.base_path="../../schemas/" -content.hierarchy.removed_props_for_leafNodes=["collections","children","usedByContent","item_sets","methods","libraries","editorState"] - -collection.keyspace = "hierarchy_store" -content.keyspace = "content_store" - -collection.image.migration.enabled=true - - - -# This is added to handle large artifacts sizes differently -content.artifact.size.for_online=209715200 - -contentTypeToPrimaryCategory { - ClassroomTeachingVideo: "Explanation Content" - ConceptMap: "Learning Resource" - Course: "Course" - CuriosityQuestionSet: "Practice Question Set" - eTextBook: "eTextbook" - ExperientialResource: "Learning Resource" - ExplanationResource: "Explanation Content" - ExplanationVideo: "Explanation Content" - FocusSpot: "Teacher Resource" - LearningOutcomeDefinition: "Teacher Resource" - MarkingSchemeRubric: "Teacher Resource" - PedagogyFlow: "Teacher Resource" - PracticeQuestionSet: "Practice Question Set" - PracticeResource: "Practice Question Set" - SelfAssess: "Course Assessment" - TeachingMethod: "Teacher Resource" - TextBook: "Digital Textbook" - Collection: "Content Playlist" - ExplanationReadingMaterial: "Learning Resource" - LearningActivity: "Learning Resource" - LessonPlan: "Content Playlist" - LessonPlanResource: "Teacher Resource" - PreviousBoardExamPapers: "Learning Resource" - TVLesson: "Explanation Content" - OnboardingResource: "Learning Resource" - ReadingMaterial: "Learning Resource" - Template: "Template" - Asset: "Asset" - Plugin: "Plugin" - LessonPlanUnit: "Lesson Plan Unit" - CourseUnit: "Course Unit" - TextBookUnit: "Textbook Unit" +languageCode { + assamese : "as" + bengali : "bn" + english : "en" + gujarati : "gu" + hindi : "hi" + kannada : "ka" + marathi : "mr" + odia : "or" + tamil : "ta" + telugu : "te" } -resourceTypeToPrimaryCategory { - Learn: "Learning Resource" - Read: "Learning Resource" - Practice: "Learning Resource" - Teach: "Teacher Resource" - Test: "Learning Resource" - Experiment: "Learning Resource" - LessonPlan: "Teacher Resource" +kafka { + urls : "localhost:9092" + topic.send.enable : true + topics.instruction : "sunbirddev.assessment.publish.request" +} +objectcategorydefinition.keyspace="category_store" +question { + keyspace = "question_store" + list.limit=20 } +questionset.keyspace="hierarchy_store" -mimeTypeToPrimaryCategory { - "application/vnd.ekstep.h5p-archive": ["Learning Resource"] - "application/vnd.ekstep.html-archive": ["Learning Resource"] - "application/vnd.android.package-archive": ["Learning Resource"] - "video/webm": ["Explanation Content"] - "video/x-youtube": ["Explanation Content"] - "video/mp4": ["Explanation Content"] - "application/pdf": ["Learning Resource", "Teacher Resource"] - "application/epub": ["Learning Resource", "Teacher Resource"] - "application/vnd.ekstep.ecml-archive": ["Learning Resource", "Teacher Resource"] - "text/x-url": ["Learnin Resource", "Teacher Resource"] +cassandra { + lp { + connection: "localhost:9042" + } + lpa { + connection: "localhost:9042" + } +} +neo4j_objecttypes_enabled=["Question"] + +import { + request_size_limit = 200 + output_topic_name = "local.auto.creation.job.request" + required_props { + question = ["name", "code", "mimeType", "framework", "channel"] + questionset = ["name", "code", "mimeType", "framework", "channel"] + } + remove_props { + question = [] + questionset = [] + } } -objectcategorydefinition.keyspace=category_store +root_node_visibility=["Default","Private"] \ No newline at end of file diff --git a/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala b/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala index 9492aea71..01bad4b13 100644 --- a/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala +++ b/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/BaseSpec.scala @@ -18,17 +18,6 @@ class BaseSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll with B var graphDb: GraphDatabaseService = null var session: Session = null - private val script_1 = "CREATE KEYSPACE IF NOT EXISTS content_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" - private val script_2 = "CREATE TABLE IF NOT EXISTS content_store.content_data (content_id text, last_updated_on timestamp,body blob,oldBody blob,stageIcons blob,PRIMARY KEY (content_id));" - private val script_5 = "CREATE KEYSPACE IF NOT EXISTS category_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" - private val script_6 = "CREATE TABLE IF NOT EXISTS category_store.category_definition_data (identifier text, objectmetadata map, forms map ,PRIMARY KEY (identifier));" - private val script_7 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_content_all', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});" - private val script_8 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:course_collection_all', {'config': '{}', 'schema': '{\"properties\":{\"trackable\":{\"type\":\"object\",\"properties\":{\"enabled\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"},\"autoBatch\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"}},\"default\":{\"enabled\":\"No\",\"autoBatch\":\"No\"},\"additionalProperties\":false},\"additionalCategories\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"default\":\"Textbook\"}},\"userConsent\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"}}}'});" - private val script_9 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:course_content_all',{'config': '{}', 'schema': '{\"properties\":{\"trackable\":{\"type\":\"object\",\"properties\":{\"enabled\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"},\"autoBatch\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"}},\"default\":{\"enabled\":\"No\",\"autoBatch\":\"No\"},\"additionalProperties\":false},\"additionalCategories\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"default\":\"Textbook\"}},\"userConsent\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"}}}'});" - private val script_10 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_collection_all', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});" - private val script_11 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_content_in.ekstep', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});" - private val script_12 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_collection_in.ekstep', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});" - def setUpEmbeddedNeo4j(): Unit = { if(null == graphDb) { val bolt: BoltConnector = new BoltConnector("0") @@ -80,7 +69,6 @@ class BaseSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll with B tearEmbeddedNeo4JSetup() setUpEmbeddedNeo4j() setUpEmbeddedCassandra() - executeCassandraQuery(script_1, script_2, script_5, script_6, script_7, script_8, script_9, script_10, script_11, script_12) } override def afterAll(): Unit = { diff --git a/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/HierarchyManagerTest.scala b/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/HierarchyManagerTest.scala new file mode 100644 index 000000000..baaaeadb8 --- /dev/null +++ b/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/HierarchyManagerTest.scala @@ -0,0 +1,128 @@ +package org.sunbird.managers + +import java.util + +import org.sunbird.common.JsonUtils +import org.sunbird.common.dto.Request +import org.sunbird.common.exception.ClientException +import org.sunbird.graph.OntologyEngineContext +import org.sunbird.utils.HierarchyConstants + +import scala.collection.JavaConversions._ +import scala.collection.JavaConverters._ + +class HierarchyManagerTest extends BaseSpec { + + implicit val oec: OntologyEngineContext = new OntologyEngineContext + + private val KEYSPACE_CREATE_SCRIPT = "CREATE KEYSPACE IF NOT EXISTS hierarchy_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" + private val TABLE_CREATE_SCRIPT = "CREATE TABLE IF NOT EXISTS hierarchy_store.questionset_hierarchy (identifier text,hierarchy text,instructions text,outcomeDeclaration text,PRIMARY KEY (identifier));" + private val CATEGORY_STORE_KEYSPACE = "CREATE KEYSPACE IF NOT EXISTS category_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" + private val CATEGORY_DEF_DATA_TABLE = "CREATE TABLE IF NOT EXISTS category_store.category_definition_data (identifier text PRIMARY KEY, forms map, objectmetadata map);" + private val CATEGORY_DEF_INPUT = List("INSERT INTO category_store.category_definition_data(identifier,objectmetadata) values ('obj-cat:survey_questionset_all',{'config': '{}', 'schema': '{\"properties\":{\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.sunbird.questionset\"]},\"allowBranching\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"},\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Education Official\",\"School leaders (HMs)\",\"Administrator\",\"Teachers\",\"Students\",\"Parents\",\"Others\"]}},\"allowScoring\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"},\"setPeriod\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"]}}}'})", + "INSERT INTO category_store.category_definition_data(identifier,objectmetadata) values ('obj-cat:observation_questionset_all',{'config': '{}', 'schema': '{\"properties\":{\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.sunbird.questionset\"]},\"allowBranching\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"},\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Education Official\",\"School leaders (HMs)\",\"Administrator\",\"Teachers\",\"Students\",\"Parents\",\"Others\"]}},\"allowScoring\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"}}}'})", + "INSERT INTO category_store.category_definition_data(identifier,objectmetadata) values ('obj-cat:practice-question-set_questionset_all',{'config': '{}', 'schema': '{\"properties\":{\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.sunbird.questionset\"]}}}'})", + "INSERT INTO category_store.category_definition_data(identifier,objectmetadata) values ('obj-cat:multiple-choice-question_question_all',{'config': '{}', 'schema': '{\"properties\":{\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.sunbird.question\"]},\"interactionTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"choice\"]}}}}'})", + "INSERT INTO category_store.category_definition_data(identifier,objectmetadata) values ('obj-cat:text_question_all',{'config': '{}', 'schema': '{\"properties\":{\"interactionTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"text\"]}},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.sunbird.question\"]}}}'})") + private val HIERARCHY_QS_1 = "INSERT INTO hierarchy_store.questionset_hierarchy (identifier,hierarchy) values('do_obs_123', '{\"identifier\":\"do_obs_123\",\"children\":[{\"parent\":\"do_obs_123\",\"code\":\"do_textq_draft_123\",\"description\":\"Text Type Question 1\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.sunbird.question\",\"createdOn\":\"2022-02-08T13:14:51.741+0000\",\"objectType\":\"Question\",\"primaryCategory\":\"Text\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2022-02-08T13:14:51.741+0000\",\"contentEncoding\":\"gzip\",\"showSolutions\":\"No\",\"allowAnonymousAccess\":\"Yes\",\"identifier\":\"do_textq_draft_123\",\"lastStatusChangedOn\":\"2022-02-08T13:14:51.741+0000\",\"visibility\":\"Parent\",\"showTimer\":\"No\",\"index\":1,\"languageCode\":[\"en\"],\"version\":1,\"versionKey\":\"1644326091747\",\"showFeedback\":\"No\",\"license\":\"CC BY 4.0\",\"depth\":1,\"compatibilityLevel\":4,\"name\":\"Q1\",\"status\":\"Draft\"},{\"parent\":\"do_obs_123\",\"code\":\"do_mcqq_draft_123\",\"description\":\"MCQ\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.sunbird.question\",\"createdOn\":\"2022-02-08T13:14:51.742+0000\",\"objectType\":\"Question\",\"primaryCategory\":\"Multiple Choice Question\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2022-02-08T13:14:51.742+0000\",\"contentEncoding\":\"gzip\",\"showSolutions\":\"No\",\"allowAnonymousAccess\":\"Yes\",\"identifier\":\"do_mcqq_draft_123\",\"lastStatusChangedOn\":\"2022-02-08T13:14:51.742+0000\",\"visibility\":\"Parent\",\"showTimer\":\"No\",\"index\":2,\"languageCode\":[\"en\"],\"version\":1,\"versionKey\":\"1644326091748\",\"showFeedback\":\"No\",\"license\":\"CC BY 4.0\",\"depth\":1,\"compatibilityLevel\":4,\"name\":\"Q2\",\"status\":\"Draft\"}]}')" + private val HIERARCHY_QS_2 = "INSERT INTO hierarchy_store.questionset_hierarchy (identifier,hierarchy) values('do_obs_with_section_123', '{\"identifier\":\"do_obs_with_section_123\",\"children\":[{\"parent\":\"do_obs_with_section_123\",\"code\":\"section-1\",\"allowScoring\":\"No\",\"allowSkip\":\"Yes\",\"containsUserData\":\"No\",\"description\":\"section-1\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.sunbird.questionset\",\"showHints\":\"No\",\"createdOn\":\"2022-02-08T18:54:51.117+0000\",\"objectType\":\"QuestionSet\",\"primaryCategory\":\"Observation\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2022-02-08T18:54:51.117+0000\",\"contentEncoding\":\"gzip\",\"showSolutions\":\"No\",\"allowAnonymousAccess\":\"Yes\",\"identifier\":\"do_section_1\",\"lastStatusChangedOn\":\"2022-02-08T18:54:51.117+0000\",\"requiresSubmit\":\"No\",\"visibility\":\"Parent\",\"showTimer\":\"No\",\"index\":1,\"setType\":\"materialised\",\"languageCode\":[\"en\"],\"version\":1,\"versionKey\":\"1644346491117\",\"showFeedback\":\"No\",\"license\":\"CC BY 4.0\",\"depth\":1,\"createdBy\":\"4e397c42-495e-4fdb-8558-f98176230916\",\"compatibilityLevel\":5,\"name\":\"section-1\",\"navigationMode\":\"non-linear\",\"allowBranching\":\"Yes\",\"shuffle\":true,\"status\":\"Draft\",\"children\":[{\"parent\":\"do_section_1\",\"code\":\"do_textq_draft_123\",\"description\":\"Text Type Question 1\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.sunbird.question\",\"createdOn\":\"2022-02-08T13:14:51.741+0000\",\"objectType\":\"Question\",\"primaryCategory\":\"Text\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2022-02-08T13:14:51.741+0000\",\"contentEncoding\":\"gzip\",\"showSolutions\":\"No\",\"allowAnonymousAccess\":\"Yes\",\"identifier\":\"do_textq_draft_123\",\"lastStatusChangedOn\":\"2022-02-08T13:14:51.741+0000\",\"visibility\":\"Parent\",\"showTimer\":\"No\",\"index\":1,\"languageCode\":[\"en\"],\"version\":1,\"versionKey\":\"1644326091747\",\"showFeedback\":\"No\",\"license\":\"CC BY 4.0\",\"depth\":1,\"compatibilityLevel\":4,\"name\":\"Q1\",\"status\":\"Draft\"},{\"parent\":\"do_section_1\",\"code\":\"do_mcqq_draft_123\",\"description\":\"MCQ\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.sunbird.question\",\"createdOn\":\"2022-02-08T13:14:51.742+0000\",\"objectType\":\"Question\",\"primaryCategory\":\"Multiple Choice Question\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2022-02-08T13:14:51.742+0000\",\"contentEncoding\":\"gzip\",\"showSolutions\":\"No\",\"allowAnonymousAccess\":\"Yes\",\"identifier\":\"do_mcqq_draft_123\",\"lastStatusChangedOn\":\"2022-02-08T13:14:51.742+0000\",\"visibility\":\"Parent\",\"showTimer\":\"No\",\"index\":2,\"languageCode\":[\"en\"],\"version\":1,\"versionKey\":\"1644326091748\",\"showFeedback\":\"No\",\"license\":\"CC BY 4.0\",\"depth\":1,\"compatibilityLevel\":4,\"name\":\"Q2\",\"status\":\"Draft\"}]}]}')" + private val HIERARCHY_QS_3 = "INSERT INTO hierarchy_store.questionset_hierarchy (identifier,hierarchy) values('do_obs_with_section_234', '{\"identifier\":\"do_obs_with_section_234\",\"children\":[{\"parent\":\"do_obs_with_section_234\",\"branchingLogic\":{\"do_textq_draft_123\":{\"target\":[\"do_mcqq_draft_123\"],\"preCondition\":{},\"source\":[]},\"do_mcqq_draft_123\":{\"target\":[],\"preCondition\":{\"and\":[{\"eq\":[{\"var\":\"do_textq_draft_123.response1.value\",\"type\":\"responseDeclaration\"},\"0\"]}]},\"source\":[\"do_textq_draft_123\"]}},\"code\":\"section-1\",\"allowScoring\":\"No\",\"allowSkip\":\"Yes\",\"containsUserData\":\"No\",\"description\":\"section-1\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.sunbird.questionset\",\"showHints\":\"No\",\"createdOn\":\"2022-02-08T18:54:51.117+0000\",\"objectType\":\"QuestionSet\",\"primaryCategory\":\"Observation\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2022-02-08T18:54:51.117+0000\",\"contentEncoding\":\"gzip\",\"showSolutions\":\"No\",\"allowAnonymousAccess\":\"Yes\",\"identifier\":\"do_section_1\",\"lastStatusChangedOn\":\"2022-02-08T18:54:51.117+0000\",\"requiresSubmit\":\"No\",\"visibility\":\"Parent\",\"showTimer\":\"No\",\"index\":1,\"setType\":\"materialised\",\"languageCode\":[\"en\"],\"version\":1,\"versionKey\":\"1644346491117\",\"showFeedback\":\"No\",\"license\":\"CC BY 4.0\",\"depth\":1,\"createdBy\":\"4e397c42-495e-4fdb-8558-f98176230916\",\"compatibilityLevel\":5,\"name\":\"section-1\",\"navigationMode\":\"non-linear\",\"allowBranching\":\"Yes\",\"shuffle\":true,\"status\":\"Draft\",\"children\":[{\"parent\":\"do_section_1\",\"code\":\"do_textq_draft_123\",\"description\":\"Text Type Question 1\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.sunbird.question\",\"createdOn\":\"2022-02-08T13:14:51.741+0000\",\"objectType\":\"Question\",\"primaryCategory\":\"Text\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2022-02-08T13:14:51.741+0000\",\"contentEncoding\":\"gzip\",\"showSolutions\":\"No\",\"allowAnonymousAccess\":\"Yes\",\"identifier\":\"do_textq_draft_123\",\"lastStatusChangedOn\":\"2022-02-08T13:14:51.741+0000\",\"visibility\":\"Parent\",\"showTimer\":\"No\",\"index\":1,\"languageCode\":[\"en\"],\"version\":1,\"versionKey\":\"1644326091747\",\"showFeedback\":\"No\",\"license\":\"CC BY 4.0\",\"depth\":1,\"compatibilityLevel\":4,\"name\":\"Q1\",\"status\":\"Draft\"},{\"parent\":\"do_section_1\",\"code\":\"do_mcqq_draft_123\",\"description\":\"MCQ\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.sunbird.question\",\"createdOn\":\"2022-02-08T13:14:51.742+0000\",\"objectType\":\"Question\",\"primaryCategory\":\"Multiple Choice Question\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2022-02-08T13:14:51.742+0000\",\"contentEncoding\":\"gzip\",\"showSolutions\":\"No\",\"allowAnonymousAccess\":\"Yes\",\"identifier\":\"do_mcqq_draft_123\",\"lastStatusChangedOn\":\"2022-02-08T13:14:51.742+0000\",\"visibility\":\"Parent\",\"showTimer\":\"No\",\"index\":2,\"languageCode\":[\"en\"],\"version\":1,\"versionKey\":\"1644326091748\",\"showFeedback\":\"No\",\"license\":\"CC BY 4.0\",\"depth\":1,\"compatibilityLevel\":4,\"name\":\"Q2\",\"status\":\"Draft\"}]}]}')" + + override def beforeAll(): Unit = { + super.beforeAll() + graphDb.execute("UNWIND [" + + "{IL_UNIQUE_ID:\"obj-cat:survey\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-20T09:35:01.320+0000\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",name:\"Survey\",lastUpdatedOn:\"2021-08-20T09:35:01.320+0000\",description:\"Survey\",languageCode:[],createdOn:\"2021-08-20T09:35:01.320+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategory\",versionKey:\"1629452101320\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:observation\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-20T09:35:19.249+0000\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",name:\"Observation\",lastUpdatedOn:\"2021-08-20T09:35:19.249+0000\",description:\"Observation\",languageCode:[],createdOn:\"2021-08-20T09:35:19.249+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategory\",versionKey:\"1629452119249\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:practice-question-set\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2020-10-12T07:07:24.631+0000\",name:\"Practice Question Set\",lastUpdatedOn:\"2020-10-12T07:07:24.631+0000\",description:\"Practice Question Set\",languageCode:[],createdOn:\"2020-10-12T07:07:24.631+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategory\",versionKey:\"1602486444631\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:multiple-choice-question\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-01-04T05:49:19.878+0000\",name:\"Multiple Choice Question\",lastUpdatedOn:\"2021-01-04T05:49:19.878+0000\",description:\"Multiple Choice Question\",languageCode:[],createdOn:\"2021-01-04T05:49:19.878+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategory\",versionKey:\"1609739359878\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:text\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-20T09:36:41.178+0000\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",name:\"Text\",lastUpdatedOn:\"2021-08-20T09:36:41.178+0000\",description:\"Text\",languageCode:[],createdOn:\"2021-08-20T09:36:41.178+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategory\",versionKey:\"1629452201178\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:survey_questionset_all\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-24T16:22:17.524+0000\",visibility:\"Default\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",description:\"Survey\",languageCode:[],createdOn:\"2021-08-24T16:22:17.524+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",versionKey:\"1643101793375\",name:\"Survey\",lastUpdatedOn:\"2022-01-25T09:09:53.375+0000\",targetObjectType:\"QuestionSet\",categoryId:\"obj-cat:survey\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:observation_questionset_all\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-24T16:22:45.014+0000\",visibility:\"Default\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",description:\"Observation\",languageCode:[],createdOn:\"2021-08-24T16:22:45.014+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",versionKey:\"1643101511878\",name:\"Observation\",lastUpdatedOn:\"2022-01-25T09:05:11.878+0000\",targetObjectType:\"QuestionSet\",categoryId:\"obj-cat:observation\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:practice-question-set_questionset_all\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2020-12-10T03:36:57.678+0000\",visibility:\"Default\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",description:\"Practice Question Set\",languageCode:[],createdOn:\"2020-12-10T03:36:57.678+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",versionKey:\"1638263933587\",name:\"Demo Practice Question Set\",lastUpdatedOn:\"2021-11-30T09:18:53.587+0000\",targetObjectType:\"QuestionSet\",categoryId:\"obj-cat:practice-question-set\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:multiple-choice-question_question_all\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-01-04T05:50:31.468+0000\",visibility:\"Default\",consumerId:\"5880ed91-28c4-4d34-9919-036982b0c4ea\",description:\"Multiple Choice Question\",languageCode:[],createdOn:\"2021-01-04T05:50:31.468+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",versionKey:\"1640699675966\",name:\"Multiple Choice Question\",lastUpdatedOn:\"2021-12-28T13:54:35.966+0000\",targetObjectType:\"Question\",categoryId:\"obj-cat:multiple-choice-question\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:text_question_all\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-24T16:23:32.869+0000\",visibility:\"Default\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",description:\"Text\",languageCode:[],createdOn:\"2021-08-24T16:23:32.869+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",versionKey:\"1641394368538\",name:\"Text\",lastUpdatedOn:\"2022-01-05T14:52:48.538+0000\",targetObjectType:\"Question\",categoryId:\"obj-cat:text\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"do_obs_123\",IL_FUNC_OBJECT_TYPE:\"QuestionSet\",IL_SYS_NODE_TYPE:\"DATA_NODE\",code:\"c288ea98-0a8d-c927-5ec3-e879a90e9e43\",allowScoring:\"No\",allowSkip:\"Yes\",containsUserData:\"No\",language:[\"English\"],mimeType:\"application/vnd.sunbird.questionset\",showHints:\"No\",createdOn:\"2022-02-06T20:43:01.535+0000\",primaryCategory:\"Observation\",contentDisposition:\"inline\",lastUpdatedOn:\"2022-02-06T20:43:01.535+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",lastStatusChangedOn:\"2022-02-06T20:43:01.535+0000\",createdFor:[\"sunbird\"],requiresSubmit:\"No\",visibility:\"Default\",showTimer:\"No\",setType:\"materialised\",languageCode:[\"en\"],version:1,versionKey:\"1644180181535\",showFeedback:\"No\",license:\"CC BY 4.0\",createdBy:\"crt-01\",compatibilityLevel:5,name:\"Test Observation\",navigationMode:\"non-linear\",allowBranching:\"Yes\",shuffle:true,status:\"Draft\"}," + + "{IL_UNIQUE_ID:\"do_obs_with_section_123\",IL_FUNC_OBJECT_TYPE:\"QuestionSet\",IL_SYS_NODE_TYPE:\"DATA_NODE\",code:\"c288ea98-0a8d-c927-5ec3-e879a90e9e43\",allowScoring:\"No\",allowSkip:\"Yes\",containsUserData:\"No\",language:[\"English\"],mimeType:\"application/vnd.sunbird.questionset\",showHints:\"No\",createdOn:\"2022-02-06T20:43:01.535+0000\",primaryCategory:\"Observation\",contentDisposition:\"inline\",lastUpdatedOn:\"2022-02-06T20:43:01.535+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",lastStatusChangedOn:\"2022-02-06T20:43:01.535+0000\",createdFor:[\"sunbird\"],requiresSubmit:\"No\",visibility:\"Default\",showTimer:\"No\",setType:\"materialised\",languageCode:[\"en\"],version:1,versionKey:\"1644180181535\",showFeedback:\"No\",license:\"CC BY 4.0\",createdBy:\"crt-01\",compatibilityLevel:5,name:\"Test Observation\",navigationMode:\"non-linear\",allowBranching:\"Yes\",shuffle:true,status:\"Draft\",childNodes:[\"do_section_1\"]}," + + "{IL_UNIQUE_ID:\"do_obs_with_section_234\",IL_FUNC_OBJECT_TYPE:\"QuestionSet\",IL_SYS_NODE_TYPE:\"DATA_NODE\",code:\"c288ea98-0a8d-c927-5ec3-e879a90e9e43\",allowScoring:\"No\",allowSkip:\"Yes\",containsUserData:\"No\",language:[\"English\"],mimeType:\"application/vnd.sunbird.questionset\",showHints:\"No\",createdOn:\"2022-02-06T20:43:01.535+0000\",primaryCategory:\"Observation\",contentDisposition:\"inline\",lastUpdatedOn:\"2022-02-06T20:43:01.535+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",lastStatusChangedOn:\"2022-02-06T20:43:01.535+0000\",createdFor:[\"sunbird\"],requiresSubmit:\"No\",visibility:\"Default\",showTimer:\"No\",setType:\"materialised\",languageCode:[\"en\"],version:1,versionKey:\"1644180181535\",showFeedback:\"No\",license:\"CC BY 4.0\",createdBy:\"crt-01\",compatibilityLevel:5,name:\"Test Observation\",navigationMode:\"non-linear\",allowBranching:\"Yes\",shuffle:true,status:\"Draft\",childNodes:[\"do_section_1\"]}," + + "{IL_UNIQUE_ID:\"do_textq_live_123\",IL_SYS_NODE_TYPE:\"DATA_NODE\",code:\"Q2_1616157220157\",showRemarks:\"No\",channel:\"sunbird\",language:[\"English\"],mimeType:\"application/vnd.sunbird.question\",createdOn:\"2022-02-07T12:49:25.908+0000\",IL_FUNC_OBJECT_TYPE:\"Question\",primaryCategory:\"Text\",contentDisposition:\"inline\",lastUpdatedOn:\"2022-02-07T12:49:25.908+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",lastStatusChangedOn:\"2022-02-07T12:49:25.908+0000\",visibility:\"Default\",showTimer:\"No\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",languageCode:[\"en\"],version:1,versionKey:\"1644238165908\",showFeedback:\"No\",license:\"CC BY 4.0\",interactionTypes:[\"text\"],compatibilityLevel:4,name:\"Text Type Question\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"do_textq_draft_123\",IL_SYS_NODE_TYPE:\"DATA_NODE\",code:\"Q2_1616157220157\",showRemarks:\"No\",channel:\"sunbird\",language:[\"English\"],mimeType:\"application/vnd.sunbird.question\",createdOn:\"2022-02-07T12:49:25.908+0000\",IL_FUNC_OBJECT_TYPE:\"Question\",primaryCategory:\"Text\",contentDisposition:\"inline\",lastUpdatedOn:\"2022-02-07T12:49:25.908+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",lastStatusChangedOn:\"2022-02-07T12:49:25.908+0000\",visibility:\"Default\",showTimer:\"No\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",languageCode:[\"en\"],version:1,versionKey:\"1644238165908\",showFeedback:\"No\",license:\"CC BY 4.0\",interactionTypes:[\"text\"],compatibilityLevel:4,name:\"Text Type Question\",status:\"Draft\"}," + + "{IL_UNIQUE_ID:\"do_mcqq_draft_123\",IL_SYS_NODE_TYPE:\"DATA_NODE\",code:\"Q2_1616157220157\",showRemarks:\"No\",channel:\"sunbird\",language:[\"English\"],mimeType:\"application/vnd.sunbird.question\",createdOn:\"2022-02-07T12:49:25.908+0000\",IL_FUNC_OBJECT_TYPE:\"Question\",primaryCategory:\"Multiple Choice Question\",contentDisposition:\"inline\",lastUpdatedOn:\"2022-02-07T12:49:25.908+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",lastStatusChangedOn:\"2022-02-07T12:49:25.908+0000\",visibility:\"Default\",showTimer:\"No\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",languageCode:[\"en\"],version:1,versionKey:\"1644238165908\",showFeedback:\"No\",license:\"CC BY 4.0\",interactionTypes:[\"choice\"],compatibilityLevel:4,name:\"Text Type Question\",status:\"Draft\"}," + + "{IL_UNIQUE_ID:\"do_mcqq_live_123\",IL_SYS_NODE_TYPE:\"DATA_NODE\",code:\"Q2_1616157220157\",showRemarks:\"No\",channel:\"sunbird\",language:[\"English\"],mimeType:\"application/vnd.sunbird.question\",createdOn:\"2022-02-07T12:49:25.908+0000\",IL_FUNC_OBJECT_TYPE:\"Question\",primaryCategory:\"Multiple Choice Question\",contentDisposition:\"inline\",lastUpdatedOn:\"2022-02-07T12:49:25.908+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",lastStatusChangedOn:\"2022-02-07T12:49:25.908+0000\",visibility:\"Default\",showTimer:\"No\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",languageCode:[\"en\"],version:1,versionKey:\"1644238165908\",showFeedback:\"No\",license:\"CC BY 4.0\",interactionTypes:[\"choice\"],compatibilityLevel:4,name:\"Text Type Question\",status:\"Live\"}" + + "] as row CREATE (n:domain) SET n += row") + executeCassandraQuery(KEYSPACE_CREATE_SCRIPT, TABLE_CREATE_SCRIPT, CATEGORY_STORE_KEYSPACE, CATEGORY_DEF_DATA_TABLE, HIERARCHY_QS_1, HIERARCHY_QS_2) + executeCassandraQuery(CATEGORY_DEF_INPUT: _*) + println("all query executed...") + + } + + override def beforeEach(): Unit = { + + } + + def getContext(objectType: String): java.util.Map[String, AnyRef] = { + new util.HashMap[String, AnyRef]() { + { + put(HierarchyConstants.GRAPH_ID, HierarchyConstants.TAXONOMY_ID) + put(HierarchyConstants.VERSION, HierarchyConstants.SCHEMA_VERSION) + put(HierarchyConstants.OBJECT_TYPE, objectType) + put(HierarchyConstants.SCHEMA_NAME, objectType.toLowerCase) + put(HierarchyConstants.CHANNEL, "sunbird") + } + } + } + + "addLeafNodesToHierarchy" should "add leaf node under root" in { + val request = new Request() + request.setContext(getContext(HierarchyConstants.QUESTIONSET_OBJECT_TYPE)) + request.putAll(mapAsJavaMap(Map(HierarchyConstants.ROOT_ID -> "do_obs_123", "mode" -> "edit", "children" -> List("do_textq_live_123").asJava))) + val future = HierarchyManager.addLeafNodesToHierarchy(request) + future.map(response => { + assert(response.getResponseCode.code() == 200) + assert(response.getResult.get("children").asInstanceOf[util.List[String]].containsAll(request.get("children").asInstanceOf[util.List[String]])) + val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.questionset_hierarchy where identifier='do_obs_123'") + .one().getString("hierarchy") + assert(hierarchy.contains("do_textq_live_123")) + }) + } + + "addLeafNodesToHierarchy with section and valid branching logic" should "add leaf node under section along with branching logic" in { + val request = new Request() + request.setContext(getContext(HierarchyConstants.QUESTIONSET_OBJECT_TYPE)) + val branchingLogicStr = "{\"do_textq_live_123\":{\"target\":[],\"preCondition\":{\"and\":[{\"eq\":[{\"var\":\"do_textq_draft_123.response1.value\",\"type\":\"responseDeclaration\"},\"0\"]}]},\"source\":[\"do_textq_draft_123\"]}}" + request.putAll(mapAsJavaMap(Map(HierarchyConstants.ROOT_ID -> "do_obs_with_section_123", "mode" -> "edit", "collectionId" -> "do_section_1", "children" -> List("do_textq_live_123").asJava, "branchingLogic" -> JsonUtils.deserialize(branchingLogicStr, classOf[util.HashMap[String, AnyRef]])))) + val future = HierarchyManager.addLeafNodesToHierarchy(request) + future.map(response => { + assert(response.getResponseCode.code() == 200) + val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.questionset_hierarchy where identifier='do_obs_with_section_123'") + .one().getString("hierarchy") + assert(hierarchy.contains("do_textq_live_123")) + assert(hierarchy.contains("branchingLogic")) + }) + } + + "addLeafNodesToHierarchy with section and invalid branching logic" should "throw client error" in { + val request = new Request() + request.setContext(getContext(HierarchyConstants.QUESTIONSET_OBJECT_TYPE)) + val branchingLogicStr = "{\"do_text_live_123\":{\"target\":[],\"preCondition\":{\"and\":[{\"eq\":[{\"var\":\"do_textq_draft_123.response1.value\",\"type\":\"responseDeclaration\"},\"0\"]}]},\"source\":[\"do_textq_draft_123\"]}}" + request.putAll(mapAsJavaMap(Map(HierarchyConstants.ROOT_ID -> "do_obs_with_section_123", "mode" -> "edit", "collectionId" -> "do_section_1", "children" -> List("do_textq_live_123").asJava, "branchingLogic" -> JsonUtils.deserialize(branchingLogicStr, classOf[util.HashMap[String, AnyRef]])))) + val exception = intercept[ClientException] { + HierarchyManager.addLeafNodesToHierarchy(request) + } + exception.getErrCode shouldEqual "ERR_BAD_REQUEST" + exception.getMessage shouldEqual "Branch Rule Found For The Node Which Is Not A Children Having Identifier : [do_text_live_123]" + } + + "removeLeafNodesToHierarchy" should "remove the leaf node as well as branching logic if present" in { + executeCassandraQuery(HIERARCHY_QS_3) + val request = new Request() + request.setContext(getContext(HierarchyConstants.QUESTIONSET_OBJECT_TYPE)) + request.putAll(mapAsJavaMap(Map(HierarchyConstants.ROOT_ID -> "do_obs_with_section_234", "mode" -> "edit", "collectionId" -> "do_section_1", "children" -> List("do_mcqq_draft_123").asJava))) + HierarchyManager.removeLeafNodesFromHierarchy(request).map(response => { + assert(response.getResponseCode.code() == 200) + val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.questionset_hierarchy where identifier='do_obs_with_section_234'") + .one().getString("hierarchy") + println("hierarchy ::: "+hierarchy) + assert(!hierarchy.contains("do_mcqq_draft_123")) + }) + } +} diff --git a/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/UpdateHierarchyManagerTest.scala b/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/UpdateHierarchyManagerTest.scala new file mode 100644 index 000000000..e2e9a3438 --- /dev/null +++ b/assessment-api/qs-hierarchy-manager/src/test/scala/org/sunbird/managers/UpdateHierarchyManagerTest.scala @@ -0,0 +1,85 @@ +package org.sunbird.managers + +import java.util + +import org.apache.commons.collections4.MapUtils +import org.parboiled.common.StringUtils +import org.sunbird.common.JsonUtils +import org.sunbird.common.dto.Request +import org.sunbird.graph.OntologyEngineContext +import org.sunbird.utils.HierarchyConstants + +class UpdateHierarchyManagerTest extends BaseSpec { + + implicit val oec: OntologyEngineContext = new OntologyEngineContext + + private val KEYSPACE_CREATE_SCRIPT = "CREATE KEYSPACE IF NOT EXISTS hierarchy_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" + private val TABLE_CREATE_SCRIPT = "CREATE TABLE IF NOT EXISTS hierarchy_store.questionset_hierarchy (identifier text,hierarchy text,instructions text,outcomeDeclaration text,PRIMARY KEY (identifier));" + private val CATEGORY_STORE_KEYSPACE = "CREATE KEYSPACE IF NOT EXISTS category_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" + private val CATEGORY_DEF_DATA_TABLE = "CREATE TABLE IF NOT EXISTS category_store.category_definition_data (identifier text PRIMARY KEY, forms map, objectmetadata map);" + private val CATEGORY_DEF_INPUT = List("INSERT INTO category_store.category_definition_data(identifier,objectmetadata) values ('obj-cat:survey_questionset_all',{'config': '{}', 'schema': '{\"properties\":{\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.sunbird.questionset\"]},\"allowBranching\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"},\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Education Official\",\"School leaders (HMs)\",\"Administrator\",\"Teachers\",\"Students\",\"Parents\",\"Others\"]}},\"allowScoring\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"},\"setPeriod\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"]}}}'})", + "INSERT INTO category_store.category_definition_data(identifier,objectmetadata) values ('obj-cat:observation_questionset_all',{'config': '{}', 'schema': '{\"properties\":{\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.sunbird.questionset\"]},\"allowBranching\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"Yes\"},\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Education Official\",\"School leaders (HMs)\",\"Administrator\",\"Teachers\",\"Students\",\"Parents\",\"Others\"]}},\"allowScoring\":{\"type\":\"string\",\"enum\":[\"Yes\",\"No\"],\"default\":\"No\"}}}'})", + "INSERT INTO category_store.category_definition_data(identifier,objectmetadata) values ('obj-cat:practice-question-set_questionset_all',{'config': '{}', 'schema': '{\"properties\":{\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.sunbird.questionset\"]}}}'})", + "INSERT INTO category_store.category_definition_data(identifier,objectmetadata) values ('obj-cat:multiple-choice-question_question_all',{'config': '{}', 'schema': '{\"properties\":{\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.sunbird.question\"]},\"interactionTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"choice\"]}}}}'})", + "INSERT INTO category_store.category_definition_data(identifier,objectmetadata) values ('obj-cat:text_question_all',{'config': '{}', 'schema': '{\"properties\":{\"interactionTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"text\"]}},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.sunbird.question\"]}}}'})") + + override def beforeAll(): Unit = { + super.beforeAll() + graphDb.execute("UNWIND [" + + "{IL_UNIQUE_ID:\"obj-cat:survey\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-20T09:35:01.320+0000\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",name:\"Survey\",lastUpdatedOn:\"2021-08-20T09:35:01.320+0000\",description:\"Survey\",languageCode:[],createdOn:\"2021-08-20T09:35:01.320+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategory\",versionKey:\"1629452101320\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:observation\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-20T09:35:19.249+0000\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",name:\"Observation\",lastUpdatedOn:\"2021-08-20T09:35:19.249+0000\",description:\"Observation\",languageCode:[],createdOn:\"2021-08-20T09:35:19.249+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategory\",versionKey:\"1629452119249\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:practice-question-set\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2020-10-12T07:07:24.631+0000\",name:\"Practice Question Set\",lastUpdatedOn:\"2020-10-12T07:07:24.631+0000\",description:\"Practice Question Set\",languageCode:[],createdOn:\"2020-10-12T07:07:24.631+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategory\",versionKey:\"1602486444631\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:multiple-choice-question\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-01-04T05:49:19.878+0000\",name:\"Multiple Choice Question\",lastUpdatedOn:\"2021-01-04T05:49:19.878+0000\",description:\"Multiple Choice Question\",languageCode:[],createdOn:\"2021-01-04T05:49:19.878+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategory\",versionKey:\"1609739359878\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:text\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-20T09:36:41.178+0000\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",name:\"Text\",lastUpdatedOn:\"2021-08-20T09:36:41.178+0000\",description:\"Text\",languageCode:[],createdOn:\"2021-08-20T09:36:41.178+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategory\",versionKey:\"1629452201178\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:survey_questionset_all\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-24T16:22:17.524+0000\",visibility:\"Default\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",description:\"Survey\",languageCode:[],createdOn:\"2021-08-24T16:22:17.524+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",versionKey:\"1643101793375\",name:\"Survey\",lastUpdatedOn:\"2022-01-25T09:09:53.375+0000\",targetObjectType:\"QuestionSet\",categoryId:\"obj-cat:survey\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:observation_questionset_all\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-24T16:22:45.014+0000\",visibility:\"Default\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",description:\"Observation\",languageCode:[],createdOn:\"2021-08-24T16:22:45.014+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",versionKey:\"1643101511878\",name:\"Observation\",lastUpdatedOn:\"2022-01-25T09:05:11.878+0000\",targetObjectType:\"QuestionSet\",categoryId:\"obj-cat:observation\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:practice-question-set_questionset_all\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2020-12-10T03:36:57.678+0000\",visibility:\"Default\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",description:\"Practice Question Set\",languageCode:[],createdOn:\"2020-12-10T03:36:57.678+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",versionKey:\"1638263933587\",name:\"Demo Practice Question Set\",lastUpdatedOn:\"2021-11-30T09:18:53.587+0000\",targetObjectType:\"QuestionSet\",categoryId:\"obj-cat:practice-question-set\",status:\"Live\"},"+ + "{IL_UNIQUE_ID:\"obj-cat:multiple-choice-question_question_all\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-01-04T05:50:31.468+0000\",visibility:\"Default\",consumerId:\"5880ed91-28c4-4d34-9919-036982b0c4ea\",description:\"Multiple Choice Question\",languageCode:[],createdOn:\"2021-01-04T05:50:31.468+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",versionKey:\"1640699675966\",name:\"Multiple Choice Question\",lastUpdatedOn:\"2021-12-28T13:54:35.966+0000\",targetObjectType:\"Question\",categoryId:\"obj-cat:multiple-choice-question\",status:\"Live\"}," + + "{IL_UNIQUE_ID:\"obj-cat:text_question_all\",IL_SYS_NODE_TYPE:\"DATA_NODE\",lastStatusChangedOn:\"2021-08-24T16:23:32.869+0000\",visibility:\"Default\",consumerId:\"64fe2164-1523-4b44-8884-319b07384234\",description:\"Text\",languageCode:[],createdOn:\"2021-08-24T16:23:32.869+0000\",IL_FUNC_OBJECT_TYPE:\"ObjectCategoryDefinition\",versionKey:\"1641394368538\",name:\"Text\",lastUpdatedOn:\"2022-01-05T14:52:48.538+0000\",targetObjectType:\"Question\",categoryId:\"obj-cat:text\",status:\"Live\"}" + + "] as row CREATE (n:domain) SET n += row") + executeCassandraQuery(KEYSPACE_CREATE_SCRIPT, TABLE_CREATE_SCRIPT, CATEGORY_STORE_KEYSPACE, CATEGORY_DEF_DATA_TABLE) + executeCassandraQuery(CATEGORY_DEF_INPUT:_*) + println("all query executed...") + + } + + override def beforeEach(): Unit = { + + } + + def getContext(objectType: String): java.util.Map[String, AnyRef] = { + new util.HashMap[String, AnyRef](){{ + put(HierarchyConstants.GRAPH_ID, HierarchyConstants.TAXONOMY_ID) + put(HierarchyConstants.VERSION , HierarchyConstants.SCHEMA_VERSION) + put(HierarchyConstants.OBJECT_TYPE , objectType) + put(HierarchyConstants.SCHEMA_NAME, objectType.toLowerCase) + put(HierarchyConstants.CHANNEL, "sunbird") + }} + } + + "updateHierarchy" should "create the hierarchy structure for questionset" in { + graphDb.execute("UNWIND [" + + "{IL_UNIQUE_ID:\"do_obs_1234\",IL_FUNC_OBJECT_TYPE:\"QuestionSet\",IL_SYS_NODE_TYPE:\"DATA_NODE\",code:\"c288ea98-0a8d-c927-5ec3-e879a90e9e43\",allowScoring:\"No\",allowSkip:\"Yes\",containsUserData:\"No\",language:[\"English\"],mimeType:\"application/vnd.sunbird.questionset\",showHints:\"No\",createdOn:\"2022-02-06T20:43:01.535+0000\",primaryCategory:\"Observation\",contentDisposition:\"inline\",lastUpdatedOn:\"2022-02-06T20:43:01.535+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",lastStatusChangedOn:\"2022-02-06T20:43:01.535+0000\",createdFor:[\"sunbird\"],requiresSubmit:\"No\",visibility:\"Default\",showTimer:\"No\",setType:\"materialised\",languageCode:[\"en\"],version:1,versionKey:\"1644180181535\",showFeedback:\"No\",license:\"CC BY 4.0\",createdBy:\"crt-01\",compatibilityLevel:5,name:\"Test Observation\",navigationMode:\"non-linear\",allowBranching:\"Yes\",shuffle:true,status:\"Draft\"}" + + "] as row CREATE (n:domain) SET n += row") + val nodesModifiedString = "{\n \"Q1\": {\n \"metadata\": {\n \"code\": \"Q1\",\n \"name\": \"Q1\",\n \"description\": \"Q1\",\n \"mimeType\": \"application/vnd.sunbird.question\",\n \"primaryCategory\": \"Text\"\n },\n \"objectType\": \"Question\",\n \"root\": false,\n \"isNew\": true\n },\n \"Q2\": {\n \"metadata\": {\n \"code\": \"Q2\",\n \"name\": \"Q2\",\n \"description\": \"Q2\",\n \"mimeType\": \"application/vnd.sunbird.question\",\n \"primaryCategory\": \"Text\"\n },\n \"objectType\": \"Question\",\n \"root\": false,\n \"isNew\": true\n },\n \"Q3\": {\n \"metadata\": {\n \"code\": \"Q3\",\n \"name\": \"Q3\",\n \"description\": \"Q3\",\n \"mimeType\": \"application/vnd.sunbird.question\",\n \"primaryCategory\": \"Text\"\n },\n \"objectType\": \"Question\",\n \"root\": false,\n \"isNew\": true\n },\n \"do_obs_1234\": {\n \"metadata\": {\n \"branchingLogic\": {\n \"Q1\": {\n \"target\": [\n \"Q2\"\n ],\n \"preCondition\": {},\n \"source\": []\n },\n \"Q2\": {\n \"target\": [],\n \"preCondition\": {\n \"and\": [\n {\n \"eq\": [\n {\n \"var\": \"Q1.response1.value\",\n \"type\": \"responseDeclaration\"\n },\n \"0\"\n ]\n }\n ]\n },\n \"source\": [\n \"Q1\"\n ]\n }\n }\n },\n \"objectType\": \"QuestionSet\",\n \"root\": true,\n \"isNew\": false\n }\n}" + val hierarchyString = "{\n \"do_obs_1234\": {\n \"children\": [\n \"Q1\",\n \"Q2\",\n \"Q3\"\n ],\n \"root\": true\n }\n }" + val request = new Request() + val context = getContext(HierarchyConstants.QUESTIONSET_OBJECT_TYPE) + context.put(HierarchyConstants.ROOT_ID, "do_obs_1234") + request.setContext(context) + request.put(HierarchyConstants.NODES_MODIFIED, JsonUtils.deserialize(nodesModifiedString, classOf[util.HashMap[String, AnyRef]])) + request.put(HierarchyConstants.HIERARCHY, JsonUtils.deserialize(hierarchyString, classOf[util.HashMap[String, AnyRef]])) + UpdateHierarchyManager.updateHierarchy(request).map(response => { + assert(response.getResponseCode.code() == 200) + val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.questionset_hierarchy where identifier='do_obs_1234'") + .one().getString("hierarchy") + val result = graphDb.execute("MATCH(n) WHERE n.IL_UNIQUE_ID=\"do_obs_1234\" RETURN n.branchingLogic AS branchingLogic;") + val record = if(result.hasNext) result.next() + val branchingLogic = JsonUtils.deserialize(record.asInstanceOf[java.util.Map[String, AnyRef]].get("branchingLogic").toString, classOf[util.HashMap[String, AnyRef]]) + assert(MapUtils.isNotEmpty(branchingLogic)) + assert(branchingLogic.size()==2) + assert(StringUtils.isNotEmpty(hierarchy)) + }) + } + +} diff --git a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestContentActor.scala b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestContentActor.scala index 515a51e1c..40778e99f 100644 --- a/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestContentActor.scala +++ b/content-api/content-actors/src/test/scala/org/sunbird/content/actors/TestContentActor.scala @@ -52,7 +52,7 @@ class TestContentActor extends BaseSpec with MockFactory { })) val request = getContentRequest() - request.getRequest.putAll( mapAsJavaMap(Map("channel"-> "in.ekstep","name" -> "New Content", "code" -> "1234", "mimeType"-> "application/vnd.ekstep.content-collection", "contentType" -> "Course", "primaryCategory" -> "Learning Resource", "channel" -> "in.ekstep", "targetBoardIds" -> new util.ArrayList[String](){{add("ncf_board_cbse")}}))) + request.getRequest.putAll( mapAsJavaMap(Map("channel"-> "in.ekstep","name" -> "New", "code" -> "1234", "mimeType"-> "application/vnd.ekstep.content-collection", "contentType" -> "Course", "primaryCategory" -> "Learning Resource", "channel" -> "in.ekstep", "targetBoardIds" -> new util.ArrayList[String](){{add("ncf_board_cbse")}}))) request.setOperation("createContent") val response = callActor(request, Props(new ContentActor())) assert(response.get("identifier") != null) diff --git a/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala b/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala index d040b39d2..1640d3680 100644 --- a/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala +++ b/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala @@ -2,11 +2,10 @@ package org.sunbird.managers import java.util import java.util.concurrent.CompletionException - import com.fasterxml.jackson.databind.ObjectMapper import org.apache.commons.lang3.StringUtils import org.sunbird.cache.impl.RedisCache -import org.sunbird.common.dto.{Request, Response, ResponseHandler} +import org.sunbird.common.dto.{Request, Response, ResponseHandler, ResponseParams} import org.sunbird.common.exception.{ClientException, ErrorCodes, ResourceNotFoundException, ResponseCode, ServerException} import org.sunbird.common.{JsonUtils, Platform} import org.sunbird.graph.dac.model.Node @@ -15,15 +14,19 @@ import org.sunbird.graph.utils.{NodeUtil, ScalaJsonUtils} import scala.collection.JavaConversions._ import scala.collection.JavaConverters -import scala.collection.JavaConverters.asJavaIterableConverter +import scala.collection.JavaConverters.{asJavaIterableConverter, mapAsScalaMapConverter} import scala.concurrent.{ExecutionContext, Future} import com.mashape.unirest.http.HttpResponse import com.mashape.unirest.http.Unirest import org.apache.commons.collections4.{CollectionUtils, MapUtils} import org.sunbird.graph.OntologyEngineContext import org.sunbird.graph.schema.{DefinitionNode, ObjectCategoryDefinition} +import org.sunbird.schema.SchemaValidatorFactory +import org.sunbird.schema.dto.ValidationResult import org.sunbird.utils.{HierarchyBackwardCompatibilityUtil, HierarchyConstants, HierarchyErrorCodes} +import scala.annotation.tailrec + object HierarchyManager { val schemaName: String = "collection" @@ -32,7 +35,7 @@ object HierarchyManager { val hierarchyPrefix: String = "hierarchy_" val statusList = List("Live", "Unlisted", "Flagged") - val keyTobeRemoved = { + val keyTobeRemoved: util.List[String] = { if(Platform.config.hasPath("content.hierarchy.removed_props_for_leafNodes")) Platform.config.getStringList("content.hierarchy.removed_props_for_leafNodes") else @@ -64,11 +67,11 @@ object HierarchyManager { val updateResponse = updateHierarchy(unitId, hierarchy, leafNodes, node, request, "add") updateResponse.map(response => { if(!ResponseHandler.checkError(response)) { - ResponseHandler.OK - .put("rootId", node.getIdentifier.replaceAll(imgSuffix, "")) - .put(unitId, request.get("children")) + ResponseHandler.OK + .put("rootId", node.getIdentifier.replaceAll(imgSuffix, "")) + .put(unitId, request.get("children")) }else { - response + response } }) }).flatMap(f => f) @@ -129,55 +132,72 @@ object HierarchyManager { } val bookmarkId = request.get("bookmarkId").asInstanceOf[String] var metadata: util.Map[String, AnyRef] = NodeUtil.serialize(rootNode, new util.ArrayList[String](), request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String]) - val hierarchy = fetchHierarchy(request, rootNode.getIdentifier) - //TODO: Remove content Mapping for backward compatibility - HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(metadata) - hierarchy.map(hierarchy => { - val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[java.util.Map[String, AnyRef]]] + + fetchRelationalMetadata(request, rootNode.getIdentifier).map(collRelationalMetadata => { + val hierarchy = fetchHierarchy(request, rootNode.getIdentifier) + //TODO: Remove content Mapping for backward compatibility - updateContentMappingInChildren(children) - val leafNodeIds = new util.ArrayList[String]() - fetchAllLeafNodes(children, leafNodeIds) - getLatestLeafNodes(leafNodeIds).map(leafNodesMap => { - updateLatestLeafNodes(children, leafNodesMap) - metadata.put("children", children) - metadata.put("identifier", request.get("rootId")) - if(StringUtils.isNotEmpty(bookmarkId)) - metadata = filterBookmarkHierarchy(metadata.get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]], bookmarkId) - if (MapUtils.isEmpty(metadata)) { - ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "bookmarkId " + bookmarkId + " does not exist") - } else { - ResponseHandler.OK.put("content", metadata) - } - }) - }).flatMap(f => f) - }).flatMap(f => f) recoverWith { case e: ResourceNotFoundException => { - val searchResponse = searchRootIdInElasticSearch(request.get("rootId").asInstanceOf[String]) - searchResponse.map(rootHierarchy => { - if(!rootHierarchy.isEmpty && StringUtils.isNotEmpty(rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]].get("identifier").asInstanceOf[String])){ - //TODO: Remove content Mapping for backward compatibility - HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]]) - val unPublishedBookmarkHierarchy = getUnpublishedBookmarkHierarchy(request, rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]].get("identifier").asInstanceOf[String]) - unPublishedBookmarkHierarchy.map(hierarchy => { - if (!hierarchy.isEmpty) { - val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[java.util.Map[String, AnyRef]]] - //TODO: Remove content Mapping for backward compatibility - updateContentMappingInChildren(children) - val leafNodeIds = new util.ArrayList[String]() - fetchAllLeafNodes(children, leafNodeIds) - getLatestLeafNodes(leafNodeIds).map(leafNodesMap => { - updateLatestLeafNodes(children, leafNodesMap) - hierarchy.put("children", children) + HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(metadata) + hierarchy.map(hierarchy => { + val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[java.util.Map[String, AnyRef]]] + + //updating relationalMetadata of children + if(collRelationalMetadata.nonEmpty) { + val newRelationMetadataMap: Map[String, AnyRef] = collRelationalMetadata.flatMap(record => { + if (record._2.asInstanceOf[java.util.Map[String, AnyRef]].containsKey(HierarchyConstants.RELATIONAL_METADATA)) { + val recRelationalMetadata = record._2.asInstanceOf[java.util.Map[String, AnyRef]].get(HierarchyConstants.RELATIONAL_METADATA).asInstanceOf[java.util.Map[String, AnyRef]] + recRelationalMetadata.asScala.toMap.flatMap(relRec => { + Map[String, AnyRef](relRec._1 + "::" + record._1 -> relRec._2) }) - ResponseHandler.OK.put("content", hierarchy) - } else - ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist") + } else Map.empty[String, AnyRef] }) - } else { - Future(ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist")) + updateRelationMetadataMappingInChildren(children, newRelationMetadataMap) } + + //TODO: Remove content Mapping for backward compatibility + updateContentMappingInChildren(children) + val leafNodeIds = new util.ArrayList[String]() + fetchAllLeafNodes(children, leafNodeIds) + getLatestLeafNodes(leafNodeIds).map(leafNodesMap => { + updateLatestLeafNodes(children, leafNodesMap) + metadata.put("children", children) + metadata.put("identifier", request.get("rootId")) + if (StringUtils.isNotEmpty(bookmarkId)) + metadata = filterBookmarkHierarchy(metadata.get("children").asInstanceOf[util.List[util.Map[String, AnyRef]]], bookmarkId) + if (MapUtils.isEmpty(metadata)) { + ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "bookmarkId " + bookmarkId + " does not exist") + } else { + ResponseHandler.OK.put("content", metadata) + } + }) }).flatMap(f => f) - } + }).flatMap(f => f) + }).flatMap(f => f) recoverWith { case e: ResourceNotFoundException => + val searchResponse = searchRootIdInElasticSearch(request.get("rootId").asInstanceOf[String]) + searchResponse.map(rootHierarchy => { + if(!rootHierarchy.isEmpty && StringUtils.isNotEmpty(rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]].get("identifier").asInstanceOf[String])){ + //TODO: Remove content Mapping for backward compatibility + HierarchyBackwardCompatibilityUtil.setContentAndCategoryTypes(rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]]) + val unPublishedBookmarkHierarchy = getUnpublishedBookmarkHierarchy(request, rootHierarchy.asInstanceOf[util.HashMap[String, AnyRef]].get("identifier").asInstanceOf[String]) + unPublishedBookmarkHierarchy.map(hierarchy => { + if (!hierarchy.isEmpty) { + val children = hierarchy.getOrDefault("children", new util.ArrayList[java.util.Map[String, AnyRef]]).asInstanceOf[util.ArrayList[java.util.Map[String, AnyRef]]] + //TODO: Remove content Mapping for backward compatibility + updateContentMappingInChildren(children) + val leafNodeIds = new util.ArrayList[String]() + fetchAllLeafNodes(children, leafNodeIds) + getLatestLeafNodes(leafNodeIds).map(leafNodesMap => { + updateLatestLeafNodes(children, leafNodesMap) + hierarchy.put("children", children) + }) + ResponseHandler.OK.put("content", hierarchy) + } else + ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist") + }) + } else { + Future(ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "rootId " + request.get("rootId") + " does not exist")) + } + }).flatMap(f => f) } } @@ -207,7 +227,7 @@ object HierarchyManager { }) } - def validateRequest(request: Request)(implicit ec: ExecutionContext) = { + def validateRequest(request: Request)(implicit ec: ExecutionContext): Unit = { val rootId = request.get("rootId").asInstanceOf[String] val unitId = request.get("unitId").asInstanceOf[String] val children = request.get("children").asInstanceOf[java.util.List[String]] @@ -237,7 +257,7 @@ object HierarchyManager { req.put("identifiers", leafNodes) val nodes = DataNode.list(req).map(nodes => { if(nodes.size() != leafNodes.size()) { - val filteredList = leafNodes.toList.filter(id => !nodes.contains(id)) + leafNodes.toList.filter(id => !nodes.contains(id)) throw new ClientException(ErrorCodes.ERR_BAD_REQUEST.name(), "Children which are not available are: " + leafNodes) } else nodes.toList @@ -254,12 +274,12 @@ object HierarchyManager { } def addChildrenToUnit(children: java.util.List[java.util.Map[String,AnyRef]], unitId:String, leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String], request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Unit = { - val childNodes = children.filter(child => ("Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String]))).toList - if(null != childNodes && !childNodes.isEmpty){ + val childNodes = children.filter(child => "Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String])).toList + if(null != childNodes && childNodes.nonEmpty){ val child = childNodes.get(0) - leafNodes.toList.map(leafNode => validateLeafNodes(child, leafNode, request)) + leafNodes.toList.foreach(leafNode => validateLeafNodes(child, leafNode, request)) val childList = child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]] - val restructuredChildren: java.util.List[java.util.Map[String,AnyRef]] = restructureUnit(childList, leafNodes, leafNodeIds, (child.get("depth").asInstanceOf[Integer] + 1), unitId) + val restructuredChildren: java.util.List[java.util.Map[String,AnyRef]] = restructureUnit(childList, leafNodes, leafNodeIds, child.get("depth").asInstanceOf[Integer] + 1, unitId) child.put("children", restructuredChildren) } else { for(child <- children) { @@ -270,11 +290,11 @@ object HierarchyManager { } def removeChildrenFromUnit(children: java.util.List[java.util.Map[String, AnyRef]], unitId: String, leafNodeIds: java.util.List[String]):Unit = { - val childNodes = children.filter(child => ("Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String]))).toList - if(null != childNodes && !childNodes.isEmpty){ + val childNodes = children.filter(child => "Parent".equalsIgnoreCase(child.get("visibility").asInstanceOf[String]) && unitId.equalsIgnoreCase(child.get("identifier").asInstanceOf[String])).toList + if(null != childNodes && childNodes.nonEmpty){ val child = childNodes.get(0) if(null != child.get("children") && !child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].isEmpty) { - var filteredLeafNodes = child.get("children").asInstanceOf[java.util.List[java.util.Map[String,AnyRef]]].filter(existingLeafNode => { + val filteredLeafNodes = child.get("children").asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]].filter(existingLeafNode => { !leafNodeIds.contains(existingLeafNode.get("identifier").asInstanceOf[String]) }) var index: Integer = 1 @@ -292,10 +312,10 @@ object HierarchyManager { } } - def updateRootNode(rootNode: Node, request: Request, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext) = { + def updateRootNode(rootNode: Node, request: Request, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = { val req = new Request(request) val leafNodes = request.get("children").asInstanceOf[java.util.List[String]] - var childNodes = new java.util.ArrayList[String]() + val childNodes = new java.util.ArrayList[String]() childNodes.addAll(rootNode.getMetadata.get("childNodes").asInstanceOf[Array[String]].toList) if(operation.equalsIgnoreCase("add")) childNodes.addAll(leafNodes) @@ -307,29 +327,62 @@ object HierarchyManager { DataNode.update(req) } - def updateHierarchy(unitId: String, hierarchy: java.util.Map[String, AnyRef], leafNodes: List[Node], rootNode: Node, request: Request, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext) = { - val children = hierarchy.get("children").asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]] - val leafNodeIds = request.get("children").asInstanceOf[java.util.List[String]] - if("add".equalsIgnoreCase(operation)){ - val leafNodesMap:java.util.List[java.util.Map[String, AnyRef]] = convertNodeToMap(leafNodes) - addChildrenToUnit(children, unitId, leafNodesMap, leafNodeIds, request) - } - if("remove".equalsIgnoreCase(operation)) { - removeChildrenFromUnit(children,unitId, leafNodeIds) + def updateHierarchy(unitId: String, hierarchy: java.util.Map[String, AnyRef], leafNodes: List[Node], rootNode: Node, request: Request, operation: String)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Response] = { + fetchRelationalMetadata(request, rootNode.getIdentifier).map(collRelationalMetadata => { + val children = hierarchy.get("children").asInstanceOf[java.util.List[java.util.Map[String, AnyRef]]] + val leafNodeIds = request.get("children").asInstanceOf[java.util.List[String]] + val unitsHierarchyMetadata = if(collRelationalMetadata.contains(unitId)) collRelationalMetadata(unitId).asInstanceOf[java.util.Map[String, AnyRef]] else new java.util.HashMap[String, AnyRef]() + if ("add".equalsIgnoreCase(operation)) { + val leafNodesMap: java.util.List[java.util.Map[String, AnyRef]] = convertNodeToMap(leafNodes) + addChildrenToUnit(children, unitId, leafNodesMap, leafNodeIds, request) + //add relationalMetadata for unit + if(collRelationalMetadata.nonEmpty && unitsHierarchyMetadata.nonEmpty) { + unitsHierarchyMetadata.get("children").asInstanceOf[java.util.List[String]].addAll(leafNodeIds) + if (request.get("relationalMetadata") != null) { + val rmSchemaValidator = SchemaValidatorFactory.getInstance(HierarchyConstants.RELATIONAL_METADATA.toLowerCase(), "1.0") + val requestRM = request.get("relationalMetadata").asInstanceOf[java.util.Map[String, AnyRef]] + requestRM.foreach(rmChild => { + rmSchemaValidator.validate(rmChild._2.asInstanceOf[Map[String, AnyRef]]) + }) + if (unitsHierarchyMetadata.containsKey("relationalMetadata")) { + unitsHierarchyMetadata.get("relationalMetadata").asInstanceOf[java.util.Map[String, AnyRef]].putAll(requestRM) + } else { + unitsHierarchyMetadata.put("relationalMetadata", requestRM) + } + } + } + } + if ("remove".equalsIgnoreCase(operation)) { + removeChildrenFromUnit(children, unitId, leafNodeIds) + //remove relationalMetadata for unit + if(collRelationalMetadata.nonEmpty && unitsHierarchyMetadata.nonEmpty) { + unitsHierarchyMetadata.get("children").asInstanceOf[java.util.List[String]].removeAll(leafNodeIds) + leafNodeIds.foreach(rec => unitsHierarchyMetadata.get("relationalMetadata").asInstanceOf[java.util.Map[String, AnyRef]].remove(rec)) + if (unitsHierarchyMetadata.get("relationalMetadata").asInstanceOf[java.util.Map[String, AnyRef]].size() == 0) unitsHierarchyMetadata.remove("relationalMetadata") + } + } + val rootId = rootNode.getIdentifier.replaceAll(imgSuffix, "") + val updatedHierarchy = new java.util.HashMap[String, AnyRef]() + updatedHierarchy.put("identifier", rootId) + updatedHierarchy.put("children", children) + val updatedCollRM = collRelationalMetadata + (unitId -> unitsHierarchyMetadata) + val req = new Request(request) + req.put("relational_metadata",ScalaJsonUtils.serialize(updatedCollRM)) + req.put("hierarchy", ScalaJsonUtils.serialize(updatedHierarchy)) + req.put("identifier", rootNode.getIdentifier) + oec.graphService.saveExternalProps(req) + }).flatMap(f => f).recoverWith { + case clientException: ClientException => if(clientException.getMessage.equalsIgnoreCase("Validation Errors")) { + Future(ResponseHandler.ERROR(ResponseCode.CLIENT_ERROR, ResponseCode.CLIENT_ERROR.name(), clientException.getMessages.mkString(","))) + } else throw clientException + case e: Exception => + Future(ResponseHandler.ERROR(ResponseCode.SERVER_ERROR, ResponseCode.SERVER_ERROR.name(), e.getMessage)) } - val rootId = rootNode.getIdentifier.replaceAll(imgSuffix, "") - val updatedHierarchy = new java.util.HashMap[String, AnyRef]() - updatedHierarchy.put("identifier", rootId) - updatedHierarchy.put("children", children) - val req = new Request(request) - req.put("hierarchy", ScalaJsonUtils.serialize(updatedHierarchy)) - req.put("identifier", rootNode.getIdentifier) - oec.graphService.saveExternalProps(req) } def restructureUnit(childList: java.util.List[java.util.Map[String, AnyRef]], leafNodes: java.util.List[java.util.Map[String, AnyRef]], leafNodeIds: java.util.List[String], depth: Integer, parent: String): java.util.List[java.util.Map[String, AnyRef]] = { var maxIndex:Integer = 0 - var leafNodeMap: java.util.Map[String, java.util.Map[String, AnyRef]] = new util.HashMap[String, java.util.Map[String, AnyRef]]() + val leafNodeMap: java.util.Map[String, java.util.Map[String, AnyRef]] = new util.HashMap[String, java.util.Map[String, AnyRef]]() for(leafNode <- leafNodes){ leafNodeMap.put(leafNode.get("identifier").asInstanceOf[String], JavaConverters.mapAsJavaMapConverter(leafNode).asJava) } @@ -337,16 +390,16 @@ object HierarchyManager { if(null != childList && !childList.isEmpty) { val childMap:Map[String, java.util.Map[String, AnyRef]] = childList.toList.map(f => f.get("identifier").asInstanceOf[String] -> f).toMap val existingLeafNodes = childMap.filter(p => leafNodeIds.contains(p._1)) - existingLeafNodes.map(en => { - leafNodeMap.get(en._1).put("index", en._2.get("index").asInstanceOf[Integer]) - }) + existingLeafNodes.map(en => { + leafNodeMap.get(en._1).put("index", en._2.get("index").asInstanceOf[Integer]) + }) filteredLeafNodes = bufferAsJavaList(childList.filter(existingLeafNode => { !leafNodeIds.contains(existingLeafNode.get("identifier").asInstanceOf[String]) })) - maxIndex = childMap.values.toList.map(child => child.get("index").asInstanceOf[Integer]).toList.max.asInstanceOf[Integer] + maxIndex = childMap.values.toList.map(child => child.get("index").asInstanceOf[Integer]).max } leafNodeIds.foreach(id => { - var node = leafNodeMap.get(id) + val node = leafNodeMap.get(id) node.put("parent", parent) node.put("depth", depth) if( null == node.get("index")) { @@ -392,11 +445,42 @@ object HierarchyManager { }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause } } + + def fetchRelationalMetadata(request: Request, identifier: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[Map[String, AnyRef]] = { + val req = new Request(request) + req.put("identifier", identifier.replaceAll(".img", "") + ".img") + val responseFuture = oec.graphService.readExternalProps(req, List("relational_metadata")) + responseFuture.map(response => { + if (!ResponseHandler.checkError(response)) { + val relationalMetadataString = response.getResult.toMap.getOrDefault("relational_metadata", "").asInstanceOf[String] + if (StringUtils.isNotEmpty(relationalMetadataString) && !relationalMetadataString.trim.isBlank) { + val relMetadataJavaMap = JsonUtils.deserialize(relationalMetadataString, classOf[java.util.Map[String, AnyRef]]) + if(relMetadataJavaMap != null && relMetadataJavaMap.size()>0) Future(relMetadataJavaMap.toMap) else Future(Map[String, AnyRef]()) + } else + Future(Map[String, AnyRef]()) + } else { + val req = new Request(request) + req.put("identifier", identifier) + val responseFuture = oec.graphService.readExternalProps(req, List("relational_metadata")) + responseFuture.map(response => { + if (!ResponseHandler.checkError(response)) { + val relationalMetadataString = response.getResult.toMap.getOrDefault("relational_metadata", "").asInstanceOf[String] + if (StringUtils.isNotEmpty(relationalMetadataString) && !relationalMetadataString.trim.isBlank) { + Future(JsonUtils.deserialize(relationalMetadataString, classOf[java.util.Map[String, AnyRef]]).toMap) + } else + Future(Map[String, AnyRef]()) + } else Future(Map[String, AnyRef]()) + }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause } + } + }).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause } + } + + def getCassandraHierarchy(request: Request)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[util.Map[String, AnyRef]] = { val rootHierarchy: util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]() val hierarchy = fetchHierarchy(request, request.getRequest.get("rootId").asInstanceOf[String]) hierarchy.map(hierarchy => { - if (!hierarchy.isEmpty) { + if (hierarchy.nonEmpty) { if (StringUtils.isNotEmpty(hierarchy.getOrDefault("status", "").asInstanceOf[String]) && statusList.contains(hierarchy.getOrDefault("status", "").asInstanceOf[String])) { //TODO: Remove mapping val hierarchyMap = mapPrimaryCategories(hierarchy) @@ -413,7 +497,7 @@ object HierarchyManager { if (StringUtils.isNotEmpty(response.getOrDefault("identifier", "").asInstanceOf[String])) { val parentHierarchy = fetchHierarchy(request, response.get("identifier").asInstanceOf[String]) parentHierarchy.map(hierarchy => { - if (!hierarchy.isEmpty) { + if (hierarchy.nonEmpty) { if (StringUtils.isNoneEmpty(hierarchy.getOrDefault("status", "").asInstanceOf[String]) && statusList.contains(hierarchy.getOrDefault("status", "").asInstanceOf[String]) && CollectionUtils.isNotEmpty(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.HashMap[String, AnyRef]]])) { val bookmarkHierarchy = filterBookmarkHierarchy(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]], request.get("rootId").asInstanceOf[String]) if (!bookmarkHierarchy.isEmpty) { @@ -449,7 +533,7 @@ object HierarchyManager { put("request", new util.HashMap[String, AnyRef]() { put("filters", new util.HashMap[String, AnyRef]() { put("status", new util.ArrayList[String]() { - add("Live"); + add("Live") add("Unlisted") }) put("mimeType", "application/vnd.ekstep.content-collection") @@ -477,6 +561,7 @@ object HierarchyManager { } } + @tailrec def filterBookmarkHierarchy(children: util.List[util.Map[String, AnyRef]], bookmarkId: String)(implicit ec: ExecutionContext): util.Map[String, AnyRef] = { if (CollectionUtils.isNotEmpty(children)) { val response = children.filter(_.get("identifier") == bookmarkId).toList @@ -499,7 +584,7 @@ object HierarchyManager { if (StringUtils.isNotEmpty(identifier)) { val parentHierarchy = fetchHierarchy(request, identifier + imgSuffix) parentHierarchy.map(hierarchy => { - if (!hierarchy.isEmpty && CollectionUtils.isNotEmpty(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]])) { + if (hierarchy.nonEmpty && CollectionUtils.isNotEmpty(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]])) { val bookmarkHierarchy = filterBookmarkHierarchy(mapAsJavaMap(hierarchy).get("children").asInstanceOf[util.ArrayList[util.Map[String, AnyRef]]], request.get("rootId").asInstanceOf[String]) if (!bookmarkHierarchy.isEmpty) { bookmarkHierarchy @@ -515,7 +600,7 @@ object HierarchyManager { } } - def validateShallowCopied(rootNodeMap: util.Map[String, AnyRef], operation: String, identifier: String) = { + def validateShallowCopied(rootNodeMap: util.Map[String, AnyRef], operation: String, identifier: String): Unit = { val originData = rootNodeMap.getOrDefault("originData", new util.HashMap[String, AnyRef]()).asInstanceOf[util.Map[String, AnyRef]] if (StringUtils.equalsIgnoreCase(originData.getOrElse("copyType", "").asInstanceOf[String], HierarchyConstants.COPY_TYPE_SHALLOW)) { operation match { @@ -554,7 +639,7 @@ object HierarchyManager { }) } - def getLatestLeafNodes(leafNodeIds : util.List[String])(implicit oec: OntologyEngineContext, ec: ExecutionContext) = { + def getLatestLeafNodes(leafNodeIds : util.List[String])(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[util.Map[String, AnyRef]] = { if(CollectionUtils.isNotEmpty(leafNodeIds)) { val request = new Request() request.setContext(new util.HashMap[String, AnyRef]() { @@ -564,14 +649,14 @@ object HierarchyManager { }) request.put("identifiers", leafNodeIds) DataNode.list(request).map(nodes => { - val leafNodeMap: Map[String, AnyRef] = nodes.toList.map(node => (node.getIdentifier, NodeUtil.serialize(node, null, node.getObjectType.toLowerCase.replace("image", ""), HierarchyConstants.SCHEMA_VERSION, true).asInstanceOf[AnyRef])).toMap + val leafNodeMap: Map[String, AnyRef] = nodes.toList.map(node => (node.getIdentifier, NodeUtil.serialize(node, null, node.getObjectType.toLowerCase.replace("image", ""), HierarchyConstants.SCHEMA_VERSION, withoutRelations = true).asInstanceOf[AnyRef])).toMap val imageNodeIds: util.List[String] = JavaConverters.seqAsJavaListConverter(leafNodeIds.toList.map(id => id + HierarchyConstants.IMAGE_SUFFIX)).asJava request.put("identifiers", imageNodeIds) DataNode.list(request).map(imageNodes => { //val imageLeafNodeMap: Map[String, AnyRef] = imageNodes.toList.map(imageNode => (imageNode.getIdentifier.replaceAll(HierarchyConstants.IMAGE_SUFFIX, ""), NodeUtil.serialize(imageNode, null, HierarchyConstants.CONTENT_SCHEMA_NAME, HierarchyConstants.SCHEMA_VERSION, true).asInstanceOf[AnyRef])).toMap val imageLeafNodeMap: Map[String, AnyRef] = imageNodes.toList.map(imageNode => { val identifier = imageNode.getIdentifier.replaceAll(HierarchyConstants.IMAGE_SUFFIX, "") - val metadata = NodeUtil.serialize(imageNode, null, imageNode.getObjectType.toLowerCase.replace("image", ""), HierarchyConstants.SCHEMA_VERSION, true) + val metadata = NodeUtil.serialize(imageNode, null, imageNode.getObjectType.toLowerCase.replace("image", ""), HierarchyConstants.SCHEMA_VERSION, withoutRelations = true) metadata.replace("identifier", identifier) (identifier, metadata.asInstanceOf[AnyRef]) }).toMap @@ -585,6 +670,16 @@ object HierarchyManager { } + def updateRelationMetadataMappingInChildren(children: util.List[util.Map[String, AnyRef]], colRelationalMetadata: Map[String,AnyRef]): List[Any] = { + children.toList.map(content => { + if(colRelationalMetadata.contains(content.get("identifier")+"::"+content.get("parent"))) { + val contentRelMetadata = colRelationalMetadata(content.get("identifier")+"::"+content.get("parent")) + content.put(HierarchyConstants.RELATIONAL_METADATA,contentRelMetadata) + } + updateRelationMetadataMappingInChildren(content.getOrDefault("children", new util.ArrayList[Map[String, AnyRef]]).asInstanceOf[util.List[util.Map[String, AnyRef]]],colRelationalMetadata) + }) + } + def updateContentMappingInChildren(children: util.List[util.Map[String, AnyRef]]): List[Any] = { children.toList.map(content => { if (mapPrimaryCategoriesEnabled) @@ -606,7 +701,7 @@ object HierarchyManager { updatedHierarchy } - def validateLeafNodes(parentNode: java.util.Map[String, AnyRef], childNode: java.util.Map[String, AnyRef], request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext) = { + def validateLeafNodes(parentNode: java.util.Map[String, AnyRef], childNode: java.util.Map[String, AnyRef], request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Unit = { val primaryCategory = parentNode.getOrDefault("primaryCategory", "").asInstanceOf[String] val channel: String = parentNode.getOrDefault("channel", "all").asInstanceOf[String] //val objectCategoryDefinition: ObjectCategoryDefinition = DefinitionNode.getObjectCategoryDefinition(primaryCategory, parentNode.getOrDefault("objectType", "").asInstanceOf[String].toLowerCase(), channel) diff --git a/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala b/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala index 6521a7a5a..01c245c6a 100644 --- a/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala +++ b/content-api/hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala @@ -1,19 +1,19 @@ package org.sunbird.managers import java.util.concurrent.CompletionException - import org.apache.commons.collections4.{CollectionUtils, MapUtils} import org.apache.commons.lang3.StringUtils import org.sunbird.common.dto.{Request, Response, ResponseHandler} -import org.sunbird.common.exception.{ClientException, ErrorCodes, ResourceNotFoundException, ServerException} +import org.sunbird.common.exception.{ClientException, ErrorCodes, ResourceNotFoundException, ResponseCode, ServerException} import org.sunbird.common.{DateUtils, JsonUtils, Platform} import org.sunbird.graph.OntologyEngineContext import org.sunbird.graph.common.Identifier import org.sunbird.graph.dac.model.Node -import org.sunbird.graph.external.ExternalPropsManager import org.sunbird.graph.nodes.DataNode import org.sunbird.graph.schema.DefinitionNode import org.sunbird.graph.utils.{NodeUtil, ScalaJsonUtils} +import org.sunbird.schema.dto.ValidationResult +import org.sunbird.schema.{ISchemaValidator, SchemaValidatorFactory} import org.sunbird.telemetry.logger.TelemetryManager import org.sunbird.utils.{HierarchyBackwardCompatibilityUtil, HierarchyConstants, HierarchyErrorCodes} @@ -46,7 +46,6 @@ object UpdateHierarchyManager { val idMap: mutable.Map[String, String] = mutable.Map() idMap += (rootId -> rootId) updateNodesModifiedInNodeList(nodes, nodesModified, request, idMap).map(modifiedNodeList => { - getChildrenHierarchy(modifiedNodeList, rootId, hierarchy, idMap, result._1, request).map(children => { TelemetryManager.log("Children for root id :" + rootId +" :: " + JsonUtils.serialize(children)) updateHierarchyData(rootId, children, modifiedNodeList, request).map(node => { @@ -58,7 +57,12 @@ object UpdateHierarchyManager { deleteHierarchy(request) Future(response) }).flatMap(f => f) - }).flatMap(f => f) + }).flatMap(f => f).recoverWith { + case clientException: ClientException => if(clientException.getMessage.equalsIgnoreCase("Validation Errors")) { + Future(ResponseHandler.ERROR(ResponseCode.CLIENT_ERROR, ResponseCode.CLIENT_ERROR.name(), clientException.getMessages.mkString(","))) + } else throw clientException + case e: Exception => throw e + } }).flatMap(f => f) }) }).flatMap(f => f).flatMap(f => f) recoverWith { case e: CompletionException => throw e.getCause } @@ -115,7 +119,7 @@ object UpdateHierarchyManager { private def getExistingHierarchy(request: Request, rootNode: Node)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[java.util.HashMap[String, AnyRef]] = { fetchHierarchy(request, rootNode).map(hierarchyString => { - if (!hierarchyString.asInstanceOf[String].isEmpty) { + if (hierarchyString.asInstanceOf[String].nonEmpty) { JsonUtils.deserialize(hierarchyString.asInstanceOf[String], classOf[java.util.HashMap[String, AnyRef]]) } else new java.util.HashMap[String, AnyRef]() }) @@ -428,6 +432,18 @@ object UpdateHierarchyManager { } def updateHierarchyData(rootId: String, children: java.util.List[java.util.Map[String, AnyRef]], nodeList: List[Node], request: Request)(implicit oec: OntologyEngineContext, ec: ExecutionContext): Future[Node] = { + val reqHierarchy: java.util.HashMap[String, AnyRef] = request.getRequest.get(HierarchyConstants.HIERARCHY).asInstanceOf[java.util.HashMap[String, AnyRef]] + val rmSchemaValidator = SchemaValidatorFactory.getInstance(HierarchyConstants.RELATIONAL_METADATA.toLowerCase(), "1.0") + + reqHierarchy.foreach(rec=> { + if(rec._2.asInstanceOf[java.util.Map[String,AnyRef]].containsKey(HierarchyConstants.RELATIONAL_METADATA)) { + val rmObj = rec._2.asInstanceOf[java.util.Map[String,AnyRef]](HierarchyConstants.RELATIONAL_METADATA) + rmObj.asInstanceOf[java.util.Map[String,AnyRef]].foreach(rmChild=>{ + rmSchemaValidator.validate(rmChild._2.asInstanceOf[java.util.Map[String, AnyRef]]) + }) + } + }) + val node = getTempNode(nodeList, rootId) val updatedHierarchy = new java.util.HashMap[String, AnyRef]() updatedHierarchy.put(HierarchyConstants.IDENTIFIER, rootId) @@ -437,6 +453,7 @@ object UpdateHierarchyManager { val metadata = cleanUpRootData(node) req.getRequest.putAll(metadata) req.put(HierarchyConstants.HIERARCHY, ScalaJsonUtils.serialize(updatedHierarchy)) + req.put(HierarchyConstants.RELATIONAL_METADATA_COL, ScalaJsonUtils.serialize(reqHierarchy)) req.put(HierarchyConstants.IDENTIFIER, rootId) req.put(HierarchyConstants.CHILDREN, new java.util.ArrayList()) req.put(HierarchyConstants.CONCEPTS, new java.util.ArrayList()) diff --git a/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala b/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala index 8e2960911..111b6088e 100644 --- a/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala +++ b/content-api/hierarchy-manager/src/main/scala/org/sunbird/utils/HierarchyConstants.scala @@ -5,6 +5,8 @@ object HierarchyConstants { val DATA_NODE: String = "DATA_NODE" val NODES_MODIFIED: String = "nodesModified" val HIERARCHY: String = "hierarchy" + val RELATIONAL_METADATA: String = "relationalMetadata" + val RELATIONAL_METADATA_COL: String = "relational_metadata" val ROOT: String = "root" val SET_DEFAULT_VALUE: String = "setDefaultValue" val COLLECTION_MIME_TYPE: String = "application/vnd.ekstep.content-collection" diff --git a/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestHierarchy.scala b/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestHierarchy.scala index 12ba37284..b32920743 100644 --- a/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestHierarchy.scala +++ b/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestHierarchy.scala @@ -1,34 +1,44 @@ package org.sunbird.managers import java.util - import org.apache.commons.collections4.CollectionUtils import org.apache.commons.lang3.StringUtils import org.sunbird.cache.impl.RedisCache +import org.sunbird.common.JsonUtils import org.sunbird.common.dto.Request import org.sunbird.common.exception.ClientException import org.sunbird.graph.OntologyEngineContext +import scala.collection.JavaConversions._ class TestHierarchy extends BaseSpec { private val script_1 = "CREATE KEYSPACE IF NOT EXISTS hierarchy_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" - private val script_2 = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text,PRIMARY KEY (identifier));" - private val script_3 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11283193441064550414', '{\"identifier\":\"do_11283193441064550414\",\"children\":[{\"parent\":\"do_11283193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\", \"objectType\":\"Collection\"}]}');" + private val script_2 = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text, relational_metadata text, PRIMARY KEY (identifier));" + private val script_3 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy, relational_metadata) values ('do_11283193441064550414', '{\"identifier\":\"do_11283193441064550414\",\"children\":[{\"parent\":\"do_11283193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\", \"objectType\":\"Collection\"}]}','{\"do_11283193441064550414\": {\"name\": \"Collection Publish T21\", \"children\": [\"do_11283193463014195215\"], \"root\": true}, \"do_11283193463014195215\": { \"name\": \"Collection Parent\", \"children\": [ \"do_112831862871203840114\" ], \"root\": false, \"relationalMetadata\": { \"do_112831862871203840114\": { \"name\": \"Test Name RM L1 - R1\", \"keywords\": [ \"Overwriting content KW1\" ] } } }, \"do_112831862871203840114\": { \"name\": \"PDF Content\", \"children\": [], \"root\": false }}');" private val script_4 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11283193441064550414', '{\"status\":\"Live\",\"children\":[{\"parent\":\"do_11283193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\"}]}}');" private val script_5 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11323126798764441611181', '{\"identifier\":\"do_11323126798764441611181\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_11323126798764441611181\",\"code\":\"U1\",\"keywords\":[],\"credentials\":{\"enabled\":\"No\"},\"channel\":\"sunbird\",\"description\":\"U1-For Content\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.ekstep.content-collection\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2021-03-07T19:24:58.991+0000\",\"objectType\":\"Collection\",\"primaryCategory\":\"Textbook Unit\",\"children\":[{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_11323126865092608011182\",\"copyright\":\"Kerala State\",\"previewUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/content/assets/do_11307457137049600011786/eng-presentation_1597086905822.pdf\",\"keywords\":[\"By the Hands of the Nature\"],\"subject\":[\"Geography\"],\"channel\":\"0126202691023585280\",\"downloadUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/ecar_files/do_11307457137049600011786/by-the-hands-of-the-nature_1597087677810_do_11307457137049600011786_1.0.ecar\",\"organisation\":[\"Kerala State\"],\"textbook_name\":[\"Contemporary India - I\"],\"showNotification\":true,\"language\":[\"English\"],\"source\":\"Kl 4\",\"mimeType\":\"application/pdf\",\"variants\":{\"spine\":{\"ecarUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/ecar_files/do_11307457137049600011786/by-the-hands-of-the-nature_1597087678819_do_11307457137049600011786_1.0_spine.ecar\",\"size\":36508.0}},\"objectType\":\"Content\",\"sourceURL\":\"https://diksha.gov.in/play/content/do_312783564254150656111171\",\"gradeLevel\":[\"Class 9\"],\"me_totalRatingsCount\":36,\"appIcon\":\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/content/do_312783564254150656111171/artifact/screenshot-from-2019-06-14-11-59-39_1560493827569.thumb.png\",\"primaryCategory\":\"Learning Resource\",\"level2Name\":[\"Physical Features of India\"],\"appId\":\"prod.diksha.portal\",\"contentEncoding\":\"identity\",\"artifactUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/content/assets/do_11307457137049600011786/eng-presentation_1597086905822.pdf\",\"me_totalPlaySessionCount\":{\"portal\":28},\"sYS_INTERNAL_LAST_UPDATED_ON\":\"2020-08-10T19:27:58.911+0000\",\"contentType\":\"Resource\",\"identifier\":\"do_11307457137049600011786\",\"audience\":[\"Student\"],\"me_totalTimeSpentInSec\":{\"portal\":2444},\"visibility\":\"Default\",\"author\":\"Kerala State\",\"consumerId\":\"89490534-126f-4f0b-82ac-3ff3e49f3468\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.quiz.app\",\"languageCode\":[\"en\"],\"lastPublishedBy\":\"ee2a003a-10a9-4152-8907-a905b9e1f943\",\"version\":2,\"pragma\":[\"external\"],\"license\":\"CC BY 4.0\",\"prevState\":\"Draft\",\"size\":1.000519E7,\"lastPublishedOn\":\"2020-08-10T19:27:57.797+0000\",\"name\":\"By the Hands of the Nature\",\"status\":\"Live\",\"code\":\"6633b233-bc5a-4936-a7a0-da37ebd33868\",\"prevStatus\":\"Processing\",\"origin\":\"do_312783564254150656111171\",\"streamingUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/content/assets/do_11307457137049600011786/eng-presentation_1597086905822.pdf\",\"medium\":[\"English\"],\"posterImage\":\"https://ntpproductionall.blob.core.windows.net/ntp-content-production/content/do_312783565429334016112815/artifact/screenshot-from-2019-06-14-11-59-39_1560493827569.png\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2020-07-29T10:03:33.003+0000\",\"copyrightYear\":2019,\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2020-08-10T19:27:57.376+0000\",\"originData\":{\"identifier\":\"do_312783564254150656111171\",\"repository\":\"https://dock.sunbirded.org/do_312783564254150656111171\"},\"level1Concept\":[\"Physical Features of India\"],\"dialcodeRequired\":\"No\",\"owner\":\"Kerala SCERT\",\"lastStatusChangedOn\":\"2020-08-10T19:27:58.907+0000\",\"createdFor\":[\"0126202691023585280\"],\"creator\":\"SAJEEV THOMAS\",\"os\":[\"All\"],\"level1Name\":[\"Contemporary India - I\"],\"pkgVersion\":1.0,\"versionKey\":\"1597087677376\",\"idealScreenDensity\":\"hdpi\",\"framework\":\"kl_k-12\",\"depth\":2,\"s3Key\":\"ecar_files/do_11307457137049600011786/by-the-hands-of-the-nature_1597087677810_do_11307457137049600011786_1.0.ecar\",\"me_averageRating\":3,\"createdBy\":\"f20a4bbf-df17-425b-8e43-bd3dd57bde83\",\"compatibilityLevel\":4,\"ownedBy\":\"0126202691023585280\",\"board\":\"CBSE\",\"resourceType\":\"Learn\"}],\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2021-03-07T19:24:58.990+0000\",\"contentEncoding\":\"gzip\",\"contentType\":\"TextBookUnit\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_11323126865092608011182\",\"lastStatusChangedOn\":\"2021-03-07T19:24:58.991+0000\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Parent\",\"index\":1,\"mediaType\":\"content\",\"osId\":\"org.ekstep.launcher\",\"languageCode\":[\"en\"],\"version\":2,\"versionKey\":\"1615145098991\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"depth\":1,\"compatibilityLevel\":1,\"name\":\"U1\",\"status\":\"Draft\"},{\"ownershipType\":[\"createdBy\"],\"parent\":\"do_11323126798764441611181\",\"code\":\"U2\",\"keywords\":[],\"credentials\":{\"enabled\":\"No\"},\"channel\":\"sunbird\",\"description\":\"U2-For Other Objects\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.ekstep.content-collection\",\"idealScreenSize\":\"normal\",\"createdOn\":\"2021-03-07T19:24:58.993+0000\",\"objectType\":\"Collection\",\"primaryCategory\":\"Textbook Unit\",\"children\":[{\"parent\":\"do_11323126865095065611184\",\"code\":\"finemanfine\",\"previewUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/do_113212597854404608111_html_1612875515166.html\",\"allowSkip\":\"Yes\",\"containsUserData\":\"No\",\"downloadUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/test-question-set_1612875514981_do_113212597854404608111_5_SPINE.ecar\",\"description\":\"Updated QS Description\",\"language\":[\"English\"],\"mimeType\":\"application/vnd.sunbird.questionset\",\"showHints\":\"No\",\"variants\":{\"spine\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/test-question-set_1612875514981_do_113212597854404608111_5_SPINE.ecar\",\"online\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/test-question-set_1612875515115_do_113212597854404608111_5_ONLINE.ecar\"},\"createdOn\":\"2021-02-09T10:19:09.026+0000\",\"objectType\":\"QuestionSet\",\"pdfUrl\":\"https://dockstorage.blob.core.windows.net/sunbird-content-dock/questionset/do_113212597854404608111/do_113212597854404608111_pdf_1612875515932.pdf\",\"primaryCategory\":\"Practice Question Set\",\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2021-02-09T12:58:36.155+0000\",\"contentEncoding\":\"gzip\",\"contentType\":\"PracticeResource\",\"showSolutions\":\"Yes\",\"allowAnonymousAccess\":\"Yes\",\"identifier\":\"do_113212597854404608111\",\"lastStatusChangedOn\":\"2021-02-09T12:58:36.155+0000\",\"requiresSubmit\":\"Yes\",\"visibility\":\"Default\",\"showTimer\":\"No\",\"summaryType\":\"Complete\",\"consumerId\":\"fa13b438-8a3d-41b1-8278-33b0c50210e4\",\"childNodes\":[\"do_113212598840246272112\",\"do_113212599692050432114\",\"do_113212600505057280116\"],\"index\":1,\"setType\":\"materialised\",\"languageCode\":[\"en\"],\"version\":1,\"pkgVersion\":5,\"versionKey\":\"1612875494848\",\"showFeedback\":\"Yes\",\"license\":\"CC BY 4.0\",\"depth\":2,\"lastPublishedOn\":\"2021-02-09T12:58:34.976+0000\",\"compatibilityLevel\":5,\"name\":\"Test Question Set\",\"navigationMode\":\"linear\",\"shuffle\":true,\"status\":\"Live\"}],\"contentDisposition\":\"inline\",\"lastUpdatedOn\":\"2021-03-07T19:24:58.993+0000\",\"contentEncoding\":\"gzip\",\"contentType\":\"TextBookUnit\",\"dialcodeRequired\":\"No\",\"identifier\":\"do_11323126865095065611184\",\"lastStatusChangedOn\":\"2021-03-07T19:24:58.993+0000\",\"audience\":[\"Student\"],\"os\":[\"All\"],\"visibility\":\"Parent\",\"index\":2,\"mediaType\":\"content\",\"osId\":\"org.ekstep.launcher\",\"languageCode\":[\"en\"],\"version\":2,\"versionKey\":\"1615145098993\",\"license\":\"CC BY 4.0\",\"idealScreenDensity\":\"hdpi\",\"depth\":1,\"compatibilityLevel\":1,\"name\":\"U2\",\"status\":\"Draft\"}]}');" - private val script_6 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_26543193441064550414', '{\"identifier\":\"do_26543193441064550414\",\"children\":[{\"parent\":\"do_26543193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\", \"objectType\":\"Collection\"}]}');" + private val script_6 = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy, relational_metadata) values ('do_26543193441064550414', '{\"identifier\":\"do_26543193441064550414\",\"children\":[{\"parent\":\"do_26543193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"status\":\"Draft\", \"objectType\":\"Collection\"}]}','{\"do_26543193441064550414\": {\"name\": \"Collection Publish T21\", \"children\": [\"do_11283193463014195215\"], \"root\": true}, \"do_11283193463014195215\": { \"name\": \"Collection Parent\", \"children\": [ \"do_113193433773948928111\" ], \"root\": false, \"relationalMetadata\": { \"do_113193433773948928111\": { \"name\": \"Test Name RM L1 - R1\", \"keywords\": [ \"Overwriting content KW1\" ] } } }, \"do_113193433773948928111\": { \"name\": \"PDF Content\", \"children\": [], \"root\": false }}');" + private val CATEGORY_DEF_INPUT = List("INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:digital-textbook_collection_all')", + "INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:textbook-unit_collection_all')", + "INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:course-unit_collection_all')", + "INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:content-playlist_collection_all')", + "INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:asset_asset_all')", + "INSERT INTO category_store.category_definition_data(identifier) values ('obj-cat:explanation-content_content_all')") + implicit val oec: OntologyEngineContext = new OntologyEngineContext override def beforeAll(): Unit = { super.beforeAll() graphDb.execute("UNWIND [{code:\"questionId\",subject:[\"Health and Physical Education\"],language:[\"English\"],medium:[\"English\"],mimeType:\"application/vnd.sunbird.question\",createdOn:\"2021-01-13T09:29:06.255+0000\",IL_FUNC_OBJECT_TYPE:\"Question\",gradeLevel:[\"Class 6\"],contentDisposition:\"inline\",lastUpdatedOn:\"2021-02-08T11:19:08.989+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",IL_UNIQUE_ID:\"do_113193462958120275141\",lastStatusChangedOn:\"2021-02-08T11:19:08.989+0000\",visibility:\"Parent\",showTimer:\"No\",author:\"Vaibhav\",qType:\"SA\",languageCode:[\"en\"],version:1,versionKey:\"1611554879383\",showFeedback:\"No\",license:\"CC BY 4.0\",prevState:\"Review\",compatibilityLevel:4,name:\"Subjective\",topic:[\"Leaves\"],board:\"CBSE\",status:\"Live\"},{code:\"questionId\",subject:[\"Health and Physical Education\"],language:[\"English\"],medium:[\"English\"],mimeType:\"application/vnd.sunbird.question\",createdOn:\"2021-01-13T09:29:06.255+0000\",IL_FUNC_OBJECT_TYPE:\"Question\",gradeLevel:[\"Class 6\"],contentDisposition:\"inline\",lastUpdatedOn:\"2021-02-08T11:19:08.989+0000\",contentEncoding:\"gzip\",showSolutions:\"No\",allowAnonymousAccess:\"Yes\",IL_UNIQUE_ID:\"do_113193462958120960141\",lastStatusChangedOn:\"2021-02-08T11:19:08.989+0000\",visibility:\"Parent\",showTimer:\"No\",author:\"Vaibhav\",qType:\"SA\",languageCode:[\"en\"],version:1,versionKey:\"1611554879383\",showFeedback:\"No\",license:\"CC BY 4.0\",prevState:\"Review\",compatibilityLevel:4,name:\"Subjective\",topic:[\"Leaves\"],board:\"CBSE\",status:\"Draft\"},{ownershipType:[\"createdBy\"],copyright:\"ORG_002\",previewUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",channel:\"01246944855007232011\",organisation:[\"ORG_002\"],showNotification:true,language:[\"English\"],mimeType:\"video/mp4\",variants:\"{\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_112831862871203840114/test-resource-cert_1566389714022_do_112831862871203840114_1.0_spine.ecar\\\",\\\"size\\\":35757.0}}\",appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_112831862871203840114/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"dev.sunbird.portal\",artifactUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_112831862871203840114/small.mp4\",contentEncoding:\"identity\",lockKey:\"be6bc445-c75e-471d-b46f-71fefe4a1d2f\",contentType:\"Resource\",lastUpdatedBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",audience:[\"Student\"],visibility:\"Default\",consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:1,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",lastPublishedOn:\"2019-08-21T12:15:13.652+0000\",size:416488,IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Test Resource Cert\",status:\"Live\",code:\"7e6630c7-3818-4319-92ac-4d08c33904d8\",streamingUrl:\"https://sunbirddevmedia-inct.streaming.media.azure.net/25d7a94c-9be3-471c-926b-51eb5d3c4c2c/small.ism/manifest(format=m3u8-aapl-v3)\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T12:11:50.644+0000\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T12:15:13.020+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-08-21T12:30:16.783+0000\",dialcodeRequired:\"No\",creator:\"Pradyumna\",lastStatusChangedOn:\"2019-08-21T12:15:14.384+0000\",createdFor:[\"01246944855007232011\"],os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566389713020\",idealScreenDensity:\"hdpi\",s3Key:\"ecar_files/do_112831862871203840114/test-resource-cert_1566389713658_do_112831862871203840114_1.0.ecar\",framework:\"K-12\",createdBy:\"c4cc494f-04c3-49f3-b3d5-7b1a1984abad\",compatibilityLevel:1,IL_UNIQUE_ID:\"do_112831862871203840114\",resourceType:\"Learn\"},{ownershipType:[\"createdBy\"],copyright:\"Sunbird\",certTemplate:\"[{\\\"name\\\":\\\"100PercentCompletionCertificate\\\",\\\"issuer\\\":{\\\"name\\\":\\\"Gujarat Council of Educational Research and Training\\\",\\\"url\\\":\\\"https://gcert.gujarat.gov.in/gcert/\\\",\\\"publicKey\\\":[\\\"1\\\",\\\"2\\\"]},\\\"signatoryList\\\":[{\\\"name\\\":\\\"CEO Gujarat\\\",\\\"id\\\":\\\"CEO\\\",\\\"designation\\\":\\\"CEO\\\",\\\"image\\\":\\\"https://cdn.pixabay.com/photo/2014/11/09/08/06/signature-523237__340.jpg\\\"}],\\\"htmlTemplate\\\":\\\"https://drive.google.com/uc?authuser=1&id=1ryB71i0Oqn2c3aqf9N6Lwvet-MZKytoM&export=download\\\",\\\"notifyTemplate\\\":{\\\"subject\\\":\\\"Course completion certificate\\\",\\\"stateImgUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/orgemailtemplate/img/File-0128212938260643843.png\\\",\\\"regardsperson\\\":\\\"Chairperson\\\",\\\"regards\\\":\\\"Minister of Gujarat\\\",\\\"emailTemplateType\\\":\\\"defaultCertTemp\\\"}}]\",downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550414/test-prad-course-cert_1566398313947_do_11283193441064550414_1.0_spine.ecar\",channel:\"b00bc992ef25f1a9a8d63291e20efc8d\",organisation:[\"Sunbird\"],language:[\"English\"],variants:\"{\\\"online\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550414/test-prad-course-cert_1566398314186_do_11283193441064550414_1.0_online.ecar\\\",\\\"size\\\":4034.0},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/ecar_files/do_11283193441064550414/test-prad-course-cert_1566398313947_do_11283193441064550414_1.0_spine.ecar\\\",\\\"size\\\":73256.0}}\",mimeType:\"application/vnd.ekstep.content-collection\",leafNodes:[\"do_112831862871203840114\"],c_sunbird_dev_private_batch_count:0,appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550414/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",appId:\"local.sunbird.portal\",contentEncoding:\"gzip\",lockKey:\"b079cf15-9e45-4865-be56-2edafa432dd3\",mimeTypesCount:\"{\\\"application/vnd.ekstep.content-collection\\\":1,\\\"video/mp4\\\":1}\",totalCompressedSize:416488,contentType:\"Course\",lastUpdatedBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",audience:[\"Student\"],toc_url:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11283193441064550414/artifact/do_11283193441064550414_toc.json\",visibility:\"Default\",contentTypesCount:\"{\\\"CourseUnit\\\":1,\\\"Resource\\\":1}\",author:\"b00bc992ef25f1a9a8d63291e20efc8d\",childNodes:[\"do_11283193463014195215\"],consumerId:\"273f3b18-5dda-4a27-984a-060c7cd398d3\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"System\",version:2,license:\"Creative Commons Attribution (CC BY)\",prevState:\"Draft\",size:73256,lastPublishedOn:\"2019-08-21T14:38:33.816+0000\",IL_FUNC_OBJECT_TYPE:\"Collection\",name:\"test prad course cert\",status:\"Live\",code:\"org.sunbird.SUi47U\",description:\"Enter description for Course\",posterImage:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11281332607717376012/artifact/033019_sz_reviews_feat_1564126718632.jpg\",idealScreenSize:\"normal\",createdOn:\"2019-08-21T14:37:23.486+0000\",reservedDialcodes:\"{\\\"I1X4R4\\\":0}\",contentDisposition:\"inline\",lastUpdatedOn:\"2019-08-21T14:38:33.212+0000\",SYS_INTERNAL_LAST_UPDATED_ON:\"2019-11-13T12:54:08.295+0000\",dialcodeRequired:\"No\",creator:\"Creation\",createdFor:[\"ORG_001\"],lastStatusChangedOn:\"2019-08-21T14:38:34.540+0000\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",pkgVersion:1,versionKey:\"1566398313212\",idealScreenDensity:\"hdpi\",dialcodes:[\"I1X4R4\"],s3Key:\"ecar_files/do_11283193441064550414/test-prad-course-cert_1566398313947_do_11283193441064550414_1.0_spine.ecar\",depth:0,framework:\"tpd\",me_averageRating:5,createdBy:\"874ed8a5-782e-4f6c-8f36-e0288455901e\",leafNodesCount:1,compatibilityLevel:4,IL_UNIQUE_ID:\"do_11283193441064550414\",c_sunbird_dev_open_batch_count:0,resourceType:\"Course\"}] as row CREATE (n:domain) SET n += row") executeCassandraQuery(script_1, script_2, script_3) + executeCassandraQuery(CATEGORY_DEF_INPUT:_*) RedisCache.delete("hierarchy_do_11283193441064550414") } "addLeafNodesToHierarchy" should "addLeafNodesToHierarchy" in { executeCassandraQuery(script_3) + graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],previewUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_1134009478823116801129/chapter_1.pdf\",keywords:[\"CPPDFContent1\",\"CPPDFContent2\",\"CollectionKW1\"],downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11340096165525094411/collection-publishing-pdf-content_1635860615969_do_11340096165525094411_1.ecar\",channel:\"0126825293972439041\",language:[\"English\"],variants:\"{\\\"full\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11340096165525094411/collection-publishing-pdf-content_1635860615969_do_11340096165525094411_1.ecar\\\",\\\"size\\\":\\\"256918\\\"},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11340096165525094411/collection-publishing-pdf-content_1635860619148_do_11340096165525094411_1_SPINE.ecar\\\",\\\"size\\\":\\\"6378\\\"}}\",mimeType:\"application/pdf\",objectType:\"Content\",appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11340094790233292811/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",primaryCategory:\"Explanation Content\",contentEncoding:\"identity\",artifactUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_1134009478823116801129/chapter_1.pdf\",contentType:\"Resource\",audience:[\"Student\"],visibility:\"Default\",discussionForum:\"{\\\"enabled\\\":\\\"No\\\"}\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"\",version:2,pragma:[\"external\"],prevState:\"Draft\",license:\"CC BY 4.0\",lastPublishedOn:\"2021-11-02T19:13:35.589+0530\",IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Collection Publishing PDF Content\",status:\"Live\",interceptionPoints:\"{}\",code:\"c9ce1ce0-b9b4-402e-a9c3-556701070838\",credentials:\"{\\\"enabled\\\":\\\"No\\\"}\",prevStatus:\"Processing\",streamingUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_1134009478823116801129/chapter_1.pdf\",idealScreenSize:\"normal\",createdOn:\"2021-11-02T18:56:17.917+0530\",copyrightYear:2021,contentDisposition:\"inline\",lastUpdatedOn:\"2021-11-02T19:13:39.729+0530\",dialcodeRequired:\"No\",creator:\"N131\",createdFor:[\"01309282781705830427\"],lastStatusChangedOn:\"2021-11-02T19:13:39.729+0530\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",se_FWIds:[\"ekstep_ncert_k-12\"],pkgVersion:1,versionKey:\"1635859577917\",idealScreenDensity:\"hdpi\",framework:\"ekstep_ncert_k-12\",createdBy:\"0b71985d-fcb0-4018-ab14-83f10c3b0426\",compatibilityLevel:4,IL_UNIQUE_ID:\"do_11340096165525094411\",resourceType:\"Learn\"}] as row CREATE (n:domain) SET n += row") val request = new Request() request.setContext(new util.HashMap[String, AnyRef]() { { @@ -42,7 +52,8 @@ class TestHierarchy extends BaseSpec { request.put("rootId", "do_11283193441064550414") request.put("unitId", "do_11283193463014195215") - request.put("children", util.Arrays.asList("do_112831862871203840114")) + request.put("children", util.Arrays.asList("do_11340096165525094411")) + request.put("relationalMetadata",mapAsJavaMap(Map("do_11340096165525094411" -> Map("relName" -> "Test Name RM", "keywords" -> Array("Overwriting content Keywords") )))) request.put("mode","edit") val future = HierarchyManager.addLeafNodesToHierarchy(request) future.map(response => { @@ -51,7 +62,16 @@ class TestHierarchy extends BaseSpec { val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'") .one().getString("hierarchy") assert(!response.getResult.get("do_11283193463014195215").asInstanceOf[util.List[String]].contains("do_11283193463014195215")) - assert(hierarchy.contains("do_112831862871203840114")) + assert(hierarchy.contains("do_11340096165525094411")) + + val relationalMetadataHierarchyString = readFromCassandra("Select relational_metadata from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'") + .one().getString("relational_metadata") + val relationalMetadataHierarchy = JsonUtils.deserialize(relationalMetadataHierarchyString, classOf[java.util.Map[String, AnyRef]]) + val unitsHierarchyMetadata = relationalMetadataHierarchy.get("do_11283193463014195215").asInstanceOf[java.util.Map[String,AnyRef]] + val unitsHierarchyChildren = unitsHierarchyMetadata.get("children").asInstanceOf[java.util.List[String]] + assert(unitsHierarchyChildren.contains("do_11340096165525094411")) + val unitsRelationalMetadata = unitsHierarchyMetadata.get("relationalMetadata").asInstanceOf[java.util.Map[String, AnyRef]] + assert(unitsRelationalMetadata.containsKey("do_11340096165525094411")) }) } @@ -244,20 +264,28 @@ class TestHierarchy extends BaseSpec { request.put("rootId", "do_11283193441064550414") request.put("unitId", "do_11283193463014195215") - request.put("children", util.Arrays.asList("do_112831862871203840114")) + request.put("children", util.Arrays.asList("do_11340096165525094411")) request.put("mode","edit") val future = HierarchyManager.addLeafNodesToHierarchy(request) future.map(response => { assert(response.getResponseCode.code() == 200) val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'") .one().getString("hierarchy") - assert(hierarchy.contains("do_112831862871203840114")) + assert(hierarchy.contains("do_11340096165525094411")) val removeFuture = HierarchyManager.removeLeafNodesFromHierarchy(request) removeFuture.map(resp => { assert(resp.getResponseCode.code() == 200) val hierarchy = readFromCassandra("Select hierarchy from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'") .one().getString("hierarchy") - assert(!hierarchy.contains("do_112831862871203840114")) + assert(!hierarchy.contains("do_11340096165525094411")) + val relationalMetadataHierarchyString = readFromCassandra("Select relational_metadata from hierarchy_store.content_hierarchy where identifier='do_11283193441064550414.img'") + .one().getString("relational_metadata") + val relationalMetadataHierarchy = JsonUtils.deserialize(relationalMetadataHierarchyString, classOf[java.util.Map[String, AnyRef]]) + val unitsHierarchyMetadata = relationalMetadataHierarchy.get("do_11283193463014195215").asInstanceOf[java.util.Map[String,AnyRef]] + val unitsHierarchyChildren = unitsHierarchyMetadata.get("children").asInstanceOf[java.util.List[String]] + assert(!unitsHierarchyChildren.contains("do_11340096165525094411")) + val unitsRelationalMetadata = unitsHierarchyMetadata.get("relationalMetadata").asInstanceOf[java.util.Map[String, AnyRef]] + assert(!unitsRelationalMetadata.containsKey("do_11340096165525094411")) }) }).flatMap(f => f) } diff --git a/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestUpdateHierarchy.scala b/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestUpdateHierarchy.scala index 032fc8a56..205f71858 100644 --- a/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestUpdateHierarchy.scala +++ b/content-api/hierarchy-manager/src/test/scala/org/sunbird/managers/TestUpdateHierarchy.scala @@ -13,7 +13,7 @@ import org.sunbird.utils.HierarchyConstants class TestUpdateHierarchy extends BaseSpec { private val KEYSPACE_CREATE_SCRIPT = "CREATE KEYSPACE IF NOT EXISTS hierarchy_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" - private val TABLE_CREATE_SCRIPT = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text,PRIMARY KEY (identifier));" + private val TABLE_CREATE_SCRIPT = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text, relational_metadata text, PRIMARY KEY (identifier));" private val HIERARCHY_TO_MIGRATE_SCRIPT = "INSERT INTO hierarchy_store.content_hierarchy(identifier, hierarchy) values ('do_11283193441064550414.img', '{\"identifier\":\"do_11283193441064550414\",\"children\":[{\"parent\":\"do_11283193441064550414\",\"identifier\":\"do_11283193463014195215\",\"copyright\":\"Sunbird\",\"lastStatusChangedOn\":\"2019-08-21T14:37:50.281+0000\",\"code\":\"2e837725-d663-45da-8ace-9577ab111982\",\"visibility\":\"Parent\",\"index\":1,\"mimeType\":\"application/vnd.ekstep.content-collection\",\"createdOn\":\"2019-08-21T14:37:50.281+0000\",\"versionKey\":\"1566398270281\",\"framework\":\"tpd\",\"depth\":1,\"children\":[],\"name\":\"U1\",\"lastUpdatedOn\":\"2019-08-21T14:37:50.281+0000\",\"contentType\":\"CourseUnit\",\"primaryCategory\":\"Learning Resource\",\"status\":\"Draft\"}]}');" private val CATEGORY_STORE_KEYSPACE = "CREATE KEYSPACE IF NOT EXISTS category_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" @@ -114,6 +114,7 @@ class TestUpdateHierarchy extends BaseSpec { // ResourceId = "do_31250856200414822416938" and TextBook id ="do_112945818874658816" "updateHierarchy with One New Unit and One Live Resource" should "update text book node, create unit and store the hierarchy in cassandra" in { + graphDb.execute("UNWIND [{ownershipType:[\"createdBy\"],previewUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_1134009478823116801129/chapter_1.pdf\",keywords:[\"CPPDFContent1\",\"CPPDFContent2\",\"CollectionKW1\"],downloadUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11340096165525094411/collection-publishing-pdf-content_1635860615969_do_11340096165525094411_1.ecar\",channel:\"0126825293972439041\",language:[\"English\"],variants:\"{\\\"full\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11340096165525094411/collection-publishing-pdf-content_1635860615969_do_11340096165525094411_1.ecar\\\",\\\"size\\\":\\\"256918\\\"},\\\"spine\\\":{\\\"ecarUrl\\\":\\\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11340096165525094411/collection-publishing-pdf-content_1635860619148_do_11340096165525094411_1_SPINE.ecar\\\",\\\"size\\\":\\\"6378\\\"}}\",mimeType:\"application/pdf\",objectType:\"Content\",appIcon:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/do_11340094790233292811/artifact/033019_sz_reviews_feat_1564126718632.thumb.jpg\",primaryCategory:\"Explanation Content\",contentEncoding:\"identity\",artifactUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_1134009478823116801129/chapter_1.pdf\",contentType:\"Resource\",audience:[\"Student\"],visibility:\"Default\",discussionForum:\"{\\\"enabled\\\":\\\"No\\\"}\",mediaType:\"content\",osId:\"org.ekstep.quiz.app\",lastPublishedBy:\"\",version:2,pragma:[\"external\"],prevState:\"Draft\",license:\"CC BY 4.0\",lastPublishedOn:\"2021-11-02T19:13:35.589+0530\",IL_FUNC_OBJECT_TYPE:\"Content\",name:\"Collection Publishing PDF Content\",status:\"Live\",interceptionPoints:\"{}\",code:\"c9ce1ce0-b9b4-402e-a9c3-556701070838\",credentials:\"{\\\"enabled\\\":\\\"No\\\"}\",prevStatus:\"Processing\",streamingUrl:\"https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content/assets/do_1134009478823116801129/chapter_1.pdf\",idealScreenSize:\"normal\",createdOn:\"2021-11-02T18:56:17.917+0530\",copyrightYear:2021,contentDisposition:\"inline\",lastUpdatedOn:\"2021-11-02T19:13:39.729+0530\",dialcodeRequired:\"No\",creator:\"N131\",createdFor:[\"01309282781705830427\"],lastStatusChangedOn:\"2021-11-02T19:13:39.729+0530\",os:[\"All\"],IL_SYS_NODE_TYPE:\"DATA_NODE\",se_FWIds:[\"ekstep_ncert_k-12\"],pkgVersion:1,versionKey:\"1635859577917\",idealScreenDensity:\"hdpi\",framework:\"ekstep_ncert_k-12\",createdBy:\"0b71985d-fcb0-4018-ab14-83f10c3b0426\",compatibilityLevel:4,IL_UNIQUE_ID:\"do_11340096165525094411\",resourceType:\"Learn\"}] as row CREATE (n:domain) SET n += row") val request = new Request() val context = getContext() context.put(HierarchyConstants.SCHEMA_NAME, "collection") @@ -392,7 +393,8 @@ class TestUpdateHierarchy extends BaseSpec { " \t},\n"+ " \t\"b9a50833-eff6-4ef5-a2a4-2413f2d51f6c\": {\n"+ " \t\t\"root\": false,\n"+ - " \t\t\"children\": [\"do_31250856200414822416938\"]\n"+ + " \t\t\"children\": [\"do_31250856200414822416938\",\"do_11340096165525094411\"],\n"+ + " \t\t\"relationalMetadata\": {\n\"do_11340096165525094411\": {\n\"relName\": \"abc\"\n,\"keywords\": [\"test\"]}\n}" + " \t}\n"+ " }" JsonUtils.deserialize(hierarchyString, classOf[util.HashMap[String, AnyRef]]) diff --git a/ontology-engine/graph-core_2.11/src/main/scala/org/sunbird/graph/OntologyEngineContext.scala b/ontology-engine/graph-core_2.11/src/main/scala/org/sunbird/graph/OntologyEngineContext.scala index fb2d92de4..590e31a8d 100644 --- a/ontology-engine/graph-core_2.11/src/main/scala/org/sunbird/graph/OntologyEngineContext.scala +++ b/ontology-engine/graph-core_2.11/src/main/scala/org/sunbird/graph/OntologyEngineContext.scala @@ -7,7 +7,7 @@ class OntologyEngineContext { private val graphDB = new GraphService private val hUtil = new HttpUtil - private val kfClient = new KafkaClient + private lazy val kfClient = new KafkaClient def graphService = { graphDB diff --git a/ontology-engine/graph-core_2.11/src/test/scala/org/sunbird/graph/BaseSpec.scala b/ontology-engine/graph-core_2.11/src/test/scala/org/sunbird/graph/BaseSpec.scala index a24a22a15..db9e90ceb 100644 --- a/ontology-engine/graph-core_2.11/src/test/scala/org/sunbird/graph/BaseSpec.scala +++ b/ontology-engine/graph-core_2.11/src/test/scala/org/sunbird/graph/BaseSpec.scala @@ -22,7 +22,7 @@ class BaseSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll { private val script_1 = "CREATE KEYSPACE IF NOT EXISTS content_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" private val script_2 = "CREATE TABLE IF NOT EXISTS content_store.content_data (content_id text, last_updated_on timestamp,body blob,oldBody blob,screenshots blob,stageIcons blob,externallink text,PRIMARY KEY (content_id));" private val script_3 = "CREATE KEYSPACE IF NOT EXISTS hierarchy_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" - private val script_4 = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text,PRIMARY KEY (identifier));" + private val script_4 = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text, relational_metadata text, PRIMARY KEY (identifier));" private val script_5 = "CREATE KEYSPACE IF NOT EXISTS category_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" private val script_6 = "CREATE TABLE IF NOT EXISTS category_store.category_definition_data (identifier text, objectmetadata map, forms map ,PRIMARY KEY (identifier));" private val script_7 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_content_all', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});" diff --git a/ontology-engine/graph-engine_2.11/src/main/scala/org/sunbird/graph/schema/DefinitionNode.scala b/ontology-engine/graph-engine_2.11/src/main/scala/org/sunbird/graph/schema/DefinitionNode.scala index 634baaebe..f09c086ee 100644 --- a/ontology-engine/graph-engine_2.11/src/main/scala/org/sunbird/graph/schema/DefinitionNode.scala +++ b/ontology-engine/graph-engine_2.11/src/main/scala/org/sunbird/graph/schema/DefinitionNode.scala @@ -250,8 +250,9 @@ object DefinitionNode { } def validateContentNodes(nodes: List[Node], graphId: String, schemaName: String, version: String)(implicit ec: ExecutionContext, oec: OntologyEngineContext): Future[List[Node]] = { - val definition = DefinitionFactory.getDefinition(graphId, schemaName, version) val futures = nodes.map(node => { + val ocd = ObjectCategoryDefinition(node.getMetadata.getOrDefault("primaryCategory", "").asInstanceOf[String], node.getObjectType, node.getMetadata.getOrDefault("channel", "all").asInstanceOf[String]) + val definition = DefinitionFactory.getDefinition(graphId, schemaName, version, ocd) definition.validate(node, "update") recoverWith { case e: CompletionException => throw e.getCause } }) Future.sequence(futures) diff --git a/ontology-engine/graph-engine_2.11/src/test/scala/org/sunbird/graph/BaseSpec.scala b/ontology-engine/graph-engine_2.11/src/test/scala/org/sunbird/graph/BaseSpec.scala index e19a75339..830e612c5 100644 --- a/ontology-engine/graph-engine_2.11/src/test/scala/org/sunbird/graph/BaseSpec.scala +++ b/ontology-engine/graph-engine_2.11/src/test/scala/org/sunbird/graph/BaseSpec.scala @@ -25,7 +25,7 @@ class BaseSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll { private val script_1 = "CREATE KEYSPACE IF NOT EXISTS content_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" private val script_2 = "CREATE TABLE IF NOT EXISTS content_store.content_data (content_id text, last_updated_on timestamp,body blob,oldBody blob,screenshots blob,stageIcons blob,externallink text,PRIMARY KEY (content_id));" private val script_3 = "CREATE KEYSPACE IF NOT EXISTS hierarchy_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" - private val script_4 = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text,PRIMARY KEY (identifier));" + private val script_4 = "CREATE TABLE IF NOT EXISTS hierarchy_store.content_hierarchy (identifier text, hierarchy text, relational_metadata text, PRIMARY KEY (identifier));" private val script_5 = "CREATE KEYSPACE IF NOT EXISTS category_store WITH replication = {'class': 'SimpleStrategy','replication_factor': '1'};" private val script_6 = "CREATE TABLE IF NOT EXISTS category_store.category_definition_data (identifier text, objectmetadata map, forms map ,PRIMARY KEY (identifier));" private val script_7 = "INSERT INTO category_store.category_definition_data (identifier, objectmetadata) VALUES ('obj-cat:learning-resource_content_all', {'config': '{}', 'schema': '{\"properties\":{\"audience\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"Student\",\"Teacher\"]},\"default\":[\"Student\"]},\"mimeType\":{\"type\":\"string\",\"enum\":[\"application/vnd.ekstep.ecml-archive\",\"application/vnd.ekstep.html-archive\",\"application/vnd.ekstep.h5p-archive\",\"application/pdf\",\"video/mp4\",\"video/webm\"]}}}'});" diff --git a/platform-modules/url-manager/src/test/java/org/sunbird/url/mgr/impl/GoogleDriveURLManagerImplTest.java b/platform-modules/url-manager/src/test/java/org/sunbird/url/mgr/impl/GoogleDriveURLManagerImplTest.java index bcc58b89a..ee7998fc5 100644 --- a/platform-modules/url-manager/src/test/java/org/sunbird/url/mgr/impl/GoogleDriveURLManagerImplTest.java +++ b/platform-modules/url-manager/src/test/java/org/sunbird/url/mgr/impl/GoogleDriveURLManagerImplTest.java @@ -27,7 +27,7 @@ public class GoogleDriveURLManagerImplTest { @Test public void testValidateUrlWithValidUrlValidCriteria() { - String driveUrl = "https://drive.google.com/file/d/1az_AFAoRwu9cXlr1R5pO9fNhHexzJKXo/view?usp=sharing"; + String driveUrl = "https://drive.google.com/file/d/1ZUSXrODwNK52pzDJZ_fuNKK9lXBzxCsS/view?usp=sharing"; Map result = gdMgr.validateURL(driveUrl, "size"); assertTrue(MapUtils.isNotEmpty(result)); assertTrue(result.size() == 2); @@ -41,7 +41,7 @@ public void testValidateUrlWithValidUrlValidCriteria() { public void testValidateUrlWithValidUrlInvalidCriteria() { exception.expect(ClientException.class); exception.expectMessage("Please Provide Valid Criteria For Validation. Supported Criteria : [size]"); - String driveUrl = "https://drive.google.com/file/d/1az_AFAoRwu9cXlr1R5pO9fNhHexzJKXo/view?usp=sharing"; + String driveUrl = "https://drive.google.com/file/d/1ZUSXrODwNK52pzDJZ_fuNKK9lXBzxCsS/view?usp=sharing"; Map result = gdMgr.validateURL(driveUrl, "name"); } @@ -56,7 +56,7 @@ public void testValidateUrlWithInvalidUrlValidCriteria() { @Test public void testReadMetadataWithValidUrl() { - String driveUrl = "https://drive.google.com/file/d/1az_AFAoRwu9cXlr1R5pO9fNhHexzJKXo/view?usp=sharing"; + String driveUrl = "https://drive.google.com/file/d/1ZUSXrODwNK52pzDJZ_fuNKK9lXBzxCsS/view?usp=sharing"; Map result = gdMgr.readMetadata(driveUrl); assertTrue(MapUtils.isNotEmpty(result)); assertTrue(result.size() == 3); diff --git a/platform-modules/url-manager/src/test/java/org/sunbird/url/util/GoogleDriveUtilTest.java b/platform-modules/url-manager/src/test/java/org/sunbird/url/util/GoogleDriveUtilTest.java index 55dd2cd82..17a5b8723 100644 --- a/platform-modules/url-manager/src/test/java/org/sunbird/url/util/GoogleDriveUtilTest.java +++ b/platform-modules/url-manager/src/test/java/org/sunbird/url/util/GoogleDriveUtilTest.java @@ -29,9 +29,9 @@ public class GoogleDriveUtilTest { @Test public void testGetDriveUrlWithValidUrl() { - String driveUrl = "https://drive.google.com/file/d/1az_AFAoRwu9cXlr1R5pO9fNhHexzJKXo/view?usp=sharing"; + String driveUrl = "https://drive.google.com/file/d/1ZUSXrODwNK52pzDJZ_fuNKK9lXBzxCsS/view?usp=sharing"; String output = GoogleDriveUrlUtil.getDriveFileId(driveUrl); - assertEquals("1az_AFAoRwu9cXlr1R5pO9fNhHexzJKXo", output); + assertEquals("1ZUSXrODwNK52pzDJZ_fuNKK9lXBzxCsS", output); } @Test @@ -43,7 +43,7 @@ public void testGetDriveUrlWithInvalidUrl() { @Test public void testGetDriveFileWithValidUrl() { - String driveUrl = "https://drive.google.com/file/d/1az_AFAoRwu9cXlr1R5pO9fNhHexzJKXo/view?usp=sharing"; + String driveUrl = "https://drive.google.com/file/d/1ZUSXrODwNK52pzDJZ_fuNKK9lXBzxCsS/view?usp=sharing"; String fileId = GoogleDriveUrlUtil.getDriveFileId(driveUrl); File driveFile = GoogleDriveUrlUtil.getDriveFile(fileId); assertNotNull(driveFile); @@ -70,7 +70,7 @@ public void testGetDriveFileWithInvalidUrl() { @Test public void testGetMetadataWithValidUrl() { - String driveUrl = "https://drive.google.com/file/d/1az_AFAoRwu9cXlr1R5pO9fNhHexzJKXo/view?usp=sharing"; + String driveUrl = "https://drive.google.com/file/d/1ZUSXrODwNK52pzDJZ_fuNKK9lXBzxCsS/view?usp=sharing"; Map result = GoogleDriveUrlUtil.getMetadata(driveUrl); assertTrue(MapUtils.isNotEmpty(result)); assertTrue(result.size() == 3); @@ -92,7 +92,7 @@ public void testGetMetadataWithInvalidUrl() { @Test public void testGetSizeWithValidUrl() { - String driveUrl = "https://drive.google.com/file/d/1az_AFAoRwu9cXlr1R5pO9fNhHexzJKXo/view?usp=sharing"; + String driveUrl = "https://drive.google.com/file/d/1ZUSXrODwNK52pzDJZ_fuNKK9lXBzxCsS/view?usp=sharing"; Long result = GoogleDriveUrlUtil.getSize(driveUrl); assertTrue(result > 0); } diff --git a/schemas/collection/1.0/config.json b/schemas/collection/1.0/config.json index c44040b28..1ce0ac324 100644 --- a/schemas/collection/1.0/config.json +++ b/schemas/collection/1.0/config.json @@ -5,6 +5,9 @@ "properties": { "hierarchy": { "type": "string" + }, + "relational_metadata": { + "type": "string" } }, "primaryKey": [ diff --git a/schemas/question/1.0/schema.json b/schemas/question/1.0/schema.json index 72aafd676..670d548e5 100644 --- a/schemas/question/1.0/schema.json +++ b/schemas/question/1.0/schema.json @@ -549,6 +549,26 @@ "rejectComment": { "type": "string" }, + "showRemarks": { + "type": "string", + "enum": [ + "Yes", + "No" + ] + }, + "remarks": { + "type": "object" + }, + "showEvidence": { + "type": "string", + "enum": [ + "Yes", + "No" + ] + }, + "evidence": { + "type": "object" + }, "accessibility": { "type": "array", "items": { diff --git a/schemas/questionset/1.0/config.json b/schemas/questionset/1.0/config.json index 84964ebd8..e6719f81f 100644 --- a/schemas/questionset/1.0/config.json +++ b/schemas/questionset/1.0/config.json @@ -71,6 +71,9 @@ }, "instructions": { "type": "string" + }, + "outcomeDeclaration": { + "type": "string" } }, "primaryKey": [ diff --git a/schemas/questionset/1.0/schema.json b/schemas/questionset/1.0/schema.json index 256337d9a..02516dc47 100644 --- a/schemas/questionset/1.0/schema.json +++ b/schemas/questionset/1.0/schema.json @@ -587,11 +587,69 @@ "rejectComment": { "type": "string" }, + "allowBranching": { + "type": "string", + "enum": [ + "Yes", + "No" + ], + "default": "No" + }, + "branchingLogic": { + "type": "object", + "description": "Store rules for dependent question" + }, + "entityType": { + "type": "string" + }, + "secondaryLanguage": { + "type": "array" + }, + "allowScoring": { + "type": "string", + "enum": [ + "Yes", + "No" + ] + }, + "allowMultipleInstances": { + "type": "string", + "enum": [ + "Yes", + "No" + ] + }, + "instances": { + "type": "object" + }, + "startDate": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "ecm": { + "type": "array" + }, + "recordedBy": { + "type": "string", + "enum": [ + "Self", + "External" + ] + }, + "renderingSequence": { + "type": "object" + }, "accessibility": { "type": "array", "items": { "type": "object" } + }, + "outcomeDeclaration": { + "type": "object", + "description": "External Property" } }, "additionalProperties": false diff --git a/schemas/relationalmetadata/1.0/config.json b/schemas/relationalmetadata/1.0/config.json new file mode 100644 index 000000000..b54a87941 --- /dev/null +++ b/schemas/relationalmetadata/1.0/config.json @@ -0,0 +1,12 @@ +{ + "objectType": "relationalmetadata", + "version": "disable", + "versionCheckMode": "OFF", + "frameworkCategories": [], + "orgFrameworkTerms": [], + "targetFrameworkTerms": [], + "restrictProps": {}, + "external": {}, + "relations": {}, + "schema_restrict_api": false +} \ No newline at end of file diff --git a/schemas/relationalmetadata/1.0/schema.json b/schemas/relationalmetadata/1.0/schema.json new file mode 100644 index 000000000..b4c4476d2 --- /dev/null +++ b/schemas/relationalmetadata/1.0/schema.json @@ -0,0 +1,31 @@ +{ + "$id": "app-schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "relationalmetadata", + "type": "object", + "required": [], + "properties": { + "relName": { + "type": "string", + "minLength": 5 + }, + "relTrackable": { + "type": "boolean", + "default": false + }, + "mandatoryQuestion": { + "type": "boolean", + "default": false + }, + "relScore": { + "type": "number" + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/scripts/definition-scripts/Date.sh b/scripts/definition-scripts/Date.sh new file mode 100644 index 000000000..95e92acc2 --- /dev/null +++ b/scripts/definition-scripts/Date.sh @@ -0,0 +1,32 @@ +curl -L -X POST '{{host}}/object/category/definition/v4/create' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategoryDefinition": { + "categoryId": "obj-cat:date", + "targetObjectType": "Question", + "objectMetadata": { + "config": {}, + "schema": { + "properties": { + "interactionTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "date" + ] + } + }, + "mimeType": { + "type": "string", + "enum": [ + "application/vnd.sunbird.question" + ] + } + } + } + } + } + } +}' \ No newline at end of file diff --git a/scripts/definition-scripts/Multiselect_Multiple_Choice_Question.sh b/scripts/definition-scripts/Multiselect_Multiple_Choice_Question.sh new file mode 100644 index 000000000..e79490cc7 --- /dev/null +++ b/scripts/definition-scripts/Multiselect_Multiple_Choice_Question.sh @@ -0,0 +1,32 @@ +curl -L -X POST '{{host}}/object/category/definition/v4/create' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategoryDefinition": { + "categoryId": "obj-cat:multiselect-multiple-choice-question", + "targetObjectType": "Question", + "objectMetadata": { + "config": {}, + "schema": { + "properties": { + "interactionTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "choice" + ] + } + }, + "mimeType": { + "type": "string", + "enum": [ + "application/vnd.sunbird.question" + ] + } + } + } + } + } + } +}' \ No newline at end of file diff --git a/scripts/definition-scripts/Observation.sh b/scripts/definition-scripts/Observation.sh new file mode 100644 index 000000000..0195bbf8f --- /dev/null +++ b/scripts/definition-scripts/Observation.sh @@ -0,0 +1,54 @@ +curl -L -X POST '{{host}}/object/category/definition/v4/create' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategoryDefinition": { + "categoryId": "obj-cat:observation", + "targetObjectType": "QuestionSet", + "objectMetadata": { + "config": {}, + "schema": { + "properties": { + "mimeType": { + "type": "string", + "enum": [ + "application/vnd.sunbird.questionset" + ] + }, + "allowBranching": { + "type": "string", + "enum": [ + "Yes", + "No" + ], + "default": "Yes" + }, + "audience": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "Education Official", + "School leaders (HMs)", + "Administrator", + "Teachers", + "Students", + "Parents", + "Others" + ] + } + }, + "allowScoring": { + "type": "string", + "enum": [ + "Yes", + "No" + ], + "default": "No" + } + } + } + } + } + } +}' \ No newline at end of file diff --git a/scripts/definition-scripts/Observation_With_Rubrics.sh b/scripts/definition-scripts/Observation_With_Rubrics.sh new file mode 100644 index 000000000..9e5cdccda --- /dev/null +++ b/scripts/definition-scripts/Observation_With_Rubrics.sh @@ -0,0 +1,54 @@ +curl -L -X POST '{{host}}/object/category/definition/v4/create' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategoryDefinition": { + "categoryId": "obj-cat:observation-with-rubrics", + "targetObjectType": "QuestionSet", + "objectMetadata": { + "config": {}, + "schema": { + "properties": { + "mimeType": { + "type": "string", + "enum": [ + "application/vnd.sunbird.questionset" + ] + }, + "allowBranching": { + "type": "string", + "enum": [ + "Yes", + "No" + ], + "default": "Yes" + }, + "audience": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "Education Official", + "School leaders (HMs)", + "Administrator", + "Teachers", + "Students", + "Parents", + "Others" + ] + } + }, + "allowScoring": { + "type": "string", + "enum": [ + "Yes", + "No" + ], + "default": "Yes" + } + } + } + } + } + } +}' \ No newline at end of file diff --git a/scripts/definition-scripts/Slider.sh b/scripts/definition-scripts/Slider.sh new file mode 100644 index 000000000..23bada7de --- /dev/null +++ b/scripts/definition-scripts/Slider.sh @@ -0,0 +1,32 @@ +curl -L -X POST '{{host}}/object/category/definition/v4/create' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategoryDefinition": { + "categoryId": "obj-cat:slider", + "targetObjectType": "Question", + "objectMetadata": { + "config": {}, + "schema": { + "properties": { + "interactionTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "slider" + ] + } + }, + "mimeType": { + "type": "string", + "enum": [ + "application/vnd.sunbird.question" + ] + } + } + } + } + } + } +}' \ No newline at end of file diff --git a/scripts/definition-scripts/Survey.sh b/scripts/definition-scripts/Survey.sh new file mode 100644 index 000000000..03823601c --- /dev/null +++ b/scripts/definition-scripts/Survey.sh @@ -0,0 +1,61 @@ +curl -L -X POST '{{host}}/object/category/definition/v4/create' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategoryDefinition": { + "categoryId": "obj-cat:survey", + "targetObjectType": "QuestionSet", + "objectMetadata": { + "config": {}, + "schema": { + "properties": { + "mimeType": { + "type": "string", + "enum": [ + "application/vnd.sunbird.questionset" + ] + }, + "allowBranching": { + "type": "string", + "enum": [ + "Yes", + "No" + ], + "default": "Yes" + }, + "audience": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "Education Official", + "School leaders (HMs)", + "Administrator", + "Teachers", + "Students", + "Parents", + "Others" + ] + } + }, + "allowScoring": { + "type": "string", + "enum": [ + "Yes", + "No" + ], + "default": "No" + }, + "setPeriod": { + "type": "string", + enum": [ + "Yes", + "No" + ] + } + } + } + } + } + } +}' \ No newline at end of file diff --git a/scripts/definition-scripts/Text.sh b/scripts/definition-scripts/Text.sh new file mode 100644 index 000000000..d1344c800 --- /dev/null +++ b/scripts/definition-scripts/Text.sh @@ -0,0 +1,32 @@ +curl -L -X POST '{{host}}/object/category/definition/v4/create' \ +-H 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategoryDefinition": { + "categoryId": "obj-cat:text", + "targetObjectType": "Question", + "objectMetadata": { + "config": {}, + "schema": { + "properties": { + "interactionTypes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "text" + ] + } + }, + "mimeType": { + "type": "string", + "enum": [ + "application/vnd.sunbird.question" + ] + } + } + } + } + } + } +}' \ No newline at end of file diff --git a/scripts/definition-scripts/master_category_create b/scripts/definition-scripts/master_category_create index c6efa3df0..ddfa79ea4 100644 --- a/scripts/definition-scripts/master_category_create +++ b/scripts/definition-scripts/master_category_create @@ -396,4 +396,81 @@ curl --location --request POST '{{host}}/object/category/v4/create' \ "description":"Foundational Learning" } } +}' + +curl --location --request POST '{{host}}/object/category/v4/create' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategory": { + "name": "Survey", + "description":"Survey" + } + } +}' + +curl --location --request POST '{{host}}/object/category/v4/create' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategory": { + "name": "Observation", + "description":"Observation" + } + } +}' + +curl --location --request POST '{{host}}/object/category/v4/create' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategory": { + "name": "Observation With Rubrics", + "description":"Observation With Rubrics" + } + } +}' + +curl --location --request POST '{{host}}/object/category/v4/create' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategory": { + "name": "Text", + "description":"Text" + } + } +}' + +curl --location --request POST '{{host}}/object/category/v4/create' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategory": { + "name": "Date", + "description":"Date" + } + } +}' + +curl --location --request POST '{{host}}/object/category/v4/create' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategory": { + "name": "Slider", + "description":"Slider" + } + } +}' + +curl --location --request POST '{{host}}/object/category/v4/create' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "request": { + "objectCategory": { + "name": "Multiselect Multiple Choice Question", + "description":"Multiselect Multiple Choice Question" + } + } }' \ No newline at end of file diff --git a/search-api/search-core/src/main/java/org/sunbird/search/client/ElasticSearchUtil.java b/search-api/search-core/src/main/java/org/sunbird/search/client/ElasticSearchUtil.java index e0b96b19c..cc031b378 100644 --- a/search-api/search-core/src/main/java/org/sunbird/search/client/ElasticSearchUtil.java +++ b/search-api/search-core/src/main/java/org/sunbird/search/client/ElasticSearchUtil.java @@ -1,5 +1,5 @@ /** - * + * */ package org.sunbird.search.client; @@ -79,6 +79,7 @@ public class ElasticSearchUtil { public static int defaultResultLimit = 10000; private static final int resultLimit = 100; + private static final int maxFieldLimit = 32000; public int defaultResultOffset = 0; private static int BATCH_SIZE = (Platform.config.hasPath("search.batch.size")) ? Platform.config.getInt("search.batch.size") @@ -92,7 +93,7 @@ public static void initialiseESClient(String indexName, String connectionInfo) { } /** - * + * */ private static void createClient(String indexName, String connectionInfo) { if (!esClient.containsKey(indexName)) { @@ -178,10 +179,9 @@ public static boolean addIndex(String indexName, String documentType, String set public static void addDocumentWithId(String indexName, String documentType, String documentId, String document) { try { - Map doc = mapper.readValue(document, new TypeReference>() { - }); - IndexResponse response = getClient(indexName) - .index(new IndexRequest(indexName, documentType, documentId).source(doc)); + Map doc = mapper.readValue(document, new TypeReference>() {}); + Map updatedDoc = checkDocStringLength(doc); + IndexResponse response = getClient(indexName).index(new IndexRequest(indexName, documentType, documentId).source(updatedDoc)); TelemetryManager.log("Added " + response.getId() + " to index " + response.getIndex()); } catch (IOException e) { TelemetryManager.error("Error while adding document to index :" + indexName, e); @@ -191,8 +191,8 @@ public static void addDocumentWithId(String indexName, String documentType, Stri public static void addDocument(String indexName, String documentType, String document) { try { Map doc = mapper.readValue(document, new TypeReference>() {}); - - IndexResponse response = getClient(indexName).index(new IndexRequest(indexName, documentType).source(doc)); + Map updatedDoc = checkDocStringLength(doc); + IndexResponse response = getClient(indexName).index(new IndexRequest(indexName, documentType).source(updatedDoc)); TelemetryManager.log("Added " + response.getId() + " to index " + response.getIndex()); } catch (IOException e) { TelemetryManager.error("Error while adding document to index :" + indexName, e); @@ -202,11 +202,10 @@ public static void addDocument(String indexName, String documentType, String doc public static void updateDocument(String indexName, String documentType, String document, String documentId) throws InterruptedException, ExecutionException { try { - Map doc = mapper.readValue(document, new TypeReference>() { - }); - IndexRequest indexRequest = new IndexRequest(indexName, documentType, documentId).source(doc); - UpdateRequest request = new UpdateRequest().index(indexName).type(documentType).id(documentId).doc(doc) - .upsert(indexRequest); + Map doc = mapper.readValue(document, new TypeReference>() {}); + Map updatedDoc = checkDocStringLength(doc); + IndexRequest indexRequest = new IndexRequest(indexName, documentType, documentId).source(updatedDoc); + UpdateRequest request = new UpdateRequest().index(indexName).type(documentType).id(documentId).doc(updatedDoc).upsert(indexRequest); UpdateResponse response = getClient(indexName).update(request); TelemetryManager.log("Updated " + response.getId() + " to index " + response.getIndex()); } catch (IOException e) { @@ -378,7 +377,7 @@ public static List wildCardSearch(Class objectClass, String textKeyWord, } public static SearchResponse wildCardSearch(String textKeyWord, String wordWildCard, String indexName, - String indexType, int limit) + String indexType, int limit) throws Exception { SearchSourceBuilder query = buildJsonForWildCardQuery(textKeyWord, wordWildCard, indexName); query.size(limit); @@ -387,7 +386,7 @@ public static SearchResponse wildCardSearch(String textKeyWord, String wordWildC @SuppressWarnings({ "rawtypes" }) public static List textFiltersSearch(Class objectClass, Map searchCriteria, - Map textFiltersMap, String indexName, String indexType, int limit) + Map textFiltersMap, String indexName, String indexType, int limit) throws Exception { SearchResponse result = search(searchCriteria, textFiltersMap, indexName, indexType, null, false, limit); return getDocumentsFromSearchResult(result, objectClass); @@ -395,8 +394,8 @@ public static List textFiltersSearch(Class objectClass, Map textFiltersGroupBySearch(Class objectClass, Map searchCriteria, - Map textFiltersMap, List> groupByList, String indexName, - String indexType) throws Exception { + Map textFiltersMap, List> groupByList, String indexName, + String indexType) throws Exception { SearchResponse result = search(searchCriteria, textFiltersMap, indexName, indexType, groupByList, false, resultLimit); List documents = getDocumentsFromSearchResult(result, objectClass); @@ -412,22 +411,22 @@ public static Map textFiltersGroupBySearch(Class objectClass, Ma @SuppressWarnings("rawtypes") public static List textSearch(Class objectClass, Map matchCriterias, - Map textFiltersMap, String indexName, String indexType) throws Exception { + Map textFiltersMap, String indexName, String indexType) throws Exception { SearchResponse result = search(matchCriterias, textFiltersMap, indexName, indexType, null, false, resultLimit); return getDocumentsFromSearchResult(result, objectClass); } @SuppressWarnings("rawtypes") public static List textSearch(Class objectClass, Map matchCriterias, - Map textFiltersMap, String indexName, String indexType, - List> groupByList, int limit) throws Exception { + Map textFiltersMap, String indexName, String indexType, + List> groupByList, int limit) throws Exception { SearchResponse result = search(matchCriterias, textFiltersMap, indexName, indexType, groupByList, false, limit); return getDocumentsFromSearchResult(result, objectClass); } public static SearchResponse search(Map matchCriterias, Map textFiltersMap, - String indexName, String indexType, List> groupBy, boolean isDistinct, int limit) + String indexName, String indexType, List> groupBy, boolean isDistinct, int limit) throws Exception { SearchSourceBuilder query = buildJsonForQuery(matchCriterias, textFiltersMap, groupBy, isDistinct, indexName); query.size(limit); @@ -468,7 +467,7 @@ public static int count(String indexName, SearchSourceBuilder searchSourceBuilde @SuppressWarnings({ "rawtypes", "unchecked" }) public static Map getCountFromAggregation(Aggregations aggregations, - List> groupByList) { + List> groupByList) { Map countMap = new HashMap(); if (aggregations != null) { for (Map aggregationsMap : groupByList) { @@ -509,7 +508,7 @@ public static Map getCountFromAggregation(Aggregations aggregati @SuppressWarnings("rawtypes") public static Map getCountOfSearch(Class objectClass, Map matchCriterias, - String indexName, String indexType, List> groupByList, int limit) + String indexName, String indexType, List> groupByList, int limit) throws Exception { SearchResponse result = search(matchCriterias, null, indexName, indexType, groupByList, false, limit); Aggregations aggregations = result.getAggregations(); @@ -518,7 +517,7 @@ public static Map getCountOfSearch(Class objectClass, Map getDistinctCountOfSearch(Map matchCriterias, String IndexName, - String IndexType, List> groupByList) throws Exception { + String IndexType, List> groupByList) throws Exception { Map countMap = new HashMap(); SearchResponse result = search(matchCriterias, null, IndexName, IndexType, groupByList, true, 0); Aggregations aggregations = result.getAggregations(); @@ -547,8 +546,8 @@ public static Map getDistinctCountOfSearch(Map m @SuppressWarnings("unchecked") public static SearchSourceBuilder buildJsonForQuery(Map matchCriterias, - Map textFiltersMap, List> groupByList, boolean isDistinct, - String indexName) { + Map textFiltersMap, List> groupByList, boolean isDistinct, + String indexName) { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); @@ -606,14 +605,14 @@ public static SearchSourceBuilder buildJsonForQuery(Map matchCri } private static SearchSourceBuilder buildJsonForWildCardQuery(String textKeyWord, String wordWildCard, - String indexName) { + String indexName) { return new SearchSourceBuilder().query(QueryBuilders.wildcardQuery(textKeyWord, wordWildCard)); } @SuppressWarnings("unchecked") public static Object getCountFromAggregation(Aggregations aggregations, List> groupByList, - IESResultTransformer transformer) { + IESResultTransformer transformer) { Map countMap = new HashMap(); if (aggregations != null) { @@ -711,4 +710,13 @@ public static void bulkDeleteDocumentById(String indexName, String documentType, } } + private static Map checkDocStringLength(Map doc) { + for (Map.Entry entry : doc.entrySet()) { + if ((entry.getValue() instanceof String) && entry.getValue().toString().length()>maxFieldLimit) { + doc.put(entry.getKey(), entry.getValue().toString().substring(0,maxFieldLimit)); + } + } + return doc; + } + } \ No newline at end of file