Index: lams_central/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r5bc88174180046cd153cfc46a0c8ef13675bd4bf -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 5bc88174180046cd153cfc46a0c8ef13675bd4bf) +++ lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -326,6 +326,8 @@ label.questions.choice.select.all = Select all label.questions.choice.missing = Please check at least one question. label.questions.choice.none.found = No questions available +label.questions.choice.generate.more = Generate more questions +label.questions.choice.generate.more.error = Error while generating more questions index.single.activity.lesson.title = Add single activity lesson index.single.activity.lesson.desc = or one-click activity: label.disable.lesson.sorting = Disable lesson sorting Index: lams_monitoring/lams_monitoring.eml =================================================================== diff -u -re7b29886c3fc2a5edef72022a8df8b1833ac6b47 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_monitoring/lams_monitoring.eml (.../lams_monitoring.eml) (revision e7b29886c3fc2a5edef72022a8df8b1833ac6b47) +++ lams_monitoring/lams_monitoring.eml (.../lams_monitoring.eml) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -1,17 +1,21 @@ - + + + + + - + - + - + - + Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/AssessmentResultDAO.java =================================================================== diff -u -r3ef9ae27663556230437c96a76b34599ca104a18 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/AssessmentResultDAO.java (.../AssessmentResultDAO.java) (revision 3ef9ae27663556230437c96a76b34599ca104a18) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/AssessmentResultDAO.java (.../AssessmentResultDAO.java) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -22,13 +22,13 @@ package org.lamsfoundation.lams.tool.assessment.dao; -import java.util.List; -import java.util.Map; - import org.lamsfoundation.lams.tool.assessment.dto.AssessmentUserDTO; import org.lamsfoundation.lams.tool.assessment.model.AssessmentResult; import org.lamsfoundation.lams.tool.assessment.model.AssessmentUser; +import java.util.List; +import java.util.Map; + public interface AssessmentResultDAO extends DAO { List getAssessmentResults(Long assessmentUid, Long userId); @@ -81,8 +81,6 @@ */ List getLastFinishedAssessmentResults(Long contentId); - int countLastFinishedAssessmentResults(long contentId); - List getLastFinishedAssessmentResultsBySession(Long sessionId); /** @@ -107,5 +105,7 @@ */ int countAttemptsPerOption(Long toolContentId, Long optionUid); - Map> getAnsweredQuestionsByUsers(long toolContentId); + Map> getAnsweredQuestionsByUsersForCompletionChart(long toolContentId); + + List getLearnersWithFinishedCurrentAttemptForCompletionChart(Long contentId); } \ No newline at end of file Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/AssessmentUserDAO.java =================================================================== diff -u -r97302bfd38611772c8baaf1900f7fcbc87024f6b -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/AssessmentUserDAO.java (.../AssessmentUserDAO.java) (revision 97302bfd38611772c8baaf1900f7fcbc87024f6b) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/AssessmentUserDAO.java (.../AssessmentUserDAO.java) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -47,10 +47,12 @@ int getCountUsersBySession(Long sessionId, String searchString); List getPagedUsersByContentId(Long contentId, int page, int size, String sortBy, - String sortOrder, String searchString, IUserManagementService userManagementService); + String sortOrder, String searchString, IUserManagementService userManagementService); - int getCountUsersByContentId(Long contentId, String searchString); + int getCountLearnersByContentId(Long contentId, String searchString); + List getLearnersByContentIdForCompletionChart(Long contentId); + List getPagedUsersBySessionAndQuestion(Long sessionId, Long questionUid, int page, int size, String sortBy, String sortOrder, String searchString, IUserManagementService userManagementService); Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/hibernate/AssessmentResultDAOHibernate.java =================================================================== diff -u -r3ef9ae27663556230437c96a76b34599ca104a18 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/hibernate/AssessmentResultDAOHibernate.java (.../AssessmentResultDAOHibernate.java) (revision 3ef9ae27663556230437c96a76b34599ca104a18) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/hibernate/AssessmentResultDAOHibernate.java (.../AssessmentResultDAOHibernate.java) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -22,11 +22,6 @@ package org.lamsfoundation.lams.tool.assessment.dao.hibernate; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import org.apache.commons.lang.StringUtils; import org.hibernate.query.NativeQuery; import org.hibernate.query.Query; @@ -45,27 +40,32 @@ import org.lamsfoundation.lams.usermanagement.User; import org.springframework.stereotype.Repository; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + @Repository public class AssessmentResultDAOHibernate extends LAMSBaseDAO implements AssessmentResultDAO { - private static final String FIND_LAST_BY_ASSESSMENT = "FROM " + AssessmentResult.class.getName() - + " AS r WHERE r.assessment.uid=:assessmentUid AND r.latest=1"; + private static final String FIND_LAST_BY_ASSESSMENT = + "FROM " + AssessmentResult.class.getName() + " AS r WHERE r.assessment.uid=:assessmentUid AND r.latest=1"; private static final String FIND_LAST_BY_ASSESSMENT_AND_USER = "FROM " + AssessmentResult.class.getName() + " AS r WHERE r.user.userId =:userId AND r.assessment.uid=:assessmentUid AND r.latest=1"; - private static final String FIND_WHETHER_LAST_RESULT_FINISHED = "SELECT COUNT(*) > 0 FROM " - + AssessmentResult.class.getName() - + " AS r WHERE r.user.userId =:userId AND r.assessment.uid=:assessmentUid AND r.latest=1 AND r.finishDate != null"; + private static final String FIND_WHETHER_LAST_RESULT_FINISHED = + "SELECT COUNT(*) > 0 FROM " + AssessmentResult.class.getName() + + " AS r WHERE r.user.userId =:userId AND r.assessment.uid=:assessmentUid AND r.latest=1 AND r.finishDate != null"; private static final String FIND_BY_ASSESSMENT_AND_USER_AND_FINISHED = "FROM " + AssessmentResult.class.getName() + " AS r WHERE r.user.userId = ? AND r.assessment.uid=? AND (r.finishDate != null) ORDER BY r.startDate ASC"; private static final String FIND_LAST_FINISHED_BY_ASSESSMENT_AND_USER = "FROM " + AssessmentResult.class.getName() + " AS r WHERE r.user.userId = :userId AND r.assessment.uid=:assessmentUid AND (r.finishDate != null) AND r.latest=1"; - private static final String FIND_BY_SESSION_AND_USER = "FROM " + AssessmentResult.class.getName() - + " AS r WHERE r.user.userId = ? AND r.sessionId=?"; + private static final String FIND_BY_SESSION_AND_USER = + "FROM " + AssessmentResult.class.getName() + " AS r WHERE r.user.userId = ? AND r.sessionId=?"; private static final String FIND_BY_SESSION_AND_USER_AND_FINISHED = "FROM " + AssessmentResult.class.getName() + " AS r WHERE r.user.userId = ? AND r.sessionId=? AND (r.finishDate != null) ORDER BY r.startDate ASC"; @@ -76,73 +76,101 @@ private static final String FIND_LAST_FINISHED_RESULTS_BY_CONTENT_ID = "FROM " + AssessmentResult.class.getName() + " AS r WHERE r.assessment.contentId = :contentId AND (r.finishDate != null) AND r.latest = 1"; - private static final String FIND_ASSESSMENT_RESULT_COUNT_BY_ASSESSMENT_AND_USER = "select COUNT(*) FROM " - + AssessmentResult.class.getName() - + " AS r WHERE r.user.userId=? AND r.assessment.uid=? AND (r.finishDate != null)"; + private static final String FIND_ASSESSMENT_RESULT_COUNT_BY_ASSESSMENT_AND_USER = + "select COUNT(*) FROM " + AssessmentResult.class.getName() + + " AS r WHERE r.user.userId=? AND r.assessment.uid=? AND (r.finishDate != null)"; - private static final String IS_ASSESSMENT_RESULT_EXIST_BY_ASSESSMENT = "select COUNT(*) > 0 FROM " - + AssessmentResult.class.getName() + " AS r WHERE r.assessment.uid=:assessmentUid"; + private static final String IS_ASSESSMENT_RESULT_EXIST_BY_ASSESSMENT = + "select COUNT(*) > 0 FROM " + AssessmentResult.class.getName() + + " AS r WHERE r.assessment.uid=:assessmentUid"; private static final String LAST_ASSESSMENT_RESULT_GRADE = "select r.grade FROM " + AssessmentResult.class.getName() + " AS r WHERE r.user.userId=:userId AND r.assessment.uid=:assessmentUid AND (r.finishDate != null) AND r.latest=1"; - private static final String LAST_ASSESSMENT_RESULT_GRADES_BY_CONTENT_ID = "select r.user.userId, r.grade FROM " - + AssessmentResult.class.getName() - + " AS r WHERE r.assessment.contentId=? AND (r.finishDate != null) AND r.latest=1"; + private static final String LAST_ASSESSMENT_RESULT_GRADES_BY_CONTENT_ID = + "select r.user.userId, r.grade FROM " + AssessmentResult.class.getName() + + " AS r WHERE r.assessment.contentId=? AND (r.finishDate != null) AND r.latest=1"; - private static final String BEST_SCORE_BY_SESSION_AND_USER = "SELECT MAX(r.grade) FROM " - + AssessmentResult.class.getName() - + " AS r WHERE r.user.userId = :userId AND r.sessionId=:sessionId AND (r.finishDate != null)"; + private static final String BEST_SCORE_BY_SESSION_AND_USER = + "SELECT MAX(r.grade) FROM " + AssessmentResult.class.getName() + + " AS r WHERE r.user.userId = :userId AND r.sessionId=:sessionId AND (r.finishDate != null)"; - private static final String BEST_SCORES_BY_CONTENT_ID = "SELECT r.user.userId, MAX(r.grade) FROM " - + AssessmentResult.class.getName() - + " AS r WHERE r.assessment.contentId=? AND (r.finishDate != null) GROUP BY r.user.userId"; + private static final String BEST_SCORES_BY_CONTENT_ID = + "SELECT r.user.userId, MAX(r.grade) FROM " + AssessmentResult.class.getName() + + " AS r WHERE r.assessment.contentId=? AND (r.finishDate != null) GROUP BY r.user.userId"; - private static final String FIRST_SCORE_BY_SESSION_AND_USER = "SELECT r.grade FROM " - + AssessmentResult.class.getName() - + " AS r WHERE r.user.userId = :userId AND r.sessionId=:sessionId AND (r.finishDate != null) ORDER BY r.startDate ASC"; + private static final String FIRST_SCORE_BY_SESSION_AND_USER = + "SELECT r.grade FROM " + AssessmentResult.class.getName() + + " AS r WHERE r.user.userId = :userId AND r.sessionId=:sessionId AND (r.finishDate != null) ORDER BY r.startDate ASC"; - private static final String AVERAGE_SCORE_BY_SESSION_AND_USER = "SELECT AVG(r.grade) FROM " - + AssessmentResult.class.getName() - + " AS r WHERE r.user.userId = :userId AND r.sessionId=:sessionId AND (r.finishDate != null)"; + private static final String AVERAGE_SCORE_BY_SESSION_AND_USER = + "SELECT AVG(r.grade) FROM " + AssessmentResult.class.getName() + + " AS r WHERE r.user.userId = :userId AND r.sessionId=:sessionId AND (r.finishDate != null)"; - private static final String AVERAGE_SCORES_BY_CONTENT_ID = "SELECT r.user.userId, AVG(r.grade) FROM " - + AssessmentResult.class.getName() - + " AS r WHERE r.assessment.contentId=? AND (r.finishDate != null) GROUP BY r.user.userId"; + private static final String AVERAGE_SCORES_BY_CONTENT_ID = + "SELECT r.user.userId, AVG(r.grade) FROM " + AssessmentResult.class.getName() + + " AS r WHERE r.assessment.contentId=? AND (r.finishDate != null) GROUP BY r.user.userId"; - private static final String FIND_LAST_ASSESSMENT_RESULT_TIME_TAKEN = "select UNIX_TIMESTAMP(r.finishDate) - UNIX_TIMESTAMP(r.startDate) FROM " - + AssessmentResult.class.getName() - + " AS r WHERE r.user.userId=? AND r.assessment.uid=? AND (r.finishDate != null) AND r.latest=1"; + private static final String FIND_LAST_ASSESSMENT_RESULT_TIME_TAKEN = + "select UNIX_TIMESTAMP(r.finishDate) - UNIX_TIMESTAMP(r.startDate) FROM " + AssessmentResult.class.getName() + + " AS r WHERE r.user.userId=? AND r.assessment.uid=? AND (r.finishDate != null) AND r.latest=1"; private static final String FIND_BY_UID = "FROM " + AssessmentResult.class.getName() + " AS r WHERE r.uid = ?"; - private static final String ANSWERED_QUESTIONS_BY_USER = "SELECT user_id, portrait_uuid, user_name, group_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 u.user_id, BIN_TO_UUID(u.portrait_uuid) AS portrait_uuid, CONCAT(u.first_name, ' ', u.last_name) AS user_name," - + " IF(a.use_select_leader_tool_ouput, s.session_name, NULL) AS group_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" - + " 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 (qbq.type = 2 AND answer_int <> -1))" - + " WHERE ar.latest = 1" - + " AND (a.use_select_leader_tool_ouput = 0 OR s.group_leader_uid = ar.user_uid)" - + " AND a.content_id = :toolContentId" - + " GROUP BY qr.uid, u.user_id) AS answered_questions GROUP BY user_id ORDER BY user_name"; + private static final String ANSWERED_QUESTIONS_BY_USER = + "SELECT user_id, portrait_uuid, user_name, group_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 u.user_id, BIN_TO_UUID(u.portrait_uuid) AS portrait_uuid, CONCAT(u.first_name, ' ', u.last_name) AS user_name," + + " s.session_name AS group_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" + + " 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 (qbq.type = 2 AND answer_int <> -1))" + + " WHERE ar.latest = 1" + + " AND (a.use_select_leader_tool_ouput = 0 OR s.group_leader_uid = ar.user_uid)" + + " AND a.content_id = :toolContentId" + + " GROUP BY qr.uid, u.user_id) AS answered_questions GROUP BY user_id ORDER BY user_name"; + private static final String FINISHED_LEARNERS_FOR_COMPLETION_CHART = + "SELECT user_id, portrait_uuid, user_name, group_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 u.user_id, BIN_TO_UUID(u.portrait_uuid) AS portrait_uuid, CONCAT(u.first_name, ' ', u.last_name) AS user_name," + + " s.session_name AS group_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" + + " 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 (qbq.type = 2 AND answer_int <> -1))" + + " WHERE ar.latest = 1" + + " AND (a.use_select_leader_tool_ouput = 0 OR s.group_leader_uid = ar.user_uid)" + + " AND a.content_id = :toolContentId" + + " GROUP BY qr.uid, u.user_id) AS answered_questions GROUP BY user_id ORDER BY user_name"; + @Override @SuppressWarnings("unchecked") public List getAssessmentResults(Long assessmentUid, Long userId) { @@ -170,13 +198,15 @@ + " AS a JOIN a.learningDesign.lessons AS l " + "WHERE qr.assessmentResult.uid = r.uid AND a.toolContentId = r.assessment.contentId " + "AND (l.organisation.organisationId = :organisationId OR " - + " l.organisation.parentOrganisation.organisationId = :organisationId" - + (parentOrganisation == null ? "" + + " l.organisation.parentOrganisation.organisationId = :organisationId" + ( + parentOrganisation == null + ? "" : " OR l.organisation.organisationId = :parentOrganisationId OR " + "l.organisation.parentOrganisation.organisationId = :parentOrganisationId") - + ") AND qr.qbToolQuestion.qbQuestion.uid = :qbQuestionUid AND " - + (qbToolQuestion.getQbQuestion().isExactMatch() ? "TRIM(qr.answer)" - : "REGEXP_REPLACE(qr.answer, '" + QbUtils.VSA_ANSWER_NORMALISE_SQL_REG_EXP + "', '')") + + ") AND qr.qbToolQuestion.qbQuestion.uid = :qbQuestionUid AND " + (qbToolQuestion.getQbQuestion() + .isExactMatch() + ? "TRIM(qr.answer)" + : "REGEXP_REPLACE(qr.answer, '" + QbUtils.VSA_ANSWER_NORMALISE_SQL_REG_EXP + "', '')") + " = :answer ORDER BY r.startDate ASC"; Query q = getSession().createQuery(FIND_BY_QBQUESTION, AssessmentResult.class); @@ -272,12 +302,12 @@ @Override public List getFirstTotalScoresByContentId(Long toolContentId) { - final String FIRST_SCORES_BY_CONTENT_ID = "SELECT user.user_id, res.grade " - + "FROM tl_laasse10_assessment_result AS res " - + "JOIN tl_laasse10_user AS user ON res.user_uid = user.uid " - + "JOIN tl_laasse10_assessment AS assess ON res.assessment_uid = assess.uid AND assess.content_id = :contentId " - + "INNER JOIN (SELECT user_uid, MIN(start_date) AS startDate FROM tl_laasse10_assessment_result WHERE finish_date IS NOT NULL GROUP BY user_uid) firstRes " - + "ON (res.user_uid = firstRes.user_uid AND res.start_date = firstRes.startDate) GROUP BY res.user_uid"; + final String FIRST_SCORES_BY_CONTENT_ID = + "SELECT user.user_id, res.grade " + "FROM tl_laasse10_assessment_result AS res " + + "JOIN tl_laasse10_user AS user ON res.user_uid = user.uid " + + "JOIN tl_laasse10_assessment AS assess ON res.assessment_uid = assess.uid AND assess.content_id = :contentId " + + "INNER JOIN (SELECT user_uid, MIN(start_date) AS startDate FROM tl_laasse10_assessment_result WHERE finish_date IS NOT NULL GROUP BY user_uid) firstRes " + + "ON (res.user_uid = firstRes.user_uid AND res.start_date = firstRes.startDate) GROUP BY res.user_uid"; NativeQuery query = getSession().createNativeQuery(FIRST_SCORES_BY_CONTENT_ID); query.setParameter("contentId", toolContentId); @@ -328,25 +358,19 @@ } @Override - public int countLastFinishedAssessmentResults(long contentId) { - return ((Long) getSession().createQuery("SELECT COUNT(*) " + FIND_LAST_FINISHED_RESULTS_BY_CONTENT_ID) - .setParameter("contentId", contentId).uniqueResult()).intValue(); - } - - @Override public List getLastFinishedAssessmentResultsBySession(Long sessionId) { - final String FIND_LAST_FINISHED_RESULTS_BY_SESSION_ID = "SELECT r, u.portraitUuid FROM " - + AssessmentResult.class.getName() + " AS r, " + User.class.getName() - + " as u WHERE r.sessionId=? AND (r.finishDate != null) AND r.latest=1 AND u.userId=r.user.userId"; + final String FIND_LAST_FINISHED_RESULTS_BY_SESSION_ID = + "SELECT r, u.portraitUuid FROM " + AssessmentResult.class.getName() + " AS r, " + User.class.getName() + + " as u WHERE r.sessionId=? AND (r.finishDate != null) AND r.latest=1 AND u.userId=r.user.userId"; return doFind(FIND_LAST_FINISHED_RESULTS_BY_SESSION_ID, new Object[] { sessionId }); } @Override public List getLeadersLastFinishedAssessmentResults(Long contentId) { - final String FIND_LAST_FINISHED_RESULTS_BY_SESSION_ID = "SELECT r, u.portraitUuid FROM " - + AssessmentResult.class.getName() + " AS r, " + User.class.getName() - + " as u WHERE r.user=r.user.session.groupLeader AND r.assessment.contentId=? AND (r.finishDate != null) AND r.latest=1 AND u.userId=r.user.userId"; + final String FIND_LAST_FINISHED_RESULTS_BY_SESSION_ID = + "SELECT r, u.portraitUuid FROM " + AssessmentResult.class.getName() + " AS r, " + User.class.getName() + + " as u WHERE r.user=r.user.session.groupLeader AND r.assessment.contentId=? AND (r.finishDate != null) AND r.latest=1 AND u.userId=r.user.userId"; return doFind(FIND_LAST_FINISHED_RESULTS_BY_SESSION_ID, new Object[] { contentId }); } @@ -362,6 +386,13 @@ } @Override + public List getLearnersWithFinishedCurrentAttemptForCompletionChart(Long contentId) { + List results = getSession().createNativeQuery(FINISHED_LEARNERS_FOR_COMPLETION_CHART) + .setParameter("toolContentId", contentId).getResultList(); + return results; + } + + @Override public boolean isAssessmentAttempted(Long assessmentUid) { Query q = getSession().createQuery(IS_ASSESSMENT_RESULT_EXIST_BY_ASSESSMENT, Boolean.class); q.setParameter("assessmentUid", assessmentUid); @@ -395,15 +426,13 @@ @Override @SuppressWarnings("unchecked") - public Map> getAnsweredQuestionsByUsers(long toolContentId) { + public Map> getAnsweredQuestionsByUsersForCompletionChart(long toolContentId) { List results = getSession().createNativeQuery(ANSWERED_QUESTIONS_BY_USER) .setParameter("toolContentId", toolContentId).getResultList(); - return results.stream() - .collect(Collectors.groupingBy(r -> ((Number) r[4]).intValue(), - Collectors.mapping( - r -> new String[] { r[0].toString(), r[1] == null ? null : r[1].toString(), - r[2] == null ? "" : r[2].toString(), r[3] == null ? null : r[3].toString() }, - Collectors.toList()))); + return results.stream().collect(Collectors.groupingBy(r -> ((Number) r[4]).intValue(), Collectors.mapping( + r -> new String[] { r[0].toString(), r[1] == null ? null : r[1].toString(), + r[2] == null ? "" : r[2].toString(), r[3] == null ? null : r[3].toString() }, + Collectors.toList()))); } private List convertResultsToAssessmentUserDTOList(List list) { Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/hibernate/AssessmentUserDAOHibernate.java =================================================================== diff -u -r97302bfd38611772c8baaf1900f7fcbc87024f6b -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/hibernate/AssessmentUserDAOHibernate.java (.../AssessmentUserDAOHibernate.java) (revision 97302bfd38611772c8baaf1900f7fcbc87024f6b) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dao/hibernate/AssessmentUserDAOHibernate.java (.../AssessmentUserDAOHibernate.java) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -38,13 +38,20 @@ import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; import org.springframework.stereotype.Repository; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + @Repository public class AssessmentUserDAOHibernate extends LAMSBaseDAO implements AssessmentUserDAO { - private static final String FIND_BY_USER_ID_SESSION_ID = "from " + AssessmentUser.class.getName() - + " as u where u.userId =? and u.session.sessionId=?"; - private static final String FIND_BY_SESSION_ID = "from " + AssessmentUser.class.getName() - + " as u where u.session.sessionId=?"; + private static final String FIND_BY_USER_ID_SESSION_ID = + "from " + AssessmentUser.class.getName() + " as u where u.userId =? and u.session.sessionId=?"; + private static final String FIND_BY_SESSION_ID = + "from " + AssessmentUser.class.getName() + " as u where u.session.sessionId=?"; + private static final String FIND_LEARNERS_BY_CONTENT_ID = "FROM " + AssessmentUser.class.getName() + " user" + + " WHERE user.session.assessment.contentId = :contentId "; private static final String LOAD_MARKS_FOR_SESSION = "SELECT grade FROM tl_laasse10_assessment_result " + " WHERE finish_date IS NOT NULL AND latest = 1 AND session_id = :sessionId"; @@ -75,6 +82,15 @@ + "ON result.user_uid = user.uid AND result.finish_date IS NOT NULL AND result.latest = 1 " + "WHERE assessment.content_id = :toolContentId "; + private static final String FIND_LEARNERS_BY_CONTENT_ID_FOR_COMPLETION_CHART = + "SELECT u.user_id, BIN_TO_UUID(u.portrait_uuid) AS portrait_uuid, " + + " CONCAT(u.first_name, ' ', u.last_name) AS user_name," + " s.session_name AS group_name" + + " FROM tl_laasse10_assessment AS a" + + " JOIN tl_laasse10_session AS s ON s.assessment_uid = a.uid" + + " JOIN tl_laasse10_user AS au ON au.session_uid = s.uid" + + " JOIN lams_user AS u USING (user_id)" + + " WHERE a.content_id = :toolContentId ORDER BY user_name"; + @SuppressWarnings("rawtypes") @Override public AssessmentUser getUserByUserIDAndSessionID(Long userID, Long sessionId) { @@ -88,8 +104,8 @@ @SuppressWarnings("rawtypes") @Override public AssessmentUser getUserCreatedAssessment(Long userId, Long contentId) { - final String FIND_BY_USER_ID_CONTENT_ID = "from " + AssessmentUser.class.getName() - + " as u where u.userId =? and u.assessment.contentId=?"; + final String FIND_BY_USER_ID_CONTENT_ID = + "from " + AssessmentUser.class.getName() + " as u where u.userId =? and u.assessment.contentId=?"; List list = doFind(FIND_BY_USER_ID_CONTENT_ID, new Object[] { userId, contentId }); if (list == null || list.size() == 0) { @@ -244,7 +260,7 @@ } @Override - public int getCountUsersByContentId(Long contentId, String searchString) { + public int getCountLearnersByContentId(Long contentId, String searchString) { String LOAD_USERS_ORDERED_BY_NAME = "SELECT COUNT(*) FROM " + AssessmentUser.class.getName() + " user" + " WHERE user.session.assessment.contentId = :contentId"; if (StringUtils.isNotBlank(searchString)) { @@ -259,21 +275,29 @@ return query.uniqueResult().intValue(); } + @Override + public List getLearnersByContentIdForCompletionChart(Long contentId) { + return getSession().createNativeQuery(FIND_LEARNERS_BY_CONTENT_ID_FOR_COMPLETION_CHART) + .setParameter("toolContentId", contentId).getResultList(); + } + private static String LOAD_USERS_ORDERED_BY_SESSION_QUESTION_SELECT = "SELECT DISTINCT question_result.uid, user.last_name, user.first_name, user.login_name, question_result.mark"; private static String LOAD_USERS_ORDERED_BY_SESSION_QUESTION_FROM = " FROM tl_laasse10_user user"; - private static String LOAD_USERS_ORDERED_BY_SESSION_QUESTION_JOIN = " INNER JOIN tl_laasse10_session session" - + " ON user.session_uid=session.uid" + + private static String LOAD_USERS_ORDERED_BY_SESSION_QUESTION_JOIN = + " INNER JOIN tl_laasse10_session session" + " ON user.session_uid=session.uid" + - " LEFT OUTER JOIN tl_laasse10_assessment_result result " + " ON result.user_uid = user.uid" - + " AND result.finish_date IS NOT NULL" + " AND result.latest = 1" + + " LEFT OUTER JOIN tl_laasse10_assessment_result result " + " ON result.user_uid = user.uid" + + " AND result.finish_date IS NOT NULL" + " AND result.latest = 1" + - " INNER JOIN lams_qb_tool_answer qbToolAnswer " + " ON qbToolAnswer.tool_question_uid = :questionUid " + + " INNER JOIN lams_qb_tool_answer qbToolAnswer " + + " ON qbToolAnswer.tool_question_uid = :questionUid " + - " INNER JOIN tl_laasse10_question_result question_result " + " ON result.uid=question_result.result_uid" - + " AND question_result.uid = qbToolAnswer.answer_uid" + + " INNER JOIN tl_laasse10_question_result question_result " + + " ON result.uid=question_result.result_uid" + + " AND question_result.uid = qbToolAnswer.answer_uid" + - " WHERE session.session_id = :sessionId " - + " AND (CONCAT(user.last_name, ' ', user.first_name) LIKE CONCAT('%', :searchString, '%')) "; + " WHERE session.session_id = :sessionId " + + " AND (CONCAT(user.last_name, ' ', user.first_name) LIKE CONCAT('%', :searchString, '%')) "; private static String LOAD_USERS_ORDERED_ORDER_BY_RESULT = "ORDER BY question_result.mark "; @SuppressWarnings("unchecked") Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -r6d67af7eb9c49d59b6b0ff579b472dcf7022d437 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 6d67af7eb9c49d59b6b0ff579b472dcf7022d437) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -23,10 +23,33 @@ package org.lamsfoundation.lams.tool.assessment.service; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidParameterException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.UUID; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; @@ -36,8 +59,11 @@ import org.lamsfoundation.lams.confidencelevel.VsaAnswerDTO; import org.lamsfoundation.lams.contentrepository.client.IToolContentHandler; import org.lamsfoundation.lams.events.IEventNotificationService; +import org.lamsfoundation.lams.flux.FluxMap; +import org.lamsfoundation.lams.flux.FluxRegistry; import org.lamsfoundation.lams.learning.service.ILearnerService; import org.lamsfoundation.lams.learningdesign.Group; +import org.lamsfoundation.lams.learningdesign.Group; import org.lamsfoundation.lams.learningdesign.Grouping; import org.lamsfoundation.lams.learningdesign.ToolActivity; import org.lamsfoundation.lams.learningdesign.service.ExportToolContentException; @@ -55,15 +81,46 @@ import org.lamsfoundation.lams.outcome.OutcomeMapping; import org.lamsfoundation.lams.outcome.service.IOutcomeService; import org.lamsfoundation.lams.qb.QbUtils; -import org.lamsfoundation.lams.qb.model.*; +import org.lamsfoundation.lams.qb.model.QbCollection; +import org.lamsfoundation.lams.qb.model.QbOption; +import org.lamsfoundation.lams.qb.model.QbQuestion; +import org.lamsfoundation.lams.qb.model.QbQuestionUnit; +import org.lamsfoundation.lams.qb.model.QbToolQuestion; import org.lamsfoundation.lams.qb.service.IQbService; import org.lamsfoundation.lams.rest.RestTags; import org.lamsfoundation.lams.rest.ToolRestManager; -import org.lamsfoundation.lams.tool.*; +import org.lamsfoundation.lams.tool.ToolCompletionStatus; +import org.lamsfoundation.lams.tool.ToolContentManager; +import org.lamsfoundation.lams.tool.ToolOutput; +import org.lamsfoundation.lams.tool.ToolOutputDefinition; +import org.lamsfoundation.lams.tool.ToolSessionExportOutputData; +import org.lamsfoundation.lams.tool.ToolSessionManager; import org.lamsfoundation.lams.tool.assessment.AssessmentConstants; -import org.lamsfoundation.lams.tool.assessment.dao.*; -import org.lamsfoundation.lams.tool.assessment.dto.*; -import org.lamsfoundation.lams.tool.assessment.model.*; +import org.lamsfoundation.lams.tool.assessment.dao.AssessmentConfigDAO; +import org.lamsfoundation.lams.tool.assessment.dao.AssessmentDAO; +import org.lamsfoundation.lams.tool.assessment.dao.AssessmentQuestionDAO; +import org.lamsfoundation.lams.tool.assessment.dao.AssessmentQuestionResultDAO; +import org.lamsfoundation.lams.tool.assessment.dao.AssessmentResultDAO; +import org.lamsfoundation.lams.tool.assessment.dao.AssessmentSessionDAO; +import org.lamsfoundation.lams.tool.assessment.dao.AssessmentUserDAO; +import org.lamsfoundation.lams.tool.assessment.dto.AssessmentResultDTO; +import org.lamsfoundation.lams.tool.assessment.dto.AssessmentUserDTO; +import org.lamsfoundation.lams.tool.assessment.dto.GradeStatsDTO; +import org.lamsfoundation.lams.tool.assessment.dto.OptionDTO; +import org.lamsfoundation.lams.tool.assessment.dto.QuestionDTO; +import org.lamsfoundation.lams.tool.assessment.dto.QuestionSummary; +import org.lamsfoundation.lams.tool.assessment.dto.ReflectDTO; +import org.lamsfoundation.lams.tool.assessment.dto.UserSummary; +import org.lamsfoundation.lams.tool.assessment.dto.UserSummaryItem; +import org.lamsfoundation.lams.tool.assessment.model.Assessment; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentOptionAnswer; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestion; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestionResult; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentResult; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentSection; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentSession; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentUser; +import org.lamsfoundation.lams.tool.assessment.model.QuestionReference; import org.lamsfoundation.lams.tool.assessment.util.AnswerIntComparator; import org.lamsfoundation.lams.tool.assessment.util.AssessmentEscapeUtils; import org.lamsfoundation.lams.tool.assessment.util.AssessmentEscapeUtils.AssessmentExcelCell; @@ -78,21 +135,23 @@ import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; -import org.lamsfoundation.lams.util.*; +import org.lamsfoundation.lams.util.FileUtil; +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; +import org.springframework.web.util.UriUtils; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.security.InvalidParameterException; -import java.sql.Timestamp; -import java.time.LocalDateTime; -import java.util.*; -import java.util.function.Function; -import java.util.regex.Pattern; -import java.util.stream.Collectors; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import reactor.core.publisher.Flux; + /** * @author Andrey Balan */ @@ -146,6 +205,13 @@ private ILearnerInteractionService learnerInteractionService; + public AssessmentServiceImpl() { + FluxRegistry.initFluxMap(AssessmentConstants.COMPLETION_CHARTS_UPDATE_FLUX_NAME, + AssessmentConstants.ANSWERS_UPDATED_SINK_NAME, null, + (Long toolContentId) -> UriUtils.encode(getCompletionChartsData(toolContentId), + StandardCharsets.UTF_8.toString()), FluxMap.SHORT_THROTTLE, FluxMap.STANDARD_TIMEOUT); + } + // ******************************************************************************* // Service method // ******************************************************************************* @@ -283,11 +349,7 @@ if (lastResult == null) { return null; } - LocalDateTime launchedDate = lastResult.getTimeLimitLaunchedDate(); - if (launchedDate != null) { - return launchedDate; - } - launchedDate = LocalDateTime.now(); + LocalDateTime launchedDate = LocalDateTime.now(); lastResult.setTimeLimitLaunchedDate(launchedDate); assessmentResultDao.saveObject(lastResult); return launchedDate; @@ -312,18 +374,25 @@ } @Override + public List getPagedUsersByContentId(Long contentId, int page, int size, String sortBy, + String sortOrder, String searchString) { + return assessmentUserDao.getPagedUsersByContentId(contentId, page, size, sortBy, sortOrder, searchString, + userManagementService); + } + + @Override public int getCountUsersBySession(Long sessionId, String searchString) { return assessmentUserDao.getCountUsersBySession(sessionId, searchString); } @Override public int getCountLearnersByContentId(Long contentId) { - return assessmentUserDao.getCountLearnersByContentId(contentId); + return getCountLearnersByContentId(contentId, null); } @Override - public List getLearnersByContentId(Long contentId) { - return assessmentUserDao.getLearnersByContentId(contentId); + public int getCountLearnersByContentId(Long contentId, String searchString) { + return assessmentUserDao.getCountLearnersByContentId(contentId, searchString); } @Override @@ -345,6 +414,15 @@ * How many learners can possibly access this activity */ @Override + public int getCountLessonLearnersByContentId(long contentId) { + long lessonId = lessonService.getLessonByToolContentId(contentId).getLessonId(); + return lessonService.getCountLessonLearners(lessonId, null); + } + + /** + * How many learners can possibly access this activity + */ + @Override public ArrayNode getLessonLearnersByContentIdJson(long contentId) { long lessonId = lessonService.getLessonByToolContentId(contentId).getLessonId(); List learners = lessonService.getLessonLearners(lessonId, null, null, null, true); @@ -719,6 +797,12 @@ learnerService.createCommandForLearners(assessment.getContentId(), userIds, jsonCommand.toString()); } + if (isAnswerModified) { + // need to flush so subscribers to sink see new answers in DB + assessmentResultDao.flush(); + FluxRegistry.emit(AssessmentConstants.ANSWERS_UPDATED_SINK_NAME, assessment.getContentId()); + } + return true; } @@ -809,6 +893,11 @@ return questionResult; } + @Override + public Flux getCompletionChartsDataFlux(long toolContentId) { + return FluxRegistry.get(AssessmentConstants.COMPLETION_CHARTS_UPDATE_FLUX_NAME, toolContentId); + } + /** * @return grade that user scored by answering that question */ @@ -1146,8 +1235,8 @@ } @Override - public int countAttemptsPerOption(Long toolContentId, Long optionUid, boolean finishedAttemptsOnly) { - return assessmentResultDao.countAttemptsPerOption(toolContentId, optionUid, finishedAttemptsOnly); + public int countAttemptsPerOption(Long toolContentId, Long optionUid) { + return assessmentResultDao.countAttemptsPerOption(toolContentId, optionUid); } @Override @@ -2857,6 +2946,7 @@ QuestionReference questionReference = new QuestionReference(); questionReference.setQuestion(assessmentQuestion); questionReference.setSequenceId(displayOrder); + questionReference.setMaxMark(qbQuestion.getMaxMark()); assessmentQuestionDao.insert(questionReference); assessment.getQuestionReferences().add(questionReference); @@ -3474,7 +3564,7 @@ } AssessmentResult assessmentResult = getLastAssessmentResult(assessment.getUid(), Integer.valueOf(userId).longValue()); - return countCorrectAnswers(assessment.getUid(), user.getUid(), assessmentResult); + return countCorrectAnswers(assessment.getUid(), assessmentResult); } @Override @@ -3485,16 +3575,19 @@ Collection assessmentResults = assessmentResultDao.getLastAssessmentResults(assessmentUid); for (AssessmentResult assessmentResult : assessmentResults) { AssessmentUser user = assessmentResult.getUser(); - int count = countCorrectAnswers(assessmentUid, user.getUid(), assessmentResult); + int count = countCorrectAnswers(assessmentUid, assessmentResult); counts.put(user.getUserId().intValue(), count); } return counts; } - private int countCorrectAnswers(long assessmentUid, long userUid, AssessmentResult assessmentResult) { + private int countCorrectAnswers(long assessmentUid, AssessmentResult assessmentResult) { if (assessmentResult == null) { return 0; } + + AssessmentUser user = assessmentResult.getUser(); + long userUid = user.getUid(); int count = 0; for (AssessmentQuestionResult questionResult : assessmentResult.getQuestionResults()) { @@ -4059,9 +4152,10 @@ @Override public Grouping getGrouping(long toolContentId) { - ToolActivity toolActivity = (ToolActivity) userManagementService.findByProperty(ToolActivity.class, - "toolContentId", toolContentId).get(0); - return toolActivity.getApplyGrouping() ? toolActivity.getGrouping() : null; + ToolActivity toolActivity = assessmentDao.findByProperty(ToolActivity.class, "toolContentId", toolContentId) + .get(0); + return toolActivity.getApplyGrouping() ? assessmentDao.find(Grouping.class, + toolActivity.getGrouping().getGroupingId()) : null; } @Override @@ -4071,39 +4165,6 @@ } @Override - public Map getAnsweredQuestionsByUsersJson(long toolContentId) { - Map> answeredQuestions = assessmentResultDao.getAnsweredQuestionsByUsersForCompletionChart( - toolContentId); - Map result = new TreeMap<>(); - if (answeredQuestions.isEmpty()) { - return result; - } - - Assessment assessment = getAssessmentByContentId(toolContentId); - int questionCount = assessment.getQuestions().size(); - - // list all question counts, from 0 to maximum possible questions - - for (int i = 0; i <= questionCount; i++) { - ArrayNode learnersJson = JsonNodeFactory.instance.arrayNode(); - result.put(i, learnersJson); - - List learnersDetails = answeredQuestions.get(i); - if (learnersDetails != null) { - for (String[] learnerDetails : learnersDetails) { - ObjectNode learner = JsonNodeFactory.instance.objectNode(); - learner.put("id", learnerDetails[0]); - learner.put("portraitUuid", learnerDetails[1] == null ? null : learnerDetails[1]); - learner.put("name", learnerDetails[2] == null ? "?" : learnerDetails[2]); - learner.put("group", learnerDetails[3] == null ? null : learnerDetails[3]); - learnersJson.add(learner); - } - } - } - return result; - } - - @Override public void changeLeaderForGroup(long toolSessionId, long leaderUserId) { AssessmentSession session = getSessionBySessionId(toolSessionId); if (AssessmentConstants.COMPLETED == session.getStatus()) { @@ -4166,4 +4227,66 @@ jsonCommand.put("hookTrigger", "assessment-leader-triggered-refresh-" + toolSessionId); learnerService.createCommandForLearners(assessment.getContentId(), userIds, jsonCommand.toString()); } + + private String getCompletionChartsData(long toolContentId) { + try { + ObjectNode chartJson = JsonNodeFactory.instance.objectNode(); + + chartJson.set("possibleLearners", getLessonLearnersByContentIdJson(toolContentId)); + chartJson.set("startedLearners", getLearnersByContentIdJson(toolContentId)); + chartJson.set("completedLearners", getLearnersWithFinishedCurrentAttemptJson(toolContentId)); + + chartJson.put("sessionCount", getSessionsByContentId(toolContentId).size()); + Map answeredQuestionsByUsers = getAnsweredQuestionsByUsersJson(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)); + } + + Assessment assessment = getAssessmentByContentId(toolContentId); + chartJson.put("useLeader", assessment.isUseSelectLeaderToolOuput()); + chartJson.put("isGrouped", isGroupedActivity(toolContentId)); + + return chartJson.toString(); + } catch (Exception e) { + log.error("Unable to fetch completion charts data for tool content ID " + toolContentId, e); + return ""; + } + + } + + private Map getAnsweredQuestionsByUsersJson(long toolContentId) { + Map> answeredQuestions = assessmentResultDao.getAnsweredQuestionsByUsersForCompletionChart( + toolContentId); + Map result = new TreeMap<>(); + if (answeredQuestions.isEmpty()) { + return result; + } + + Assessment assessment = getAssessmentByContentId(toolContentId); + int questionCount = assessment.getQuestions().size(); + + // list all question counts, from 0 to maximum possible questions + + for (int i = 0; i <= questionCount; i++) { + ArrayNode learnersJson = JsonNodeFactory.instance.arrayNode(); + result.put(i, learnersJson); + + List learnersDetails = answeredQuestions.get(i); + if (learnersDetails != null) { + for (String[] learnerDetails : learnersDetails) { + ObjectNode learner = JsonNodeFactory.instance.objectNode(); + learner.put("id", learnerDetails[0]); + learner.put("portraitUuid", learnerDetails[1] == null ? null : learnerDetails[1]); + learner.put("name", learnerDetails[2] == null ? "?" : learnerDetails[2]); + learner.put("group", learnerDetails[3] == null ? null : learnerDetails[3]); + learnersJson.add(learner); + } + } + } + return result; + } + } \ No newline at end of file Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/IAssessmentService.java =================================================================== diff -u -r0586f1081f3b7e5b550da93b64463175ea698c28 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/IAssessmentService.java (.../IAssessmentService.java) (revision 0586f1081f3b7e5b550da93b64463175ea698c28) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/IAssessmentService.java (.../IAssessmentService.java) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -23,6 +23,15 @@ package org.lamsfoundation.lams.tool.assessment.service; +import com.fasterxml.jackson.databind.node.ArrayNode; +import org.lamsfoundation.lams.learningdesign.Grouping; +import org.lamsfoundation.lams.notebook.model.NotebookEntry; +import org.lamsfoundation.lams.tool.assessment.dto.*; +import org.lamsfoundation.lams.tool.assessment.model.*; +import org.lamsfoundation.lams.tool.service.ICommonToolService; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.util.excel.ExcelSheet; + import java.lang.reflect.InvocationTargetException; import java.time.LocalDateTime; import java.util.Collection; @@ -49,7 +58,6 @@ import org.lamsfoundation.lams.tool.service.ICommonToolService; import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.util.excel.ExcelSheet; - import reactor.core.publisher.Flux; /** @@ -115,14 +123,18 @@ int getCountUsersBySession(Long sessionId, String searchString); - int getCountUsersByContentId(Long contentId); - - int getCountUsersByContentId(Long contentId, String searchString); + int getCountLearnersByContentId(Long contentId); + int getCountLearnersByContentId(Long contentId, String searchString); + int getCountLessonLearnersByContentId(long contentId); - int getCountLearnersWithFinishedCurrentAttempt(long contentId); + ArrayNode getLearnersByContentIdJson(Long contentId); + ArrayNode getLessonLearnersByContentIdJson(long contentId); + + ArrayNode getLearnersWithFinishedCurrentAttemptJson(long contentId); + List getPagedUsersBySessionAndQuestion(Long sessionId, Long questionUid, int page, int size, String sortBy, String sortOrder, String searchString); @@ -145,6 +157,7 @@ Assessment getDefaultContent(Long contentId) throws AssessmentApplicationException; // ********** for user methods ************* + /** * Create a new user in database. */ @@ -241,8 +254,7 @@ * @param userId * @param pagedQuestions * @param isAutosave - * indicates whether it's autosave request - * + * indicates whether it's autosave request * @return whether storing results is allowed, false otherwise */ boolean storeUserAnswers(Assessment assessment, Long userId, List> pagedQuestions, @@ -438,23 +450,21 @@ List exportSummary(Assessment assessment, long toolContentId); /** - * Gets the basic statistics for the grades for the Leaders when an Assessment is done using - * Group Leaders. So the averages, etc are for the whole Assessment, not for a Group. + * Gets the basic statistics for the grades for the Leaders when an Assessment is done using Group Leaders. So the + * averages, etc are for the whole Assessment, not for a Group. */ GradeStatsDTO getStatsDtoForLeaders(Long contentId); /** * Prepares data for the marks summary graph on the statistics page - * */ List getMarksArray(Long sessionId); List getMarksArrayByContentId(Long toolContentId); /** - * Prepares data for the marks summary graph on the statistics page, using the grades for the Leaders - * when an Assessment is done using Group Leaders. So the grades are for the whole Assessment, not for a Group. - * + * Prepares data for the marks summary graph on the statistics page, using the grades for the Leaders when an + * Assessment is done using Group Leaders. So the grades are for the whole Assessment, not for a Group. */ List getMarksArrayForLeaders(Long contentId); @@ -508,8 +518,8 @@ AssessmentQuestion getAssessmentQuestionByUid(Long questionUid); /** - * Sends a websocket command to learners who have assessment results open - * to refresh page because new data is available + * Sends a websocket command to learners who have assessment results open to refresh page because new data is + * available */ void notifyLearnersOnAnswerDisclose(long toolContentId); @@ -523,7 +533,5 @@ List getPossibleIndividualTimeLimitUsers(long toolContentId, String searchString); - 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 -r6d67af7eb9c49d59b6b0ff579b472dcf7022d437 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java (.../MonitoringController.java) (revision 6d67af7eb9c49d59b6b0ff579b472dcf7022d437) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java (.../MonitoringController.java) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -23,11 +23,23 @@ package org.lamsfoundation.lams.tool.assessment.web.controller; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.IOException; +import java.security.InvalidParameterException; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; +import org.lamsfoundation.lams.flux.FluxRegistry; import org.lamsfoundation.lams.learningdesign.Group; import org.lamsfoundation.lams.learningdesign.Grouping; import org.lamsfoundation.lams.logevent.LearnerInteractionEvent; @@ -41,13 +53,26 @@ import org.lamsfoundation.lams.rating.service.IRatingService; import org.lamsfoundation.lams.tool.assessment.AssessmentConstants; import org.lamsfoundation.lams.tool.assessment.dto.*; -import org.lamsfoundation.lams.tool.assessment.model.*; +import org.lamsfoundation.lams.tool.assessment.model.Assessment; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestion; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestionResult; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentResult; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentSession; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentUser; +import org.lamsfoundation.lams.tool.assessment.model.QuestionReference; import org.lamsfoundation.lams.tool.assessment.service.IAssessmentService; import org.lamsfoundation.lams.tool.assessment.util.AssessmentEscapeUtils; +import org.lamsfoundation.lams.tool.assessment.util.AssessmentSessionComparator; import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; -import org.lamsfoundation.lams.util.*; +import org.lamsfoundation.lams.util.CommonConstants; +import org.lamsfoundation.lams.util.Configuration; +import org.lamsfoundation.lams.util.ConfigurationKeys; +import org.lamsfoundation.lams.util.DateUtil; +import org.lamsfoundation.lams.util.JsonUtil; +import org.lamsfoundation.lams.util.NumberUtil; +import org.lamsfoundation.lams.util.WebUtil; import org.lamsfoundation.lams.util.excel.ExcelSheet; import org.lamsfoundation.lams.util.excel.ExcelUtil; import org.lamsfoundation.lams.web.session.SessionManager; @@ -58,22 +83,20 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.util.HtmlUtils; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.IOException; -import java.security.InvalidParameterException; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import reactor.core.publisher.Flux; + @Controller @RequestMapping("/monitoring") public class MonitoringController { @@ -179,7 +202,7 @@ displayStudentChoices = false; } - if (question.getType() == QbQuestion.TYPE_VERY_SHORT_ANSWERS) { + if (question.getType().equals(QbQuestion.TYPE_VERY_SHORT_ANSWERS)) { vsaPresent = true; } } @@ -199,7 +222,7 @@ // build candidate dtos for (OptionDTO optionDto : questionDto.getOptionDtos()) { - int optionAttemptCount = service.countAttemptsPerOption(contentId, optionDto.getUid(), false); + int optionAttemptCount = service.countAttemptsPerOption(contentId, optionDto.getUid()); float percentage = (float) (optionAttemptCount * 100) / totalNumberOfUsers; optionDto.setPercentage(percentage); @@ -208,6 +231,14 @@ request.setAttribute("questions", questionDtos); } + List tblQuestionDtos = TblMonitoringController.getTblAssessmentQuestionDtos(contentId, + true, service); + request.setAttribute("questionDtos", tblQuestionDtos); + SortedSet sessions = new TreeSet<>(new AssessmentSessionComparator()); + sessions.addAll(service.getSessionsByContentId(assessment.getContentId())); + + request.setAttribute("sessions", sessions); + // lists all code styles used in this assessment Set codeStyles = questionList.stream().filter(q -> q.getQbQuestion().getCodeStyle() != null) .collect(Collectors.mapping(q -> q.getQbQuestion().getCodeStyle(), Collectors.toSet())); @@ -219,31 +250,11 @@ return "pages/monitoring/monitoring"; } - @RequestMapping(path = "/getCompletionChartsData") + @RequestMapping(path = "/getCompletionChartsData", method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE) @ResponseBody - public String getCompletionChartsData(@RequestParam long toolContentId, HttpServletResponse response) - throws IOException { - ObjectNode chartJson = JsonNodeFactory.instance.objectNode(); - - chartJson.set("possibleLearners", service.getLessonLearnersByContentIdJson(toolContentId)); - chartJson.set("startedLearners", service.getLearnersByContentIdJson(toolContentId)); - chartJson.set("completedLearners", service.getLearnersWithFinishedCurrentAttemptJson(toolContentId)); - - chartJson.put("sessionCount", service.getSessionsByContentId(toolContentId).size()); - Map answeredQuestionsByUsers = service.getAnsweredQuestionsByUsersJson(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)); - } - - Assessment assessment = service.getAssessmentByContentId(toolContentId); - chartJson.put("useLeader", assessment.isUseSelectLeaderToolOuput()); - chartJson.put("isGrouped", service.isGroupedActivity(toolContentId)); - - response.setContentType("application/json;charset=utf-8"); - return chartJson.toString(); + public Flux getCompletionChartsData(@RequestParam long toolContentId) + throws JsonProcessingException, IOException { + return service.getCompletionChartsDataFlux(toolContentId); } @RequestMapping("/userMasterDetail") @@ -395,7 +406,7 @@ SessionMap sessionMap = getSessionMap(request); Assessment assessment = (Assessment) sessionMap.get(AssessmentConstants.ATTR_ASSESSMENT); - Long sessionId = WebUtil.readLongParam(request, "sessionId"); + Long sessionId = WebUtil.readLongParam(request, "sessionId", true); // Getting the params passed in from the jqGrid int page = WebUtil.readIntParam(request, CommonConstants.PARAM_PAGE); @@ -409,33 +420,41 @@ List userDtos = new ArrayList<>(); int countSessionUsers = 0; - //in case of UseSelectLeaderToolOuput - display only one user - if (assessment.isUseSelectLeaderToolOuput()) { + if (sessionId == null) { + userDtos = service.getPagedUsersByContentId(assessment.getContentId(), page - 1, rowLimit, sortBy, + sortOrder, searchString); + countSessionUsers = service.getCountLearnersByContentId(assessment.getContentId(), searchString); + } else + //in case of UseSelectLeaderToolOuput - display only one user + if (assessment.isUseSelectLeaderToolOuput()) { - AssessmentSession session = service.getSessionBySessionId(sessionId); - AssessmentUser groupLeader = session.getGroupLeader(); + AssessmentSession session = service.getSessionBySessionId(sessionId); + AssessmentUser groupLeader = session.getGroupLeader(); - if (groupLeader != null) { + if (groupLeader != null) { - Float assessmentResult = service.getLastTotalScoreByUser(assessment.getUid(), groupLeader.getUserId()); - String portraitId = service.getPortraitId(groupLeader.getUserId()); + Float assessmentResult = service.getLastTotalScoreByUser(assessment.getUid(), + groupLeader.getUserId()); + String portraitId = service.getPortraitId(groupLeader.getUserId()); - AssessmentUserDTO userDto = new AssessmentUserDTO(); - userDto.setUserId(groupLeader.getUserId()); - userDto.setFirstName(groupLeader.getFirstName()); - userDto.setLastName(groupLeader.getLastName()); - userDto.setGrade(assessmentResult == null ? 0 : assessmentResult); - userDto.setPortraitId(portraitId); - userDtos.add(userDto); - countSessionUsers = 1; + AssessmentUserDTO userDto = new AssessmentUserDTO(); + userDto.setUserId(groupLeader.getUserId()); + userDto.setSessionId(sessionId); + userDto.setFirstName(groupLeader.getFirstName()); + userDto.setLastName(groupLeader.getLastName()); + userDto.setGrade(assessmentResult == null ? 0 : assessmentResult); + userDto.setPortraitId(portraitId); + userDtos.add(userDto); + countSessionUsers = 1; + } + + } else { + // Get the user list from the db + userDtos = service.getPagedUsersBySession(sessionId, page - 1, rowLimit, sortBy, sortOrder, + searchString); + countSessionUsers = service.getCountUsersBySession(sessionId, searchString); } - } else { - // Get the user list from the db - userDtos = service.getPagedUsersBySession(sessionId, page - 1, rowLimit, sortBy, sortOrder, searchString); - countSessionUsers = service.getCountUsersBySession(sessionId, searchString); - } - int totalPages = Double.valueOf(Math.ceil(Double.valueOf(countSessionUsers) / Double.valueOf(rowLimit))) .intValue(); @@ -445,7 +464,7 @@ ArrayNode userData = JsonNodeFactory.instance.arrayNode(); userData.add(userDto.getUserId()); - userData.add(sessionId); + userData.add(userDto.getSessionId()); String fullName = HtmlUtils.htmlEscape(userDto.getFirstName() + " " + userDto.getLastName()); userData.add(fullName); userData.add(userDto.getGrade()); @@ -910,6 +929,9 @@ ? null : LocalDateTime.ofEpochSecond(absoluteTimeLimit, 0, OffsetDateTime.now().getOffset())); service.saveOrUpdateAssessment(assessment); + + // update monitoring UI where time limits are reflected on dashboard + FluxRegistry.emit(CommonConstants.ACTIVITY_TIME_LIMIT_CHANGED_SINK_NAME, Set.of(toolContentId)); } @RequestMapping(path = "/getPossibleIndividualTimeLimits", method = RequestMethod.GET) Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/TblMonitoringController.java =================================================================== diff -u -r6d67af7eb9c49d59b6b0ff579b472dcf7022d437 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/TblMonitoringController.java (.../TblMonitoringController.java) (revision 6d67af7eb9c49d59b6b0ff579b472dcf7022d437) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/TblMonitoringController.java (.../TblMonitoringController.java) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -1,11 +1,32 @@ package org.lamsfoundation.lams.tool.assessment.web.controller; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.lang.StringUtils; -import org.lamsfoundation.lams.learning.service.ILearnerService; import org.lamsfoundation.lams.qb.model.QbQuestion; import org.lamsfoundation.lams.tool.assessment.AssessmentConstants; -import org.lamsfoundation.lams.tool.assessment.dto.*; -import org.lamsfoundation.lams.tool.assessment.model.*; +import org.lamsfoundation.lams.tool.assessment.dto.AssessmentResultDTO; +import org.lamsfoundation.lams.tool.assessment.dto.OptionDTO; +import org.lamsfoundation.lams.tool.assessment.dto.QuestionDTO; +import org.lamsfoundation.lams.tool.assessment.dto.QuestionSummary; +import org.lamsfoundation.lams.tool.assessment.dto.TblAssessmentDTO; +import org.lamsfoundation.lams.tool.assessment.dto.TblAssessmentQuestionDTO; +import org.lamsfoundation.lams.tool.assessment.dto.TblAssessmentQuestionResultDTO; +import org.lamsfoundation.lams.tool.assessment.model.Assessment; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentOptionAnswer; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestion; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentQuestionResult; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentSession; +import org.lamsfoundation.lams.tool.assessment.model.AssessmentUser; +import org.lamsfoundation.lams.tool.assessment.model.QuestionReference; import org.lamsfoundation.lams.tool.assessment.service.AssessmentServiceImpl; import org.lamsfoundation.lams.tool.assessment.service.IAssessmentService; import org.lamsfoundation.lams.tool.assessment.util.AssessmentEscapeUtils; @@ -30,9 +51,6 @@ @Qualifier("laasseAssessmentService") private IAssessmentService assessmentService; - @Autowired - private ILearnerService learnerService; - /** * Shows ira page in case of Assessment activity */ @@ -51,6 +69,24 @@ Long toolContentId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); + request.setAttribute(AttributeNames.PARAM_TOOL_CONTENT_ID, toolContentId); + request.setAttribute("assessment", assessment); + + for (AssessmentQuestion question : assessment.getQuestions()) { + if (question.getType().equals(QbQuestion.TYPE_VERY_SHORT_ANSWERS)) { + request.setAttribute("vsaPresent", true); + break; + } + } + + return "pages/tblmonitoring/iraAssessmentStudentChoices"; + } + + @RequestMapping("iraAssessmentStudentChoicesTable") + public String iraAssessmentStudentChoicesTable(HttpServletRequest request) { + Long toolContentId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); + //prepare list of the questions, filtering out questions that aren't supposed to be answered Set questionList = new LinkedHashSet<>(); //in case there is at least one random question - we need to show all questions in a drop down select @@ -86,27 +122,29 @@ request.setAttribute("maxOptionsInQuestion", maxOptionsInQuestion); request.setAttribute("vsaPresent", vsaPresent); - int totalNumberOfUsers = assessmentService.getCountLearnersByContentId(toolContentId); - if (totalNumberOfUsers > 0) { - for (QuestionDTO questionDto : questionDtos) { + for (QuestionDTO questionDto : questionDtos) { + Map optionsAttempts = new HashMap<>(); + int questionAttempts = 0; + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + int optionAttempts = assessmentService.countAttemptsPerOption(toolContentId, optionDto.getUid()); - // build candidate dtos - for (OptionDTO optionDto : questionDto.getOptionDtos()) { - int optionAttemptCount = assessmentService.countAttemptsPerOption(toolContentId, optionDto.getUid(), - false); + optionsAttempts.put(optionDto.getUid(), optionAttempts); + questionAttempts += optionAttempts; + } - float percentage = (float) (optionAttemptCount * 100) / totalNumberOfUsers; - optionDto.setPercentage(percentage); + for (OptionDTO optionDto : questionDto.getOptionDtos()) { + if (questionAttempts == 0) { + optionDto.setPercentage(-1); + continue; } + + float percentage = (float) (optionsAttempts.get(optionDto.getUid()) * 100) / questionAttempts; + optionDto.setPercentage(percentage); } } request.setAttribute("questions", questionDtos); - request.setAttribute(AttributeNames.PARAM_TOOL_CONTENT_ID, toolContentId); - request.setAttribute("assessment", assessment); - request.setAttribute("isTbl", true); - - return "pages/tblmonitoring/iraAssessmentStudentChoices"; + return "pages/monitoring/parts/mcqStudentChoices5"; } private List getAssessmentDtos(String[] toolContentIds, String[] activityTitles) { @@ -178,6 +216,22 @@ /** * Shows ira StudentChoices page */ + @RequestMapping("aesStudentChoicesTable") + public String aesStudentChoicesTable(HttpServletRequest request) { + Long toolContentId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + List tblQuestionDtos = TblMonitoringController.getTblAssessmentQuestionDtos( + toolContentId, false, assessmentService); + + Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); + SortedSet sessions = new TreeSet<>(new AssessmentSessionComparator()); + sessions.addAll(assessmentService.getSessionsByContentId(assessment.getContentId())); + + request.setAttribute("sessions", sessions); + request.setAttribute("questionDtos", tblQuestionDtos); + + return "pages/tblmonitoring/assessmentStudentChoicesTable"; + } + @RequestMapping("aesStudentChoices") public String aesStudentChoices(HttpServletRequest request) { Long toolContentId = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); @@ -189,13 +243,39 @@ TblAssessmentQuestionDTO tblQuestionDto = new TblAssessmentQuestionDTO(); tblQuestionDto.setTitle(questionDto.getTitle()); - tblQuestionDto - .setQuestionTypeLabel(AssessmentServiceImpl.getQuestionTypeLanguageLabel(questionDto.getType())); - tblQuestionDto.setCorrectAnswer(getAssessmentCorrectAnswer(questionDto)); + tblQuestionDto.setQuestionTypeLabel( + AssessmentServiceImpl.getQuestionTypeLanguageLabel(questionDto.getType())); + tblQuestionDto.setCorrectAnswer(TblMonitoringController.getAssessmentCorrectAnswer(questionDto)); + tblQuestionDtos.add(tblQuestionDto); + } + + request.setAttribute("questionDtos", tblQuestionDtos); + request.setAttribute(AttributeNames.PARAM_TOOL_CONTENT_ID, toolContentId); + request.setAttribute("groupsInAnsweredQuestionsChart", assessment.isUseSelectLeaderToolOuput()); + request.setAttribute("assessment", assessment); + + return "pages/tblmonitoring/assessmentStudentChoices"; + } + + static List getTblAssessmentQuestionDtos(Long toolContentId, boolean addHeaderData, + IAssessmentService assessmentService) { + Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); + Map questionSummaries = assessmentService.getQuestionSummaryForExport(assessment, false); + List tblQuestionDtos = new ArrayList<>(); + for (QuestionSummary questionSummary : questionSummaries.values()) { + QuestionDTO questionDto = questionSummary.getQuestionDto(); + + TblAssessmentQuestionDTO tblQuestionDto = new TblAssessmentQuestionDTO(); + if (addHeaderData) { + tblQuestionDto.setTitle(questionDto.getTitle()); + tblQuestionDto.setQuestionTypeLabel( + AssessmentServiceImpl.getQuestionTypeLanguageLabel(questionDto.getType())); + tblQuestionDto.setCorrectAnswer(TblMonitoringController.getAssessmentCorrectAnswer(questionDto)); + } + List sessionQuestionResults = new ArrayList<>(); - for (List questionResultsPerSession : questionSummary - .getQuestionResultsPerSession()) { + for (List questionResultsPerSession : questionSummary.getQuestionResultsPerSession()) { TblAssessmentQuestionResultDTO tblQuestionResultDto = new TblAssessmentQuestionResultDTO(); String answer = ""; @@ -230,8 +310,8 @@ } } } else { - correct = questionResult.getPenalty() + questionResult.getMark() + 0.1 >= questionResult - .getMaxMark(); + correct = questionResult.getPenalty() + questionResult.getMark() + 0.1 + >= questionResult.getMaxMark(); } } @@ -246,22 +326,13 @@ tblQuestionDtos.add(tblQuestionDto); } - SortedSet sessions = new TreeSet<>(new AssessmentSessionComparator()); - sessions.addAll(assessmentService.getSessionsByContentId(assessment.getContentId())); - - request.setAttribute("sessions", sessions); - request.setAttribute("questionDtos", tblQuestionDtos); - request.setAttribute(AttributeNames.PARAM_TOOL_CONTENT_ID, toolContentId); - request.setAttribute("assessment", assessment); - request.setAttribute("isTbl", true); - - return "pages/tblmonitoring/assessmentStudentChoices"; + return tblQuestionDtos; } /** * Used only for excell export (for getUserSummaryData() method). */ - private String getAssessmentCorrectAnswer(QuestionDTO questionDto) { + private static String getAssessmentCorrectAnswer(QuestionDTO questionDto) { StringBuilder sb = new StringBuilder(); if (questionDto != null) { @@ -281,10 +352,13 @@ case QbQuestion.TYPE_VERY_SHORT_ANSWERS: for (OptionDTO optionDto : questionDto.getOptionDtos()) { if (optionDto.getMaxMark() == 1f) { - return optionDto.getName(); + if (sb.length() > 0) { + sb.append("
"); + } + sb.append(optionDto.getName()); } } - break; + return sb.toString(); case QbQuestion.TYPE_NUMERICAL: for (OptionDTO optionDto : questionDto.getOptionDtos()) { Index: lams_tool_assessment/web/includes/javascript/chart.js =================================================================== diff -u -r6e6de7ca18164b180cd108d5862b166e7e078bf3 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/web/includes/javascript/chart.js (.../chart.js) (revision 6e6de7ca18164b180cd108d5862b166e7e078bf3) +++ lams_tool_assessment/web/includes/javascript/chart.js (.../chart.js) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -1,5 +1,5 @@ -function drawCompletionCharts(toolContentId, useGroups, animate) { - +function drawCompletionCharts(toolContentId, animate) { + const source = new EventSource( WEB_APP_URL + 'monitoring/getCompletionChartsData.do?toolContentId=' + toolContentId); source.onmessage = function (event) { @@ -8,24 +8,57 @@ } var data = JSON.parse(decodeURIComponent(event.data)); drawActivityCompletionChart(data, animate); - drawAnsweredQuestionsChart(data, useGroups, animate); + drawAnsweredQuestionsChart(data, animate); } } function drawActivityCompletionChart(data, animate){ - var newData = [ data.possibleLearners - data.startedLearners, - data.startedLearners - data.completedLearners, - data.completedLearners - ]; + // prepare data for the chart + let notStartedLearners = data.possibleLearners.filter(function (learner) { + let found = false; + $.each(data.startedLearners, function (index, startedLearner) { + if (learner.id == startedLearner.id) { + found = true; + return false; + } + }); + return !found; + }), + startedNotCompletedLearners = data.startedLearners.filter(function (learner) { + let found = false; + $.each(data.completedLearners, function (index, completedLearner) { + if (learner.id == completedLearner.id) { + found = true; + return false; + } + }); + return !found; + }), + newData = [notStartedLearners.length, + startedNotCompletedLearners.length, + data.completedLearners.length + ]; + + let chartPlaceholder = $('#activity-completion-chart'); + if (chartPlaceholder.length === 0) { + // no sessions yet, so no chart placeholder + return; + } + + // store current data for custom tooltip + chartPlaceholder.data('tooltip-input', [notStartedLearners, startedNotCompletedLearners, data.completedLearners]); + chartPlaceholder.data('useGroupsAsNames', false); + chartPlaceholder.data('isGrouped', data.isGrouped); + if (activityCompletionChart != null) { // chart already exists, just update data activityCompletionChart.data.datasets[0].data = newData; activityCompletionChart.update(); return; } - - let ctx = document.getElementById('activity-completion-chart').getContext('2d'); - + + let ctx = chartPlaceholder[0].getContext('2d'); + activityCompletionChart = new Chart(ctx, { type : 'doughnut', borderWidth : 0, @@ -39,14 +72,14 @@ datasets : [ { data : newData, backgroundColor : [ 'rgba(5, 204, 214, 1)', - 'rgba(255, 195, 55, 1)', - 'rgba(253, 60, 165, 1)', - ], + 'rgba(255, 195, 55, 1)', + 'rgba(253, 60, 165, 1)', + ], borderWidth : 0, } ], labels : [ LABELS.ACTIVITY_COMPLETION_CHART_POSSIBLE_LEARNERS, - LABELS.ACTIVITY_COMPLETION_CHART_STARTED_LEARNERS, - LABELS.ACTIVITY_COMPLETION_CHART_COMPLETED_LEARNERS ] + LABELS.ACTIVITY_COMPLETION_CHART_STARTED_LEARNERS, + LABELS.ACTIVITY_COMPLETION_CHART_COMPLETED_LEARNERS ] }, options : { layout : { @@ -91,34 +124,51 @@ animateScale : true, animateRotate : true, duration : animate ? 1000 : 0 + }, + tooltips : { + enabled : false, + custom : function(tooltipModel) { + listCompletionChartLearners.call(this, chartPlaceholder, tooltipModel) + } } } }); } -function drawAnsweredQuestionsChart(data, useGroups, animate){ + +function drawAnsweredQuestionsChart(data, animate){ if (!data.answeredQuestionsByUsers) { return; } - + + let chartPlaceholder = $('#answered-questions-chart'), + useGroupsAsNames = data.useLeader && data.isGrouped; + + if (chartPlaceholder.length === 0) { + // no sessions yet, so no chart placeholder + return; + } // store current data for custom tooltip - $('#answered-questions-chart').data('tooltip-input', data.answeredQuestionsByUsers); - + chartPlaceholder.data('tooltip-input', data.answeredQuestionsByUsers); + chartPlaceholder.data('useGroupsAsNames', useGroupsAsNames); + chartPlaceholder.data('isGrouped', data.isGrouped); + if (answeredQuestionsChart != null) { // chart already exists, just update data answeredQuestionsChart.data.datasets[0].data = Object.values(data.answeredQuestionsByUsersCount); answeredQuestionsChart.update(); return; } - - let ctx = document.getElementById('answered-questions-chart').getContext('2d'); + + let ctx = chartPlaceholder[0].getContext('2d'); + answeredQuestionsChart = new Chart(ctx, { type : 'bar', data : { datasets : [ { data : Object.values(data.answeredQuestionsByUsersCount), backgroundColor : 'rgba(255, 195, 55, 1)' - + } ], labels : Object.keys(data.answeredQuestionsByUsersCount), }, @@ -135,102 +185,114 @@ display: true, fontSize : '15', lineHeight: 3, - text : useGroups ? LABELS.ANSWERED_QUESTIONS_CHART_TITLE_GROUPS : LABELS.ANSWERED_QUESTIONS_CHART_TITLE + text : useGroupsAsNames ? LABELS.ANSWERED_QUESTIONS_CHART_TITLE_GROUPS : LABELS.ANSWERED_QUESTIONS_CHART_TITLE }, animation : { duration : animate ? 1000 : 0 }, scales : { xAxes : [{ - scaleLabel : { - display : true, - labelString : LABELS.ANSWERED_QUESTIONS_CHART_X_AXIS - } + scaleLabel : { + display : true, + labelString : LABELS.ANSWERED_QUESTIONS_CHART_X_AXIS } + } ], yAxes : [ { - ticks : { + ticks : { beginAtZero : true, stepSize : 1, maxTicksLimit : 5, // prevent scale to change on each update // set suggested max number of students to 3/4 // of all possible learners - suggestedMax : Math.max(2, Math.floor(3 * (useGroups ? data.sessionCount : data.possibleLearners) / 4)) + suggestedMax : Math.max(2, Math.floor(3 * (useGroupsAsNames ? data.sessionCount : data.possibleLearners.length) / 4)) }, scaleLabel : { display : true, - labelString : useGroups ? LABELS.ANSWERED_QUESTIONS_CHART_Y_AXIS_GROUPS : LABELS.ANSWERED_QUESTIONS_CHART_Y_AXIS_STUDENTS, + labelString : useGroupsAsNames ? LABELS.ANSWERED_QUESTIONS_CHART_Y_AXIS_GROUPS : LABELS.ANSWERED_QUESTIONS_CHART_Y_AXIS_STUDENTS, fontSize : 14 } } ] }, 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' - }); + enabled : false, + custom : function(tooltipModel) { + listCompletionChartLearners.call(this, chartPlaceholder, tooltipModel) + } + } + } + }); +} - $('').text(this[3] ? this[3] + ' (' + this[2] + ')' : 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', - }); - } + +function listCompletionChartLearners(chartPlaceholder, tooltipModel) { + + let tooltipClassName = chartPlaceholder.attr('id') + '-tooltip'; + // always remove the tooltip at the beginning + $('.' + tooltipClassName).remove(); + if (tooltipModel.opacity === 0) { + // if it should be hidden, there is nothing to do + return; + } + + // create tooltip + var tooltipEl = $('
').addClass(tooltipClassName) + .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, + data = chartPlaceholder.data('tooltip-input'), + useGroupsAsNames = chartPlaceholder.data('useGroupsAsNames'), + isGrouped = chartPlaceholder.data('isGrouped'), + users = data[tooltipModel.dataPoints[0].index]; + $(users).each(function(){ + var portraitDiv = $(definePortrait(this.portraitUuid, this.id, STYLE_SMALL, true, LAMS_URL)).css({ + 'vertical-align' : 'middle' + }), + userDiv = $('
').append(portraitDiv).appendTo(tooltipEl).css({ + 'padding-bottom' : '5px' + }); + + $('').text(useGroupsAsNames ? this.group + ' (' + this.name + ')' + : (this.name + (isGrouped && this.group ? ' (' + this.group + ') ' : ''))) + .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', + }); } \ No newline at end of file Index: lams_tool_assessment/web/includes/javascript/chart5.js =================================================================== diff -u -r35dd47046a83ab2f9e80f76dc22d1521412219b5 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/web/includes/javascript/chart5.js (.../chart5.js) (revision 35dd47046a83ab2f9e80f76dc22d1521412219b5) +++ lams_tool_assessment/web/includes/javascript/chart5.js (.../chart5.js) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -1,24 +1,58 @@ var GRAPH_COLORS = { - blue: 'rgba(1, 117, 226, 0.85)', - yellow: 'rgba(249, 248, 113, 0.85)', - green: 'rgba(0, 145, 74, 0.85)', - gray: '#6c757d' - }; + blue: 'rgba(1, 117, 226, 0.85)', + yellow: 'rgba(249, 248, 113, 0.85)', + green: 'rgba(0, 145, 74, 0.85)', + gray: '#6c757d' +}; function drawActivityCompletionChart(data, animate){ - var newData = [ data.startedLearners - data.completedLearners, - data.completedLearners, - data.possibleLearners - data.startedLearners - ]; + // prepare data for the chart + let notStartedLearners = data.possibleLearners.filter(function (learner) { + let found = false; + $.each(data.startedLearners, function (index, startedLearner) { + if (learner.id == startedLearner.id) { + found = true; + return false; + } + }); + return !found; + }), + startedNotCompletedLearners = data.startedLearners.filter(function (learner) { + let found = false; + $.each(data.completedLearners, function (index, completedLearner) { + if (learner.id == completedLearner.id) { + found = true; + return false; + } + }); + return !found; + }), + newData = [ + startedNotCompletedLearners.length, + data.completedLearners.length, + notStartedLearners.length + ]; + + let chartPlaceholder = $('#activity-completion-chart'); + if (chartPlaceholder.length === 0) { + // no sessions yet, so no chart placeholder + return; + } + + // store current data for custom tooltip + chartPlaceholder.data('tooltip-input', [startedNotCompletedLearners, data.completedLearners, notStartedLearners]); + chartPlaceholder.data('useGroupsAsNames', false); + chartPlaceholder.data('isGrouped', data.isGrouped); + if (activityCompletionChart != null) { // chart already exists, just update data activityCompletionChart.data.datasets[0].data = newData; activityCompletionChart.update(); return; } - - let ctx = document.getElementById('activity-completion-chart').getContext('2d'); - + + let ctx = chartPlaceholder[0].getContext('2d'); + activityCompletionChart = new Chart(ctx, { type : 'doughnut', borderWidth : 0, @@ -32,17 +66,16 @@ datasets : [ { data : newData, backgroundColor : [ - GRAPH_COLORS.yellow, - GRAPH_COLORS.green, - GRAPH_COLORS.blue, - ], + GRAPH_COLORS.yellow, + GRAPH_COLORS.green, + GRAPH_COLORS.blue, + ], borderWidth : 1, borderColor : COLORS.gray } ], - labels : [ - LABELS.ACTIVITY_COMPLETION_CHART_STARTED_LEARNERS, - LABELS.ACTIVITY_COMPLETION_CHART_COMPLETED_LEARNERS, - LABELS.ACTIVITY_COMPLETION_CHART_POSSIBLE_LEARNERS, ] + labels : [ LABELS.ACTIVITY_COMPLETION_CHART_STARTED_LEARNERS, + LABELS.ACTIVITY_COMPLETION_CHART_COMPLETED_LEARNERS, + LABELS.ACTIVITY_COMPLETION_CHART_POSSIBLE_LEARNERS, ] }, options : { layout : { @@ -88,37 +121,55 @@ animateScale : true, animateRotate : true, duration : animate ? 1000 : 0 + }, + tooltips : { + enabled : false, + custom : function(tooltipModel) { + listCompletionChartLearners.call(this, chartPlaceholder, tooltipModel) + } } } }); } -function drawAnsweredQuestionsChart(data, useGroups, animate){ + +function drawAnsweredQuestionsChart(data, animate){ if (!data.answeredQuestionsByUsers) { $('#answered-questions-chart-none').show(); return; } - + + let chartPlaceholder = $('#answered-questions-chart'), + useGroupsAsNames = data.useLeader && data.isGrouped; + + if (chartPlaceholder.length === 0) { + // no sessions yet, so no chart placeholder + return; + } + $('#answered-questions-chart-none').hide(); - + // store current data for custom tooltip - $('#answered-questions-chart').data('tooltip-input', data.answeredQuestionsByUsers); - + chartPlaceholder.data('tooltip-input', data.answeredQuestionsByUsers); + chartPlaceholder.data('useGroupsAsNames', useGroupsAsNames); + chartPlaceholder.data('isGrouped', data.isGrouped); + if (answeredQuestionsChart != null) { // chart already exists, just update data answeredQuestionsChart.data.datasets[0].data = Object.values(data.answeredQuestionsByUsersCount); answeredQuestionsChart.update(); return; } - - let ctx = document.getElementById('answered-questions-chart').getContext('2d'); + + let ctx = chartPlaceholder[0].getContext('2d'); + answeredQuestionsChart = new Chart(ctx, { type : 'bar', data : { datasets : [ { data : Object.values(data.answeredQuestionsByUsersCount), backgroundColor : GRAPH_COLORS.green - + } ], labels : Object.keys(data.answeredQuestionsByUsersCount), }, @@ -135,102 +186,114 @@ display: true, fontSize : '18', lineHeight: 2, - text : useGroups ? LABELS.ANSWERED_QUESTIONS_CHART_TITLE_GROUPS : LABELS.ANSWERED_QUESTIONS_CHART_TITLE + text : useGroupsAsNames ? LABELS.ANSWERED_QUESTIONS_CHART_TITLE_GROUPS : LABELS.ANSWERED_QUESTIONS_CHART_TITLE }, animation : { duration : animate ? 1000 : 0 }, scales : { xAxes : [{ - scaleLabel : { - display : true, - labelString : LABELS.ANSWERED_QUESTIONS_CHART_X_AXIS - } + scaleLabel : { + display : true, + labelString : LABELS.ANSWERED_QUESTIONS_CHART_X_AXIS } + } ], yAxes : [ { - ticks : { + ticks : { beginAtZero : true, stepSize : 1, maxTicksLimit : 5, // prevent scale to change on each update // set suggested max number of students to 3/4 // of all possible learners - suggestedMax : Math.max(2, Math.floor(3 * (useGroups ? data.sessionCount : data.possibleLearners) / 4)) + suggestedMax : Math.max(2, Math.floor(3 * (useGroupsAsNames ? data.sessionCount : data.possibleLearners.length) / 4)) }, scaleLabel : { display : true, - labelString : useGroups ? LABELS.ANSWERED_QUESTIONS_CHART_Y_AXIS_GROUPS : LABELS.ANSWERED_QUESTIONS_CHART_Y_AXIS_STUDENTS, + labelString : useGroupsAsNames ? LABELS.ANSWERED_QUESTIONS_CHART_Y_AXIS_GROUPS : LABELS.ANSWERED_QUESTIONS_CHART_Y_AXIS_STUDENTS, fontSize : 14 } } ] }, 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' - }); + enabled : false, + custom : function(tooltipModel) { + listCompletionChartLearners.call(this, chartPlaceholder, tooltipModel) + } + } + } + }); +} - $('').text(this[3] ? this[3] + ' (' + this[2] + ')' : 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', - }); - } + +function listCompletionChartLearners(chartPlaceholder, tooltipModel) { + + let tooltipClassName = chartPlaceholder.attr('id') + '-tooltip'; + // always remove the tooltip at the beginning + $('.' + tooltipClassName).remove(); + if (tooltipModel.opacity === 0) { + // if it should be hidden, there is nothing to do + return; + } + + // create tooltip + var tooltipEl = $('
').addClass(tooltipClassName) + .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, + data = chartPlaceholder.data('tooltip-input'), + useGroupsAsNames = chartPlaceholder.data('useGroupsAsNames'), + isGrouped = chartPlaceholder.data('isGrouped'), + users = data[tooltipModel.dataPoints[0].index]; + $(users).each(function () { + var portraitDiv = $(definePortrait(this.portraitUuid, this.id, STYLE_SMALL, true, LAMS_URL)).css({ + 'vertical-align': 'middle' + }), + userDiv = $('
').append(portraitDiv).appendTo(tooltipEl).css({ + 'padding-bottom': '5px' + }); + + $('').text(useGroupsAsNames ? this.group + ' (' + this.name + ')' + : (this.name + (isGrouped && this.group ? ' (' + this.group + ') ' : ''))) + .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', + }); } \ No newline at end of file Index: lams_tool_assessment/web/pages/monitoring/summary.jsp =================================================================== diff -u -r1b3d3c47bc8c4f4fb543becb89f41612b08de889 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/web/pages/monitoring/summary.jsp (.../summary.jsp) (revision 1b3d3c47bc8c4f4fb543becb89f41612b08de889) +++ lams_tool_assessment/web/pages/monitoring/summary.jsp (.../summary.jsp) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -18,7 +18,7 @@ .question-title { overflow: auto; min-width: 150px; - } + } <%@ include file="parts/discloseAnswers.jsp"%> @@ -43,7 +43,7 @@ buildJqgridLearnerTable('-all-learners'); - + //jqgrid autowidth (http://stackoverflow.com/a/1610197) $(window).bind('resize', function() { resizeJqgrid(jQuery(".ui-jqgrid-btable:visible")); @@ -97,13 +97,13 @@ var sessionId = jQuery("#list" + sessionId).getCell(rowid, 'sessionId'); var userSummaryUrl = ''; var newUserSummaryHref = userSummaryUrl + "&userID=" + userId + "&sessionId=" + sessionId + "&KeepThis=true&TB_iframe=true&modal=true"; - $("#userSummaryHref").attr("href", newUserSummaryHref); - $("#userSummaryHref").click(); + $("#userSummaryHref").attr("href", newUserSummaryHref); + $("#userSummaryHref").click(); }, - onSelectRow: function(rowid) { - if(rowid == null) { - rowid=0; - } + onSelectRow: function(rowid) { + if(rowid == null) { + rowid=0; + } var tableName = $(this).data('sessionId'); var sessionId = jQuery("#list" + tableName).getCell(rowid, 'sessionId'); var userId = jQuery("#list" + tableName).getCell(rowid, 'userId'); @@ -117,7 +117,7 @@ tableName: tableName, sessionMapID: '${sessionMapID}' } - ); + ); }, loadError: function(xhr,st,err) { var sessionId = $(this).data('sessionId'); @@ -129,12 +129,12 @@ } }) - .jqGrid('filterToolbar', { + .jqGrid('filterToolbar', { searchOnEnter: false }) .navGrid("#listPager" + sessionId, {edit:false,add:false,del:false,search:false}); - + var oldValue = 0; jQuery("#userSummary" + sessionId).jqGrid({ datatype: "local", @@ -157,7 +157,7 @@ "", "", "" - ], + ], colModel:[ {name:'id', index:'id', width:20, sorttype:"int"}, {name:'questionResultUid', index:'questionResultUid', width:0, hidden: true}, @@ -166,7 +166,7 @@ {name:'confidence', index:'confidence', width: 80, classes: 'vertical-align', formatter: gradientNumberFormatter}, - {name:'grade', index:'grade', width:80, sorttype:"float", editable:true, + {name:'grade', index:'grade', width:80, sorttype:"float", editable:true, editoptions: {size:4, maxlength: 4}, align:"right", classes: 'vertical-align', title : false }, {name:'marker', index:'marker', width: 80, title: false}, {name:'markerComment', index:'markerComment', width:120, editable:true, sortable: false, @@ -198,9 +198,9 @@ if (isNaN(val)) { return null; } - + // get maxGrade attribute which was set in masterDetailLoadUp.jsp - var maxGrade = jQuery("table#userSummary" + sessionId + " tr#" + iRow + var maxGrade = jQuery("table#userSummary" + sessionId + " tr#" + iRow + " td[aria-describedby$='_" + name + "']").attr("maxGrade"); if (+val > +maxGrade) { return maxGrade; @@ -211,13 +211,13 @@ return; } if (isNaN(val)) { - jQuery("#userSummary" + sessionId).restoreCell(iRow,iCol); + jQuery("#userSummary" + sessionId).restoreCell(iRow,iCol); } else { var parentSelectedRowId = jQuery("#list" + sessionId).getGridParam("selrow"); var previousTotal = eval(jQuery("#list" + sessionId).getCell(parentSelectedRowId, 'total')); jQuery("#list" + sessionId).setCell(parentSelectedRowId, 'total', previousTotal - oldValue + eval(val), {}, {}); } - }, + }, beforeSubmitCell : function (rowid,name,val,iRow,iCol){ if (name == "grade" && isNaN(val)) { return {nan:true}; @@ -354,7 +354,7 @@ - + Question type @@ -363,7 +363,7 @@ - + Correct answer @@ -372,26 +372,26 @@ - + - + - + ${session.sessionName} - + success" > ${questionResultDto.answer} - + - +
@@ -413,7 +413,7 @@
- + @@ -470,20 +470,20 @@
-
-
- +
- +
Index: lams_tool_assessment/web/pages/tblmonitoring/assessmentStudentChoices.jsp =================================================================== diff -u -r6e6de7ca18164b180cd108d5862b166e7e078bf3 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/web/pages/tblmonitoring/assessmentStudentChoices.jsp (.../assessmentStudentChoices.jsp) (revision 6e6de7ca18164b180cd108d5862b166e7e078bf3) +++ lams_tool_assessment/web/pages/tblmonitoring/assessmentStudentChoices.jsp (.../assessmentStudentChoices.jsp) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -14,7 +14,7 @@ .question-title { overflow: auto; min-width: 150px; - } + } @@ -46,15 +46,15 @@ activityCompletionChart = null, answeredQuestionsChart = null, COMPLETION_CHART_UPDATE_INTERVAL = 10 * 1000; - + $(document).ready(function(){ openEventSource('monitoring/getCompletionChartsData.do?toolContentId=${toolContentID}', function(event) { if (!event.data) { return; } var data = JSON.parse(decodeURIComponent(event.data)); drawActivityCompletionChart(data, true); - drawAnsweredQuestionsChart(data, ${groupsInAnsweredQuestionsChart}, true); + drawAnsweredQuestionsChart(data, true); $('#student-choices-table').load('tblmonitoring/aesStudentChoicesTable.do?toolContentID=${toolContentID}'); }); @@ -74,7 +74,7 @@
- +
@@ -94,14 +94,14 @@
- +
- +

@@ -111,13 +111,13 @@

- - + +
- + @@ -135,7 +135,7 @@ - + @@ -144,7 +144,7 @@ - + @@ -153,19 +153,19 @@ - + - + - +
Question type
Correct answer
-
- +
+
Index: lams_tool_assessment/web/pages/tblmonitoring/iraAssessmentStudentChoices.jsp =================================================================== diff -u -r6d67af7eb9c49d59b6b0ff579b472dcf7022d437 -rf8f156c0f021d60857760ea60579a41e1380288c --- lams_tool_assessment/web/pages/tblmonitoring/iraAssessmentStudentChoices.jsp (.../iraAssessmentStudentChoices.jsp) (revision 6d67af7eb9c49d59b6b0ff579b472dcf7022d437) +++ lams_tool_assessment/web/pages/tblmonitoring/iraAssessmentStudentChoices.jsp (.../iraAssessmentStudentChoices.jsp) (revision f8f156c0f021d60857760ea60579a41e1380288c) @@ -1,124 +1,142 @@ <%@ include file="/common/taglibs.jsp"%> <% pageContext.setAttribute("newLineChar", "\r\n"); %> -monitoring/timeLimit.jsp +monitoring/timeLimit5.jsp - + - - - - + - - - -
-
-

- -

+
+
+
+ + + +

+ +

+
-
- - -
-
+
+
+
+ +
+
+ +
+
+

+ +

+ +
+
- - -
-
- -
-
- + <%-- Include student's choices part --%> +
+
+
- -
- -
-
-<%-- Include student's choices part --%> -<%@ include file="/pages/monitoring/parts/mcqStudentChoices.jsp" %> - -
\ No newline at end of file +
+
+
+
+
\ No newline at end of file