Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/AssessmentResultDAO.java =================================================================== diff -u -re194f22014c7cbffaf5168f1982aa89e7c34c34d -rff9b1c659da59060b4690a29bfdd493e8741b224 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/AssessmentResultDAO.java (.../AssessmentResultDAO.java) (revision e194f22014c7cbffaf5168f1982aa89e7c34c34d) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/AssessmentResultDAO.java (.../AssessmentResultDAO.java) (revision ff9b1c659da59060b4690a29bfdd493e8741b224) @@ -109,5 +109,5 @@ */ int countAttemptsPerOption(Long toolContentId, Long optionUid, boolean finishedAttemptsOnly); - Map countAnsweredQuestionsByUsers(long toolContentId); + Map> getAnsweredQuestionsByUsers(long toolContentId); } \ No newline at end of file Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/hibernate/AssessmentResultDAOHibernate.java =================================================================== diff -u -r8a1b895ac8bd80a6808e80079f422a04664633f4 -rff9b1c659da59060b4690a29bfdd493e8741b224 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/hibernate/AssessmentResultDAOHibernate.java (.../AssessmentResultDAOHibernate.java) (revision 8a1b895ac8bd80a6808e80079f422a04664633f4) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/hibernate/AssessmentResultDAOHibernate.java (.../AssessmentResultDAOHibernate.java) (revision ff9b1c659da59060b4690a29bfdd493e8741b224) @@ -25,7 +25,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.TreeMap; +import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; import org.hibernate.query.NativeQuery; @@ -112,32 +112,32 @@ private static final String FIND_BY_UID = "FROM " + AssessmentResult.class.getName() + " AS r WHERE r.uid = ?"; - private static final String ANSWERED_QUESTIONS_BY_USER_COUNT = "" - + "SELECT answered_question_count, COUNT(user_uid) AS user_count FROM" - + " (SELECT user_uid, SUM(IF( " + private static final String ANSWERED_QUESTIONS_BY_USER = "SELECT user_id, portrait_uuid, user_name, SUM(IF(" + " (type = 1 AND answer_boolean = 1) OR" + " (type = 2 AND answer_int <> -1) OR" + " ((type BETWEEN 3 AND 6) AND (answer IS NOT NULL AND TRIM(answer) <> '')) OR" + " (type = 7 AND mark > 0) OR " + " (type = 8 AND answer_int > 0)" + " ,1, 0)) AS answered_question_count FROM" - + " (SELECT ar.user_uid, qbq.type, qr.mark, qbta.answer, oa.answer_boolean, oa.answer_int" + + " (SELECT u.user_id, BIN_TO_UUID(u.portrait_uuid) AS portrait_uuid, CONCAT(u.first_name, ' ', u.last_name) AS user_name," + + " qbq.type, qr.mark, qbta.answer, oa.answer_boolean, oa.answer_int" + " FROM tl_laasse10_assessment AS a" + " JOIN tl_laasse10_assessment_result AS ar ON a.uid = ar.assessment_uid" + + " JOIN tl_laasse10_user AS au ON ar.user_uid = au.uid" + + " JOIN lams_user AS u USING (user_id)" + " JOIN tl_laasse10_session AS s USING (session_id)" + " JOIN tl_laasse10_question_result AS qr ON ar.uid = qr.result_uid" - + " JOIN lams_qb_tool_answer AS qbta ON qbta.answer_uid = qr.uid" - + " JOIN lams_qb_tool_question AS qbtq USING (tool_question_uid)" - + " JOIN lams_qb_question AS qbq ON qbq.uid = qbtq.qb_question_uid" - + " LEFT JOIN tl_laasse10_option_answer AS oa ON oa.question_result_uid = qr.uid" + + " LEFT JOIN lams_qb_tool_answer AS qbta ON qbta.answer_uid = qr.uid" + + " LEFT JOIN lams_qb_tool_question AS qbtq USING (tool_question_uid)" + + " LEFT JOIN lams_qb_question AS qbq ON qbq.uid = qbtq.qb_question_uid" + + " LEFT JOIN tl_laasse10_option_answer AS oa" + + " ON oa.question_result_uid = qr.uid" + + " AND ((qbq.type = 7 AND qr.mark > 0) OR (qbta.answer IS NOT NULL AND TRIM(qbta.answer) <> '')" + + " OR oa.answer_boolean IS NULL OR oa.answer_boolean = 1 OR answer_int <> -1)" + " WHERE ar.latest = 1" + " AND (a.use_select_leader_tool_ouput = 0 OR s.group_leader_uid = ar.user_uid)" - + " AND ((qbq.type = 7 AND qr.mark > 0) OR (qbta.answer IS NOT NULL AND TRIM(qbta.answer) <> '') " - + " OR oa.answer_boolean IS NULL OR oa.answer_boolean = 1 OR answer_int <> -1)" + " AND a.content_id = :toolContentId" - + " GROUP BY qr.uid, ar.user_uid) AS answered_questions " - + " GROUP BY user_uid) AS answered_questions_by_user_count " - + "GROUP BY answered_question_count"; + + " GROUP BY qr.uid, u.user_id) AS answered_questions GROUP BY user_id ORDER BY user_name"; @Override @SuppressWarnings("unchecked") @@ -377,14 +377,15 @@ @Override @SuppressWarnings("unchecked") - public Map countAnsweredQuestionsByUsers(long toolContentId) { - Map answeredQuestions = new TreeMap<>(); - List results = getSession().createNativeQuery(ANSWERED_QUESTIONS_BY_USER_COUNT) + public Map> getAnsweredQuestionsByUsers(long toolContentId) { + List results = getSession().createNativeQuery(ANSWERED_QUESTIONS_BY_USER) .setParameter("toolContentId", toolContentId).getResultList(); - for (Object[] result : results) { - answeredQuestions.put(((Number) result[0]).intValue(), ((Number) result[1]).intValue()); - } - return answeredQuestions; + return results.stream() + .collect( + Collectors.groupingBy(r -> ((Number) r[3]).intValue(), + Collectors.mapping(r -> new String[] { r[0].toString(), + r[1] == null ? null : r[1].toString(), r[2] == null ? "" : r[2].toString() }, + Collectors.toList()))); } private List convertResultsToAssessmentUserDTOList(List list) { Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -ra48dabb57cf3cecf938cb394515b1e388d350ddf -rff9b1c659da59060b4690a29bfdd493e8741b224 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision a48dabb57cf3cecf938cb394515b1e388d350ddf) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision ff9b1c659da59060b4690a29bfdd493e8741b224) @@ -4018,8 +4018,8 @@ } @Override - public Map getCountAnsweredQuestionsByUsers(long toolContentId) { - Map answeredQuestions = assessmentResultDao.countAnsweredQuestionsByUsers(toolContentId); + public Map> getAnsweredQuestionsByUsers(long toolContentId) { + Map> answeredQuestions = assessmentResultDao.getAnsweredQuestionsByUsers(toolContentId); if (answeredQuestions.isEmpty()) { return answeredQuestions; } @@ -4028,9 +4028,9 @@ int questionCount = assessment.getQuestions().size(); // list all question counts, from 0 to maximum possible questions - Map result = new HashMap<>(); + Map> result = new TreeMap<>(); for (int i = 0; i <= questionCount; i++) { - result.put(i, 0); + result.put(i, List.of()); } result.putAll(answeredQuestions); return result; Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/IAssessmentService.java =================================================================== diff -u -re194f22014c7cbffaf5168f1982aa89e7c34c34d -rff9b1c659da59060b4690a29bfdd493e8741b224 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/IAssessmentService.java (.../IAssessmentService.java) (revision e194f22014c7cbffaf5168f1982aa89e7c34c34d) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/IAssessmentService.java (.../IAssessmentService.java) (revision ff9b1c659da59060b4690a29bfdd493e8741b224) @@ -539,7 +539,7 @@ List getPossibleIndividualTimeLimitUsers(long toolContentId, String searchString); - Map getCountAnsweredQuestionsByUsers(long toolContentId); + Map> getAnsweredQuestionsByUsers(long toolContentId); void changeLeaderForGroup(long toolSessionId, long leaderUserId); } \ No newline at end of file Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java =================================================================== diff -u -r9416dfbb422c46b7ee5f22c1cab3db64ef29570a -rff9b1c659da59060b4690a29bfdd493e8741b224 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java (.../MonitoringController.java) (revision 9416dfbb422c46b7ee5f22c1cab3db64ef29570a) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java (.../MonitoringController.java) (revision ff9b1c659da59060b4690a29bfdd493e8741b224) @@ -270,9 +270,12 @@ chartJson.put("completedLearners", service.getCountLearnersWithFinishedCurrentAttempt(toolContentId)); chartJson.put("sessionCount", service.getSessionsByContentId(toolContentId).size()); - Map answeredQuestionsByUsers = service.getCountAnsweredQuestionsByUsers(toolContentId); + Map> answeredQuestionsByUsers = service.getAnsweredQuestionsByUsers(toolContentId); if (!answeredQuestionsByUsers.isEmpty()) { chartJson.set("answeredQuestionsByUsers", JsonUtil.readObject(answeredQuestionsByUsers)); + Map answeredQuestionsByUsersCount = answeredQuestionsByUsers.entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().size())); + chartJson.set("answeredQuestionsByUsersCount", JsonUtil.readObject(answeredQuestionsByUsersCount)); } response.setContentType("application/json;charset=utf-8"); Index: lams_tool_assessment/web/includes/javascript/chart.js =================================================================== diff -u -rb50f7a1c4696df0cd6f88b74ee49fa5472061e0d -rff9b1c659da59060b4690a29bfdd493e8741b224 --- lams_tool_assessment/web/includes/javascript/chart.js (.../chart.js) (revision b50f7a1c4696df0cd6f88b74ee49fa5472061e0d) +++ lams_tool_assessment/web/includes/javascript/chart.js (.../chart.js) (revision ff9b1c659da59060b4690a29bfdd493e8741b224) @@ -113,10 +113,12 @@ return; } - var newData = Object.values(data.answeredQuestionsByUsers); + // store current data for custom tooltip + $('#answered-questions-chart').data('tooltip-input', data.answeredQuestionsByUsers); + if (answeredQuestionsChart != null) { // chart already exists, just update data - answeredQuestionsChart.data.datasets[0].data = newData; + answeredQuestionsChart.data.datasets[0].data = Object.values(data.answeredQuestionsByUsersCount); answeredQuestionsChart.update(); return; } @@ -126,11 +128,11 @@ type : 'bar', data : { datasets : [ { - data : newData, + data : Object.values(data.answeredQuestionsByUsersCount), backgroundColor : 'rgba(255, 195, 55, 1)' } ], - labels : Object.keys(data.answeredQuestionsByUsers), + labels : Object.keys(data.answeredQuestionsByUsersCount), }, options : { layout : { @@ -176,6 +178,69 @@ } } ] + }, + tooltips : { + enabled : false, + custom : function(tooltipModel) { + // always remove the tooltip at the beginning + $('.answered-questions-chart-tooltip').remove(); + if (tooltipModel.opacity === 0) { + // if it should be hidden, there is nothing to do + return; + } + + // create tooltip + var tooltipEl = $('
').addClass('answered-questions-chart-tooltip') + .appendTo(document.body) + .css({ + 'opacity' : 1, + 'position' :'absolute', + 'pointerEvents' : 'none', + 'background-color' : 'white', + 'padding' : '15px', + 'border' : 'thin solid #ddd', + 'border-radius' : '25px' + }); + + // iterate over learners, get their names and portraits + var counter = 0, + users = $('#answered-questions-chart').data('tooltip-input')[tooltipModel.dataPoints[0].label]; + $(users).each(function(){ + var portraitDiv = $(definePortrait(this[1], this[0], STYLE_SMALL, true, LAMS_URL)).css({ + 'vertical-align' : 'middle' + }), + userDiv = $('
').append(portraitDiv).appendTo(tooltipEl).css({ + 'padding-bottom' : '5px' + }); + $('').text(this[2]).appendTo(userDiv).css({ + 'padding-left' : '10px' + }); + + if (counter === 15) { + // do not display more than 15 learners + if (users.length > 16) { + $('
').text('...').appendTo(tooltipEl).css({ + 'font-weight' : 'bold', + 'font-size' : '20px', + 'text-align' : 'center' + }); + } + return false; + } + counter++; + }); + + // do not add padding for the last element as the tooltip does not look symmetric + tooltipEl.children(':last-child').css({ + 'padding-bottom' : '0' + }); + + var position = this._chart.canvas.getBoundingClientRect(); + tooltipEl.css({ + 'left' : position.left + window.pageXOffset + tooltipModel.caretX - tooltipEl.width() - 60 + 'px', + 'top' : Math.max(10, position.top + window.pageYOffset + tooltipModel.caretY - tooltipEl.height()/2) + 'px', + }); + } } } }); Index: lams_tool_assessment/web/pages/monitoring/monitoring.jsp =================================================================== diff -u -r9dcc3e2d26b504f92d919f53f0d45dd4093a82a2 -rff9b1c659da59060b4690a29bfdd493e8741b224 --- lams_tool_assessment/web/pages/monitoring/monitoring.jsp (.../monitoring.jsp) (revision 9dcc3e2d26b504f92d919f53f0d45dd4093a82a2) +++ lams_tool_assessment/web/pages/monitoring/monitoring.jsp (.../monitoring.jsp) (revision ff9b1c659da59060b4690a29bfdd493e8741b224) @@ -38,7 +38,8 @@ // avoid name clash between bootstrap and jQuery UI $.fn.bootstrapTooltip = $.fn.tooltip.noConflict(); - var WEB_APP_URL = '', + var LAMS_URL = '', + WEB_APP_URL = '', LABELS = { ACTIVITY_COMPLETION_CHART_TITLE : '',