Index: lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/gradebookApplicationContext.xml =================================================================== diff -u -r01c2a55767c613282c319ed8b38a0c78712e3661 -r81b83a2cbd3cfe68e9096211cd0df9eada37fc4a --- lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/gradebookApplicationContext.xml (.../gradebookApplicationContext.xml) (revision 01c2a55767c613282c319ed8b38a0c78712e3661) +++ lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/gradebookApplicationContext.xml (.../gradebookApplicationContext.xml) (revision 81b83a2cbd3cfe68e9096211cd0df9eada37fc4a) @@ -26,6 +26,7 @@ + Index: lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/service/GradebookService.java =================================================================== diff -u -r148241ef71d53c340db44d23458316416e8f3c0f -r81b83a2cbd3cfe68e9096211cd0df9eada37fc4a --- lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/service/GradebookService.java (.../GradebookService.java) (revision 148241ef71d53c340db44d23458316416e8f3c0f) +++ lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/service/GradebookService.java (.../GradebookService.java) (revision 81b83a2cbd3cfe68e9096211cd0df9eada37fc4a) @@ -37,8 +37,10 @@ import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; +import java.util.stream.Collectors; import org.apache.log4j.Logger; +import org.lamsfoundation.lams.events.IEventNotificationService; import org.lamsfoundation.lams.gradebook.GradebookUserActivity; import org.lamsfoundation.lams.gradebook.GradebookUserLesson; import org.lamsfoundation.lams.gradebook.dao.IGradebookDAO; @@ -50,7 +52,9 @@ import org.lamsfoundation.lams.gradebook.model.GradebookUserActivityArchive; import org.lamsfoundation.lams.gradebook.model.GradebookUserLessonArchive; import org.lamsfoundation.lams.gradebook.util.GBGridView; +import org.lamsfoundation.lams.gradebook.util.GradebookUtil; import org.lamsfoundation.lams.gradebook.util.LessonComparator; +import org.lamsfoundation.lams.gradebook.util.ReleaseMarksJob; import org.lamsfoundation.lams.integration.service.IIntegrationService; import org.lamsfoundation.lams.learning.service.ILearnerService; import org.lamsfoundation.lams.learningdesign.Activity; @@ -104,6 +108,13 @@ import org.lamsfoundation.lams.util.excel.ExcelSheet; import org.lamsfoundation.lams.web.session.SessionManager; import org.lamsfoundation.lams.web.util.AttributeNames; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.util.HtmlUtils; @@ -138,6 +149,8 @@ private IIntegrationService integrationService; private IOutcomeService outcomeService; + private Scheduler scheduler; + @Override public List getGBActivityRowsForLearner(Long lessonId, Integer userId, TimeZone userTimezone) { logger.debug("Getting gradebook user data for lesson: " + lessonId + ". For user: " + userId); @@ -915,18 +928,183 @@ Lesson lesson = lessonService.getLesson(lessonId); - boolean isMarksReleased = lesson.getMarksReleased(); - lesson.setMarksReleased(!isMarksReleased); + boolean isMarksReleased = !lesson.getMarksReleased(); + lesson.setMarksReleased(isMarksReleased); userService.save(lesson); + if (logger.isDebugEnabled()) { + if (isMarksReleased) { + logger.debug("Marks were released for lesson ID: " + lessonId); + } else { + logger.debug("Marks were hidden for lesson ID: " + lessonId); + } + } + // audit log marks released UserDTO monitor = (UserDTO) SessionManager.getSession().getAttribute(AttributeNames.USER); - String messageKey = (isMarksReleased) ? "audit.marks.released.off" : "audit.marks.released.on"; + String messageKey = (isMarksReleased) ? "audit.marks.released.on" : "audit.marks.released.off"; String message = messageService.getMessage(messageKey, new String[] { lessonId.toString() }); logEventService.logEvent(LogEvent.TYPE_MARK_RELEASED, monitor.getUserID(), null, lessonId, null, message); } @Override + public boolean releaseMarks(Long lessonId, int userId) { + Lesson lesson = lessonService.getLesson(lessonId); + + boolean wasMarksReleased = lesson.getMarksReleased(); + if (wasMarksReleased) { + return false; + } + + lesson.setMarksReleased(true); + userService.save(lesson); + + // audit log marks released + String message = messageService.getMessage("audit.marks.released.on", new String[] { lessonId.toString() }); + logEventService.logEvent(LogEvent.TYPE_MARK_RELEASED, userId, null, lessonId, null, message); + + if (logger.isDebugEnabled()) { + logger.debug("Marks were released for lesson ID: " + lessonId); + } + + return true; + } + + @Override + public Date getReleaseMarksScheduleDate(long lessonId, int currentUserId) { + String triggerName = "releaseMarksTrigger:" + lessonId; + try { + Trigger releaseMarksTrigger = scheduler.getTrigger(TriggerKey.triggerKey(triggerName)); + if (releaseMarksTrigger == null) { + return null; + } + User user = gradebookDAO.find(User.class, currentUserId); + TimeZone userTimeZone = TimeZone.getTimeZone(user.getTimeZone()); + Date scheduleDate = releaseMarksTrigger.getFireTimeAfter(new Date()); + if (scheduleDate == null) { + return null; + } + + return DateUtil.convertToTimeZoneFromDefault(userTimeZone, scheduleDate); + + } catch (SchedulerException e) { + logger.error("Error while fetching Quartz trigger \"" + triggerName + "\"", e); + } + + return null; + } + + @Override + public void scheduleReleaseMarks(long lessonId, int currentUserId, boolean sendEmails, Date scheduleDate) + throws SchedulerException { + User user = gradebookDAO.find(User.class, currentUserId); + TimeZone userTimeZone = TimeZone.getTimeZone(user.getTimeZone()); + Date tzScheduleDate = scheduleDate == null ? null + : DateUtil.convertFromTimeZoneToDefault(userTimeZone, scheduleDate); + String triggerName = "releaseMarksTrigger:" + lessonId; + + Trigger releaseMarksTrigger = null; + + try { + releaseMarksTrigger = scheduler.getTrigger(TriggerKey.triggerKey(triggerName)); + } catch (SchedulerException e) { + logger.error("Error while fetching Quartz trigger \"" + triggerName + "\"", e); + } + + if (releaseMarksTrigger == null) { + if (scheduleDate == null) { + // the job was not scheduled and we do not want to schedule it anyway, so just return + return; + } + // setup the message for scheduling job + JobDetail releaseMarksJob = JobBuilder.newJob(ReleaseMarksJob.class) + .withIdentity("releaseMarks:" + lessonId) + .withDescription("Release marks for lesson " + lessonId + " by user " + currentUserId) + .usingJobData(AttributeNames.PARAM_LESSON_ID, lessonId) + .usingJobData(AttributeNames.PARAM_USER_ID, currentUserId).usingJobData("sendEmails", sendEmails) + .build(); + + // create customised triggers + releaseMarksTrigger = TriggerBuilder.newTrigger().withIdentity(triggerName).startAt(tzScheduleDate).build(); + scheduler.scheduleJob(releaseMarksJob, releaseMarksTrigger); + } else if (scheduleDate == null) { + scheduler.deleteJob(releaseMarksTrigger.getJobKey()); + } else { + releaseMarksTrigger = releaseMarksTrigger.getTriggerBuilder().startAt(tzScheduleDate).build(); + scheduler.rescheduleJob(releaseMarksTrigger.getKey(), releaseMarksTrigger); + } + } + + @Override + public String getReleaseMarksEmailContent(long lessonID, int userID) { + StringBuilder content = new StringBuilder(); + + User user = gradebookDAO.find(User.class, userID); + Lesson lesson = lessonService.getLesson(lessonID); + LearnerProgress learnerProgress = lessonService.getUserProgressForLesson(userID, lessonID); + content.append("Hi ").append(user.getFirstName()).append(",

here are your results of lesson \"") + .append(lesson.getLessonName()).append("\" in which you participated on ") + .append(RELEASE_MARKS_EMAIL_DATE_FORMAT.format(learnerProgress.getStartDate())).append(".

"); + + content.append( + "") + .append("") + .append(""); + + List gradebookActivityDTOs = getGBLessonComplete(lessonID, userID); + for (GradebookGridRowDTO activityDTO : gradebookActivityDTOs) { + content.append(""); + } + content.append("
ActivityProgressAverage scoreScore
").append(activityDTO.getRowName()) + .append(""); + + if (activityDTO.getStatus().contains("success")) { + content.append("✓"); + } else if (activityDTO.getStatus().contains("cog")) { + content.append("⚙"); + } else { + content.append("-"); + } + + content.append(""); + if (activityDTO.getAverageMark() != null) { + content.append(GradebookUtil.niceFormatting(activityDTO.getAverageMark())); + } + + content.append(""); + if (activityDTO.getMark() != null) { + content.append(GradebookUtil.niceFormatting(activityDTO.getMark())); + } + + content.append("

Regards,
LAMS team"); + + return content.toString(); + } + + @SuppressWarnings("unchecked") + @Override + public void sendReleaseMarksEmails(long lessonId, Collection recipientIDs, + IEventNotificationService eventNotificationService) { + Lesson lesson = lessonService.getLesson(lessonId); + String emailSubject = new StringBuilder("Results of LAMS lesson \"").append(lesson.getLessonName()).append("\"") + .toString(); + + if (recipientIDs == null) { + recipientIDs = ((List) lessonService.getActiveLessonLearners(lessonId)).stream() + .collect(Collectors.mapping(User::getUserId, Collectors.toSet())); + } + + for (Integer recipientId : recipientIDs) { + eventNotificationService.sendMessage(null, recipientId, IEventNotificationService.DELIVERY_METHOD_MAIL, + emailSubject, getReleaseMarksEmailContent(lessonId, recipientId), true); + } + + if (logger.isDebugEnabled()) { + logger.debug("Sent emails with marks to learners of lesson ID: " + lessonId); + } + } + + @Override public List getGBLessonRows(Organisation organisation, User user, User viewer, boolean isGroupManager, GBGridView view, int page, int size, String sortBy, String sortOrder, String searchString, TimeZone userTimeZone) { @@ -2620,4 +2798,8 @@ public void setIntegrationService(IIntegrationService integrationService) { this.integrationService = integrationService; } + + public void setScheduler(Scheduler scheduler) { + this.scheduler = scheduler; + } } \ No newline at end of file Index: lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/service/IGradebookFullService.java =================================================================== diff -u -r20aa6cbca9fc96d341080e6ad39f82593443f792 -r81b83a2cbd3cfe68e9096211cd0df9eada37fc4a --- lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/service/IGradebookFullService.java (.../IGradebookFullService.java) (revision 20aa6cbca9fc96d341080e6ad39f82593443f792) +++ lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/service/IGradebookFullService.java (.../IGradebookFullService.java) (revision 81b83a2cbd3cfe68e9096211cd0df9eada37fc4a) @@ -22,11 +22,15 @@ package org.lamsfoundation.lams.gradebook.service; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.LinkedHashMap; +import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.TimeZone; +import org.lamsfoundation.lams.events.IEventNotificationService; import org.lamsfoundation.lams.gradebook.GradebookUserActivity; import org.lamsfoundation.lams.gradebook.dto.GBLessonGridRowDTO; import org.lamsfoundation.lams.gradebook.dto.GBUserGridRowDTO; @@ -38,11 +42,13 @@ import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.usermanagement.Organisation; import org.lamsfoundation.lams.usermanagement.User; -import org.lamsfoundation.lams.util.excel.ExcelCell; import org.lamsfoundation.lams.util.excel.ExcelSheet; +import org.quartz.SchedulerException; public interface IGradebookFullService extends IGradebookService { + static final DateFormat RELEASE_MARKS_EMAIL_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + /** * Gets all the activity rows for a lesson, with the mark for each activity being the average for all users in the * lesson. Only set escapeTitles to false if the output is *not* going to a webpage, but is instead going to a @@ -192,11 +198,21 @@ /** * Toggle on/off marks released option - * - * @param lessonId */ void toggleMarksReleased(Long lessonId); + boolean releaseMarks(Long lessonId, int userId); + + Date getReleaseMarksScheduleDate(long lessonId, int currentUserId); + + void scheduleReleaseMarks(long lessonId, int currentUserId, boolean sendEmails, Date scheduleDate) + throws SchedulerException; + + String getReleaseMarksEmailContent(long lessonId, int userID); + + void sendReleaseMarksEmails(long lessonId, Collection recipientIDs, + IEventNotificationService eventNotificationService); + /** * Gets the lesson row dtos for a given organisation * Index: lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/util/ReleaseMarksJob.java =================================================================== diff -u --- lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/util/ReleaseMarksJob.java (revision 0) +++ lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/util/ReleaseMarksJob.java (revision 81b83a2cbd3cfe68e9096211cd0df9eada37fc4a) @@ -0,0 +1,43 @@ +package org.lamsfoundation.lams.gradebook.util; + +import java.util.Map; + +import org.lamsfoundation.lams.events.IEventNotificationService; +import org.lamsfoundation.lams.gradebook.service.IGradebookFullService; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.SchedulerContext; +import org.quartz.SchedulerException; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.quartz.QuartzJobBean; + +public class ReleaseMarksJob extends QuartzJobBean { + private static final String CONTEXT_NAME = "context.central"; + + @Override + protected void executeInternal(JobExecutionContext context) throws JobExecutionException { + IGradebookFullService gradebookService = (IGradebookFullService) getService(context, "gradebookService"); + + Map properties = context.getJobDetail().getJobDataMap(); + long lessonId = (Long) properties.get(AttributeNames.PARAM_LESSON_ID); + int userId = (Integer) properties.get(AttributeNames.PARAM_USER_ID); + boolean sendEmails = (Boolean) properties.get("sendEmails"); + + boolean marksReleased = gradebookService.releaseMarks(lessonId, userId); + if (sendEmails && marksReleased) { + gradebookService.sendReleaseMarksEmails(lessonId, null, + (IEventNotificationService) getService(context, "eventNotificationService")); + } + } + + private Object getService(JobExecutionContext context, String serviceName) throws JobExecutionException { + try { + SchedulerContext sc = context.getScheduler().getContext(); + ApplicationContext cxt = (ApplicationContext) sc.get(CONTEXT_NAME); + return cxt.getBean(serviceName); + } catch (SchedulerException e) { + throw new JobExecutionException("Failed look up the " + serviceName + " " + e.toString()); + } + } +} \ No newline at end of file Index: lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/web/controller/GradebookMonitoringController.java =================================================================== diff -u -ra8e04b385dbb553c581bd705426a86ab92e85f9a -r81b83a2cbd3cfe68e9096211cd0df9eada37fc4a --- lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/web/controller/GradebookMonitoringController.java (.../GradebookMonitoringController.java) (revision a8e04b385dbb553c581bd705426a86ab92e85f9a) +++ lams_gradebook/src/java/org/lamsfoundation/lams/gradebook/web/controller/GradebookMonitoringController.java (.../GradebookMonitoringController.java) (revision 81b83a2cbd3cfe68e9096211cd0df9eada37fc4a) @@ -24,8 +24,10 @@ import java.io.IOException; import java.text.DateFormat; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -39,13 +41,11 @@ import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.lamsfoundation.lams.events.IEventNotificationService; -import org.lamsfoundation.lams.gradebook.dto.GradebookGridRowDTO; import org.lamsfoundation.lams.gradebook.service.IGradebookFullService; import org.lamsfoundation.lams.gradebook.util.GBGridView; import org.lamsfoundation.lams.gradebook.util.GradebookConstants; import org.lamsfoundation.lams.gradebook.util.GradebookUtil; import org.lamsfoundation.lams.learningdesign.Activity; -import org.lamsfoundation.lams.lesson.LearnerProgress; import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.lesson.dto.LessonDetailsDTO; import org.lamsfoundation.lams.lesson.service.ILessonService; @@ -62,8 +62,10 @@ import org.lamsfoundation.lams.util.excel.ExcelUtil; import org.lamsfoundation.lams.web.session.SessionManager; import org.lamsfoundation.lams.web.util.AttributeNames; +import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -96,7 +98,7 @@ @Autowired private IEventNotificationService eventNotificationService; - private static final DateFormat RELEASE_MARKS_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + private static final DateFormat RELEASE_MARKS_SCHEDULE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); @RequestMapping("") public String unspecified(HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -267,55 +269,18 @@ } @RequestMapping("/displayReleaseMarksPanel") - public String displayReleaseMarksPanel() { + public String displayReleaseMarksPanel(@RequestParam long lessonID, Model model) { + Date scheduleDate = gradebookService.getReleaseMarksScheduleDate(lessonID, getUser().getUserID()); + if (scheduleDate != null) { + model.addAttribute("releaseMarksScheduleDate", RELEASE_MARKS_SCHEDULE_DATE_FORMAT.format(scheduleDate)); + } return "releaseLessonMarks"; } @RequestMapping("/getReleaseMarksEmailContent") @ResponseBody public String getReleaseMarksEmailContent(@RequestParam long lessonID, @RequestParam int userID) { - StringBuilder content = new StringBuilder(); - - User user = userManagementService.getUserById(userID); - Lesson lesson = lessonService.getLesson(lessonID); - LearnerProgress learnerProgress = lessonService.getUserProgressForLesson(userID, lessonID); - content.append("Hi ").append(user.getFirstName()).append(",

here are your results of lesson \"") - .append(lesson.getLessonName()).append("\" in which you participated on ") - .append(RELEASE_MARKS_DATE_FORMAT.format(learnerProgress.getStartDate())).append(".

"); - - content.append( - "") - .append("") - .append(""); - - List gradebookActivityDTOs = gradebookService.getGBLessonComplete(lessonID, userID); - for (GradebookGridRowDTO activityDTO : gradebookActivityDTOs) { - content.append(""); - } - content.append("
ActivityProgressAverage scoreScore
").append(activityDTO.getRowName()) - .append(""); - - if (activityDTO.getStatus().contains("success")) { - content.append("✓"); - } else if (activityDTO.getStatus().contains("cog")) { - content.append("⚙"); - } else { - content.append("-"); - } - - content.append(""); - if (activityDTO.getAverageMark() != null) { - content.append(GradebookUtil.niceFormatting(activityDTO.getAverageMark())); - } - - content.append(""); - if (activityDTO.getMark() != null) { - content.append(GradebookUtil.niceFormatting(activityDTO.getMark())); - } - - content.append("

Regards,
LAMS team"); - - return content.toString(); + return gradebookService.getReleaseMarksEmailContent(lessonID, userID); } @RequestMapping("/sendReleaseMarksEmails") @@ -334,12 +299,11 @@ } try { - Lesson lesson = lessonService.getLesson(lessonID); - Set recipients = new HashSet<>(); + Set recipientIDs = new HashSet<>(); if (excludedLearners == null) { // we send emails only to selected learners for (int learnerIndex = 0; learnerIndex < includedLearners.size(); learnerIndex++) { - recipients.add(includedLearners.get(learnerIndex).asInt()); + recipientIDs.add(includedLearners.get(learnerIndex).asInt()); } } else { List learners = lessonService.getActiveLessonLearners(lessonID); @@ -353,25 +317,29 @@ } } if (!excludedLearnerFound) { - recipients.add(learner.getUserId()); + recipientIDs.add(learner.getUserId()); } } } - String emailSubject = new StringBuilder("Results of LAMS lesson \"").append(lesson.getLessonName()).append("\"") - .toString(); + gradebookService.sendReleaseMarksEmails(lessonID, recipientIDs, eventNotificationService); - for (Integer recipientId : recipients) { - eventNotificationService.sendMessage(null, recipientId, IEventNotificationService.DELIVERY_METHOD_MAIL, - emailSubject, getReleaseMarksEmailContent(lessonID, recipientId), true); - } } catch (Exception e) { return e.getMessage(); } return "success"; } + @RequestMapping("/scheduleReleaseMarks") + public void scheduleReleaseMarks(@RequestParam long lessonID, @RequestParam boolean sendEmails, + @RequestParam(name = "scheduleDate", required = false) String scheduleDateString) + throws ParseException, SchedulerException { + Date scheduleDate = StringUtils.isBlank(scheduleDateString) ? null + : RELEASE_MARKS_SCHEDULE_DATE_FORMAT.parse(scheduleDateString); + gradebookService.scheduleReleaseMarks(lessonID, getUser().getUserID(), sendEmails, scheduleDate); + } + /** * Exports Lesson Gradebook into excel. */ Index: lams_gradebook/web/gradebookMonitor.jsp =================================================================== diff -u -rfed0712b189d8ab5fec43305954799ec58d66e4e -r81b83a2cbd3cfe68e9096211cd0df9eada37fc4a --- lams_gradebook/web/gradebookMonitor.jsp (.../gradebookMonitor.jsp) (revision fed0712b189d8ab5fec43305954799ec58d66e4e) +++ lams_gradebook/web/gradebookMonitor.jsp (.../gradebookMonitor.jsp) (revision 81b83a2cbd3cfe68e9096211cd0df9eada37fc4a) @@ -13,14 +13,12 @@ - - @@ -29,6 +27,9 @@ + + +
-
-
-
+
+
+
-
+
+ + + + +
-
- + +
+

Email preview

+
+
+
+ +
+
+
+ Schedule date: +
+ +
+ - - +
+
- -
-

Email preview

-
-
\ No newline at end of file