diff --git a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java index e45eccaf8a4a2ba1f16f30845373114b55f5c3f4..d66a926ffeea30b9d68dd0a430544f75ea25baab 100644 --- a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java +++ b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java @@ -51,8 +51,22 @@ public interface UserCourseInformationsManager { public Map<Long,Date> getRecentLaunchDates(Long courseResourceId, List<Identity> identities); + /** + * Return the initial launch dates of a list of users. + * @param courseResourceId + * @param identities + * @return + */ public Map<Long,Date> getInitialLaunchDates(Long courseResourceId, List<Identity> identities); + /** + * Return all initial launch dates of a course. + * + * @param courseResourceId + * @return + */ + public Map<Long,Date> getInitialLaunchDates(Long courseResourceId); + public int deleteUserCourseInformations(RepositoryEntry entry); } diff --git a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java index 55252b5f5edc29e24d9343aaa5112fddce53d6f1..0cdb16e5cbbcdab18e7c95ada7d3a352a1af4836 100644 --- a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java +++ b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java @@ -338,7 +338,40 @@ public class UserCourseInformationsManagerImpl implements UserCourseInformations return Collections.emptyMap(); } } + + /** + * Return a map of identity keys to initial launch date. + * + * @param courseResourceId The course resourceable id + * @return + */ + @Override + public Map<Long,Date> getInitialLaunchDates(Long courseResourceId) { + try { + + StringBuilder sb = new StringBuilder(); + sb.append("select infos.identity.key, infos.initialLaunch from ").append(UserCourseInfosImpl.class.getName()).append(" as infos ") + .append(" inner join infos.resource as resource") + .append(" where resource.resId=:resId and resource.resName='CourseModule'"); + + TypedQuery<Object[]> query = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Object[].class) + .setParameter("resId", courseResourceId); + List<Object[]> infoList = query.getResultList(); + Map<Long,Date> dateMap = new HashMap<Long,Date>(); + for(Object[] infos:infoList) { + Long identityKey = (Long)infos[0]; + Date initialLaunch = (Date)infos[1]; + if(identityKey != null && initialLaunch != null) { + dateMap.put(identityKey, initialLaunch); + } + } + return dateMap; + } catch (Exception e) { + log.error("Cannot retrieve course informations for: " + courseResourceId, e); + return Collections.emptyMap(); + } + } /** * Return a map of identity keys to initial launch date. diff --git a/src/main/java/org/olat/course/nodes/GTACourseNode.java b/src/main/java/org/olat/course/nodes/GTACourseNode.java index caefb37813fdd7ff32738971a08f9262a60ca89c..ecd475adf1c86915fa7cade2d7b194c9047a8eab 100644 --- a/src/main/java/org/olat/course/nodes/GTACourseNode.java +++ b/src/main/java/org/olat/course/nodes/GTACourseNode.java @@ -108,16 +108,24 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses public static final String GTASK_AREAS = "grouptask.areas"; public static final String GTASK_ASSIGNMENT = "grouptask.assignement"; public static final String GTASK_ASSIGNMENT_DEADLINE = "grouptask.assignment.deadline"; + public static final String GTASK_ASSIGNMENT_DEADLINE_RELATIVE = "grouptask.assignment.deadline.relative"; + public static final String GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO = "grouptask.assignment.deadline.relative.to"; public static final String GTASK_SUBMIT = "grouptask.submit"; public static final String GTASK_SUBMIT_DEADLINE = "grouptask.submit.deadline"; + public static final String GTASK_SUBMIT_DEADLINE_RELATIVE = "grouptask.submit.deadline.relative"; + public static final String GTASK_SUBMIT_DEADLINE_RELATIVE_TO = "grouptask.submit.deadline.relative.to"; public static final String GTASK_REVIEW_AND_CORRECTION = "grouptask.review.and.correction"; public static final String GTASK_REVISION_PERIOD = "grouptask.revision.period"; public static final String GTASK_SAMPLE_SOLUTION = "grouptask.solution"; public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER = "grouptask.solution.visible.after"; + public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE = "grouptask.solution.visible.after.relative"; + public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO = "grouptask.solution.visible.after.relative.to"; public static final String GTASK_GRADING = "grouptask.grading"; public static final String GTASK_TASKS = "grouptask.tasks"; + public static final String GTASK_RELATIVE_DATES = "grouptask.rel.dates"; + public static final String GTASK_ASSIGNEMENT_TYPE = "grouptask.assignement.type"; public static final String GTASK_ASSIGNEMENT_TYPE_AUTO = "auto"; public static final String GTASK_ASSIGNEMENT_TYPE_MANUAL = "manual"; diff --git a/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java b/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java new file mode 100644 index 0000000000000000000000000000000000000000..4faf419755f4686d191b0976c8f9b98439438345 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java @@ -0,0 +1,34 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.gta; + +/** + * + * Initial date: 08.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public enum GTARelativeToDates { + + courseStart,// relative to course start defined by a life-cycle + courseLaunch,// relative to the course launch by a user + enrollment//relative to the enrollment date + +} diff --git a/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java index 868517dbdc2759013df742280930d318b870a16b..364de565118c75f16b2b508ca3eae448f7cca5e4 100644 --- a/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java +++ b/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java @@ -19,20 +19,28 @@ */ package org.olat.course.nodes.gta.rule; +import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import org.olat.basesecurity.GroupRoles; +import org.olat.core.CoreSpringFactory; import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; import org.olat.course.CourseFactory; import org.olat.course.ICourse; +import org.olat.course.assessment.manager.UserCourseInformationsManager; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; +import org.olat.course.nodes.gta.GTARelativeToDates; import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskList; @@ -45,7 +53,9 @@ import org.olat.modules.reminder.model.ReminderRuleImpl; import org.olat.modules.reminder.rule.LaunchUnit; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRelationType; +import org.olat.repository.RepositoryService; import org.olat.repository.manager.RepositoryEntryRelationDAO; +import org.olat.repository.model.RepositoryEntryLifecycle; import org.springframework.beans.factory.annotation.Autowired; /** @@ -79,20 +89,21 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu return identities == null ? Collections.<Identity>emptyList() : identities; } - protected List<Identity> evaluateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl r) { + protected List<Identity> evaluateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) { List<Identity> identities = null; - Date dueDate = getDueDate(gtaNode); - if(dueDate != null) { - int value = Integer.parseInt(r.getRightOperand()); - String unit = r.getRightUnit(); - Date now = new Date(); - if(near(dueDate, now, value, LaunchUnit.valueOf(unit))) { + if(gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES)) { + identities = evaluateRelativeDateRule(entry, gtaNode, rule); + } else { + Date dueDate = getDueDate(gtaNode); + if(dueDate != null && isNear(dueDate, now(), rule)) { identities = getPeopleToRemind(entry, gtaNode); } } return identities == null ? Collections.<Identity>emptyList() : identities; } + protected abstract List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl r); + protected abstract Date getDueDate(GTACourseNode gtaNode); protected List<Identity> getPeopleToRemind(RepositoryEntry entry, GTACourseNode gtaNode) { @@ -105,6 +116,95 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu } } + protected List<Identity> getPeopleToRemindRelativeTo(RepositoryEntry entry, GTACourseNode gtaNode, + int numOfDays, String relativeTo, ReminderRuleImpl rule) { + List<Identity> identities = null; + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + GTARelativeToDates rel = GTARelativeToDates.valueOf(relativeTo); + switch(rel) { + case courseStart: { + RepositoryEntryLifecycle lifecycle = entry.getLifecycle(); + if(lifecycle != null && lifecycle.getValidFrom() != null) { + Date referenceDate = getDate(lifecycle.getValidFrom(), numOfDays); + if(isNear(referenceDate, now(), rule)) { + identities = getPeopleToRemind(entry, gtaNode); + } + } + break; + } + + case courseLaunch: { + UserCourseInformationsManager userCourseInformationsManager = CoreSpringFactory.getImpl(UserCourseInformationsManager.class); + Map<Long,Date> initialLaunchDates = userCourseInformationsManager.getInitialLaunchDates(entry.getOlatResource().getResourceableId()); + Map<Long,Date> dueDates = getDueDates(initialLaunchDates, numOfDays); + identities = getPeopleToRemindRelativeTo(entry, gtaNode, dueDates, rule); + break; + } + case enrollment: { + RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); + Map<Long,Date> enrollmentDates = repositoryService.getEnrollmentDates(entry); + Map<Long,Date> dueDates = getDueDates(enrollmentDates, numOfDays); + identities = getPeopleToRemindRelativeTo(entry, gtaNode, dueDates, rule); + break; + } + } + } + return identities; + } + + protected List<Identity> getPeopleToRemindRelativeTo(RepositoryEntry entry, GTACourseNode gtaNode, + Map<Long,Date> dates, ReminderRuleImpl rule) { + + Date now = now(); + Set<Long> potentialidentityKeys = new HashSet<>(); + for(Map.Entry<Long, Date> entryDate:dates.entrySet()) { + Long identityKey = entryDate.getKey(); + Date date = entryDate.getValue(); + if(isNear(date, now, rule)) { + potentialidentityKeys.add(identityKey); + } + } + + List<Identity> identities = null; + if(potentialidentityKeys.size() > 0) { + List<Identity> allIdentities = getPeopleToRemind(entry, gtaNode); + identities = new ArrayList<>(); + for(Identity identity:allIdentities) { + if(potentialidentityKeys.contains(identity.getKey())) { + identities.add(identity); + } + } + } + return identities; + } + + private Map<Long,Date> getDueDates(Map<Long,Date> referenceDates, int numOfDays) { + Map<Long, Date> dueDates = new HashMap<>(); + if(referenceDates != null && referenceDates.size() > 0) { + Calendar cal = Calendar.getInstance(); + for(Map.Entry<Long, Date> referenceEntry:referenceDates.entrySet()) { + Long identityKey = referenceEntry.getKey(); + cal.setTime(referenceEntry.getValue()); + cal.add(Calendar.DATE, numOfDays); + dueDates.put(identityKey, cal.getTime()); + } + } + return dueDates; + } + + private Date getDate(Date referenceDate, int numOfDays) { + Date date = null; + if(referenceDate != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(referenceDate); + cal.add(Calendar.DATE, numOfDays); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + date = cal.getTime(); + } + return date; + } + protected List<Identity> getGroupsToRemind(TaskList taskList, GTACourseNode gtaNode) { List<Task> tasks = gtaManager.getTasks(taskList); Set<BusinessGroup> doneTasks = new HashSet<BusinessGroup>(); @@ -143,6 +243,18 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu return identities; } + protected Date now() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } + + protected boolean isNear(Date dueDate, Date now, ReminderRuleImpl r) { + int value = Integer.parseInt(r.getRightOperand()); + String unit = r.getRightUnit(); + return near(dueDate, now, value, LaunchUnit.valueOf(unit)); + } private boolean near(Date date, Date now, int distance, LaunchUnit unit) { double between = -1; @@ -160,7 +272,8 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu between = yearsBetween(now, date); break; } - return between <= distance || between < 0.0; + // 0.1 to let +- 2 hours to match + return between <= distance || between - 0.1 <= distance || between < 0.0; } private double daysBetween(Date d1, Date d2) { diff --git a/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java index d5ac5d5595776589fb81c92b3840b0433e8f85df..6d8feebbe2720f62d3ef3a14ab120dba887b9d6b 100644 --- a/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java +++ b/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java @@ -20,12 +20,16 @@ package org.olat.course.nodes.gta.rule; import java.util.Date; +import java.util.List; +import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.ui.BeforeDateTaskRuleEditor; import org.olat.modules.ModuleConfiguration; import org.olat.modules.reminder.ReminderRule; import org.olat.modules.reminder.RuleEditorFragment; +import org.olat.modules.reminder.model.ReminderRuleImpl; import org.olat.repository.RepositoryEntry; import org.springframework.stereotype.Service; @@ -63,4 +67,15 @@ public class AssignTaskRuleSPI extends AbstractDueDateTaskRuleSPI { } return dueDate; } + + @Override + protected List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) { + List<Identity> identities = null; + int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1); + String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO); + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + identities = getPeopleToRemindRelativeTo(entry, gtaNode, numOfDays, relativeTo, rule); + } + return identities; + } } diff --git a/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java index a22e6eae58692835b78043362bc2a98d3b1de155..326c05d16b11bf6e5ab381fc550396ab354dcaa5 100644 --- a/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java +++ b/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java @@ -20,7 +20,10 @@ package org.olat.course.nodes.gta.rule; import java.util.Date; +import java.util.List; +import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.ui.BeforeDateTaskRuleEditor; @@ -28,6 +31,7 @@ import org.olat.group.BusinessGroupService; import org.olat.modules.ModuleConfiguration; import org.olat.modules.reminder.ReminderRule; import org.olat.modules.reminder.RuleEditorFragment; +import org.olat.modules.reminder.model.ReminderRuleImpl; import org.olat.repository.RepositoryEntry; import org.olat.repository.manager.RepositoryEntryRelationDAO; import org.springframework.beans.factory.annotation.Autowired; @@ -74,4 +78,15 @@ public class SubmissionTaskRuleSPI extends AbstractDueDateTaskRuleSPI { } return dueDate; } + + @Override + protected List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) { + List<Identity> identities = null; + int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1); + String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO); + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + identities = getPeopleToRemindRelativeTo(entry, gtaNode, numOfDays, relativeTo, rule); + } + return identities; + } } diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java index 9df3055f6a36f1cd09d161ad433493ba9000f1fa..5e4350d42fd5a568c6e383f951a9dfd68b52e5b9 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java @@ -19,6 +19,7 @@ */ package org.olat.course.nodes.gta.ui; +import java.util.Calendar; import java.util.Date; import org.olat.core.commons.services.notifications.PublisherData; @@ -33,8 +34,10 @@ import org.olat.core.gui.control.controller.BasicController; import org.olat.core.id.Identity; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; +import org.olat.course.assessment.manager.UserCourseInformationsManager; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; +import org.olat.course.nodes.gta.GTARelativeToDates; import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskList; @@ -44,6 +47,8 @@ import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.modules.ModuleConfiguration; import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryService; +import org.olat.repository.model.RepositoryEntryLifecycle; import org.springframework.beans.factory.annotation.Autowired; /** @@ -78,8 +83,16 @@ public abstract class GTAAbstractController extends BasicController { private ContextualSubscriptionController contextualSubscriptionCtr; + private Date assignmentDueDate; + private Date submissionDueDate; + private Date solutionDueDate; + @Autowired protected GTAManager gtaManager; + @Autowired + protected RepositoryService repositoryService; + @Autowired + protected UserCourseInformationsManager userCourseInformationsManager; public GTAAbstractController(UserRequest ureq, WindowControl wControl, GTACourseNode gtaNode, CourseEnvironment courseEnv, boolean withTitle, boolean withGrading) { @@ -224,7 +237,7 @@ public abstract class GTAAbstractController extends BasicController { } protected Task stepAssignment(@SuppressWarnings("unused") UserRequest ureq, Task assignedTask) { - Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE); + Date dueDate = getAssignementDueDate(); if(dueDate != null) { String date = Formatter.getInstance(getLocale()).formatDateAndTime(dueDate); mainVC.contextPut("assignmentDueDate", date); @@ -238,8 +251,66 @@ public abstract class GTAAbstractController extends BasicController { return assignedTask; } + protected void resetDueDates() { + assignmentDueDate = null; + submissionDueDate = null; + solutionDueDate = null; + } + + protected Date getAssignementDueDate() { + if(assignmentDueDate == null) { + Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE); + boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES); + if(relativeDate) { + int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1); + String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO); + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + assignmentDueDate = getReferenceDate(numOfDays, relativeTo); + } + } else if(dueDate != null) { + assignmentDueDate = dueDate; + } + } + return assignmentDueDate; + } + + protected Date getReferenceDate(int numOfDays, String relativeTo) { + Date dueDate = null; + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + GTARelativeToDates rel = GTARelativeToDates.valueOf(relativeTo); + Date referenceDate = null; + switch(rel) { + case courseStart: { + RepositoryEntryLifecycle lifecycle = courseEntry.getLifecycle(); + if(lifecycle != null && lifecycle.getValidFrom() != null) { + referenceDate = lifecycle.getValidFrom(); + } + break; + } + case courseLaunch: { + referenceDate = userCourseInformationsManager + .getInitialLaunchDate(courseEnv.getCourseResourceableId(), assessedIdentity); + break; + } + case enrollment: { + referenceDate = repositoryService + .getEnrollmentDate(courseEntry, assessedIdentity); + break; + } + } + + if(referenceDate != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(referenceDate); + cal.add(Calendar.DATE, numOfDays); + dueDate = cal.getTime(); + } + } + return dueDate; + } + protected Task stepSubmit(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) { - Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE); + Date dueDate = getSubmissionDueDate(); if(dueDate != null) { String date = Formatter.getInstance(getLocale()).formatDateAndTime(dueDate); mainVC.contextPut("submitDueDate", date); @@ -254,6 +325,23 @@ public abstract class GTAAbstractController extends BasicController { return assignedTask; } + protected Date getSubmissionDueDate() { + if(submissionDueDate == null) { + Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE); + boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES); + if(relativeDate) { + int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1); + String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO); + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + submissionDueDate = getReferenceDate(numOfDays, relativeTo); + } + } else if(dueDate != null) { + submissionDueDate = dueDate; + } + } + return submissionDueDate; + } + protected Task stepReviewAndCorrection(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) { return assignedTask; } @@ -263,7 +351,7 @@ public abstract class GTAAbstractController extends BasicController { } protected Task stepSolution(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) { - Date availableDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER); + Date availableDate = getSolutionDueDate(); if(availableDate != null) { String date = Formatter.getInstance(getLocale()).formatDateAndTime(availableDate); mainVC.contextPut("solutionAvailableDate", date); @@ -271,6 +359,23 @@ public abstract class GTAAbstractController extends BasicController { return assignedTask; } + protected Date getSolutionDueDate() { + if(solutionDueDate == null) { + boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES); + Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER); + if(relativeDate) { + int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, -1); + String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO); + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + solutionDueDate = getReferenceDate(numOfDays, relativeTo); + } + } else if(dueDate != null) { + solutionDueDate = dueDate; + } + } + return solutionDueDate; + } + protected Task stepGrading(@SuppressWarnings("unused") UserRequest ureq, Task assignedTask) { if(businessGroupTask) { String groupLog = courseEnv.getAuditManager().getUserNodeLog(gtaNode, assessedGroup); diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java index 47ebbeb9fd294af5e1b11d405cfcf40a74335cb3..42d13b40627b5ad4d3676c69e9d092bbefbcf676 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java @@ -144,7 +144,7 @@ public class GTAParticipantController extends GTAAbstractController { mainVC.contextPut("assignmentCssClass", "o_active"); //assignment open? - Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE); + Date dueDate = getAssignementDueDate(); if(dueDate != null && dueDate.compareTo(new Date()) < 0) { //assignment is closed mainVC.contextPut("assignmentClosed", Boolean.TRUE); @@ -450,7 +450,7 @@ public class GTAParticipantController extends GTAAbstractController { } private void setSolutions(UserRequest ureq) { - Date availableDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER); + Date availableDate = getSolutionDueDate(); boolean visible = availableDate == null || availableDate.compareTo(new Date()) <= 0; if(visible) { File documentsDir = gtaManager.getSolutionsDirectory(courseEnv, gtaNode); @@ -570,6 +570,7 @@ public class GTAParticipantController extends GTAAbstractController { } else if(businessGroupChooserCtrl == source) { if(event == Event.DONE_EVENT && businessGroupChooserCtrl.getSelectGroup() != null) { cleanUpProcess(); + resetDueDates(); assessedGroup = businessGroupChooserCtrl.getSelectGroup(); process(ureq); } diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java index 2d5bd0c88893af2b2930ca85ea340864ec098c56..c0c79a17687270728a1855db663c4018381aa9b3 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java @@ -31,6 +31,7 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink; import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; import org.olat.core.gui.components.form.flexible.elements.SingleSelection; import org.olat.core.gui.components.form.flexible.elements.StaticTextElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; @@ -44,6 +45,7 @@ import org.olat.course.condition.AreaSelectionController; import org.olat.course.condition.GroupSelectionController; import org.olat.course.editor.CourseEditorEnv; import org.olat.course.nodes.GTACourseNode; +import org.olat.course.nodes.gta.GTARelativeToDates; import org.olat.course.nodes.gta.GTAType; import org.olat.group.BusinessGroupService; import org.olat.group.BusinessGroupShort; @@ -62,6 +64,11 @@ public class GTAWorkflowEditController extends FormBasicController { private static final String[] keys = new String[]{ "on" }; private static final String[] executionKeys = new String[]{ GTAType.group.name(), GTAType.individual.name() }; + private static final String[] relativeDatesKeys = new String[] { + GTARelativeToDates.courseStart.name(), GTARelativeToDates.courseLaunch.name(), + GTARelativeToDates.enrollment.name() + }; + private final String[] relativeDatesValues; private CloseableModalController cmc; private AreaSelectionController areaSelectionCtrl; @@ -71,8 +78,10 @@ public class GTAWorkflowEditController extends FormBasicController { private FormLink chooseGroupButton, chooseAreaButton; private StaticTextElement groupListEl, areaListEl; private DateChooser assignmentDeadlineEl, submissionDeadlineEl, solutionVisibleAfterEl; - private MultipleSelectionElement taskAssignmentEl, submissionEl, reviewEl, revisionEl, sampleEl, gradingEl; - private FormLayoutContainer stepsCont; + private MultipleSelectionElement relativeDatesEl, taskAssignmentEl, submissionEl, reviewEl, revisionEl, sampleEl, gradingEl; + private FormLayoutContainer stepsCont, assignmentRelDeadlineCont, submissionRelDeadlineCont, solutionVisibleRelCont; + private TextElement assignementDeadlineDaysEl, submissionDeadlineDaysEl, solutionVisibleRelDaysEl; + private SingleSelection assignementDeadlineRelToEl, submissionDeadlineRelToEl, solutionVisibleRelToEl; private final GTACourseNode gtaNode; private final ModuleConfiguration config; @@ -90,15 +99,20 @@ public class GTAWorkflowEditController extends FormBasicController { this.gtaNode = gtaNode; this.config = gtaNode.getModuleConfiguration(); this.courseEditorEnv = courseEditorEnv; + + relativeDatesValues = new String[] { + translate("relative.to.course.start"), + translate("relative.to.course.launch"), + translate("relative.to.enrollment") + }; + initForm(ureq); } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - String type = config.getStringValue(GTACourseNode.GTASK_TYPE); - FormLayoutContainer typeCont = FormLayoutContainer.createDefaultFormLayout("type", getTranslator()); typeCont.setFormTitle(translate("task.type.title")); typeCont.setFormDescription(translate("task.type.description")); @@ -175,6 +189,11 @@ public class GTAWorkflowEditController extends FormBasicController { stepsCont.setFormDescription(translate("task.steps.description")); stepsCont.setRootForm(mainForm); formLayout.add(stepsCont); + + relativeDatesEl = uifactory.addCheckboxesHorizontal("relative.dates", "relative.dates", stepsCont, keys, new String[]{ "" }); + relativeDatesEl.addActionListener(FormEvent.ONCHANGE); + boolean useRelativeDates = config.getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES); + relativeDatesEl.select(keys[0], useRelativeDates); //assignment String[] assignmentValues = new String[] { translate("enabled") }; @@ -186,7 +205,37 @@ public class GTAWorkflowEditController extends FormBasicController { Date assignmentDeadline = config.getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE); assignmentDeadlineEl = uifactory.addDateChooser("assignementdeadline", "assignment.deadline", assignmentDeadline, stepsCont); assignmentDeadlineEl.setDateChooserTimeEnabled(true); - assignmentDeadlineEl.setVisible(assignement); + assignmentDeadlineEl.setVisible(assignement && !useRelativeDates); + + String relativeDatePage = velocity_root + "/assignment_relative_date.html"; + assignmentRelDeadlineCont = FormLayoutContainer.createCustomFormLayout("assignmentRelativeDeadline", getTranslator(), relativeDatePage); + assignmentRelDeadlineCont.setRootForm(mainForm); + assignmentRelDeadlineCont.setLabel("assignment.deadline", null); + assignmentRelDeadlineCont.setVisible(assignement && useRelativeDates); + stepsCont.add(assignmentRelDeadlineCont); + + int numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1); + String assignmentNumOfDays = numOfDays >= 0 ? Integer.toString(numOfDays) : ""; + String assignmentRelativeTo = config.getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO); + assignementDeadlineDaysEl = uifactory.addTextElement("assignment.numOfDays", null, 4, assignmentNumOfDays, assignmentRelDeadlineCont); + assignementDeadlineDaysEl.setDisplaySize(4); + assignementDeadlineDaysEl.setDomReplacementWrapperRequired(false); + assignementDeadlineRelToEl = uifactory + .addDropdownSingleselect("assignmentrelativeto", "assignment.relative.to", null, assignmentRelDeadlineCont, relativeDatesKeys, relativeDatesValues, null); + assignementDeadlineRelToEl.setDomReplacementWrapperRequired(false); + + boolean found = false; + if(StringHelper.containsNonWhitespace(assignmentRelativeTo)) { + for(String relativeDatesKey:relativeDatesKeys) { + if(relativeDatesKey.equals(assignmentRelativeTo)) { + assignementDeadlineRelToEl.select(relativeDatesKey, true); + found = true; + } + } + } + if(!found) { + assignementDeadlineRelToEl.select(relativeDatesKeys[0], true); + } //turning in String[] submissionValues = new String[] { translate("submission.enabled") }; @@ -198,7 +247,37 @@ public class GTAWorkflowEditController extends FormBasicController { Date submissionDeadline = config.getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE); submissionDeadlineEl = uifactory.addDateChooser("submitdeadline", "submit.deadline", submissionDeadline, stepsCont); submissionDeadlineEl.setDateChooserTimeEnabled(true); - submissionDeadlineEl.setVisible(submit); + submissionDeadlineEl.setVisible(submit && !useRelativeDates); + + //relative deadline + String submitPage = velocity_root + "/submit_relative_date.html"; + submissionRelDeadlineCont = FormLayoutContainer.createCustomFormLayout("submitRelativeDeadline", getTranslator(), submitPage); + submissionRelDeadlineCont.setRootForm(mainForm); + submissionRelDeadlineCont.setLabel("submit.deadline", null); + submissionRelDeadlineCont.setVisible(submit && useRelativeDates); + stepsCont.add(submissionRelDeadlineCont); + + numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1); + String submitRelDays = numOfDays >= 0 ? Integer.toString(numOfDays) : ""; + String submitRelTo = config.getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO); + submissionDeadlineDaysEl = uifactory.addTextElement("submit.numOfDays", null, 4, submitRelDays, submissionRelDeadlineCont); + submissionDeadlineDaysEl.setDomReplacementWrapperRequired(false); + submissionDeadlineDaysEl.setDisplaySize(4); + submissionDeadlineRelToEl = uifactory + .addDropdownSingleselect("submitrelativeto", "submit.relative.to", null, submissionRelDeadlineCont, relativeDatesKeys, relativeDatesValues, null); + submissionDeadlineRelToEl.setDomReplacementWrapperRequired(false); + found = false; + if(StringHelper.containsNonWhitespace(submitRelTo)) { + for(String relativeDatesKey:relativeDatesKeys) { + if(relativeDatesKey.equals(submitRelTo)) { + submissionDeadlineRelToEl.select(relativeDatesKey, true); + found = true; + } + } + } + if(!found) { + submissionDeadlineRelToEl.select(relativeDatesKeys[0], true); + } //review and correction String[] reviewValues = new String[] { translate("review.enabled") }; @@ -225,6 +304,36 @@ public class GTAWorkflowEditController extends FormBasicController { solutionVisibleAfterEl = uifactory.addDateChooser("visibleafter", "sample.solution.visible.after", solutionVisibleAfter, stepsCont); solutionVisibleAfterEl.setDateChooserTimeEnabled(true); solutionVisibleAfterEl.setVisible(sample); + + //relative deadline + String solutionPage = velocity_root + "/solution_relative_date.html"; + solutionVisibleRelCont = FormLayoutContainer.createCustomFormLayout("solutionRelativeDeadline", getTranslator(), solutionPage); + solutionVisibleRelCont.setRootForm(mainForm); + solutionVisibleRelCont.setLabel("sample.solution.visible.after", null); + solutionVisibleRelCont.setVisible(submit && useRelativeDates); + stepsCont.add(solutionVisibleRelCont); + + numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, -1); + String solutionRelDays = numOfDays >= 0 ? Integer.toString(numOfDays) : ""; + String solutionRelTo = config.getStringValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO); + solutionVisibleRelDaysEl = uifactory.addTextElement("solution.numOfDays", null, 4, solutionRelDays, solutionVisibleRelCont); + solutionVisibleRelDaysEl.setDisplaySize(4); + solutionVisibleRelDaysEl.setDomReplacementWrapperRequired(false); + solutionVisibleRelToEl = uifactory + .addDropdownSingleselect("solutionrelativeto", "solution.relative.to", null, solutionVisibleRelCont, relativeDatesKeys, relativeDatesValues, null); + solutionVisibleRelToEl.setDomReplacementWrapperRequired(false); + found = false; + if(StringHelper.containsNonWhitespace(solutionRelTo)) { + for(String relativeDatesKey:relativeDatesKeys) { + if(relativeDatesKey.equals(solutionRelTo)) { + solutionVisibleRelToEl.select(relativeDatesKey, true); + found = true; + } + } + } + if(!found) { + solutionVisibleRelToEl.select(relativeDatesKeys[0], true); + } //grading String[] gradingValues = new String[] { translate("enabled") }; @@ -263,6 +372,22 @@ public class GTAWorkflowEditController extends FormBasicController { } } + boolean relativeDates = relativeDatesEl.isAtLeastSelected(1); + assignementDeadlineDaysEl.clearError(); + if(relativeDates && taskAssignmentEl.isAtLeastSelected(1)) { + allOk &= validateIntegerOrEmpty(assignementDeadlineDaysEl); + } + + submissionDeadlineDaysEl.clearError(); + if(relativeDates && submissionEl.isAtLeastSelected(1)) { + allOk &= validateIntegerOrEmpty(submissionDeadlineDaysEl); + } + + solutionVisibleRelDaysEl.clearError(); + if(relativeDates && sampleEl.isAtLeastSelected(1)) { + allOk &= validateIntegerOrEmpty(solutionVisibleRelDaysEl); + } + taskAssignmentEl.clearError(); if(!taskAssignmentEl.isAtLeastSelected(1) && !submissionEl.isAtLeastSelected(1) && !reviewEl.isAtLeastSelected(1) && !revisionEl.isAtLeastSelected(1) @@ -274,6 +399,21 @@ public class GTAWorkflowEditController extends FormBasicController { return allOk & super.validateFormLogic(ureq); } + + private boolean validateIntegerOrEmpty(TextElement textEl) { + boolean allOk = true; + textEl.clearError(); + String val = textEl.getValue(); + if(StringHelper.containsNonWhitespace(val)) { + if(StringHelper.isLong(val)) { + + } else { + textEl.setErrorKey("integer.element.int.error", null); + allOk &= false; + } + } + return allOk; + } @Override protected void formOK(UserRequest ureq) { @@ -287,18 +427,31 @@ public class GTAWorkflowEditController extends FormBasicController { config.setList(GTACourseNode.GTASK_GROUPS, new ArrayList<Long>(0)); } + boolean relativeDates = relativeDatesEl.isAtLeastSelected(1); + config.setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, relativeDates); + boolean assignment = taskAssignmentEl.isAtLeastSelected(1); config.setBooleanEntry(GTACourseNode.GTASK_ASSIGNMENT, assignment); - if(assignment && assignmentDeadlineEl.getDate() != null) { - config.setDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE, assignmentDeadlineEl.getDate()); + if(assignment) { + if(relativeDates) { + setRelativeDates(assignementDeadlineDaysEl, GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, + assignementDeadlineRelToEl, GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO); + } else if(assignmentDeadlineEl.getDate() != null) { + config.setDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE, assignmentDeadlineEl.getDate()); + } } else { config.remove(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE); } boolean turningIn = submissionEl.isAtLeastSelected(1); config.setBooleanEntry(GTACourseNode.GTASK_SUBMIT, turningIn); - if(turningIn && submissionDeadlineEl.getDate() != null) { - config.setDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE, submissionDeadlineEl.getDate()); + if(turningIn) { + if(relativeDates) { + setRelativeDates(submissionDeadlineDaysEl, GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, + submissionDeadlineRelToEl, GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO); + } else { + config.setDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE, submissionDeadlineEl.getDate()); + } } else { config.remove(GTACourseNode.GTASK_SUBMIT_DEADLINE); } @@ -308,25 +461,47 @@ public class GTAWorkflowEditController extends FormBasicController { boolean sample = sampleEl.isAtLeastSelected(1); config.setBooleanEntry(GTACourseNode.GTASK_SAMPLE_SOLUTION, sample); - if(sample && solutionVisibleAfterEl.getDate() != null) { - config.setDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER, solutionVisibleAfterEl.getDate()); + if(sample) { + if(relativeDates) { + setRelativeDates(solutionVisibleRelDaysEl, GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, + solutionVisibleRelToEl, GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO); + } else if(solutionVisibleAfterEl.getDate() != null) { + config.setDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER, solutionVisibleAfterEl.getDate()); + } } else { config.remove(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER); } config.setBooleanEntry(GTACourseNode.GTASK_GRADING, gradingEl.isAtLeastSelected(1)); - fireEvent(ureq, Event.DONE_EVENT); } + + private void setRelativeDates(TextElement daysEl, String daysKey, SingleSelection relativeToEl, String relativeToKey) { + String val = daysEl.getValue(); + if(StringHelper.isLong(val)) { + try { + config.setIntValue(daysKey, Integer.parseInt(val)); + } catch (NumberFormatException e) { + logWarn("", e); + } + + String relativeTo = relativeToEl.getSelectedKey(); + config.setStringValue(relativeToKey, relativeTo); + } + } @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { if(submissionEl == source) { - submissionDeadlineEl.setVisible(submissionEl.isAtLeastSelected(1)); + updateSubmissionDeadline(); } else if(taskAssignmentEl == source) { - assignmentDeadlineEl.setVisible(taskAssignmentEl.isAtLeastSelected(1)); + updateAssignmentDeadline(); } else if(sampleEl == source) { - solutionVisibleAfterEl.setVisible(sampleEl.isAtLeastSelected(1)); + updateSolutionDeadline(); + } else if(relativeDatesEl == source) { + updateAssignmentDeadline(); + updateSubmissionDeadline(); + updateSolutionDeadline(); } else if(chooseGroupButton == source) { doChooseGroup(ureq); } else if(chooseAreaButton == source) { @@ -336,6 +511,27 @@ public class GTAWorkflowEditController extends FormBasicController { super.formInnerEvent(ureq, source, event); } + private void updateAssignmentDeadline() { + boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1); + boolean assignment = taskAssignmentEl.isAtLeastSelected(1); + assignmentDeadlineEl.setVisible(assignment && !userRelativeDate); + assignmentRelDeadlineCont.setVisible(assignment && userRelativeDate); + } + + private void updateSubmissionDeadline() { + boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1); + boolean submit = submissionEl.isAtLeastSelected(1); + submissionDeadlineEl.setVisible(submit && !userRelativeDate); + submissionRelDeadlineCont.setVisible(submit && userRelativeDate); + } + + private void updateSolutionDeadline() { + boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1); + boolean solution = sampleEl.isAtLeastSelected(1); + solutionVisibleAfterEl.setVisible(solution && !userRelativeDate); + solutionVisibleRelCont.setVisible(solution && userRelativeDate); + } + @Override protected void event(UserRequest ureq, Controller source, Event event) { if(groupSelectionCtrl == source) { diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html new file mode 100644 index 0000000000000000000000000000000000000000..f1b4c29ad6f7ab910720bcb188a19b4619154d1c --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html @@ -0,0 +1,6 @@ +<div class="form-inline">$r.render("assignment.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("assignment.relative.to")</div> +#if($f.hasError("assignment.numOfDays")) + <div class="form-inline"> + $r.render("assignment.numOfDays_ERROR") + </div> +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html new file mode 100644 index 0000000000000000000000000000000000000000..0e6f0ac5a33db349489cc823976f1827e10d1d3a --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html @@ -0,0 +1,6 @@ +<div class="form-inline">$r.render("solution.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("solution.relative.to")</div> +#if($f.hasError("solution.numOfDays")) + <div class="form-inline"> + $r.render("solution.numOfDays_ERROR") + </div> +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html new file mode 100644 index 0000000000000000000000000000000000000000..e190568d69f4ef1a172ff311f52e92cdf04213c5 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html @@ -0,0 +1,6 @@ +<div class="form-inline">$r.render("submit.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("submit.relative.to")</div> +#if($f.hasError("submit.numOfDays")) + <div class="form-inline"> + $r.render("submit.numOfDays_ERROR") + </div> +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties index 0f24f1f1a1c837a25a9e36eb8aaa87efefc28dff..baf1a7fb5d8d634f327274ee315673d50c6b2ba3 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties @@ -33,6 +33,7 @@ confirm.delete.solution.title=L\u00F6sung l\u00F6schen confirmation.title=Abgabe Best\u00E4tigung create.areas=Lernbereich erstellen create.groups=Gruppe erstellen +days.after=Tage nach document=Abgegebene Dokumente document.date=Datum download.task=Aufgabe herunterladen @@ -85,6 +86,10 @@ pane.tab.workflow=Workflow preview=$org.olat.course.nodes.ta\:form.task.preview preview.disabled=$org.olat.course.nodes.ta\:form.task.without.preview preview.enabled=$org.olat.course.nodes.ta\:form.task.with.preview +relative.dates=Relative Datum +relative.to.course.start=Kurs Beginn +relative.to.course.launch=Kurs erstes Besuch +relative.to.enrollment=Enrollment replace=Austauschen replace.document=Dokument austauschen review.and.correction=\u00DCberarbeitung und Korrektur diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties index 28491b9983253e3806f151b7c2d1d684bf1d07e3..ecb811532129fb2027dc0cc7893d6d00d679001f 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties @@ -33,6 +33,7 @@ confirm.delete.solution.title=Delete submitted solution confirmation.title=Submit confirmation create.areas=Create learning area create.groups=Create group +days.after=Days after document=Submitted documents document.date=Date download.task=Download task @@ -85,6 +86,10 @@ pane.tab.workflow=Workflow preview=$org.olat.course.nodes.ta\:form.task.preview preview.disabled=$org.olat.course.nodes.ta\:form.task.without.preview preview.enabled=$org.olat.course.nodes.ta\:form.task.with.preview +relative.dates=Relative dates +relative.to.course.start=Course begin +relative.to.course.launch=Course first launch +relative.to.enrollment=Enrollment replace=Replace replace.document=Replace document review.and.correction=Review and correction diff --git a/src/main/java/org/olat/repository/RepositoryService.java b/src/main/java/org/olat/repository/RepositoryService.java index a6705bf391d5f48d6c232db2cd303ef756140489..5bf667a94ae41a116a464c5cfc31068416dab289 100644 --- a/src/main/java/org/olat/repository/RepositoryService.java +++ b/src/main/java/org/olat/repository/RepositoryService.java @@ -20,8 +20,10 @@ package org.olat.repository; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Map; import org.olat.basesecurity.Group; import org.olat.basesecurity.IdentityRef; @@ -110,6 +112,24 @@ public interface RepositoryService { */ public int countMembers(List<? extends RepositoryEntryRef> res); + /** + * Return the smallest enrollment date. + * + * @param re + * @param identity + * @return + */ + public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles); + + /** + * Return the smallest enrollment date. + * + * @param re + * @param identity + * @return + */ + public Map<Long,Date> getEnrollmentDates(RepositoryEntryRef re, String... roles); + /** * @param re The repository entry * @return True if the configuration allowed user to leave the entry right now diff --git a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java index 62757174ac1311c3d5c2f56cafb793d7d53b2a3e..ece81e4c76fc0d8e6d91adf54ee57ab4986e8bff 100644 --- a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java +++ b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java @@ -22,9 +22,11 @@ package org.olat.repository.manager; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import javax.persistence.EntityManager; @@ -273,6 +275,81 @@ public class RepositoryEntryRelationDAO { return count == null ? 0 : count.intValue(); } + public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles) { + if(re == null || identity == null) return null; + + List<String> roleList = null; + if(roles != null && roles.length > 0 && roles[0] != null) { + roleList = new ArrayList<>(roles.length); + for(String role:roles) { + roleList.add(role); + } + } + + StringBuilder sb = new StringBuilder(); + sb.append("select min(members.creationDate) from ").append(RepositoryEntry.class.getName()).append(" as v") + .append(" inner join v.groups as relGroup") + .append(" inner join relGroup.group as baseGroup") + .append(" inner join baseGroup.members as members") + .append(" where v.key=:repoKey and members.identity.key=:identityKey"); + if(roleList != null && roleList.size() > 0) { + sb.append(" and members.role in (:roles)"); + } + + TypedQuery<Date> datesQuery = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Date.class) + .setParameter("repoKey", re.getKey()) + .setParameter("identityKey", identity.getKey()); + if(roleList != null && roleList.size() > 0) { + datesQuery.setParameter("roles", roleList); + } + + List<Date> dates = datesQuery.getResultList(); + return dates.isEmpty() ? null : dates.get(0); + } + + public Map<Long,Date> getEnrollmentDates(RepositoryEntryRef re, String... roles) { + if(re == null) return null; + + List<String> roleList = null; + if(roles != null && roles.length > 0 && roles[0] != null) { + roleList = new ArrayList<>(roles.length); + for(String role:roles) { + roleList.add(role); + } + } + + StringBuilder sb = new StringBuilder(); + sb.append("select members.identity.key, min(members.creationDate) from ").append(RepositoryEntry.class.getName()).append(" as v") + .append(" inner join v.groups as relGroup") + .append(" inner join relGroup.group as baseGroup") + .append(" inner join baseGroup.members as members") + .append(" where v.key=:repoKey"); + if(roleList != null && roleList.size() > 0) { + sb.append(" and members.role in (:roles)"); + } + sb.append(" group by members.identity.key"); + + TypedQuery<Object[]> datesQuery = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Object[].class) + .setParameter("repoKey", re.getKey()); + if(roleList != null && roleList.size() > 0) { + datesQuery.setParameter("roles", roleList); + } + + List<Object[]> dateList = datesQuery.getResultList(); + Map<Long,Date> dateMap = new HashMap<>((dateList.size() * 2) + 1); + for(Object[] dateArr:dateList) { + Long key = (Long)dateArr[0]; + Date date = (Date)dateArr[1]; + if(key != null && date != null) { + dateMap.put(key, date); + } + } + + return dateMap; + } + public List<Long> getAuthorKeys(RepositoryEntryRef re) { diff --git a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java index 8aaa4288bb276117df47a5926fa6bb90bba0122f..3978977bff08bfb96f009d2e0be31a2000fe6f75 100644 --- a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java +++ b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java @@ -26,6 +26,7 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import org.olat.basesecurity.BaseSecurity; @@ -440,6 +441,16 @@ public class RepositoryServiceImpl implements RepositoryService { public int countMembers(List<? extends RepositoryEntryRef> res) { return reToGroupDao.countMembers(res); } + + @Override + public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles) { + return reToGroupDao.getEnrollmentDate(re, identity, roles); + } + + @Override + public Map<Long, Date> getEnrollmentDates(RepositoryEntryRef re, String... roles) { + return reToGroupDao.getEnrollmentDates(re, roles); + } @Override public List<Long> getAuthors(RepositoryEntryRef re) { diff --git a/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java b/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java index 8f0f97db9a2daf4467f334ea034f7e831d061999..0505275d38538fe477ea8873e532ec7d3f0f9ac8 100644 --- a/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java +++ b/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java @@ -145,6 +145,32 @@ public class UserCourseInformationsManagerTest extends OlatTestCase { Assert.assertNotNull(launchDates.get(user2.getKey())); } + @Test + public void getInitialLaunchDates_noIdentites() { + Identity user1 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-7-"); + Identity user2 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-8-"); + Identity user3 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-9-"); + ICourse course = CoursesWebService.createEmptyCourse(user1, "course-launch-dates", "course long name", null); + dbInstance.commitAndCloseSession(); + + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user1, true); + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user2, true); + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user3, true); + dbInstance.commitAndCloseSession(); + + //get all launch dates + Map<Long,Date> launchDates = userCourseInformationsManager.getInitialLaunchDates(course.getResourceableId()); + Assert.assertNotNull(launchDates); + Assert.assertEquals(3, launchDates.size()); + Assert.assertTrue(launchDates.containsKey(user1.getKey())); + Assert.assertNotNull(launchDates.get(user1.getKey())); + Assert.assertTrue(launchDates.containsKey(user2.getKey())); + Assert.assertNotNull(launchDates.get(user2.getKey())); + Assert.assertTrue(launchDates.containsKey(user3.getKey())); + Assert.assertNotNull(launchDates.get(user3.getKey())); + } + + /** * This test is to analyze a red screen diff --git a/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java b/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java index 27de702b6b9fef27b7b3ffe6d32ae5af22075549..f482bfe454ad6afdd713ed23b7d0140c391855c9 100644 --- a/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java +++ b/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java @@ -22,15 +22,22 @@ package org.olat.course.nodes.gta.rule; import java.io.File; import java.util.ArrayList; import java.util.Calendar; +import java.util.Date; import java.util.List; +import java.util.UUID; import org.junit.Assert; import org.junit.Test; import org.olat.basesecurity.GroupRoles; +import org.olat.basesecurity.model.GroupMembershipImpl; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; +import org.olat.course.ICourse; +import org.olat.course.assessment.manager.UserCourseInformationsManager; +import org.olat.course.assessment.model.UserCourseInfosImpl; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.AssignmentResponse; +import org.olat.course.nodes.gta.GTARelativeToDates; import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.TaskList; import org.olat.course.nodes.gta.manager.GTAManagerImpl; @@ -41,7 +48,11 @@ import org.olat.modules.reminder.model.ReminderRuleImpl; import org.olat.modules.reminder.rule.LaunchUnit; import org.olat.modules.vitero.model.GroupRole; import org.olat.repository.RepositoryEntry; +import org.olat.repository.manager.RepositoryEntryLifecycleDAO; import org.olat.repository.manager.RepositoryEntryRelationDAO; +import org.olat.repository.model.RepositoryEntryLifecycle; +import org.olat.repository.model.RepositoryEntryToGroupRelation; +import org.olat.restapi.repository.course.CoursesWebService; import org.olat.test.JunitTestHelper; import org.olat.test.OlatTestCase; import org.springframework.beans.factory.annotation.Autowired; @@ -61,9 +72,13 @@ public class GTAReminderRuleTest extends OlatTestCase { @Autowired private BusinessGroupDAO businessGroupDao; @Autowired + private RepositoryEntryLifecycleDAO reLifeCycleDao; + @Autowired private BusinessGroupRelationDAO businessGroupRelationDao; @Autowired private RepositoryEntryRelationDAO repositoryEntryRelationDao; + @Autowired + private UserCourseInformationsManager userCourseInformationsManager; @Autowired @@ -211,6 +226,179 @@ public class GTAReminderRuleTest extends OlatTestCase { Assert.assertTrue(toRemind.contains(participant4)); } + @Test + public void assignTask_relativeToDateEnrollment() { + //prepare a course with a volatile task + Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-1"); + Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-2"); + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false); + addEnrollmentDate(re, participant1, GroupRoles.participant, -12, Calendar.DATE); + addEnrollmentDate(re, participant2, GroupRoles.participant, -5, Calendar.DATE); + dbInstance.commit(); + + // create a fake node + GTACourseNode node = new GTACourseNode(); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name()); + node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true); + node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, 15); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO, GTARelativeToDates.enrollment.name()); + + // need the task list + TaskList tasks = gtaManager.createIfNotExists(re, node); + Assert.assertNotNull(tasks); + dbInstance.commit(); + + // participant 1 has still 3 days to choose a task + // participant 2 has still 10 days to choose a task + + { // check before 1 day + ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.day); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(0, all.size()); + } + + { // check before 5 days + ReminderRuleImpl rule = getAssignedTaskRules(5, LaunchUnit.day); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(1, all.size()); + Assert.assertTrue(all.contains(participant1)); + } + + { // check before 1 week + ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.week); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(1, all.size()); + Assert.assertTrue(all.contains(participant1)); + } + + { // check before 1 month + ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.month); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + } + + private void addEnrollmentDate(RepositoryEntry entry, Identity id, GroupRoles role, int amount, int field) { + RepositoryEntryToGroupRelation rel = entry.getGroups().iterator().next(); + rel.getGroup(); + + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.add(field, amount); + + GroupMembershipImpl membership = new GroupMembershipImpl(); + membership.setCreationDate(cal.getTime()); + membership.setLastModified(cal.getTime()); + membership.setGroup(rel.getGroup()); + membership.setIdentity(id); + membership.setRole(role.name()); + dbInstance.getCurrentEntityManager().persist(membership); + dbInstance.commit(); + } + + @Test + public void assignTask_relativeToInitialLaunchDate() { + //create a course with 3 members + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-1"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-2"); + Identity id3 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-3"); + + ICourse course = CoursesWebService.createEmptyCourse(null, "initial-launch-dates", "course long name", null); + RepositoryEntry re = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); + repositoryEntryRelationDao.addRole(id1, re, GroupRoles.participant.name()); + repositoryEntryRelationDao.addRole(id2, re, GroupRoles.participant.name()); + repositoryEntryRelationDao.addRole(id3, re, GroupRoles.participant.name()); + dbInstance.commit(); + + //create user course infos + Long courseResId = course.getCourseEnvironment().getCourseResourceableId(); + userCourseInformationsManager.updateUserCourseInformations(courseResId, id1, true); + userCourseInformationsManager.updateUserCourseInformations(courseResId, id2, true); + userCourseInformationsManager.updateUserCourseInformations(courseResId, id3, true); + dbInstance.commit(); + + //fake the date + updateInitialLaunchDate(courseResId, id1, -5, Calendar.DATE); + updateInitialLaunchDate(courseResId, id2, -35, Calendar.DATE); + updateInitialLaunchDate(courseResId, id3, -75, Calendar.DATE); + dbInstance.commitAndCloseSession(); + + // create a fake node + GTACourseNode node = new GTACourseNode(); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name()); + node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true); + node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, 40); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO, GTARelativeToDates.courseLaunch.name()); + + // need the task list + TaskList tasks = gtaManager.createIfNotExists(re, node); + Assert.assertNotNull(tasks); + dbInstance.commit(); + + { // check 3 days + ReminderRuleImpl rule = getAssignedTaskRules(3, LaunchUnit.day); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(1, all.size()); + Assert.assertTrue(all.contains(id3)); + } + + { // check 5 days + ReminderRuleImpl rule = getAssignedTaskRules(5, LaunchUnit.day); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(id2)); + Assert.assertTrue(all.contains(id3)); + } + + { // check 1 week + ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.week); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(id2)); + Assert.assertTrue(all.contains(id3)); + } + + { // check 1 month + ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.month); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(id2)); + Assert.assertTrue(all.contains(id3)); + } + + { // check 2 month + ReminderRuleImpl rule = getAssignedTaskRules(2, LaunchUnit.month); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(3, all.size()); + Assert.assertTrue(all.contains(id1)); + Assert.assertTrue(all.contains(id2)); + Assert.assertTrue(all.contains(id3)); + } + + } + + private void updateInitialLaunchDate(Long courseResId, Identity id, int amount, int field) { + UserCourseInfosImpl userCourseInfos = (UserCourseInfosImpl)userCourseInformationsManager.getUserCourseInformations(courseResId, id); + Date initialLaunch = userCourseInfos.getInitialLaunch(); + Calendar cal = Calendar.getInstance(); + cal.setTime(initialLaunch); + cal.add(field, amount); + userCourseInfos.setInitialLaunch(cal.getTime()); + dbInstance.getCurrentEntityManager().merge(userCourseInfos); + dbInstance.commit(); + } + @Test public void submitTask_individual() { //prepare a course with a volatile task @@ -297,6 +485,112 @@ public class GTAReminderRuleTest extends OlatTestCase { } } + @Test + public void submitTask_relativeLifecycle() { + //prepare a course with a volatile task + Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-1"); + Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-2"); + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false); + repositoryEntryRelationDao.addRole(participant1, re, GroupRoles.participant.name()); + repositoryEntryRelationDao.addRole(participant2, re, GroupRoles.participant.name()); + dbInstance.commit(); + + String label = "Life cycle for relative date"; + String softKey = UUID.randomUUID().toString(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.DATE, -5); + Date from = cal.getTime(); + cal.add(Calendar.DATE, 20); + Date to = cal.getTime(); + RepositoryEntryLifecycle lifecycle = reLifeCycleDao.create(label, softKey, true, from, to); + re.setLifecycle(lifecycle); + re = dbInstance.getCurrentEntityManager().merge(re); + dbInstance.commit(); + + //create a fake node with a relative submit deadline 15 days after the start of the course + GTACourseNode node = new GTACourseNode(); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name()); + node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true); + node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, 15); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO, GTARelativeToDates.courseStart.name()); + + TaskList tasks = gtaManager.createIfNotExists(re, node); + Assert.assertNotNull(tasks); + dbInstance.commitAndCloseSession(); + + //the course has start 5 days before, deadline is 15 days after it + //conclusion the deadline is 10 days from now + + { // check before 5 days + ReminderRuleImpl rule = getSubmitTaskRules(5, LaunchUnit.day); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(0, all.size()); + } + + { // check before 1 week + ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.week); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(0, all.size()); + } + + { // check before 10 days + ReminderRuleImpl rule = getSubmitTaskRules(10, LaunchUnit.day); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + + { // check before 2 days + ReminderRuleImpl rule = getSubmitTaskRules(10, LaunchUnit.week); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + + { // check before 30 days + ReminderRuleImpl rule = getSubmitTaskRules(30, LaunchUnit.day); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + + { // check before 1 months + ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.month); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + + { // check before 5 months + ReminderRuleImpl rule = getSubmitTaskRules(5, LaunchUnit.month); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + + { // check before 1 year + ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.year); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + } + private ReminderRuleImpl getSubmitTaskRules(int amount, LaunchUnit unit) { ReminderRuleImpl rule = new ReminderRuleImpl(); rule.setType(SubmissionTaskRuleSPI.class.getSimpleName()); diff --git a/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java b/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java index a37d6dcaa89455edda31e358b39143c490e10bcd..c4b576c8f6c9998cffae266d0f322cfc0e984eb7 100644 --- a/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java +++ b/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java @@ -21,7 +21,9 @@ package org.olat.repository.manager; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; +import java.util.Map; import java.util.UUID; import org.junit.Assert; @@ -181,6 +183,65 @@ public class RepositoryEntryRelationDAOTest extends OlatTestCase { Assert.assertEquals(4, numOfMembers); } + @Test + public void getEnrollmentDate() { + Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-1-"); + Identity wid = JunitTestHelper.createAndPersistIdentityAsRndUser("not-enroll-date-1-"); + RepositoryEntry re = repositoryService.create("Rei Ayanami", "rel", "rel", null, null); + dbInstance.commit(); + repositoryEntryRelationDao.addRole(id, re, GroupRoles.owner.name()); + repositoryEntryRelationDao.addRole(id, re, GroupRoles.participant.name()); + dbInstance.commit(); + + //enrollment date + Date enrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id); + Assert.assertNotNull(enrollmentDate); + + //this user isn't enrolled + Date withoutEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, wid); + Assert.assertNull(withoutEnrollmentDate); + + //as participant + Date participantEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.participant.name()); + Assert.assertNotNull(participantEnrollmentDate); + //as owner + Date ownerEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.owner.name()); + Assert.assertNotNull(ownerEnrollmentDate); + //is not enrolled as coached + Date coachEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.coach.name()); + Assert.assertNull(coachEnrollmentDate); + } + + @Test + public void getEnrollmentDates() { + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-2-"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-3-"); + Identity wid = JunitTestHelper.createAndPersistIdentityAsRndUser("not-enroll-date-2-"); + RepositoryEntry re = repositoryService.create("Rei Ayanami", "rel", "rel", null, null); + dbInstance.commit(); + repositoryEntryRelationDao.addRole(id1, re, GroupRoles.owner.name()); + repositoryEntryRelationDao.addRole(id2, re, GroupRoles.participant.name()); + dbInstance.commit(); + + //enrollment date + Map<Long,Date> enrollmentDates = repositoryEntryRelationDao.getEnrollmentDates(re); + Assert.assertNotNull(enrollmentDates); + Assert.assertEquals(2, enrollmentDates.size()); + Assert.assertTrue(enrollmentDates.containsKey(id1.getKey())); + Assert.assertTrue(enrollmentDates.containsKey(id2.getKey())); + Assert.assertFalse(enrollmentDates.containsKey(wid.getKey())); + } + + @Test + public void getEnrollmentDates_emptyCourse() { + //enrollment of an empty course + RepositoryEntry notEnrolledRe = repositoryService.create("Rei Ayanami", "rel", "rel", null, null); + dbInstance.commit(); + Map<Long,Date> notEnrollmentDates = repositoryEntryRelationDao.getEnrollmentDates(notEnrolledRe); + Assert.assertNotNull(notEnrollmentDates); + Assert.assertEquals(0, notEnrollmentDates.size()); + } + @Test public void getAuthorKeys() { Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("auth-1-");