Index: lams_central/web/includes/javascript/authoring/authoringGeneral.js =================================================================== diff -u -reff4591166587e12af17581dc551a6104673cceb -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_central/web/includes/javascript/authoring/authoringGeneral.js (.../authoringGeneral.js) (revision eff4591166587e12af17581dc551a6104673cceb) +++ lams_central/web/includes/javascript/authoring/authoringGeneral.js (.../authoringGeneral.js) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -1689,7 +1689,13 @@ */ checkQuestionExistsInToolActivities : function(qbQuestionUid) { let resultToolContentIds = [], - candidateToolContentIds = []; + candidateToolContentIds = [], + isTBL = GeneralLib.checkTBLGrouping() !== null; + + if (isTBL) { + // TBL has more sophisticated ways of syncing questions + return []; + } // list all tool activities $.each(layout.activities, function() { Index: lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/ILearningDesignService.java =================================================================== diff -u -r72d525026e011d4ac75686e07377a4242c3eb09f -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/ILearningDesignService.java (.../ILearningDesignService.java) (revision 72d525026e011d4ac75686e07377a4242c3eb09f) +++ lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/ILearningDesignService.java (.../ILearningDesignService.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -107,4 +107,16 @@ */ String getUniqueNameForLearningDesign(String originalTitle, Integer workspaceFolderId); + /** + * If learning design contains the following activities Grouping->(MCQ or Assessment)->Leader Selection->Scratchie + * (potentially with some other gates or activities in the middle), there is a good chance this is a TBL sequence + * and all activities must be grouped. + */ + boolean isTBLSequence(long learningDesignId); + + /** + * Checks if it is a TBL sequence. If so and given tool content ID is a iRAT or tRAT activity, + * it returns tool content ID of a matching tRAT / iRAT activity. + */ + Long findMatchingRatActivity(long toolContentId); } Index: lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/LearningDesignService.java =================================================================== diff -u -r72d525026e011d4ac75686e07377a4242c3eb09f -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/LearningDesignService.java (.../LearningDesignService.java) (revision 72d525026e011d4ac75686e07377a4242c3eb09f) +++ lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/LearningDesignService.java (.../LearningDesignService.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -38,10 +38,12 @@ import org.apache.log4j.Logger; import org.lamsfoundation.lams.learningdesign.Activity; import org.lamsfoundation.lams.learningdesign.ComplexActivity; +import org.lamsfoundation.lams.learningdesign.GroupingActivity; import org.lamsfoundation.lams.learningdesign.LearningDesign; import org.lamsfoundation.lams.learningdesign.LearningLibrary; import org.lamsfoundation.lams.learningdesign.ParallelActivity; import org.lamsfoundation.lams.learningdesign.ToolActivity; +import org.lamsfoundation.lams.learningdesign.Transition; import org.lamsfoundation.lams.learningdesign.dao.IActivityDAO; import org.lamsfoundation.lams.learningdesign.dao.IGroupingDAO; import org.lamsfoundation.lams.learningdesign.dao.ILearningDesignDAO; @@ -56,6 +58,7 @@ import org.lamsfoundation.lams.tool.dto.ToolDTO; import org.lamsfoundation.lams.tool.dto.ToolDTONameComparator; import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.util.CommonConstants; import org.lamsfoundation.lams.util.FileUtil; import org.lamsfoundation.lams.util.ILoadedMessageSourceService; import org.lamsfoundation.lams.util.MessageService; @@ -498,4 +501,137 @@ return newName; } + /** + * If learning design contains the following activities Grouping->(MCQ or Assessment)->Leader Selection->Scratchie + * (potentially with some other gates or activities in the middle), there is a good chance this is a TBL sequence + * and all activities must be grouped. + */ + @Override + public boolean isTBLSequence(long learningDesignId) { + LearningDesign learningDesign = getLearningDesign(learningDesignId); + Long firstActivityId = learningDesign.getFirstActivity().getActivityId(); + // Hibernate CGLIB is failing to load the first activity in the sequence as a ToolActivity + Activity firstActivity = activityDAO.getActivityByActivityId(firstActivityId); + + return verifyNextActivityFitsTbl(firstActivity, "Grouping", null, null) != null; + } + + /** + * Checks if it is a TBL sequence. If so and given tool content ID is a iRAT or tRAT activity, + * it returns tool content ID of a matching tRAT / iRAT activity. + */ + @Override + public Long findMatchingRatActivity(long toolContentId) { + ToolActivity ratActivity = activityDAO.getToolActivityByToolContentId(toolContentId); + Long iRatToolContentId = null; + Long tRatToolContentId = null; + + if (CommonConstants.TOOL_SIGNATURE_SCRATCHIE.equals(ratActivity.getTool().getToolSignature())) { + tRatToolContentId = toolContentId; + } else if (CommonConstants.TOOL_SIGNATURE_ASSESSMENT.equals(ratActivity.getTool().getToolSignature()) + || CommonConstants.TOOL_SIGNATURE_MCQ.equals(ratActivity.getTool().getToolSignature())) { + iRatToolContentId = toolContentId; + } + + if (iRatToolContentId == null && tRatToolContentId == null) { + return null; + } + + LearningDesign learningDesign = ratActivity.getLearningDesign(); + Long firstActivityId = learningDesign.getFirstActivity().getActivityId(); + // Hibernate CGLIB is failing to load the first activity in the sequence as a ToolActivity + Activity firstActivity = activityDAO.getActivityByActivityId(firstActivityId); + + return verifyNextActivityFitsTbl(firstActivity, "Grouping", iRatToolContentId, tRatToolContentId); + } + + /** + * Traverses the learning design verifying it follows typical TBL structure. + * Also returns matching iRAT / tRAT activity to the provided iRatToolContentId or tRatToolContentId. + * + * @param activity + * @param anticipatedActivity + * could be either "Grouping", "MCQ or Assessment", "Leaderselection" or "Scratchie" + */ + private Long verifyNextActivityFitsTbl(Activity activity, String anticipatedActivity, Long iRatToolContentId, + Long tRatToolContentId) { + + Transition transitionFromActivity = activity.getTransitionFrom(); + //TBL can finish with the Scratchie + if (transitionFromActivity == null && !"Scratchie".equals(anticipatedActivity)) { + return null; + } + // query activity from DB as transition holds only proxied activity object + Long nextActivityId = transitionFromActivity == null ? null + : transitionFromActivity.getToActivity().getActivityId(); + Activity nextActivity = nextActivityId == null ? null : activityDAO.getActivityByActivityId(nextActivityId); + + switch (anticipatedActivity) { + case "Grouping": + //the first activity should be a grouping + if (activity instanceof GroupingActivity) { + return verifyNextActivityFitsTbl(nextActivity, "MCQ or Assessment", iRatToolContentId, + tRatToolContentId); + + } else { + return verifyNextActivityFitsTbl(nextActivity, "Grouping", iRatToolContentId, tRatToolContentId); + } + + case "MCQ or Assessment": + //the second activity shall be a MCQ or Assessment + if (activity.isToolActivity() && (CommonConstants.TOOL_SIGNATURE_ASSESSMENT + .equals(((ToolActivity) activity).getTool().getToolSignature()) + || CommonConstants.TOOL_SIGNATURE_MCQ + .equals(((ToolActivity) activity).getTool().getToolSignature()))) { + if (iRatToolContentId == null) { + iRatToolContentId = ((ToolActivity) activity).getToolContentId(); + } + return verifyNextActivityFitsTbl(nextActivity, "Leaderselection", iRatToolContentId, + tRatToolContentId); + + } else { + return verifyNextActivityFitsTbl(nextActivity, "MCQ or Assessment", iRatToolContentId, + tRatToolContentId); + } + + case "Leaderselection": + //the third activity shall be a Leader Selection + if (activity.isToolActivity() && CommonConstants.TOOL_SIGNATURE_LEADERSELECTION + .equals(((ToolActivity) activity).getTool().getToolSignature())) { + return verifyNextActivityFitsTbl(nextActivity, "Scratchie", iRatToolContentId, tRatToolContentId); + + } else { + return verifyNextActivityFitsTbl(nextActivity, "Leaderselection", iRatToolContentId, + tRatToolContentId); + } + + case "Scratchie": + //the fourth activity shall be Scratchie + if (activity.isToolActivity() && CommonConstants.TOOL_SIGNATURE_SCRATCHIE + .equals(((ToolActivity) activity).getTool().getToolSignature())) { + // if at this point iRAT content ID is null, it means that it was not found + // and it is not a TBL sequence + if (iRatToolContentId == null) { + return null; + } + // if tRAT content ID is null it means that iRAT content ID was provided as a parameter + // and we are looking for tRAT content ID, i.e. this activity + if (tRatToolContentId == null) { + tRatToolContentId = ((ToolActivity) activity).getToolContentId(); + return tRatToolContentId; + } + // if tRAT content ID was not null it means that we are looking for iRAT content ID + return iRatToolContentId; + + } else if (nextActivity == null) { + return null; + + } else { + return verifyNextActivityFitsTbl(nextActivity, "Scratchie", iRatToolContentId, tRatToolContentId); + } + + default: + return null; + } + } } \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/tool/service/ILamsToolService.java =================================================================== diff -u -r3085af1c7e3e6c3496af23a748d998886d7168fd -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_common/src/java/org/lamsfoundation/lams/tool/service/ILamsToolService.java (.../ILamsToolService.java) (revision 3085af1c7e3e6c3496af23a748d998886d7168fd) +++ lams_common/src/java/org/lamsfoundation/lams/tool/service/ILamsToolService.java (.../ILamsToolService.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -248,4 +248,9 @@ * Get a count of all the users that would be returned by getUsersForActivity(Long toolSessionId); */ Integer getCountUsersForActivity(Long toolSessionId); + + /** + * Updates TBL iRAT/tRAT activity with questions from matching tRAT/iRAT activity + */ + boolean syncRatQuestions(long toolContentId, List newQuestionUids); } \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/tool/service/IQbToolService.java =================================================================== diff -u -rc82d171dbdd8918839e396f2851d6e11b68bb9e8 -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_common/src/java/org/lamsfoundation/lams/tool/service/IQbToolService.java (.../IQbToolService.java) (revision c82d171dbdd8918839e396f2851d6e11b68bb9e8) +++ lams_common/src/java/org/lamsfoundation/lams/tool/service/IQbToolService.java (.../IQbToolService.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -22,4 +22,9 @@ * Replaces existing question in an activity with one provided as a parameter. */ void replaceQuestion(long toolContentId, long oldQbQuestionUid, long newQbQuestionUid); + + /** + * Updates TBL iRAT/tRAT activity with questions from matching tRAT/iRAT activity + */ + boolean syncRatQuestions(long toolContentId, List newQuestionUids); } \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/tool/service/LamsToolService.java =================================================================== diff -u -rb074034d65483c22c90989799ad534947078bd98 -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_common/src/java/org/lamsfoundation/lams/tool/service/LamsToolService.java (.../LamsToolService.java) (revision b074034d65483c22c90989799ad534947078bd98) +++ lams_common/src/java/org/lamsfoundation/lams/tool/service/LamsToolService.java (.../LamsToolService.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -51,6 +51,7 @@ import org.lamsfoundation.lams.learningdesign.dao.IActivityDAO; import org.lamsfoundation.lams.learningdesign.dao.IDataFlowDAO; import org.lamsfoundation.lams.learningdesign.dto.ActivityPositionDTO; +import org.lamsfoundation.lams.learningdesign.service.ILearningDesignService; import org.lamsfoundation.lams.lesson.CompletedActivityProgress; import org.lamsfoundation.lams.lesson.LearnerProgress; import org.lamsfoundation.lams.lesson.service.ILessonService; @@ -93,6 +94,7 @@ private ILamsCoreToolService lamsCoreToolService; private ILessonService lessonService; private ILearnerService learnerService; + private ILearningDesignService learningDesignService; private IUserManagementService userManagementService; private IDataFlowDAO dataFlowDAO; @@ -579,6 +581,22 @@ return session.getLearners().size(); } + /** + * Updates TBL iRAT/tRAT activity with questions from matching tRAT/iRAT activity + */ + @Override + public boolean syncRatQuestions(long toolContentId, List newQuestionUids) { + Long matchingRATActivityId = learningDesignService.findMatchingRatActivity(toolContentId); + if (matchingRATActivityId == null) { + return false; + } + + ToolActivity ratActivity = activityDAO.getToolActivityByToolContentId(matchingRATActivityId); + Tool tool = ratActivity.getTool(); + IQbToolService qbToolService = (IQbToolService) lamsCoreToolService.findToolService(tool); + return qbToolService.syncRatQuestions(matchingRATActivityId, newQuestionUids); + } + // --------------------------------------------------------------------- // Inversion of Control Methods - Method injection // --------------------------------------------------------------------- @@ -629,6 +647,10 @@ this.learnerService = learnerService; } + public void setLearningDesignService(ILearningDesignService learningDesignService) { + this.learningDesignService = learningDesignService; + } + /** * @param userService * User Management Service Index: lams_common/src/java/org/lamsfoundation/lams/toolApplicationContext.xml =================================================================== diff -u -r713a4d2055ba7766354709db2ae65dacfb5d2b1a -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_common/src/java/org/lamsfoundation/lams/toolApplicationContext.xml (.../toolApplicationContext.xml) (revision 713a4d2055ba7766354709db2ae65dacfb5d2b1a) +++ lams_common/src/java/org/lamsfoundation/lams/toolApplicationContext.xml (.../toolApplicationContext.xml) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -92,6 +92,7 @@ + Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java =================================================================== diff -u -r0630ee5b816a637cb7f3f2a9b91173569d13262d -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java (.../MonitoringController.java) (revision 0630ee5b816a637cb7f3f2a9b91173569d13262d) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java (.../MonitoringController.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -59,13 +59,13 @@ import org.lamsfoundation.lams.learningdesign.ContributionTypes; import org.lamsfoundation.lams.learningdesign.GateActivity; import org.lamsfoundation.lams.learningdesign.Group; -import org.lamsfoundation.lams.learningdesign.GroupingActivity; import org.lamsfoundation.lams.learningdesign.LearningDesign; import org.lamsfoundation.lams.learningdesign.OptionsWithSequencesActivity; import org.lamsfoundation.lams.learningdesign.SequenceActivity; import org.lamsfoundation.lams.learningdesign.ToolActivity; import org.lamsfoundation.lams.learningdesign.Transition; import org.lamsfoundation.lams.learningdesign.exception.LearningDesignException; +import org.lamsfoundation.lams.learningdesign.service.ILearningDesignService; import org.lamsfoundation.lams.lesson.LearnerProgress; import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.lesson.dto.LessonDetailsDTO; @@ -126,6 +126,8 @@ @Autowired private ILogEventService logEventService; @Autowired + private ILearningDesignService learningDesignService; + @Autowired private ILessonService lessonService; @Autowired private ISecurityService securityService; @@ -968,95 +970,12 @@ && userManagementService.isUserInRole(user.getUserID(), organisation.getOrganisationId(), Role.AUTHOR); request.setAttribute("enableLiveEdit", enableLiveEdit); request.setAttribute("lesson", lessonDTO); - request.setAttribute("isTBLSequence", isTBLSequence(lessonId)); + request.setAttribute("isTBLSequence", learningDesignService.isTBLSequence(lessonDTO.getLearningDesignID())); return "monitor"; } /** - * If learning design contains the following activities Grouping->(MCQ or Assessment)->Leader Selection->Scratchie - * (potentially with some other gates or activities in the middle), there is a good chance this is a TBL sequence - * and all activities must be grouped. - */ - private boolean isTBLSequence(Long lessonId) { - Lesson lesson = lessonService.getLesson(lessonId); - Long firstActivityId = lesson.getLearningDesign().getFirstActivity().getActivityId(); - //Hibernate CGLIB is failing to load the first activity in the sequence as a ToolActivity - Activity firstActivity = monitoringService.getActivityById(firstActivityId); - - return verifyNextActivityFitsTbl(firstActivity, "Grouping"); - } - - /** - * Traverses the learning design verifying it follows typical TBL structure - * - * @param activity - * @param anticipatedActivity - * could be either "Grouping", "MCQ or Assessment", "Leaderselection" or "Scratchie" - */ - private boolean verifyNextActivityFitsTbl(Activity activity, String anticipatedActivity) { - - Transition transitionFromActivity = activity.getTransitionFrom(); - //TBL can finish with the Scratchie - if (transitionFromActivity == null && !"Scratchie".equals(anticipatedActivity)) { - return false; - } - // query activity from DB as transition holds only proxied activity object - Long nextActivityId = transitionFromActivity == null ? null - : transitionFromActivity.getToActivity().getActivityId(); - Activity nextActivity = nextActivityId == null ? null : monitoringService.getActivityById(nextActivityId); - - switch (anticipatedActivity) { - case "Grouping": - //the first activity should be a grouping - if (activity instanceof GroupingActivity) { - return verifyNextActivityFitsTbl(nextActivity, "MCQ or Assessment"); - - } else { - return verifyNextActivityFitsTbl(nextActivity, "Grouping"); - } - - case "MCQ or Assessment": - //the second activity shall be a MCQ or Assessment - if (activity.isToolActivity() && (CommonConstants.TOOL_SIGNATURE_ASSESSMENT - .equals(((ToolActivity) activity).getTool().getToolSignature()) - || CommonConstants.TOOL_SIGNATURE_MCQ - .equals(((ToolActivity) activity).getTool().getToolSignature()))) { - return verifyNextActivityFitsTbl(nextActivity, "Leaderselection"); - - } else { - return verifyNextActivityFitsTbl(nextActivity, "MCQ or Assessment"); - } - - case "Leaderselection": - //the third activity shall be a Leader Selection - if (activity.isToolActivity() && CommonConstants.TOOL_SIGNATURE_LEADERSELECTION - .equals(((ToolActivity) activity).getTool().getToolSignature())) { - return verifyNextActivityFitsTbl(nextActivity, "Scratchie"); - - } else { - return verifyNextActivityFitsTbl(nextActivity, "Leaderselection"); - } - - case "Scratchie": - //the fourth activity shall be Scratchie - if (activity.isToolActivity() && CommonConstants.TOOL_SIGNATURE_SCRATCHIE - .equals(((ToolActivity) activity).getTool().getToolSignature())) { - return true; - - } else if (nextActivity == null) { - return false; - - } else { - return verifyNextActivityFitsTbl(nextActivity, "Scratchie"); - } - - default: - return false; - } - } - - /** * Gets users whose progress bars will be displayed in Learner tab in Monitor. */ @RequestMapping("/getLearnerProgressPage") Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -r0e096dc12d51aa84a925f05454e6dd973c99d94f -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 0e096dc12d51aa84a925f05454e6dd973c99d94f) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -1440,7 +1440,7 @@ if (StringUtils.isBlank(answer)) { continue; } - + Set notAllocatedAnswers = new HashSet<>(); boolean isAnswerAllocated = QbUtils.isVSAnswerAllocated(qbQuestion, answer, notAllocatedAnswers); if (!isAnswerAllocated) { @@ -2791,7 +2791,99 @@ return result; } + /** + * Updates updates this iRAT activity with tRAT questions. + */ @Override + public boolean syncRatQuestions(long toolContentId, List newQuestionUids) { + Assessment assessment = getAssessmentByContentId(toolContentId); + + List existingReferences = new ArrayList<>(assessment.getQuestionReferences()); + List newReferences = new ArrayList<>(); + Set referencesToRemove = new HashSet<>(existingReferences); + List newAssessmentQuestions = new ArrayList<>(); + + int displayOrder = 0; + boolean syncNeeded = false; + for (Long newQuestionUid : newQuestionUids) { + QbQuestion newQuestion = qbService.getQuestionByUid(newQuestionUid); + displayOrder++; + + QuestionReference matchingReference = null; + for (QuestionReference existingReference : existingReferences) { + // try to find exactly same question + if (newQuestion.getUid().equals(existingReference.getQuestion().getQbQuestion().getUid())) { + matchingReference = existingReference; + syncNeeded |= displayOrder != existingReference.getSequenceId(); + break; + } + } + + if (matchingReference == null) { + syncNeeded = true; + for (QuestionReference existingReference : existingReferences) { + // try to find same question with another version + if (newQuestion.getQuestionId() + .equals(existingReference.getQuestion().getQbQuestion().getQuestionId())) { + existingReference.getQuestion().setQbQuestion(newQuestion); + matchingReference = existingReference; + break; + } + } + } + if (matchingReference == null) { + // build question reference from scratch + AssessmentQuestion assessmentQuestion = new AssessmentQuestion(); + assessmentQuestion.setDisplayOrder(displayOrder); + assessmentQuestion.setAnswerRequired(true); + assessmentQuestion.setQbQuestion(newQuestion); + assessmentQuestion.setToolContentId(toolContentId); + assessmentQuestionDao.insert(assessmentQuestion); + + matchingReference = new QuestionReference(); + matchingReference.setQuestion(assessmentQuestion); + matchingReference.setSequenceId(displayOrder); + matchingReference.setMaxMark(1); + assessmentQuestionDao.insert(matchingReference); + } else { + matchingReference.setSequenceId(displayOrder); + matchingReference.getQuestion().setDisplayOrder(displayOrder); + referencesToRemove.remove(matchingReference); + } + + newReferences.add(matchingReference); + newAssessmentQuestions.add(matchingReference.getQuestion()); + } + + // all this collections clearing is for Hibernate to feel safe + existingReferences.clear(); + syncNeeded |= !referencesToRemove.isEmpty(); + + if (!syncNeeded) { + return false; + } + + assessment.getQuestionReferences().clear(); + assessment.getQuestions().clear(); + + for (QuestionReference referenceToRemove : referencesToRemove) { + // remove question removed from the matching RAT activity + assessmentQuestionDao.delete(referenceToRemove.getQuestion()); + assessmentQuestionDao.delete(referenceToRemove); + } + referencesToRemove.clear(); + + assessment.getQuestions().addAll(newAssessmentQuestions); + assessment.getQuestionReferences().addAll(newReferences); + newAssessmentQuestions.clear(); + newReferences.clear(); + + assessmentDao.update(assessment); + + return true; + } + + @Override public void replaceQuestion(long toolContentId, long oldQbQuestionUid, long newQbQuestionUid) { Assessment assessment = getAssessmentByContentId(toolContentId); QbQuestion newQbQuestion = null; Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java =================================================================== diff -u -rb26d70cd134d5030a7baae04351d2ccc61862d6f -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java (.../AuthoringController.java) (revision b26d70cd134d5030a7baae04351d2ccc61862d6f) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java (.../AuthoringController.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -41,6 +41,7 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -66,6 +67,7 @@ import org.lamsfoundation.lams.tool.assessment.service.IAssessmentService; import org.lamsfoundation.lams.tool.assessment.util.SequencableComparator; import org.lamsfoundation.lams.tool.assessment.web.form.AssessmentForm; +import org.lamsfoundation.lams.tool.service.ILamsToolService; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; import org.lamsfoundation.lams.util.CommonConstants; import org.lamsfoundation.lams.util.Configuration; @@ -104,6 +106,8 @@ private IAssessmentService service; @Autowired private IQbService qbService; + @Autowired + private ILamsToolService lamsToolService; /** * Read assessment data from database and put them into HttpSession. It will redirect to init.do directly after this @@ -350,6 +354,10 @@ } } + List newQuestionUids = assessmentPO.getQuestionReferences().stream() + .collect(Collectors.mapping(q -> q.getQuestion().getQbQuestion().getUid(), Collectors.toList())); + lamsToolService.syncRatQuestions(assessmentPO.getContentId(), newQuestionUids); + request.setAttribute(CommonConstants.LAMS_AUTHORING_SUCCESS_FLAG, Boolean.TRUE); request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMap.getSessionID()); return "pages/authoring/authoring"; Index: lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/service/McService.java =================================================================== diff -u -re581d656fb9eae279adb11f28b8419fd49cd05c2 -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/service/McService.java (.../McService.java) (revision e581d656fb9eae279adb11f28b8419fd49cd05c2) +++ lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/service/McService.java (.../McService.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -2241,6 +2241,9 @@ : IQbService.QUESTION_MODIFIED_NONE; } + /** + * Updates updates this iRAT activity with tRAT questions. + */ @Override public List replaceQuestions(long toolContentId, String newActivityName, List newQuestions) { @@ -2270,6 +2273,82 @@ } @Override + public boolean syncRatQuestions(long toolContentId, List newQuestionUids) { + McContent mcContent = getMcContent(toolContentId); + + List existingReferences = new ArrayList<>(mcContent.getMcQueContents()); + List newReferences = new ArrayList<>(); + Set referencesToRemove = new HashSet<>(existingReferences); + + int displayOrder = 0; + boolean syncNeeded = false; + for (Long newQuestionUid : newQuestionUids) { + QbQuestion newQuestion = qbService.getQuestionByUid(newQuestionUid); + displayOrder++; + + McQueContent matchingReference = null; + for (McQueContent existingReference : existingReferences) { + // try to find exactly same question + if (newQuestion.getUid().equals(existingReference.getQbQuestion().getUid())) { + matchingReference = existingReference; + syncNeeded |= displayOrder != existingReference.getDisplayOrder(); + break; + } + } + + if (matchingReference == null) { + syncNeeded = true; + for (McQueContent existingReference : existingReferences) { + // try to find same question with another version + if (newQuestion.getQuestionId().equals(existingReference.getQbQuestion().getQuestionId())) { + existingReference.setQbQuestion(newQuestion); + matchingReference = existingReference; + break; + } + } + } + + if (matchingReference == null) { + // build question reference from scratch + matchingReference = new McQueContent(); + matchingReference.setDisplayOrder(displayOrder); + matchingReference.setAnswerRequired(true); + matchingReference.setQbQuestion(newQuestion); + matchingReference.setToolContentId(toolContentId); + mcQueContentDAO.saveOrUpdateMcQueContent(matchingReference); + } else { + matchingReference.setDisplayOrder(displayOrder); + referencesToRemove.remove(matchingReference); + } + + newReferences.add(matchingReference); + } + + // all this collections clearing is for Hibernate to feel safe + existingReferences.clear(); + syncNeeded |= !referencesToRemove.isEmpty(); + + if (!syncNeeded) { + return false; + } + + mcContent.getMcQueContents().clear(); + + for (McQueContent referenceToRemove : referencesToRemove) { + // remove question removed from the matching RAT activity + mcQueContentDAO.removeMcQueContent(referenceToRemove); + } + referencesToRemove.clear(); + + mcContent.getMcQueContents().addAll(newReferences); + newReferences.clear(); + + mcContentDAO.saveOrUpdateMc(mcContent); + + return true; + } + + @Override public void replaceQuestion(long toolContentId, long oldQbQuestionUid, long newQbQuestionUid) { throw new UnsupportedOperationException("MCQ tool does not support single question replacement yet"); } Index: lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/AuthoringController.java =================================================================== diff -u -rc6f5e5e1aeb31463657f9fe739fc889d2d7e196d -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/AuthoringController.java (.../AuthoringController.java) (revision c6f5e5e1aeb31463657f9fe739fc889d2d7e196d) +++ lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/web/controller/AuthoringController.java (.../AuthoringController.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -29,6 +29,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -52,6 +53,7 @@ import org.lamsfoundation.lams.tool.mc.service.IMcService; import org.lamsfoundation.lams.tool.mc.util.AuthoringUtil; import org.lamsfoundation.lams.tool.mc.web.form.McAuthoringForm; +import org.lamsfoundation.lams.tool.service.ILamsToolService; import org.lamsfoundation.lams.util.CommonConstants; import org.lamsfoundation.lams.util.FileUtil; import org.lamsfoundation.lams.util.MessageService; @@ -90,6 +92,9 @@ private IQbService qbService; @Autowired + private ILamsToolService lamsToolService; + + @Autowired @Qualifier("lamcMessageService") private MessageService messageService; @@ -242,6 +247,11 @@ mcService.saveOrUpdateMcQueContent(existingQuestion); displayOrder++; } + + List newQuestionUids = sortedQuestions.stream() + .collect(Collectors.mapping(q -> q.getQbQuestion().getUid(), Collectors.toList())); + lamsToolService.syncRatQuestions(toolContentID, newQuestionUids); + } request.setAttribute(CommonConstants.LAMS_AUTHORING_SUCCESS_FLAG, Boolean.TRUE); @@ -837,10 +847,10 @@ question.setTitle(mcQuestion.getName()); question.setText(mcQuestion.getDescription()); question.setFeedback(mcQuestion.getFeedback()); - + QbQuestion qbQuestion = qbService.getQuestionByUid(mcQuestion.getQbQuestionUid()); question.setLabel(QuestionParser.UUID_LABEL_PREFIX + qbQuestion.getUuid()); - + List answers = new ArrayList<>(); for (McOptionDTO mcAnswer : mcQuestion.getOptionDtos()) { Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java =================================================================== diff -u -reebd98dce318486b1b04b5d683404622517af1e8 -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision eebd98dce318486b1b04b5d683404622517af1e8) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -2360,7 +2360,92 @@ return result; } + /** + * Updates this tRAT activity with iRAT questions. + */ @Override + public boolean syncRatQuestions(long toolContentId, List newQuestionUids) { + Scratchie scratchie = getScratchieByContentId(toolContentId); + + List existingItems = new ArrayList<>(scratchie.getScratchieItems()); + List newItems = new ArrayList<>(); + Set itemsToRemove = new HashSet<>(existingItems); + + int displayOrder = 0; + boolean syncNeeded = false; + for (Long newQuestionUid : newQuestionUids) { + QbQuestion newQuestion = qbService.getQuestionByUid(newQuestionUid); + + if (!(newQuestion.getType().equals(QbQuestion.TYPE_MULTIPLE_CHOICE) + || newQuestion.getType().equals(QbQuestion.TYPE_MARK_HEDGING) + || newQuestion.getType().equals(QbQuestion.TYPE_VERY_SHORT_ANSWERS))) { + log.warn("QB question with UID " + newQuestionUid + " is not supported by TBL tRAT, skipping"); + continue; + } + + displayOrder++; + ScratchieItem matchingItem = null; + for (ScratchieItem existingItem : existingItems) { + // try to find exactly same question + if (newQuestion.getUid().equals(existingItem.getQbQuestion().getUid())) { + matchingItem = existingItem; + syncNeeded |= displayOrder != existingItem.getDisplayOrder(); + break; + } + } + + if (matchingItem == null) { + syncNeeded = true; + for (ScratchieItem existingItem : existingItems) { + // try to find same question with another version + if (newQuestion.getQuestionId().equals(existingItem.getQbQuestion().getQuestionId())) { + existingItem.setQbQuestion(newQuestion); + matchingItem = existingItem; + break; + } + } + } + + if (matchingItem == null) { + matchingItem = new ScratchieItem(); + matchingItem.setDisplayOrder(displayOrder); + matchingItem.setAnswerRequired(true); + matchingItem.setQbQuestion(newQuestion); + matchingItem.setToolContentId(toolContentId); + scratchieItemDao.insert(matchingItem); + } else { + matchingItem.setDisplayOrder(displayOrder); + itemsToRemove.remove(matchingItem); + } + + newItems.add(matchingItem); + } + + // all this collections clearing is for Hibernate to feel safe + existingItems.clear(); + syncNeeded |= !itemsToRemove.isEmpty(); + + if (!syncNeeded) { + return false; + } + + scratchie.getScratchieItems().clear(); + + for (ScratchieItem itemToRemove : itemsToRemove) { + // remove question removed from the matching RAT activity + scratchieItemDao.delete(itemToRemove); + } + itemsToRemove.clear(); + + scratchie.getScratchieItems().addAll(newItems); + newItems.clear(); + + scratchieDao.update(scratchie); + + return true; + } + + @Override public void replaceQuestion(long toolContentId, long oldQbQuestionUid, long newQbQuestionUid) { Scratchie scratchie = getScratchieByContentId(toolContentId); QbQuestion newQbQuestion = null; Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/controller/AuthoringController.java =================================================================== diff -u -red8e4fb944665c1f48832c59b95944c5b5875a78 -rf6247c953cc7bf9a17d4025ea8e8728ae55c11ed --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/controller/AuthoringController.java (.../AuthoringController.java) (revision ed8e4fb944665c1f48832c59b95944c5b5875a78) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/controller/AuthoringController.java (.../AuthoringController.java) (revision f6247c953cc7bf9a17d4025ea8e8728ae55c11ed) @@ -39,6 +39,7 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.UUID; +import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -63,6 +64,7 @@ import org.lamsfoundation.lams.tool.scratchie.service.IScratchieService; import org.lamsfoundation.lams.tool.scratchie.util.ScratchieItemComparator; import org.lamsfoundation.lams.tool.scratchie.web.form.ScratchieForm; +import org.lamsfoundation.lams.tool.service.ILamsToolService; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; import org.lamsfoundation.lams.util.CommonConstants; import org.lamsfoundation.lams.util.Configuration; @@ -101,6 +103,8 @@ @Autowired private IQbService qbService; @Autowired + private ILamsToolService lamsToolService; + @Autowired private IUserManagementService userManagementService; @Autowired @Qualifier("scratchieMessageService") @@ -310,6 +314,10 @@ } } + List newQuestionUids = scratchiePO.getScratchieItems().stream() + .collect(Collectors.mapping(q -> q.getQbQuestion().getUid(), Collectors.toList())); + lamsToolService.syncRatQuestions(scratchiePO.getContentId(), newQuestionUids); + request.setAttribute(CommonConstants.LAMS_AUTHORING_SUCCESS_FLAG, Boolean.TRUE); request.setAttribute(AttributeNames.ATTR_MODE, mode.toString());