Index: lams_common/src/java/org/lamsfoundation/lams/commonContext.xml =================================================================== diff -u -r292df3a6fb3cce2b31c9d9ee33c61bbe931e58ab -rbfad24196397ef8759dd17934284e5dfdbc45ff6 --- lams_common/src/java/org/lamsfoundation/lams/commonContext.xml (.../commonContext.xml) (revision 292df3a6fb3cce2b31c9d9ee33c61bbe931e58ab) +++ lams_common/src/java/org/lamsfoundation/lams/commonContext.xml (.../commonContext.xml) (revision bfad24196397ef8759dd17934284e5dfdbc45ff6) @@ -497,7 +497,7 @@ - + true @@ -727,6 +727,11 @@ + + + + + Index: lams_common/src/java/org/lamsfoundation/lams/logevent/dao/ILearnerInteractionDAO.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/logevent/dao/ILearnerInteractionDAO.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/logevent/dao/ILearnerInteractionDAO.java (revision bfad24196397ef8759dd17934284e5dfdbc45ff6) @@ -0,0 +1,35 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +package org.lamsfoundation.lams.logevent.dao; + +import java.util.Map; + +import org.lamsfoundation.lams.dao.IBaseDAO; +import org.lamsfoundation.lams.logevent.LearnerInteractionEvent; + +public interface ILearnerInteractionDAO extends IBaseDAO { + /** + * Returns a map of QbToolQuestion UID -> learner interaction event with the question the first time + */ + Map getFirstLearnerInteractions(long toolContentId, int userId); +} \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/logevent/dao/hibernate/LearnerInteractionDAO.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/logevent/dao/hibernate/LearnerInteractionDAO.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/logevent/dao/hibernate/LearnerInteractionDAO.java (revision bfad24196397ef8759dd17934284e5dfdbc45ff6) @@ -0,0 +1,22 @@ +package org.lamsfoundation.lams.logevent.dao.hibernate; + +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.lamsfoundation.lams.dao.hibernate.LAMSBaseDAO; +import org.lamsfoundation.lams.logevent.LearnerInteractionEvent; +import org.lamsfoundation.lams.logevent.dao.ILearnerInteractionDAO; + +public class LearnerInteractionDAO extends LAMSBaseDAO implements ILearnerInteractionDAO { + private static final String FIND_FIRST_LEARNER_INTERACTIONS = "SELECT i.* FROM lams_learner_interaction_event AS i " + + "JOIN lams_qb_tool_question AS q ON i.qb_tool_question_uid = q.tool_question_uid " + + "WHERE q.tool_content_id = :toolContentId AND i.user_id = :userId GROUP BY i.qb_tool_question_uid"; + + @Override + public Map getFirstLearnerInteractions(long toolContentId, int userId) { + return getSession().createNativeQuery(FIND_FIRST_LEARNER_INTERACTIONS, LearnerInteractionEvent.class) + .setParameter("toolContentId", toolContentId).setParameter("userId", userId).list().stream() + .collect(Collectors.toMap(LearnerInteractionEvent::getQbToolQuestionUid, Function.identity())); + } +} \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/logevent/service/ILearnerInteractionService.java =================================================================== diff -u -r292df3a6fb3cce2b31c9d9ee33c61bbe931e58ab -rbfad24196397ef8759dd17934284e5dfdbc45ff6 --- lams_common/src/java/org/lamsfoundation/lams/logevent/service/ILearnerInteractionService.java (.../ILearnerInteractionService.java) (revision 292df3a6fb3cce2b31c9d9ee33c61bbe931e58ab) +++ lams_common/src/java/org/lamsfoundation/lams/logevent/service/ILearnerInteractionService.java (.../ILearnerInteractionService.java) (revision bfad24196397ef8759dd17934284e5dfdbc45ff6) @@ -1,7 +1,11 @@ package org.lamsfoundation.lams.logevent.service; +import java.util.Map; + import org.lamsfoundation.lams.logevent.LearnerInteractionEvent; public interface ILearnerInteractionService { void saveEvent(LearnerInteractionEvent event); + + Map getFirstLearnerInteractions(long toolContentId, int userId); } \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/logevent/service/LearnerInteractionService.java =================================================================== diff -u -r292df3a6fb3cce2b31c9d9ee33c61bbe931e58ab -rbfad24196397ef8759dd17934284e5dfdbc45ff6 --- lams_common/src/java/org/lamsfoundation/lams/logevent/service/LearnerInteractionService.java (.../LearnerInteractionService.java) (revision 292df3a6fb3cce2b31c9d9ee33c61bbe931e58ab) +++ lams_common/src/java/org/lamsfoundation/lams/logevent/service/LearnerInteractionService.java (.../LearnerInteractionService.java) (revision bfad24196397ef8759dd17934284e5dfdbc45ff6) @@ -1,18 +1,28 @@ package org.lamsfoundation.lams.logevent.service; -import org.lamsfoundation.lams.dao.IBaseDAO; +import java.util.Map; + import org.lamsfoundation.lams.logevent.LearnerInteractionEvent; +import org.lamsfoundation.lams.logevent.dao.ILearnerInteractionDAO; public class LearnerInteractionService implements ILearnerInteractionService { - private IBaseDAO baseDAO; + private ILearnerInteractionDAO learnerInteractionDAO; @Override public void saveEvent(LearnerInteractionEvent event) { - baseDAO.insert(event); + learnerInteractionDAO.insert(event); } - public void setBaseDAO(IBaseDAO baseDAO) { - this.baseDAO = baseDAO; + /** + * Returns a map of QbToolQuestion UID -> learner interaction event with the question the first time + */ + @Override + public Map getFirstLearnerInteractions(long toolContentId, int userId) { + return learnerInteractionDAO.getFirstLearnerInteractions(toolContentId, userId); } + + public void setLearnerInteractionDAO(ILearnerInteractionDAO learnerInteractionDAO) { + this.learnerInteractionDAO = learnerInteractionDAO; + } } \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/util/FileUtil.java =================================================================== diff -u -r9367000b12e2272e6e0756d91ae1fdd5d7d4a220 -rbfad24196397ef8759dd17934284e5dfdbc45ff6 --- lams_common/src/java/org/lamsfoundation/lams/util/FileUtil.java (.../FileUtil.java) (revision 9367000b12e2272e6e0756d91ae1fdd5d7d4a220) +++ lams_common/src/java/org/lamsfoundation/lams/util/FileUtil.java (.../FileUtil.java) (revision bfad24196397ef8759dd17934284e5dfdbc45ff6) @@ -34,6 +34,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.text.SimpleDateFormat; +import java.time.format.DateTimeFormatter; import java.util.Collection; import java.util.Date; import java.util.Map; @@ -81,6 +82,8 @@ public static final String ENCODING_UTF_8 = "UTF8"; public static final SimpleDateFormat EXPORT_TO_SPREADSHEET_TITLE_DATE_FORMAT = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss"); + public static final DateTimeFormatter EXPORT_TO_SPREADSHEET_TITLE_DATE_FORMATTER = DateTimeFormatter + .ofPattern("dd/MM/yyyy HH:mm:ss"); public static final SimpleDateFormat EXPORT_TO_SPREADSHEET_CELL_DATE_FORMAT = new SimpleDateFormat("dd/MM/yyyy"); public static final String LAMS_WWW_SECURE_DIR = "secure"; Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/assessmentApplicationContext.xml =================================================================== diff -u -rd9a9f033cd1e6050279d05ef7cbca24f243ecf6a -rbfad24196397ef8759dd17934284e5dfdbc45ff6 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/assessmentApplicationContext.xml (.../assessmentApplicationContext.xml) (revision d9a9f033cd1e6050279d05ef7cbca24f243ecf6a) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/assessmentApplicationContext.xml (.../assessmentApplicationContext.xml) (revision bfad24196397ef8759dd17934284e5dfdbc45ff6) @@ -128,6 +128,9 @@ + + + Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -rdac907ce79fde14b5b168324b3a2da18404d6dcb -rbfad24196397ef8759dd17934284e5dfdbc45ff6 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision dac907ce79fde14b5b168324b3a2da18404d6dcb) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision bfad24196397ef8759dd17934284e5dfdbc45ff6) @@ -65,6 +65,8 @@ import org.lamsfoundation.lams.learningdesign.service.ImportToolContentException; import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.logevent.LearnerInteractionEvent; +import org.lamsfoundation.lams.logevent.service.ILearnerInteractionService; import org.lamsfoundation.lams.logevent.service.ILogEventService; import org.lamsfoundation.lams.notebook.model.NotebookEntry; import org.lamsfoundation.lams.notebook.service.CoreNotebookConstants; @@ -129,6 +131,7 @@ import org.lamsfoundation.lams.util.JsonUtil; import org.lamsfoundation.lams.util.MessageService; import org.lamsfoundation.lams.util.NumberUtil; +import org.lamsfoundation.lams.util.WebUtil; import org.lamsfoundation.lams.util.excel.ExcelCell; import org.lamsfoundation.lams.util.excel.ExcelRow; import org.lamsfoundation.lams.util.excel.ExcelSheet; @@ -188,6 +191,8 @@ private IOutcomeService outcomeService; + private ILearnerInteractionService learnerInteractionService; + // ******************************************************************************* // Service method // ******************************************************************************* @@ -1904,7 +1909,7 @@ } } else { - userResultRow.addCell(AssessmentEscapeUtils.printResponsesForExcelExport(questionResult)); + AssessmentEscapeUtils.addResponseCellForExcelExport(questionResult, userResultRow, false); if (doSummaryTable) { summaryNACount = updateSummaryCounts(question, questionResult, summaryOfAnswers, @@ -1963,173 +1968,340 @@ } } + { + // ------------------------------------------------------------------ + // -------------- Third tab: User Summary --------------------------- + + ExcelSheet userSummarySheet = new ExcelSheet(getMessage("label.export.summary.by.user")); + sheets.add(userSummarySheet); + + // Create the question summary + ExcelRow userSummaryTitle = userSummarySheet.initRow(); + userSummaryTitle.addCell(getMessage("label.export.user.summary"), true); + + ExcelRow summaryRowTitle = userSummarySheet.initRow(); + summaryRowTitle.addCell("#", true, ExcelCell.BORDER_STYLE_BOTTOM_THIN); + summaryRowTitle.addCell(getMessage("label.monitoring.question.summary.question"), true, + ExcelCell.BORDER_STYLE_BOTTOM_THIN); + summaryRowTitle.addCell(getMessage("label.authoring.basic.list.header.type"), true, + ExcelCell.BORDER_STYLE_BOTTOM_THIN); + summaryRowTitle.addCell(getMessage("label.authoring.basic.penalty.factor"), true, + ExcelCell.BORDER_STYLE_BOTTOM_THIN); + summaryRowTitle.addCell(getMessage("label.monitoring.question.summary.default.mark"), true, + ExcelCell.BORDER_STYLE_BOTTOM_THIN); + summaryRowTitle.addCell(getMessage("label.monitoring.question.summary.average.mark"), true, + ExcelCell.BORDER_STYLE_BOTTOM_THIN); + + Float totalGradesPossible = 0F; + Float totalAverage = 0F; + int questionIndex = 1; + if (assessment.getQuestionReferences() != null) { + Set questionReferences = new TreeSet<>(new SequencableComparator()); + questionReferences.addAll(assessment.getQuestionReferences()); + + int randomQuestionsCount = 1; + for (QuestionReference questionReference : questionReferences) { + + String title; + String questionType; + Float penaltyFactor; + Float averageMark = null; + if (questionReference.isRandomQuestion()) { + + title = getMessage("label.authoring.basic.type.random.question") + randomQuestionsCount++; + questionType = getMessage("label.authoring.basic.type.random.question"); + penaltyFactor = null; + averageMark = null; + } else { + + AssessmentQuestion question = questionReference.getQuestion(); + title = question.getQbQuestion().getName(); + questionType = AssessmentServiceImpl.getQuestionTypeLanguageLabel(question.getType()); + penaltyFactor = question.getQbQuestion().getPenaltyFactor(); + + QuestionSummary questionSummary = questionSummaries.get(question.getUid()); + if (questionSummary != null) { + averageMark = questionSummary.getAverageMark(); + totalAverage += questionSummary.getAverageMark(); + } + } + + int maxGrade = questionReference.getMaxMark(); + totalGradesPossible += maxGrade; + + ExcelRow questCellRow = userSummarySheet.initRow(); + questCellRow.addCell(questionIndex++); + questCellRow.addCell(title); + questCellRow.addCell(questionType); + questCellRow.addCell(penaltyFactor); + questCellRow.addCell(maxGrade); + questCellRow.addCell(averageMark); + } + + if (totalGradesPossible.floatValue() > 0) { + ExcelRow totalCellRow = userSummarySheet.initRow(); + totalCellRow.addEmptyCells(2); + totalCellRow.addCell(getMessage("label.monitoring.summary.total"), true); + totalCellRow.addCell(totalGradesPossible); + totalCellRow.addCell(totalAverage); + } + userSummarySheet.addEmptyRow(); + } + + if (sessionDtos != null) { + List assessmentResults = assessmentResultDao + .getLastFinishedAssessmentResults(assessment.getContentId()); + Map userUidToResultMap = new HashMap<>(); + for (AssessmentResult assessmentResult : assessmentResults) { + userUidToResultMap.put(assessmentResult.getUser().getUid(), assessmentResult); + } + + for (SessionDTO sessionDTO : sessionDtos) { + userSummarySheet.addEmptyRow(); + + ExcelRow sessionTitle = userSummarySheet.initRow(); + sessionTitle.addCell(sessionDTO.getSessionName(), true); + + AssessmentSession assessmentSession = getSessionBySessionId(sessionDTO.getSessionId()); + Set assessmentUsers = assessmentSession.getAssessmentUsers(); + if (assessmentUsers != null) { + for (AssessmentUser assessmentUser : assessmentUsers) { + ExcelRow userTitleRow = userSummarySheet.initRow(); + userTitleRow.addCell(getMessage("label.export.user.id"), true); + userTitleRow.addCell(getMessage("label.monitoring.user.summary.full.name"), true); + userTitleRow.addCell(getMessage("label.export.date.attempted"), true); + userTitleRow.addCell(getMessage("label.monitoring.question.summary.question"), true); + userTitleRow.addCell(getMessage("label.authoring.basic.option.answer"), true); + if (assessment.isEnableConfidenceLevels()) { + userTitleRow.addCell(getMessage("label.confidence"), true); + } + userTitleRow.addCell(getMessage("label.export.mark"), true); + + if (assessment.isAllowAnswerJustification()) { + userTitleRow.addCell(getMessage("label.answer.justification"), true); + } + + AssessmentResult assessmentResult = userUidToResultMap.get(assessmentUser.getUid()); + if (assessmentResult != null) { + Set questionResults = assessmentResult.getQuestionResults(); + if (questionResults != null) { + for (AssessmentQuestionResult questionResult : questionResults) { + ExcelRow userResultRow = userSummarySheet.initRow(); + userResultRow.addCell(assessmentUser.getLoginName()); + userResultRow.addCell(assessmentUser.getFullName()); + userResultRow.addCell(assessmentResult.getStartDate()); + userResultRow.addCell(questionResult.getQbQuestion().getName()); + + AssessmentEscapeUtils.addResponseCellForExcelExport(questionResult, + userResultRow, false); + + if (assessment.isEnableConfidenceLevels()) { + String confidenceLevel = null; + + switch (assessment.getConfidenceLevelsType()) { + case 2: + confidenceLevel = new String[] { getMessage("label.not.confident"), + getMessage("label.confident"), + getMessage("label.very.confident") }[questionResult + .getConfidenceLevel() / 5]; + break; + case 3: + confidenceLevel = new String[] { getMessage("label.not.sure"), + getMessage("label.sure"), + getMessage("label.very.sure") }[questionResult + .getConfidenceLevel() / 5]; + break; + default: + confidenceLevel = questionResult.getConfidenceLevel() * 10 + "%"; + } + + userResultRow.addCell(confidenceLevel); + } + + userResultRow.addCell(questionResult.getMark()); + + if (assessment.isAllowAnswerJustification()) { + userResultRow.addCell(AssessmentEscapeUtils + .escapeStringForExcelExport(questionResult.getJustification())); + } + } + } + + ExcelRow userTotalRow = userSummarySheet.initRow(); + userTotalRow.addEmptyCells(assessment.isEnableConfidenceLevels() ? 5 : 4); + userTotalRow.addCell(getMessage("label.monitoring.summary.total"), true); + userTotalRow.addCell(assessmentResult.getGrade()); + userSummarySheet.addEmptyRow(); + } + } + } + } + } + } // ------------------------------------------------------------------ - // -------------- Third tab: User Summary --------------------------- + // -------------- Third tab: Learner summary --------------------------- ExcelSheet userSummarySheet = new ExcelSheet(getMessage("label.export.summary.by.user")); sheets.add(userSummarySheet); - // Create the question summary - ExcelRow userSummaryTitle = userSummarySheet.initRow(); - userSummaryTitle.addCell(getMessage("label.export.user.summary"), true); + if (sessionDtos != null && assessment.getQuestionReferences() != null) { + // if there are multiple session, then the activity has to be grouped + boolean isActivityGrouped = sessionDtos.size() > 1; - ExcelRow summaryRowTitle = userSummarySheet.initRow(); - summaryRowTitle.addCell("#", true, ExcelCell.BORDER_STYLE_BOTTOM_THIN); - summaryRowTitle.addCell(getMessage("label.monitoring.question.summary.question"), true, - ExcelCell.BORDER_STYLE_BOTTOM_THIN); - summaryRowTitle.addCell(getMessage("label.authoring.basic.list.header.type"), true, - ExcelCell.BORDER_STYLE_BOTTOM_THIN); - summaryRowTitle.addCell(getMessage("label.authoring.basic.penalty.factor"), true, - ExcelCell.BORDER_STYLE_BOTTOM_THIN); - summaryRowTitle.addCell(getMessage("label.monitoring.question.summary.default.mark"), true, - ExcelCell.BORDER_STYLE_BOTTOM_THIN); - summaryRowTitle.addCell(getMessage("label.monitoring.question.summary.average.mark"), true, - ExcelCell.BORDER_STYLE_BOTTOM_THIN); + // 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("Questions", true, ExcelCell.BORDER_STYLE_LEFT_THIN); - Float totalGradesPossible = 0F; - Float totalAverage = 0F; - int questionIndex = 1; - if (assessment.getQuestionReferences() != null) { + // Row with question titles + ExcelRow questionTitlesRow = userSummarySheet.initRow(); + questionTitlesRow.addEmptyCells(questionLeftPadding); + Set questionReferences = new TreeSet<>(new SequencableComparator()); questionReferences.addAll(assessment.getQuestionReferences()); - int randomQuestionsCount = 1; + // print out all question titles for (QuestionReference questionReference : questionReferences) { + AssessmentQuestion question = questionReference.getQuestion(); + String title = question.getQbQuestion().getName(); + // leave pure text of title + title = WebUtil.removeHTMLtags(title).strip(); - String title; - String questionType; - Float penaltyFactor; - Float averageMark = null; - if (questionReference.isRandomQuestion()) { + // shorten long title + if (title.length() > 80) { + title = title.substring(0, 80) + "..."; + } + questionTitlesRow.addCell(title, true, ExcelCell.BORDER_STYLE_LEFT_THIN); - title = getMessage("label.authoring.basic.type.random.question") + randomQuestionsCount++; - questionType = getMessage("label.authoring.basic.type.random.question"); - penaltyFactor = null; - averageMark = null; - } else { - - AssessmentQuestion question = questionReference.getQuestion(); - title = question.getQbQuestion().getName(); - questionType = AssessmentServiceImpl.getQuestionTypeLanguageLabel(question.getType()); - penaltyFactor = question.getQbQuestion().getPenaltyFactor(); - - QuestionSummary questionSummary = questionSummaries.get(question.getUid()); - if (questionSummary != null) { - averageMark = questionSummary.getAverageMark(); - totalAverage += questionSummary.getAverageMark(); - } + int columnShift = 1; + // currently only MCQ and True/False questions have learner interaction logged + // for other question types, do not include the column + boolean addAnsweredDateColumn = QbQuestion.TYPE_MULTIPLE_CHOICE == question.getType() + || QbQuestion.TYPE_TRUE_FALSE == question.getType(); + if (addAnsweredDateColumn) { + columnShift++; } + if (assessment.isEnableConfidenceLevels()) { + columnShift++; + } + questionTitlesRow.addEmptyCells(columnShift); + userSummarySheet.addMergedCells(5, questionLeftPadding, questionLeftPadding + columnShift); - int maxGrade = questionReference.getMaxMark(); - totalGradesPossible += maxGrade; + questionLeftPadding += columnShift + 1; + } + questionTitlesRow.addCell("", ExcelCell.BORDER_STYLE_LEFT_THIN); - ExcelRow questCellRow = userSummarySheet.initRow(); - questCellRow.addCell(questionIndex++); - questCellRow.addCell(title); - questCellRow.addCell(questionType); - questCellRow.addCell(penaltyFactor); - questCellRow.addCell(maxGrade); - questCellRow.addCell(averageMark); + // Row with column header below question titles + ExcelRow userSummaryUserHeadersRow = userSummarySheet.initRow(); + if (isActivityGrouped) { + userSummaryUserHeadersRow.addCell("Group", true); } + userSummaryUserHeadersRow.addCell("User name", true); + userSummaryUserHeadersRow.addCell("Full name", true); - if (totalGradesPossible.floatValue() > 0) { - ExcelRow totalCellRow = userSummarySheet.initRow(); - totalCellRow.addEmptyCells(2); - totalCellRow.addCell(getMessage("label.monitoring.summary.total"), true); - totalCellRow.addCell(totalGradesPossible); - totalCellRow.addCell(totalAverage); + for (QuestionReference questionReference : questionReferences) { + userSummaryUserHeadersRow.addCell("Score", ExcelCell.BORDER_STYLE_LEFT_THIN); + userSummaryUserHeadersRow.addCell("Answer"); + + AssessmentQuestion question = questionReference.getQuestion(); + boolean addAnsweredDateColumn = QbQuestion.TYPE_MULTIPLE_CHOICE == question.getType() + || QbQuestion.TYPE_TRUE_FALSE == question.getType(); + if (addAnsweredDateColumn) { + userSummaryUserHeadersRow.addCell("Date time"); + } + if (assessment.isEnableConfidenceLevels()) { + userSummaryUserHeadersRow.addCell("Confidence"); + } } - userSummarySheet.addEmptyRow(); - } - if (sessionDtos != null) { + // a single column at the end of previous headers + userSummaryUserHeadersRow.addCell("Total", ExcelCell.BORDER_STYLE_LEFT_THIN); + List assessmentResults = assessmentResultDao .getLastFinishedAssessmentResults(assessment.getContentId()); - Map userUidToResultMap = new HashMap<>(); - for (AssessmentResult assessmentResult : assessmentResults) { - userUidToResultMap.put(assessmentResult.getUser().getUid(), assessmentResult); - } + Map userUidToResultMap = assessmentResults.stream() + .collect(Collectors.toMap(r -> r.getUser().getUid(), r -> r)); for (SessionDTO sessionDTO : sessionDtos) { - userSummarySheet.addEmptyRow(); - - ExcelRow sessionTitle = userSummarySheet.initRow(); - sessionTitle.addCell(sessionDTO.getSessionName(), true); - AssessmentSession assessmentSession = getSessionBySessionId(sessionDTO.getSessionId()); Set assessmentUsers = assessmentSession.getAssessmentUsers(); - if (assessmentUsers != null) { - for (AssessmentUser assessmentUser : assessmentUsers) { - ExcelRow userTitleRow = userSummarySheet.initRow(); - userTitleRow.addCell(getMessage("label.export.user.id"), true); - userTitleRow.addCell(getMessage("label.monitoring.user.summary.full.name"), true); - userTitleRow.addCell(getMessage("label.export.date.attempted"), true); - userTitleRow.addCell(getMessage("label.monitoring.question.summary.question"), true); - userTitleRow.addCell(getMessage("label.authoring.basic.option.answer"), true); - if (assessment.isEnableConfidenceLevels()) { - userTitleRow.addCell(getMessage("label.confidence"), true); - } - userTitleRow.addCell(getMessage("label.export.mark"), true); + for (AssessmentUser assessmentUser : assessmentUsers) { + ExcelRow userResultRow = userSummarySheet.initRow(); + if (isActivityGrouped) { + userResultRow.addCell(sessionDTO.getSessionName()); + } + userResultRow.addCell(assessmentUser.getLoginName()); + userResultRow.addCell(assessmentUser.getFullName()); - if (assessment.isAllowAnswerJustification()) { - userTitleRow.addCell(getMessage("label.answer.justification"), true); - } + AssessmentResult assessmentResult = userUidToResultMap.get(assessmentUser.getUid()); + if (assessmentResult == null) { + continue; + } - AssessmentResult assessmentResult = userUidToResultMap.get(assessmentUser.getUid()); - if (assessmentResult != null) { - Set questionResults = assessmentResult.getQuestionResults(); - if (questionResults != null) { - for (AssessmentQuestionResult questionResult : questionResults) { - ExcelRow userResultRow = userSummarySheet.initRow(); - userResultRow.addCell(assessmentUser.getLoginName()); - userResultRow.addCell(assessmentUser.getFullName()); - userResultRow.addCell(assessmentResult.getStartDate()); - userResultRow.addCell(questionResult.getQbQuestion().getName()); - userResultRow.addCell( - AssessmentEscapeUtils.printResponsesForExcelExport(questionResult)); - if (assessment.isEnableConfidenceLevels()) { - String confidenceLevel = null; + Set questionResults = assessmentResult.getQuestionResults(); + if (questionResults == null) { + continue; + } - switch (assessment.getConfidenceLevelsType()) { - case 2: - confidenceLevel = new String[] { getMessage("label.not.confident"), - getMessage("label.confident"), - getMessage("label.very.confident") }[questionResult - .getConfidenceLevel() / 5]; - break; - case 3: - confidenceLevel = new String[] { getMessage("label.not.sure"), - getMessage("label.sure"), - getMessage("label.very.sure") }[questionResult - .getConfidenceLevel() / 5]; - break; - default: - confidenceLevel = questionResult.getConfidenceLevel() * 10 + "%"; - } + // get information when a learner started interaction with given questions + Map learnerInteractions = learnerInteractionService + .getFirstLearnerInteractions(assessment.getContentId(), + assessmentUser.getUserId().intValue()); - userResultRow.addCell(confidenceLevel); - } + for (AssessmentQuestionResult questionResult : questionResults) { + // mark + userResultRow.addCell(questionResult.getMark(), ExcelCell.BORDER_STYLE_LEFT_THIN); - userResultRow.addCell(questionResult.getMark()); + // option chosen or full answer + AssessmentEscapeUtils.addResponseCellForExcelExport(questionResult, userResultRow, true); - if (assessment.isAllowAnswerJustification()) { - userResultRow.addCell(AssessmentEscapeUtils - .escapeStringForExcelExport(questionResult.getJustification())); - } - } + // learner interaction + QbQuestion question = questionResult.getQbQuestion(); + boolean addAnsweredDateColumn = QbQuestion.TYPE_MULTIPLE_CHOICE == question.getType() + || QbQuestion.TYPE_TRUE_FALSE == question.getType(); + if (addAnsweredDateColumn) { + LearnerInteractionEvent interaction = learnerInteractions + .get(questionResult.getQbToolQuestion().getUid()); + if (interaction == null) { + userResultRow.addEmptyCell(); + } else { + userResultRow.addCell(interaction.getOccuredDateTime() + .format(FileUtil.EXPORT_TO_SPREADSHEET_TITLE_DATE_FORMATTER)); } + } - ExcelRow userTotalRow = userSummarySheet.initRow(); - userTotalRow.addEmptyCells(assessment.isEnableConfidenceLevels() ? 5 : 4); - userTotalRow.addCell(getMessage("label.monitoring.summary.total"), true); - userTotalRow.addCell(assessmentResult.getGrade()); - userSummarySheet.addEmptyRow(); + // confidence level + if (assessment.isEnableConfidenceLevels()) { + String confidenceLevel = null; + + switch (assessment.getConfidenceLevelsType()) { + case 2: + confidenceLevel = new String[] { getMessage("label.not.confident"), + getMessage("label.confident"), + getMessage("label.very.confident") }[questionResult.getConfidenceLevel() + / 5]; + break; + case 3: + confidenceLevel = new String[] { getMessage("label.not.sure"), + getMessage("label.sure"), + getMessage("label.very.sure") }[questionResult.getConfidenceLevel() / 5]; + break; + default: + confidenceLevel = questionResult.getConfidenceLevel() * 10 + "%"; + } + + userResultRow.addCell(confidenceLevel); } } + userResultRow.addCell(assessmentResult.getGrade(), ExcelCell.BORDER_STYLE_LEFT_THIN); + + userSummarySheet.addEmptyRow(); + userSummarySheet.addEmptyRow(); } } } - return sheets; } @@ -3389,6 +3561,10 @@ this.assessmentOutputFactory = assessmentOutputFactory; } + public void setLearnerInteractionService(ILearnerInteractionService learnerInteractionService) { + this.learnerInteractionService = learnerInteractionService; + } + @Override public Class[] getSupportedToolOutputDefinitionClasses(int definitionType) { return getAssessmentOutputFactory().getSupportedDefinitionClasses(definitionType); @@ -3641,7 +3817,7 @@ if (collectionUUIDs != null) { addToCollection &= !collectionUUIDs.contains(uuid); } - + int isModified = qbQuestion.isQbQuestionModified(oldQbQuestion); if (isModified == IQbService.QUESTION_MODIFIED_VERSION_BUMP) { qbQuestion.clearID(); Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/util/AssessmentEscapeUtils.java =================================================================== diff -u -r7089187749475a10fc520379a3b4689077139d32 -rbfad24196397ef8759dd17934284e5dfdbc45ff6 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/util/AssessmentEscapeUtils.java (.../AssessmentEscapeUtils.java) (revision 7089187749475a10fc520379a3b4689077139d32) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/util/AssessmentEscapeUtils.java (.../AssessmentEscapeUtils.java) (revision bfad24196397ef8759dd17934284e5dfdbc45ff6) @@ -27,6 +27,7 @@ import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; +import org.apache.poi.ss.usermodel.IndexedColors; import org.lamsfoundation.lams.qb.model.QbOption; import org.lamsfoundation.lams.qb.model.QbQuestion; import org.lamsfoundation.lams.tool.assessment.dto.AssessmentResultDTO; @@ -37,6 +38,7 @@ import org.lamsfoundation.lams.tool.assessment.dto.UserSummaryItem; import org.lamsfoundation.lams.tool.assessment.model.AssessmentOptionAnswer; import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestionResult; +import org.lamsfoundation.lams.util.excel.ExcelRow; public class AssessmentEscapeUtils { @@ -228,33 +230,43 @@ /** * Used only for excell export (for getUserSummaryData() method). */ - public static Object printResponsesForExcelExport(AssessmentQuestionResult questionResult) { - Object ret = null; + public static void addResponseCellForExcelExport(AssessmentQuestionResult questionResult, ExcelRow row, + boolean useLettersForMcq) { + if (questionResult == null) { + row.addEmptyCell(); + return; + } - if (questionResult != null) { - switch (questionResult.getQbQuestion().getType()) { - case QbQuestion.TYPE_ESSAY: - String answer = questionResult.getAnswer(); - return AssessmentEscapeUtils.escapeStringForExcelExport(answer); - case QbQuestion.TYPE_MATCHING_PAIRS: - return AssessmentEscapeUtils.getOptionResponse(questionResult, QbQuestion.TYPE_MATCHING_PAIRS); - case QbQuestion.TYPE_MULTIPLE_CHOICE: - return AssessmentEscapeUtils.getOptionResponse(questionResult, QbQuestion.TYPE_MULTIPLE_CHOICE); - case QbQuestion.TYPE_NUMERICAL: - return questionResult.getAnswer(); - case QbQuestion.TYPE_ORDERING: - return AssessmentEscapeUtils.getOptionResponse(questionResult, QbQuestion.TYPE_ORDERING); - case QbQuestion.TYPE_VERY_SHORT_ANSWERS: - return questionResult.getAnswer(); - case QbQuestion.TYPE_TRUE_FALSE: - return questionResult.getAnswerBoolean(); - case QbQuestion.TYPE_MARK_HEDGING: - //taken care beforehand - default: - return null; - } + Object value = null; + switch (questionResult.getQbQuestion().getType()) { + case QbQuestion.TYPE_ESSAY: + value = AssessmentEscapeUtils.escapeStringForExcelExport(questionResult.getAnswer()); + break; + case QbQuestion.TYPE_MATCHING_PAIRS: + AssessmentEscapeUtils.getOptionResponse(questionResult, QbQuestion.TYPE_MATCHING_PAIRS, row, + useLettersForMcq); + return; + case QbQuestion.TYPE_MULTIPLE_CHOICE: + AssessmentEscapeUtils.getOptionResponse(questionResult, QbQuestion.TYPE_MULTIPLE_CHOICE, row, + useLettersForMcq); + return; + case QbQuestion.TYPE_NUMERICAL: + value = questionResult.getAnswer(); + break; + case QbQuestion.TYPE_ORDERING: + AssessmentEscapeUtils.getOptionResponse(questionResult, QbQuestion.TYPE_ORDERING, row, + useLettersForMcq); + return; + case QbQuestion.TYPE_VERY_SHORT_ANSWERS: + value = questionResult.getAnswer(); + break; + case QbQuestion.TYPE_TRUE_FALSE: + value = questionResult.getAnswerBoolean(); + break; + case QbQuestion.TYPE_MARK_HEDGING: + //taken care beforehand } - return ret; + row.addCell(value); } public static String escapeStringForExcelExport(String input) { @@ -264,26 +276,37 @@ /** * Used only for excell export (for getUserSummaryData() method). */ - private static String getOptionResponse(AssessmentQuestionResult questionResult, int type) { + private static void getOptionResponse(AssessmentQuestionResult questionResult, int type, ExcelRow row, + boolean useLettersForMcq) { StringBuilder sb = new StringBuilder(); //whether there is a need to remove last comma boolean trimLastComma = false; + boolean highlightCell = false; List options = questionResult.getQbQuestion().getQbOptions(); Set optionAnswers = questionResult.getOptionAnswers(); if (optionAnswers != null) { - if (type == QbQuestion.TYPE_MULTIPLE_CHOICE) { - for (AssessmentOptionAnswer optionAnswer : optionAnswers) { - if (optionAnswer.getAnswerBoolean()) { - for (QbOption option : options) { - if (option.getUid().equals(optionAnswer.getOptionUid())) { - sb.append(option.getName() + ", "); + highlightCell = useLettersForMcq; + int letter = 'A'; + for (QbOption option : options) { + for (AssessmentOptionAnswer optionAnswer : optionAnswers) { + if (option.getUid().equals(optionAnswer.getOptionUid())) { + if (optionAnswer.getAnswerBoolean()) { + // either we display full answers or just letters of chosen options + sb.append((useLettersForMcq ? String.valueOf((char) letter) : option.getName()) + ", "); trimLastComma = true; + + // if any answer is wrong, we do not highlight correct answer + if (option.getMaxMark() <= 0) { + highlightCell = false; + } } + break; } } + letter++; } } else if (type == QbQuestion.TYPE_ORDERING) { @@ -327,7 +350,6 @@ ret = ret.substring(0, ret.lastIndexOf(",")); } - return ret; + row.addCell(ret, highlightCell ? IndexedColors.GREEN : IndexedColors.AUTOMATIC); } - -} +} \ No newline at end of file