Index: lams_tool_assessment/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r8e938309ccf4eade5bc117b91ecd37a542cc3c13 -rb2a6839475729da885747e41082b4cb9c24e9359 --- lams_tool_assessment/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 8e938309ccf4eade5bc117b91ecd37a542cc3c13) +++ lams_tool_assessment/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision b2a6839475729da885747e41082b4cb9c24e9359) @@ -199,6 +199,7 @@ label.export.summary.by.user = Summary by learner lable.export.summary.by.question = Summary by question label.export.user.id = User name +label.export.section.mark = Marks per section label.authoring.basic.allow.learners.rich.editor = Allow learners to use rich text editor label.authoring.advance.allow.students.right.answers = Indicate choice(s) that have been answered correctly. label.authoring.advance.allow.students.wrong.answers = Indicate choice(s) that have been answered incorrectly. Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -r330b0d5f8fa4a8deb6d7454f92bdbd289e89cb82 -rb2a6839475729da885747e41082b4cb9c24e9359 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 330b0d5f8fa4a8deb6d7454f92bdbd289e89cb82) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision b2a6839475729da885747e41082b4cb9c24e9359) @@ -863,7 +863,7 @@ .append(" \"").append(assessmentResult.getUser().getLoginName()).append("\" for question ") .append(questionDto.getUid()).append(" for question result ").append(questionResult.getUid()) .append(" answer was \"").append(questionResult.getAnswer()) - .append("\" and it is now blank, skipping save."); + .append("\" and it is now blank, skipping save to DB."); log.warn(autosaveLogBuilder); if (logAutosave.isTraceEnabled()) { logAutosave.trace(autosaveLogBuilder); @@ -2111,11 +2111,24 @@ if (sessionDtos != null && assessment.getQuestionReferences() != null) { // if there are multiple session, then the activity has to be grouped boolean isActivityGrouped = sessionDtos.size() > 1; + int questionLeftPadding = isActivityGrouped ? 3 : 2; + List sections = + assessment.getQuestionsPerPage() == -1 ? new ArrayList<>(assessment.getSections()) : null; + ExcelRow sectionRow = null; + if (sections != null) { + sectionRow = userSummarySheet.initRow(); + sectionRow.addEmptyCells(questionLeftPadding); + sectionRow.addCell(getMessage("label.authoring.advance.sections"), false, + ExcelCell.BORDER_STYLE_LEFT_THIN); + sectionRow = userSummarySheet.initRow(); + sectionRow.addEmptyCells(questionLeftPadding); + } + // Row with just "Questions" header ExcelRow userSummaryTitle = userSummarySheet.initRow(); // if there is no grouping, then we skip "Group" column - int questionLeftPadding = isActivityGrouped ? 3 : 2; + userSummaryTitle.addEmptyCells(questionLeftPadding); userSummaryTitle.addCell(getMessage("label.export.questions"), true, ExcelCell.BORDER_STYLE_LEFT_THIN); @@ -2127,8 +2140,37 @@ questionReferences.addAll(assessment.getQuestionReferences()); int questionCounter = 1; + int sectionCounter = 0; + AssessmentSection currentSection = sections == null ? null : sections.get(sectionCounter); + int sectionQuestionCounter = 0; + Set sectionBreaks = new HashSet<>(); + // print out all question titles for (QuestionReference questionReference : questionReferences) { + if (sections != null) { + int currentSectionQuestionCount = currentSection.getQuestionCount(); + if (currentSection != null && currentSectionQuestionCount != 0 + && questionCounter - sectionQuestionCounter > currentSectionQuestionCount) { + sectionBreaks.add(questionCounter - 1); + + sectionRow.addEmptyCell(); + questionTitlesRow.addEmptyCell(); + questionLeftPadding++; + + sectionQuestionCounter += currentSectionQuestionCount; + sectionCounter++; + currentSection = sectionCounter < sections.size() ? sections.get(sectionCounter) : null; + } + if (currentSection != null) { + String sectionName = currentSection.getName(); + if (StringUtils.isBlank(sectionName)) { + sectionName = getMessage("label.learning.section.default.name", + new Object[] { sectionCounter + 1 }); + } + sectionRow.addCell(sectionName, false, ExcelCell.BORDER_STYLE_LEFT_THIN); + } + } + AssessmentQuestion question = questionReference.getQuestion(); String title = question.getQbQuestion().getName(); // leave pure text of title @@ -2158,7 +2200,13 @@ columnShift++; } questionTitlesRow.addEmptyCells(columnShift); + if (sections != null) { + sectionRow.addEmptyCells(columnShift); + } userSummarySheet.addMergedCells(5, questionLeftPadding, questionLeftPadding + columnShift); + if (sections != null) { + userSummarySheet.addMergedCells(7, questionLeftPadding, questionLeftPadding + columnShift); + } questionLeftPadding += columnShift + 1; } @@ -2172,6 +2220,7 @@ userSummaryUserHeadersRow.addCell(getMessage("label.export.user.id"), true); userSummaryUserHeadersRow.addCell(getMessage("label.monitoring.user.summary.full.name"), true); + questionCounter = 1; for (QuestionReference questionReference : questionReferences) { userSummaryUserHeadersRow.addCell(getMessage("label.export.mark"), ExcelCell.BORDER_STYLE_LEFT_THIN); userSummaryUserHeadersRow.addCell(getMessage("label.authoring.basic.option.answer")); @@ -2190,6 +2239,11 @@ userSummaryUserHeadersRow.addCell(getMessage("label.confidence")); } + if (sections != null && (questionCounter == questionReferences.size() || sectionBreaks.contains( + questionCounter))) { + userSummaryUserHeadersRow.addCell(getMessage("label.export.section.mark"), true); + } + questionCounter++; } // a single column at the end of previous headers @@ -2229,12 +2283,15 @@ Map learnerInteractions = learnerInteractionService.getFirstLearnerInteractions( assessment.getContentId(), assessmentUser.getUserId().intValue()); + questionCounter = 1; + float sectionMark = 0; // follow question reference ordering, to QbToolQuestion's for (QuestionReference questionReference : questionReferences) { AssessmentQuestionResult questionResult = questionResultsMap.get( questionReference.getQuestion().getUid()); // mark userResultRow.addCell(questionResult.getMark(), ExcelCell.BORDER_STYLE_LEFT_THIN); + sectionMark += questionResult.getMark(); // option chosen or full answer AssessmentExcelCell assessmentCell = AssessmentEscapeUtils.addResponseCellForExcelExport( @@ -2287,6 +2344,12 @@ userResultRow.addCell(confidenceLevel); } + if (sections != null && (questionCounter == questionReferences.size() || sectionBreaks.contains( + questionCounter))) { + userResultRow.addCell(sectionMark); + sectionMark = 0; + } + questionCounter++; } userResultRow.addCell(assessmentResult.getGrade(), ExcelCell.BORDER_STYLE_LEFT_THIN); } @@ -2556,45 +2619,48 @@ for (AssessmentQuestion newQuestion : newQuestions) { if (oldQuestion.getDisplayOrder() == newQuestion.getDisplayOrder()) { - boolean isQuestionModified = false; + boolean isQuestionModified = !oldQuestion.getQbQuestion().getUid() + .equals(newQuestion.getQbQuestion().getUid()); + if (!isQuestionModified) { - // title or question is different - do nothing. Also question grade can't be changed + // title or question is different - do nothing. Also question grade can't be changed - //QbQuestion.TYPE_TRUE_FALSE - if (oldQuestion.getQbQuestion().getCorrectAnswer() != newQuestion.getQbQuestion() - .getCorrectAnswer()) { - isQuestionModified = true; - } + //QbQuestion.TYPE_TRUE_FALSE + if (oldQuestion.getQbQuestion().getCorrectAnswer() != newQuestion.getQbQuestion() + .getCorrectAnswer()) { + isQuestionModified = true; + } - // options are different - List oldOptions = oldQuestion.getQbQuestion().getQbOptions(); - List newOptions = newQuestion.getQbQuestion().getQbOptions(); - for (QbOption oldOption : oldOptions) { - for (QbOption newOption : newOptions) { - if (oldOption.getDisplayOrder() == newOption.getDisplayOrder()) { + // options are different + List oldOptions = oldQuestion.getQbQuestion().getQbOptions(); + List newOptions = newQuestion.getQbQuestion().getQbOptions(); + for (QbOption oldOption : oldOptions) { + for (QbOption newOption : newOptions) { + if (oldOption.getDisplayOrder() == newOption.getDisplayOrder()) { - //short answer - if (((oldQuestion.getType() == QbQuestion.TYPE_VERY_SHORT_ANSWERS) - && !StringUtils.equals(oldOption.getName(), newOption.getName())) - //numbering - || (oldOption.getNumericalOption() != newOption.getNumericalOption()) || ( - oldOption.getAcceptedError() != newOption.getAcceptedError()) - //option grade - || (oldOption.getMaxMark() != newOption.getMaxMark()) - //changed correct option - || (oldOption.isCorrect() != newOption.isCorrect())) { - isQuestionModified = true; - break; + //short answer + if (((oldQuestion.getType() == QbQuestion.TYPE_VERY_SHORT_ANSWERS) + && !StringUtils.equals(oldOption.getName(), newOption.getName())) + //numbering + || (oldOption.getNumericalOption() != newOption.getNumericalOption()) || ( + oldOption.getAcceptedError() != newOption.getAcceptedError()) + //option grade + || (oldOption.getMaxMark() != newOption.getMaxMark()) + //changed correct option + || (oldOption.isCorrect() != newOption.isCorrect())) { + isQuestionModified = true; + break; + } } } + if (isQuestionModified) { + break; + } } - if (isQuestionModified) { - break; + if (oldOptions.size() != newOptions.size()) { + isQuestionModified = true; } } - if (oldOptions.size() != newOptions.size()) { - isQuestionModified = true; - } if (isQuestionModified) { modifiedQuestions.add(newQuestion); Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java =================================================================== diff -u -r32a2a44474f2a866af0ce193d299f5ac3554865a -rb2a6839475729da885747e41082b4cb9c24e9359 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java (.../AuthoringController.java) (revision 32a2a44474f2a866af0ce193d299f5ac3554865a) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/AuthoringController.java (.../AuthoringController.java) (revision b2a6839475729da885747e41082b4cb9c24e9359) @@ -372,7 +372,7 @@ sections.add(section); sectionCounter++; } - } while (sectionQuestionCount != null); + } while (sectionQuestionCount != null && sectionQuestionCount > 0); assessmentPO.setSections(sections); @@ -789,7 +789,13 @@ qbService.fillVersionMap(newQbQuestion); question.setQbQuestion(newQbQuestion); - return "pages/authoring/parts/questionlist"; + //in case of edit in monitor and at least one attempted user, we show authoring page with restricted options + boolean isAuthoringRestricted = (boolean) sessionMap.get(AssessmentConstants.ATTR_IS_AUTHORING_RESTRICTED); + if (isAuthoringRestricted) { + return "pages/authoring/parts/questionlistRestricted"; + } else { + return "pages/authoring/parts/questionlist"; + } } @RequestMapping("/getAllQbQuestionUids") Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningController.java =================================================================== diff -u -r330b0d5f8fa4a8deb6d7454f92bdbd289e89cb82 -rb2a6839475729da885747e41082b4cb9c24e9359 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningController.java (.../LearningController.java) (revision 330b0d5f8fa4a8deb6d7454f92bdbd289e89cb82) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningController.java (.../LearningController.java) (revision b2a6839475729da885747e41082b4cb9c24e9359) @@ -115,6 +115,7 @@ public class LearningController { private static Logger log = Logger.getLogger(LearningController.class); + private static Logger logAutosave = Logger.getLogger(AssessmentServiceImpl.class.getName() + "_autosave"); @Autowired @Qualifier("laasseAssessmentService") @@ -294,7 +295,7 @@ //user is allowed to answer questions if assessment activity doesn't have leaders or he is the leader boolean hasEditRight = - (!assessment.isUseSelectLeaderToolOuput() || assessment.isUseSelectLeaderToolOuput() && isUserLeader) && !mode.isTeacher(); + !assessment.isUseSelectLeaderToolOuput() || assessment.isUseSelectLeaderToolOuput() && isUserLeader; //showResults if user has finished the last result boolean showResults = (lastResult != null) && (lastResult.getFinishDate() != null); @@ -467,6 +468,7 @@ @PostMapping("/nextPage") public String nextPage(HttpServletRequest request) throws ServletException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { +// printIncomingParameters(request); return nextPage(request, false, -1); } @@ -694,6 +696,8 @@ @ResponseBody public String autoSaveAnswers(HttpServletRequest request, HttpServletResponse response) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException { +// printIncomingParameters(request); + SessionMap sessionMap = getSessionMap(request); if (sessionMap == null) { log.warn("No sessionMap found in session for user: " + request.getRemoteUser()); @@ -821,6 +825,28 @@ } else if (questionType == QbQuestion.TYPE_ESSAY) { String answer = request.getParameter(AssessmentConstants.ATTR_QUESTION_PREFIX + i); + + boolean isAnswerNowBlank = + StringUtils.isNotBlank(questionDto.getAnswer()) && StringUtils.isBlank(answer); + + if (logAutosave.isTraceEnabled()) { + AssessmentUser learner = (AssessmentUser) sessionMap.get(AssessmentConstants.ATTR_USER); + if (learner != null) { + StringBuilder logBuilder = new StringBuilder("For learner ").append(learner.getUid()) + .append(" \"").append(learner.getLoginName()).append("\" for essay question ") + .append(questionUid).append(" the answer was \"").append(questionDto.getAnswer()); + if (isAnswerNowBlank) { + logBuilder.append("\" and now it is blank, skipping save to session."); + } else { + logBuilder.append("\" and now it is \"").append(answer).append("\""); + } + logAutosave.trace(logBuilder.toString()); + } + } + if (isAnswerNowBlank) { + continue; + } + if (questionDto.getCodeStyle() == null) { answer = answer.replaceAll("[\n\r\f]", ""); } else { @@ -1347,4 +1373,21 @@ request.setAttribute(AssessmentConstants.ATTR_SESSION_MAP_ID, sessionMapID); return (SessionMap) request.getSession().getAttribute(sessionMapID); } + +// private void printIncomingParameters(HttpServletRequest request) { +// if (!logAutosave.isTraceEnabled()) { +// return; +// } +// StringBuilder logBuilder = new StringBuilder("Incoming parameters for autosave/next page\n"); +// Enumeration parameterNames = request.getParameterNames(); +// while (parameterNames.hasMoreElements()) { +// String paramName = parameterNames.nextElement(); +// String[] paramValues = request.getParameterValues(paramName); +// for (int i = 0; i < paramValues.length; i++) { +// String paramValue = paramValues[i]; +// logBuilder.append(paramName).append(" = ").append(paramValue).append('\n'); +// } +// } +// logAutosave.trace(logBuilder.toString()); +// } } \ No newline at end of file Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java =================================================================== diff -u -r330b0d5f8fa4a8deb6d7454f92bdbd289e89cb82 -rb2a6839475729da885747e41082b4cb9c24e9359 --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision 330b0d5f8fa4a8deb6d7454f92bdbd289e89cb82) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision b2a6839475729da885747e41082b4cb9c24e9359) @@ -558,36 +558,38 @@ public void recalculateUserAnswers(Scratchie scratchie, Set oldItems, Set newItems, String oldPresetMarks) { // create list of modified questions - List modifiedItems = new ArrayList<>(); + + Map modifiedItems = new LinkedHashMap<>(); for (ScratchieItem oldItem : oldItems) { for (ScratchieItem newItem : newItems) { if (oldItem.getDisplayOrder() == newItem.getDisplayOrder()) { + boolean isItemModified = !oldItem.getQbQuestion().getUid().equals(newItem.getQbQuestion().getUid()); + if (!isItemModified) { + // title or question is different - do nothing - // title or question is different - do nothing + // options are different + List oldOptions = oldItem.getQbQuestion().getQbOptions(); + List newOptions = newItem.getQbQuestion().getQbOptions(); - // options are different - List oldOptions = oldItem.getQbQuestion().getQbOptions(); - List newOptions = newItem.getQbQuestion().getQbOptions(); - boolean isItemModified = oldOptions.size() != newOptions.size(); + for (QbOption oldOption : oldOptions) { + if (isItemModified) { + break; + } - for (QbOption oldOption : oldOptions) { - if (isItemModified) { - break; - } + for (QbOption newOption : newOptions) { + if (oldOption.getDisplayOrder() == newOption.getDisplayOrder()) { - for (QbOption newOption : newOptions) { - if (oldOption.getDisplayOrder() == newOption.getDisplayOrder()) { - - if (oldOption.isCorrect() != newOption.isCorrect()) { - isItemModified = true; - break; + if (oldOption.isCorrect() != newOption.isCorrect()) { + isItemModified = true; + break; + } } } } } if (isItemModified) { - modifiedItems.add(newItem); + modifiedItems.put(oldItem, newItem); } } } @@ -596,30 +598,59 @@ List sessionList = scratchieSessionDao.getByContentId(scratchie.getContentId()); for (ScratchieSession session : sessionList) { Long toolSessionId = session.getSessionId(); - List visitLogsToDelete = new ArrayList<>(); String newPresetMarks = scratchie.getPresetMarks(); boolean isRecalculateMarks = oldPresetMarks == null ? newPresetMarks != null : newPresetMarks == null || !oldPresetMarks.equals(newPresetMarks); - // remove all scratches for modified items + List visitLogsToDelete = new LinkedList<>(); // [+] if the question is modified - for (ScratchieItem modifiedItem : modifiedItems) { - List visitLogs = scratchieAnswerVisitDao.getLogsBySessionAndItem(toolSessionId, - modifiedItem.getUid()); - visitLogsToDelete.addAll(visitLogs); + for (Map.Entry modifiedItem : modifiedItems.entrySet()) { + ScratchieItem oldItem = modifiedItem.getKey(); + ScratchieItem newItem = modifiedItem.getValue(); + List oldVisitLogs = scratchieAnswerVisitDao.getLogsBySessionAndItem( + toolSessionId, oldItem.getUid()); + if (oldVisitLogs.isEmpty()) { + continue; + } + isRecalculateMarks = true; + boolean correctAnswerFound = false; + for (ScratchieAnswerVisitLog visitLog : oldVisitLogs) { + if (correctAnswerFound) { + visitLogsToDelete.add(visitLog); + continue; + } + visitLog.setQbToolQuestion(newItem); + + int oldOptionDisplayOrder = 1; + for (QbOption oldOption : oldItem.getQbQuestion().getQbOptions()) { + if (oldOption.getUid().equals(visitLog.getQbOption().getUid())) { + int newOptionDisplayOrder = 1; + for (QbOption newOption : newItem.getQbQuestion().getQbOptions()) { + if (oldOptionDisplayOrder == newOptionDisplayOrder) { + visitLog.setQbOption(newOption); + scratchieAnswerVisitDao.saveObject(visitLog); + if (newOption.isCorrect()) { + correctAnswerFound = true; + } + break; + } + newOptionDisplayOrder++; + } + break; + } + oldOptionDisplayOrder++; + } + } } // remove all visit logs marked for deletion Iterator iter = visitLogsToDelete.iterator(); while (iter.hasNext()) { ScratchieAnswerVisitLog visitLogToDelete = iter.next(); - iter.remove(); scratchieAnswerVisitDao.removeObject(ScratchieAnswerVisitLog.class, visitLogToDelete.getUid()); - isRecalculateMarks = true; } - // [+] doing nothing if the new question was added // recalculate marks if it's required @@ -1439,11 +1470,11 @@ int answerCount = 1; for (OptionDTO answer : item.getOptionDtos()) { if (answer.isCorrect()) { - correctAnswerLetter = String.valueOf((char) ((answerCount + 'A') - 1)); - break; + correctAnswerLetter += ((char) ((answerCount + 'A') - 1)) + " "; } answerCount++; } + correctAnswerLetter = correctAnswerLetter.trim().replace(" ", ", "); } else { List options = item.getQbQuestion().getQbOptions(); @@ -2146,7 +2177,7 @@ OptionDTO optionDto = new OptionDTO(); optionDto.setAnswer(optionLetter); - optionDto.setCorrect(correctOptionLetter.equals(optionLetter)); + optionDto.setCorrect(correctOptionLetter.contains(optionLetter)); optionDtos.add(optionDto); } Index: lams_tool_scratchie/web/pages/authoring/authoring.jsp =================================================================== diff -u -rf993635c56cbdd4ee0d6e448e37302004adf3535 -rb2a6839475729da885747e41082b4cb9c24e9359 --- lams_tool_scratchie/web/pages/authoring/authoring.jsp (.../authoring.jsp) (revision f993635c56cbdd4ee0d6e448e37302004adf3535) +++ lams_tool_scratchie/web/pages/authoring/authoring.jsp (.../authoring.jsp) (revision b2a6839475729da885747e41082b4cb9c24e9359) @@ -3,6 +3,7 @@ <%@ page import="org.lamsfoundation.lams.tool.scratchie.ScratchieConstants"%> + @@ -77,7 +78,8 @@ $('#relativeTimeLimit').val(0); } - $('#syncRatQuestions').val(hasMatchingRatActivity && questionsEdited && + let isAuthoringRestricted = ${isAuthoringRestricted}; + $('#syncRatQuestions').val(!isAuthoringRestricted && hasMatchingRatActivity && questionsEdited && confirm("")); return true; @@ -119,7 +121,7 @@ - <%-- Default value + <%-- Default value cancelButtonLabelKey="label.authoring.cancel.button" saveButtonLabelKey="label.authoring.save.button" cancelConfirmMsgKey="authoring.msg.cancel.save"