Index: lams_central/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r51254ff2df01d1941265996bbc17c7a31f4942ef -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 51254ff2df01d1941265996bbc17c7a31f4942ef) +++ lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -285,6 +285,7 @@ label.tab.lesson = Lesson label.tab.class = Class label.tab.advanced = Advanced +label.tab.grouping = Groups label.tab.conditions = Conditions label.tab.lesson.title = Select the design to add a lesson, and click on Add now label.tab.class.title = Use drag n' drop to select or unselect monitors and learners @@ -313,6 +314,8 @@ label.tab.advanced.field.split.number = No. learners per lesson label.tab.advanced.field.scheduling = Enable scheduling label.tab.advanced.split.desc = [0] instances of this lesson will be created and approximately [1] will be allocated to each lesson +label.tab.grouping.desc = You can select one of the preset course groupings below to apply it directly to all grouping activities in the chosen design. +label.tab.grouping.none = The selected design does not contain an activity to apply course grouping to. label.tab.conditions.dependencies = Dependencies label.tab.conditions.dependencies.desc = Select a lesson that learners will need to complete before they can see the lesson you are about to create. label.tab.conditions.timelimit = Time limitations Index: lams_central/src/java/org/lamsfoundation/lams/web/GroupingController.java =================================================================== diff -u -r0132ac7723f6060a3a656d216ac93a1540634c7f -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_central/src/java/org/lamsfoundation/lams/web/GroupingController.java (.../GroupingController.java) (revision 0132ac7723f6060a3a656d216ac93a1540634c7f) +++ lams_central/src/java/org/lamsfoundation/lams/web/GroupingController.java (.../GroupingController.java) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -52,6 +52,7 @@ import org.lamsfoundation.lams.learningdesign.GroupComparator; import org.lamsfoundation.lams.learningdesign.Grouping; import org.lamsfoundation.lams.learningdesign.GroupingActivity; +import org.lamsfoundation.lams.learningdesign.service.LearningDesignService; import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.lesson.service.ILessonService; import org.lamsfoundation.lams.security.ISecurityService; @@ -144,7 +145,7 @@ // show groups page if this is a lesson mode and user have already chosen a grouping or there is no organisation // groupings available boolean lessonGroupsExist = (grouping != null) && (grouping.getGroups() != null) - && !grouping.getGroups().isEmpty() && !isDefaultChosenGrouping(grouping); + && !grouping.getGroups().isEmpty() && !LearningDesignService.isDefaultChosenGrouping(grouping); if (lessonGroupsExist || (activityID != null && orgGroupings.isEmpty())) { return viewGroups(request, response, organisationId, targetOrganisationId); } @@ -249,7 +250,7 @@ Grouping lessonGrouping = getLessonGrouping(activityId); Set lessonGroups = lessonGrouping == null ? null : lessonGrouping.getGroups(); if ((activityId != null) && (lessonGrouping != null) && (isExternalGroupsSelected || (orgGroupingId != null)) - && isDefaultChosenGrouping(lessonGrouping)) { + && LearningDesignService.isDefaultChosenGrouping(lessonGrouping)) { if (log.isDebugEnabled()) { log.debug("Removing default groups for grouping " + orgGroupingId); } Index: lams_central/src/java/org/lamsfoundation/lams/web/HomeController.java =================================================================== diff -u -ra40a77e307317e8038ed9e6b8699c18386286497 -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_central/src/java/org/lamsfoundation/lams/web/HomeController.java (.../HomeController.java) (revision a40a77e307317e8038ed9e6b8699c18386286497) +++ lams_central/src/java/org/lamsfoundation/lams/web/HomeController.java (.../HomeController.java) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.Date; +import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.Vector; @@ -53,8 +54,10 @@ import org.lamsfoundation.lams.logevent.service.ILogEventService; import org.lamsfoundation.lams.security.ISecurityService; import org.lamsfoundation.lams.usermanagement.Organisation; +import org.lamsfoundation.lams.usermanagement.OrganisationGrouping; import org.lamsfoundation.lams.usermanagement.Role; import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.dto.OrganisationGroupingDTO; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; import org.lamsfoundation.lams.usermanagement.exception.UserAccessDeniedException; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; @@ -304,6 +307,14 @@ // find subgroups which can be set as multiple lessons start req.setAttribute("subgroups", organisation.getChildOrganisations()); + List orgGroupings = userManagementService.findByProperty(OrganisationGrouping.class, + "organisationId", organisationID); + Set orgGroupingDTOs = new TreeSet<>(); + for (OrganisationGrouping orgGrouping : orgGroupings) { + orgGroupingDTOs.add(new OrganisationGroupingDTO(orgGrouping)); + } + req.setAttribute("orgGroupings", orgGroupingDTOs); + return "addLesson"; } Index: lams_central/web/includes/javascript/addLesson.js =================================================================== diff -u -r7e26cc267b556ce7f7b2116b13a9afe02f353d74 -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_central/web/includes/javascript/addLesson.js (.../addLesson.js) (revision 7e26cc267b556ce7f7b2116b13a9afe02f353d74) +++ lams_central/web/includes/javascript/addLesson.js (.../addLesson.js) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -244,10 +244,12 @@ }); $('#schedulingDatetimeField').datetimepicker({ - 'minDate' : 0 + 'minDate' : 0, + 'dateFormat' : 'yy-mm-dd' }); $('#schedulingEndDatetimeField').datetimepicker({ - 'minDate' : 0 + 'minDate' : 0, + 'dateFormat' : 'yy-mm-dd' }); Index: lams_common/src/java/org/lamsfoundation/lams/lesson/service/ILessonService.java =================================================================== diff -u -r616c7119289412164b78e0f420725a865e60c933 -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_common/src/java/org/lamsfoundation/lams/lesson/service/ILessonService.java (.../ILessonService.java) (revision 616c7119289412164b78e0f420725a865e60c933) +++ lams_common/src/java/org/lamsfoundation/lams/lesson/service/ILessonService.java (.../ILessonService.java) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -122,6 +122,11 @@ void performGrouping(Long lessonId, GroupingActivity groupingActivity, User learner) throws LessonServiceException; /** + * Applies given course grouping to all non-branch, non-modified groupings in the given Learnin Design. + */ + void performGrouping(long learningDesignId, long orgGroupingId); + + /** * Perform the grouping, setting the given list of learners as one group. * * @param groupingActivity Index: lams_common/src/java/org/lamsfoundation/lams/lesson/service/LessonService.java =================================================================== diff -u -r0132ac7723f6060a3a656d216ac93a1540634c7f -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_common/src/java/org/lamsfoundation/lams/lesson/service/LessonService.java (.../LessonService.java) (revision 0132ac7723f6060a3a656d216ac93a1540634c7f) +++ lams_common/src/java/org/lamsfoundation/lams/lesson/service/LessonService.java (.../LessonService.java) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -40,9 +40,11 @@ import org.lamsfoundation.lams.learningdesign.Grouper; import org.lamsfoundation.lams.learningdesign.Grouping; import org.lamsfoundation.lams.learningdesign.GroupingActivity; +import org.lamsfoundation.lams.learningdesign.LearningDesign; import org.lamsfoundation.lams.learningdesign.ToolActivity; import org.lamsfoundation.lams.learningdesign.dao.IGroupingDAO; import org.lamsfoundation.lams.learningdesign.exception.GroupingException; +import org.lamsfoundation.lams.learningdesign.service.LearningDesignService; import org.lamsfoundation.lams.lesson.LearnerProgress; import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.lesson.LessonClass; @@ -51,6 +53,8 @@ import org.lamsfoundation.lams.lesson.dao.ILessonDAO; import org.lamsfoundation.lams.lesson.dto.ActivityTimeLimitDTO; import org.lamsfoundation.lams.lesson.dto.LessonDetailsDTO; +import org.lamsfoundation.lams.usermanagement.OrganisationGroup; +import org.lamsfoundation.lams.usermanagement.OrganisationGrouping; import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.util.MessageService; @@ -332,6 +336,40 @@ } @Override + public void performGrouping(long learningDesignId, long orgGroupingId) { + LearningDesign learningDesign = baseDAO.find(LearningDesign.class, learningDesignId); + OrganisationGrouping orgGrouping = groupingDAO.find(OrganisationGrouping.class, orgGroupingId); + // find all grouping activities + for (Activity activity : learningDesign.getActivities()) { + if (!activity.isGroupingActivity()) { + continue; + } + // skip groupings used for branching + // and ones that have already been initialised + GroupingActivity groupingActivity = (GroupingActivity) activity; + Grouping grouping = groupingActivity.getCreateGrouping(); + if (grouping == null || grouping.isUsedForBranching()) { + continue; + } + boolean groupingAlreadyApplied = grouping.getGroups() != null && !grouping.getGroups().isEmpty() + && !LearningDesignService.isDefaultChosenGrouping(grouping); + if (groupingAlreadyApplied) { + continue; + } + + // nuke all existing groups and create new ones + removeAllLearnersFromGrouping(grouping); + groupingDAO.flush(); + for (OrganisationGroup orgGroup : orgGrouping.getGroups()) { + Group group = Group.createLearnerGroup(grouping, orgGroup.getName(), + new HashSet<>(orgGroup.getUsers())); + grouping.getGroups().add(group); + } + groupingDAO.update(grouping); + } + } + + @Override public boolean addLearner(Long lessonId, Integer userId) throws LessonServiceException { Lesson lesson = lessonDAO.getLesson(lessonId); if (lesson == null) { Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java =================================================================== diff -u -rdbbe9d80bcbeda8f746963fcde183242996fcc68 -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java (.../MonitoringController.java) (revision dbbe9d80bcbeda8f746963fcde183242996fcc68) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java (.../MonitoringController.java) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -51,8 +51,11 @@ import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.lamsfoundation.lams.authoring.IAuthoringService; +import org.lamsfoundation.lams.flux.FluxMap; +import org.lamsfoundation.lams.flux.FluxRegistry; import org.lamsfoundation.lams.learning.service.ILearnerService; import org.lamsfoundation.lams.learningdesign.Activity; +import org.lamsfoundation.lams.learningdesign.ActivityOrderComparator; import org.lamsfoundation.lams.learningdesign.BranchingActivity; import org.lamsfoundation.lams.learningdesign.ChosenBranchingActivity; import org.lamsfoundation.lams.learningdesign.ComplexActivity; @@ -65,12 +68,16 @@ import org.lamsfoundation.lams.learningdesign.SequenceActivity; import org.lamsfoundation.lams.learningdesign.ToolActivity; import org.lamsfoundation.lams.learningdesign.Transition; +import org.lamsfoundation.lams.learningdesign.dao.IActivityDAO; import org.lamsfoundation.lams.learningdesign.exception.LearningDesignException; import org.lamsfoundation.lams.learningdesign.service.ILearningDesignService; import org.lamsfoundation.lams.lesson.LearnerProgress; import org.lamsfoundation.lams.lesson.Lesson; +import org.lamsfoundation.lams.lesson.dto.ActivityTimeLimitDTO; import org.lamsfoundation.lams.lesson.dto.LessonDetailsDTO; import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.lesson.util.LearnerActivityCompleteFluxItem; +import org.lamsfoundation.lams.lesson.util.LearnerLessonJoinFluxItem; import org.lamsfoundation.lams.logevent.LogEvent; import org.lamsfoundation.lams.logevent.service.ILogEventService; import org.lamsfoundation.lams.monitoring.MonitoringConstants; @@ -79,6 +86,7 @@ import org.lamsfoundation.lams.monitoring.service.IMonitoringService; import org.lamsfoundation.lams.security.ISecurityService; import org.lamsfoundation.lams.tool.exception.LamsToolServiceException; +import org.lamsfoundation.lams.tool.service.ICommonScratchieService; import org.lamsfoundation.lams.tool.service.ILamsToolService; import org.lamsfoundation.lams.usermanagement.Organisation; import org.lamsfoundation.lams.usermanagement.Role; @@ -96,17 +104,22 @@ import org.lamsfoundation.lams.web.util.AttributeNames; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; 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.util.HtmlUtils; +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; + /** * The action servlet that provide all the monitoring functionalities. It interact with the teacher via JSP monitoring * interface. @@ -118,7 +131,7 @@ public class MonitoringController { private static Logger log = Logger.getLogger(MonitoringController.class); - private static final DateFormat LESSON_SCHEDULING_DATETIME_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm"); + private static final DateFormat LESSON_SCHEDULING_DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); private static final int LATEST_LEARNER_PROGRESS_LESSON_DISPLAY_LIMIT = 53; private static final int LATEST_LEARNER_PROGRESS_ACTIVITY_DISPLAY_LIMIT = 7; @@ -133,6 +146,8 @@ @Autowired private ISecurityService securityService; @Autowired + private IActivityDAO activityDAO; + @Autowired private IMonitoringFullService monitoringService; @Autowired private IUserManagementService userManagementService; @@ -145,7 +160,30 @@ private MessageService messageService; @Autowired private IAuthoringService authoringService; + @Autowired + @Qualifier("scratchieService") + private ICommonScratchieService commonScratchieService; + public MonitoringController() { + // bind sinks so a learner finishing an activity also triggers an update in lesson progress + FluxRegistry.bindSink(CommonConstants.ACTIVITY_ENTERED_SINK_NAME, CommonConstants.LESSON_PROGRESSED_SINK_NAME, + learnerProgressFluxItem -> ((LearnerActivityCompleteFluxItem) learnerProgressFluxItem).getLessonId()); + // bind sinks so a learner entering a lesson also triggers an update in lesson progress + FluxRegistry.bindSink(CommonConstants.LESSON_JOINED_SINK_NAME, CommonConstants.LESSON_PROGRESSED_SINK_NAME, + lessonJoinedFluxItem -> ((LearnerLessonJoinFluxItem) lessonJoinedFluxItem).getLessonId()); + + FluxRegistry.initFluxMap(MonitoringConstants.CANVAS_REFRESH_FLUX_NAME, + CommonConstants.LESSON_PROGRESSED_SINK_NAME, null, lessonId -> "doRefresh", FluxMap.STANDARD_THROTTLE, + FluxMap.STANDARD_TIMEOUT); + FluxRegistry.initFluxMap(MonitoringConstants.GRADEBOOK_REFRESH_FLUX_NAME, + CommonConstants.LESSON_PROGRESSED_SINK_NAME, null, lessonId -> "doRefresh", FluxMap.STANDARD_THROTTLE, + FluxMap.STANDARD_TIMEOUT); + FluxRegistry.initFluxMap(MonitoringConstants.TIME_LIMIT_REFRESH_FLUX_NAME, + CommonConstants.ACTIVITY_TIME_LIMIT_CHANGED_SINK_NAME, + (Collection key, Collection item) -> key.containsAll(item), toolContentIds -> "doRefresh", + FluxMap.SHORT_THROTTLE, FluxMap.STANDARD_TIMEOUT); + } + private Integer getUserId() { HttpSession ss = SessionManager.getSession(); UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); @@ -162,6 +200,27 @@ return null; } + @RequestMapping(path = "/getLearnerProgressUpdateFlux", method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE) + @ResponseBody + public Flux getLearnerProgressUpdateFlux(@RequestParam long lessonId) + throws JsonProcessingException, IOException { + return FluxRegistry.get(MonitoringConstants.CANVAS_REFRESH_FLUX_NAME, lessonId); + } + + @RequestMapping(path = "/getGradebookUpdateFlux", method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE) + @ResponseBody + public Flux getGradebookUpdateFlux(@RequestParam long lessonId) + throws JsonProcessingException, IOException { + return FluxRegistry.get(MonitoringConstants.GRADEBOOK_REFRESH_FLUX_NAME, lessonId); + } + + @RequestMapping(path = "/getTimeLimitUpdateFlux", method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE) + @ResponseBody + public Flux getTimeLimitUpdateFlux(@RequestParam Set toolContentIds) + throws JsonProcessingException, IOException { + return FluxRegistry.get(MonitoringConstants.TIME_LIMIT_REFRESH_FLUX_NAME, toolContentIds); + } + /** * Initializes a lesson for specific learning design with the given lesson title and lesson description. If * initialization is successful, this method will the ID of new lesson. @@ -976,11 +1035,39 @@ && userManagementService.isUserInRole(user.getUserID(), organisation.getOrganisationId(), Role.AUTHOR); request.setAttribute("enableLiveEdit", enableLiveEdit); request.setAttribute("lesson", lessonDTO); - request.setAttribute("isTBLSequence", learningDesignService.isTBLSequence(lessonDTO.getLearningDesignID())); + boolean isTBLSequence = learningDesignService.isTBLSequence(lessonDTO.getLearningDesignID()); + request.setAttribute("isTBLSequence", isTBLSequence); + if (isTBLSequence) { + List lessonActivities = getLessonActivities(lessonService.getLesson(lessonId)); + TblMonitoringController.setupAvailableActivityTypes(request, lessonActivities); + boolean burningQuestionsEnabled = false; + Long traToolActivityId = (Long) request.getAttribute("traToolActivityId"); + if (traToolActivityId != null) { + long traToolContentId = activityDAO.find(ToolActivity.class, traToolActivityId).getToolContentId(); + burningQuestionsEnabled = commonScratchieService.isBurningQuestionsEnabled(traToolContentId); + } + request.setAttribute("burningQuestionsEnabled", burningQuestionsEnabled); + } + return "monitor"; } + @RequestMapping("/displaySequenceTab") + public String displaySequenceTab() { + return "monitor-sequence-tab"; + } + + @RequestMapping("/displayLearnersTab") + public String displayLearnersTab() { + return "monitor-learners-tab"; + } + + @RequestMapping("/displayGradebookTab") + public String displayGradebookTab() { + return "monitor-gradebook-tab"; + } + /** * Gets users whose progress bars will be displayed in Learner tab in Monitor. */ @@ -993,25 +1080,40 @@ return null; } - String searchPhrase = request.getParameter("searchPhrase"); - Integer pageNumber = WebUtil.readIntParam(request, "pageNumber", true); - if (pageNumber == null || pageNumber < 1) { - pageNumber = 1; + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + Integer searchedLearnerId = WebUtil.readIntParam(request, "searchedLearnerID", true); + List learners = null; + if (searchedLearnerId == null) { + Integer pageNumber = WebUtil.readIntParam(request, "pageNumber", true); + if (pageNumber == null || pageNumber < 1) { + pageNumber = 1; + } + // are the learners sorted by the most completed first? + boolean isProgressSorted = WebUtil.readBooleanParam(request, "isProgressSorted", false); + + // either sort by name or how much a learner progressed into the lesson + learners = isProgressSorted + ? monitoringService.getLearnersByMostProgress(lessonId, null, 10, (pageNumber - 1) * 10) + : lessonService.getLessonLearners(lessonId, null, 10, (pageNumber - 1) * 10, true); + + // get all possible learners matching the given phrase, if any; used for max page number + responseJSON.put("learnerPossibleNumber", lessonService.getCountLessonLearners(lessonId, null)); + } else { + // only one learner is searched + User learner = userManagementService.getUserById(searchedLearnerId); + learners = List.of(learner); + responseJSON.put("learnerPossibleNumber", 1); } - // are the learners sorted by the most completed first? - boolean isProgressSorted = WebUtil.readBooleanParam(request, "isProgressSorted", false); - // either sort by name or how much a learner progressed into the lesson - List learners = isProgressSorted - ? monitoringService.getLearnersByMostProgress(lessonId, searchPhrase, 10, (pageNumber - 1) * 10) - : lessonService.getLessonLearners(lessonId, searchPhrase, 10, (pageNumber - 1) * 10, true); - ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); for (User learner : learners) { - responseJSON.withArray("learners").add(WebUtil.userToJSON(learner)); + ObjectNode learnerJSON = WebUtil.userToJSON(learner); + LearnerProgress learnerProgress = lessonService.getUserProgressForLesson(learner.getUserId(), lessonId); + learnerJSON.put("completedActivityCount", + learnerProgress == null ? 0 : learnerProgress.getCompletedActivities().size()); + learnerJSON.put("completedLesson", learnerProgress != null && learnerProgress.isComplete()); + responseJSON.withArray("learners").add(learnerJSON); } - // get all possible learners matching the given phrase, if any; used for max page number - responseJSON.put("learnerPossibleNumber", lessonService.getCountLessonLearners(lessonId, searchPhrase)); response.setContentType("application/json;charset=utf-8"); return responseJSON.toString(); } @@ -1066,11 +1168,6 @@ indfm.format(tzFinishDate) + " " + user.getTimeZone().getDisplayName(userLocale)); } - List contributeActivities = getContributeActivities(lessonId, false, false); - if (contributeActivities != null) { - responseJSON.set("contributeActivities", JsonUtil.readArray(contributeActivities)); - } - response.setContentType("application/json;charset=utf-8"); return responseJSON.toString(); } @@ -1086,21 +1183,24 @@ Integer notCompletedLearnersCount = possibleLearnersCount - completedLearnersCount - startedLearnersCount; ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); - ObjectNode notStartedJSON = JsonNodeFactory.instance.objectNode(); - notStartedJSON.put("name", messageService.getMessage("lesson.chart.not.completed")); - notStartedJSON.put("value", Math.round(notCompletedLearnersCount.doubleValue() / possibleLearnersCount * 100)); - responseJSON.withArray("data").add(notStartedJSON); - ObjectNode startedJSON = JsonNodeFactory.instance.objectNode(); startedJSON.put("name", messageService.getMessage("lesson.chart.started")); startedJSON.put("value", Math.round((startedLearnersCount.doubleValue()) / possibleLearnersCount * 100)); + startedJSON.put("raw", startedLearnersCount); responseJSON.withArray("data").add(startedJSON); ObjectNode completedJSON = JsonNodeFactory.instance.objectNode(); completedJSON.put("name", messageService.getMessage("lesson.chart.completed")); completedJSON.put("value", Math.round(completedLearnersCount.doubleValue() / possibleLearnersCount * 100)); + completedJSON.put("raw", completedLearnersCount); responseJSON.withArray("data").add(completedJSON); + ObjectNode notStartedJSON = JsonNodeFactory.instance.objectNode(); + notStartedJSON.put("name", messageService.getMessage("lesson.chart.not.completed")); + notStartedJSON.put("value", Math.round(notCompletedLearnersCount.doubleValue() / possibleLearnersCount * 100)); + notStartedJSON.put("raw", notCompletedLearnersCount); + responseJSON.withArray("data").add(notStartedJSON); + response.setContentType("application/json;charset=utf-8"); return responseJSON.toString(); } @@ -1136,7 +1236,7 @@ } ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); - List contributeActivities = getContributeActivities(lessonId, true, true); + List contributeActivities = getContributeActivities(lessonId, true, false); if (contributeActivities != null) { responseJSON.set("contributeActivities", JsonUtil.readArray(contributeActivities)); } @@ -1306,6 +1406,11 @@ } } + List absoluteTimeLimits = lessonService.getRunningAbsoluteTimeLimits(lessonId); + if (!absoluteTimeLimits.isEmpty()) { + responseJSON.set("timeLimits", JsonUtil.readArray(absoluteTimeLimits)); + } + response.setContentType("application/json;charset=utf-8"); return responseJSON.toString(); @@ -1545,6 +1650,22 @@ return "timer"; } + @GetMapping("/getTimeLimits") + @ResponseBody + public String getTimeLimits(@RequestParam long lessonID, HttpServletResponse response) throws IOException { + ArrayNode responseJSON = null; + + List absoluteTimeLimits = lessonService.getRunningAbsoluteTimeLimits(lessonID); + if (absoluteTimeLimits.isEmpty()) { + responseJSON = JsonNodeFactory.instance.arrayNode(); + } else { + responseJSON = JsonUtil.readArray(absoluteTimeLimits); + } + + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + @RequestMapping(path = "/isLearningDesignHasGroupings", method = RequestMethod.GET) @ResponseBody public String isLearningDesignHasGroupings(@RequestParam long learningDesignId) { @@ -1640,4 +1761,66 @@ } return updatedLatestLearners; } -} + + private List getLessonActivities(Lesson lesson) { + /* + * Hibernate CGLIB is failing to load the first activity in the sequence as a ToolActivity for some mysterious + * reason Causes a ClassCastException when you try to cast it, even if it is a ToolActivity. + * + * THIS IS A HACK to retrieve the first tool activity manually so it can be cast as a ToolActivity - if it is + * one + */ + Activity firstActivity = activityDAO + .getActivityByActivityId(lesson.getLearningDesign().getFirstActivity().getActivityId()); + List activities = new ArrayList<>(); + sortActivitiesByLearningDesignOrder(firstActivity, activities); + + return activities; + } + + @SuppressWarnings("unchecked") + private void sortActivitiesByLearningDesignOrder(Activity activity, List sortedActivities) { + sortedActivities.add(activity); + + //in case of branching activity - add all activities based on their orderId + if (activity.isBranchingActivity()) { + BranchingActivity branchingActivity = (BranchingActivity) activity; + Set sequenceActivities = new TreeSet<>(new ActivityOrderComparator()); + sequenceActivities.addAll((Set) (Set) branchingActivity.getActivities()); + for (Activity sequenceActivityNotInitialized : sequenceActivities) { + SequenceActivity sequenceActivity = (SequenceActivity) monitoringService + .getActivityById(sequenceActivityNotInitialized.getActivityId()); + Set childActivities = new TreeSet<>(new ActivityOrderComparator()); + childActivities.addAll(sequenceActivity.getActivities()); + + //add one by one in order to initialize all activities + for (Activity childActivity : childActivities) { + Activity activityInit = monitoringService.getActivityById(childActivity.getActivityId()); + sortedActivities.add(activityInit); + } + } + + // In case of complex activity (parallel, help or optional activity) add all its children activities. + // They will be sorted by orderId + } else if (activity.isComplexActivity()) { + ComplexActivity complexActivity = (ComplexActivity) activity; + Set childActivities = new TreeSet<>(new ActivityOrderComparator()); + childActivities.addAll(complexActivity.getActivities()); + + // add one by one in order to initialize all activities + for (Activity childActivity : childActivities) { + Activity activityInit = monitoringService.getActivityById(childActivity.getActivityId()); + sortedActivities.add(activityInit); + } + } + + Transition transitionFrom = activity.getTransitionFrom(); + if (transitionFrom != null) { + // query activity from DB as transition holds only proxied activity object + Long nextActivityId = transitionFrom.getToActivity().getActivityId(); + Activity nextActivity = monitoringService.getActivityById(nextActivityId); + + sortActivitiesByLearningDesignOrder(nextActivity, sortedActivities); + } + } +} \ No newline at end of file Index: lams_tool_assessment/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r51254ff2df01d1941265996bbc17c7a31f4942ef -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_tool_assessment/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 51254ff2df01d1941265996bbc17c7a31f4942ef) +++ lams_tool_assessment/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -157,6 +157,8 @@ label.monitoring.user.summary.grade = Grade label.monitoring.user.summary.title = Title: label.monitoring.user.summary.question = Question: +label.monitoring.user.summary.grade.required = requires grading +label.monitoring.user.summary.grade.by = graded by {0} label.monitoring.question.summary.history.responses = Responses for the question label.monitoring.question.summary.title = Title label.monitoring.question.summary.question = Question Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -rac8d72126e5c0f76d0cb23eb5d66a7bd4f213268 -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision ac8d72126e5c0f76d0cb23eb5d66a7bd4f213268) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -2363,7 +2363,7 @@ } @Override - public void changeQuestionResultMark(Long questionResultUid, float newMark) { + public void changeQuestionResultMark(Long questionResultUid, float newMark, Integer teacherId) { AssessmentQuestionResult questionResult = assessmentQuestionResultDao .getAssessmentQuestionResultByUid(questionResultUid); float oldMark = questionResult.getMark(); @@ -2374,6 +2374,11 @@ Assessment assessment = assessmentResult.getAssessment(); Long questionUid = questionResult.getQbToolQuestion().getUid(); + AssessmentUser teacher = null; + if (teacherId != null) { + teacher = getUserByIdAndContent(teacherId.longValue(), assessment.getContentId()); + } + // When changing a mark for user and isUseSelectLeaderToolOuput is true, the mark should be propagated to all // students within the group List users = new ArrayList<>(); @@ -2401,6 +2406,9 @@ AssessmentQuestionResult lastAssessmentQuestionResult = (AssessmentQuestionResult) lastAssessmentQuestionResultObj[0]; lastAssessmentQuestionResult.setMark(newMark); + if (teacher != null) { + lastAssessmentQuestionResult.setMarkedBy(teacher); + } assessmentQuestionResultDao.saveObject(lastAssessmentQuestionResult); AssessmentResult result = lastAssessmentQuestionResult.getAssessmentResult(); Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/IAssessmentService.java =================================================================== diff -u -r97302bfd38611772c8baaf1900f7fcbc87024f6b -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/IAssessmentService.java (.../IAssessmentService.java) (revision 97302bfd38611772c8baaf1900f7fcbc87024f6b) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/IAssessmentService.java (.../IAssessmentService.java) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -456,7 +456,7 @@ */ List getMarksArrayForLeaders(Long contentId); - void changeQuestionResultMark(Long questionResultUid, float newMark); + void changeQuestionResultMark(Long questionResultUid, float newMark, Integer teacherId); void notifyTeachersOnAttemptCompletion(Long sessionId, String userName); Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java =================================================================== diff -u -r97302bfd38611772c8baaf1900f7fcbc87024f6b -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java (.../MonitoringController.java) (revision 97302bfd38611772c8baaf1900f7fcbc87024f6b) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java (.../MonitoringController.java) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -339,7 +339,9 @@ && !StringUtils.isEmpty(request.getParameter(AssessmentConstants.PARAM_QUESTION_RESULT_UID))) { Long questionResultUid = WebUtil.readLongParam(request, AssessmentConstants.PARAM_QUESTION_RESULT_UID); float newGrade = Float.valueOf(request.getParameter(AssessmentConstants.PARAM_GRADE)); - service.changeQuestionResultMark(questionResultUid, newGrade); + HttpSession ss = SessionManager.getSession(); + UserDTO teacher = (UserDTO) ss.getAttribute(AttributeNames.USER); + service.changeQuestionResultMark(questionResultUid, newGrade, teacher.getUserID()); } } @@ -618,6 +620,7 @@ } userData.add(response); + userData.add(questionResult.getMarkedBy() == null ? "" : questionResult.getMarkedBy().getFullName()); } else { userData.add(""); userData.add(""); @@ -630,6 +633,7 @@ userData.add("-"); } userData.add("-"); + userData.add(""); } userData.add(userDto.getUserId()); Index: lams_tool_assessment/web/pages/monitoring/parts/masterDetailLoadUp.jsp =================================================================== diff -u -r97302bfd38611772c8baaf1900f7fcbc87024f6b -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_tool_assessment/web/pages/monitoring/parts/masterDetailLoadUp.jsp (.../masterDetailLoadUp.jsp) (revision 97302bfd38611772c8baaf1900f7fcbc87024f6b) +++ lams_tool_assessment/web/pages/monitoring/parts/masterDetailLoadUp.jsp (.../masterDetailLoadUp.jsp) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -25,10 +25,30 @@ grade:"" }); + // set maxGrade attribute to cell DOM element - table.setCell(${i.index + 1}, "grade", "", null, {"maxGrade" : "${questionResult.maxMark}"}); + table.setCell(${i.index + 1}, "grade", "", ${requiresMarking ? "'requires-grading'" : "null"}, + {"maxGrade" : "${questionResult.maxMark}" + + + ,"title" : "" + ,"data-toggle" : "tooltip" + ,"data-container" : "body" + + + ,"title" : " + + " + ,"data-toggle" : "tooltip" + ,"data-container" : "body" + + + }); + $('[data-toggle="tooltip"]').bootstrapTooltip(); + if (typeof CodeMirror != 'undefined') { CodeMirror.colorize($('.code-style')); } Index: lams_tool_assessment/web/pages/monitoring/summary.jsp =================================================================== diff -u -r97302bfd38611772c8baaf1900f7fcbc87024f6b -r7b3eba5d7b856941bb0b89cb6e49a1498a8feac4 --- lams_tool_assessment/web/pages/monitoring/summary.jsp (.../summary.jsp) (revision 97302bfd38611772c8baaf1900f7fcbc87024f6b) +++ lams_tool_assessment/web/pages/monitoring/summary.jsp (.../summary.jsp) (revision 7b3eba5d7b856941bb0b89cb6e49a1498a8feac4) @@ -154,7 +154,8 @@ {name:'id', index:'id', width:20, sorttype:"int"}, {name:'questionResultUid', index:'questionResultUid', width:0, hidden: true}, {name:'title', index:'title', width: 200}, - {name:'grade', index:'grade', width:80, sorttype:"float", editable:true, editoptions: {size:4, maxlength: 4}, align:"right", classes: 'vertical-align' }, + {name:'grade', index:'grade', width:80, sorttype:"float", editable:true, + editoptions: {size:4, maxlength: 4}, align:"right", classes: 'vertical-align', title : false }, {name:'confidence', index:'confidence', width: 80, classes: 'vertical-align', formatter: gradientNumberFormatter},