diff --git a/src/main/java/org/olat/commons/coordinate/cluster/lock/ClusterLockManager.java b/src/main/java/org/olat/commons/coordinate/cluster/lock/ClusterLockManager.java index c51a19b46f1ecbf93570f87dcc2c0faecffdae49..b7ca02b281ebd65574bcc344f0ee4d6da9aec2bf 100644 --- a/src/main/java/org/olat/commons/coordinate/cluster/lock/ClusterLockManager.java +++ b/src/main/java/org/olat/commons/coordinate/cluster/lock/ClusterLockManager.java @@ -65,6 +65,18 @@ public class ClusterLockManager { return res.get(0); } } + + boolean isLocked(String asset) { + String sb = "select alock.key from org.olat.commons.coordinate.cluster.lock.LockImpl as alock where alock.asset=:asset"; + + List<Long> res = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Long.class) + .setParameter("asset", asset) + .setFirstResult(0) + .setMaxResults(1) + .getResultList(); + return res != null && res.size() > 0 && res.get(0) != null && res.get(0).longValue() > 0; + } LockImpl createLockImpl(String asset, Identity owner) { log.info("createLockImpl: "+asset+" by "+ owner); diff --git a/src/main/java/org/olat/commons/coordinate/cluster/lock/ClusterLocker.java b/src/main/java/org/olat/commons/coordinate/cluster/lock/ClusterLocker.java index a7ef668ffdc7cb89b3db5a9294be9f3838837901..0fff2897b4b2d45f41ffa01f042de2cabf145364 100644 --- a/src/main/java/org/olat/commons/coordinate/cluster/lock/ClusterLocker.java +++ b/src/main/java/org/olat/commons/coordinate/cluster/lock/ClusterLocker.java @@ -167,13 +167,20 @@ public class ClusterLocker implements Locker, GenericEventListener { } } - + @Override public boolean isLocked(OLATResourceable ores, String locksubkey) { + final String asset = OresHelper.createStringRepresenting(ores, locksubkey); + return clusterLockManager.isLocked(asset); + } + + @Override + public Identity getLockedBy(OLATResourceable ores, String locksubkey) { final String asset = OresHelper.createStringRepresenting(ores, locksubkey); LockImpl li = clusterLockManager.findLock(asset); - return (li != null); + return li == null ? null : li.getOwner(); } + @Override public void releaseLock(LockResult lockResult) { // if the lock has not been acquired, then nothing is to be released - // return silently to make cleaning up easier diff --git a/src/main/java/org/olat/core/commons/editor/htmleditor/HTMLEditorController.java b/src/main/java/org/olat/core/commons/editor/htmleditor/HTMLEditorController.java index 010853e76e6c47ec37cfd0475e250a32c89d4bf1..5cd91341412701e6325049686fb85b1d5b04790c 100644 --- a/src/main/java/org/olat/core/commons/editor/htmleditor/HTMLEditorController.java +++ b/src/main/java/org/olat/core/commons/editor/htmleditor/HTMLEditorController.java @@ -182,11 +182,11 @@ public class HTMLEditorController extends FormBasicController { if (fileLeaf instanceof LocalFileImpl) { // Cast to LocalFile necessary because the VFSItem is missing some // ID mechanism that identifies an item within the system - OLATResourceable lockResourceable = OresHelper.createOLATResourceableTypeWithoutCheck(fileLeaf.toString()); + OLATResourceable lockResourceable = createLockResourceable(fileLeaf); // OLAT-5066: the use of "fileName" gives users the (false) impression that the file they wish to access // is already locked by someone else. Since the lock token must be smaller than 50 characters we us an // MD5 hash of the absolute file path which will always be 32 characters long and virtually unique. - String lockToken = Encoder.md5hash(getFileDebuggingPath(bContainer, relFilePath)); + String lockToken = createLockToken(bContainer, relFilePath); lock = CoordinatorManager.getInstance().getCoordinator().getLocker().acquireLock(lockResourceable, getIdentity(), lockToken); VelocityContainer vc = (VelocityContainer) flc.getComponent(); if (!lock.isSuccess()) { @@ -467,6 +467,20 @@ public class HTMLEditorController extends FormBasicController { htmlElement.setNewOriginalValue(content); return true; } + + protected static OLATResourceable createLockResourceable(VFSLeaf fileLeaf) { + return OresHelper.createOLATResourceableTypeWithoutCheck(fileLeaf.toString()); + } + + /** + * OLAT-5066: the use of "fileName" gives users the (false) impression that the file they wish to access + * is already locked by someone else. Since the lock token must be smaller than 50 characters we us an + * MD5 hash of the absolute file path which will always be 32 characters long and virtually unique. + * @return + */ + protected static String createLockToken(VFSContainer container, String relFilePath) { + return Encoder.md5hash(getFileDebuggingPath(container, relFilePath)); + } /** @@ -477,7 +491,7 @@ public class HTMLEditorController extends FormBasicController { * @param relPath * @return */ - private String getFileDebuggingPath(VFSContainer root, String relPath) { + private static String getFileDebuggingPath(VFSContainer root, String relPath) { String path = relPath; VFSItem item = root.resolve(relPath); if (item instanceof LocalFileImpl) { diff --git a/src/main/java/org/olat/core/commons/editor/htmleditor/WysiwygFactory.java b/src/main/java/org/olat/core/commons/editor/htmleditor/WysiwygFactory.java index b1d319a80aae8a28d21b329f746c0401b93befea..5452e4f364c5c28ec3f97b6f36333f6383da8b1b 100644 --- a/src/main/java/org/olat/core/commons/editor/htmleditor/WysiwygFactory.java +++ b/src/main/java/org/olat/core/commons/editor/htmleditor/WysiwygFactory.java @@ -29,7 +29,9 @@ package org.olat.core.commons.editor.htmleditor; import org.olat.core.commons.controllers.linkchooser.CustomLinkTreeModel; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.WindowControl; +import org.olat.core.id.OLATResourceable; import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSLeaf; /** * Description: The WYSIWYGFactory provides a full-fledged WYSIWYG HTML editor @@ -157,4 +159,12 @@ public class WysiwygFactory { fileContent.append(HTMLEditorController.CLOSE_BODY_HTML); return fileContent.toString(); } + + public static OLATResourceable createLockResourceable(VFSLeaf fileLeaf) { + return HTMLEditorController.createLockResourceable(fileLeaf); + } + + public static String createLockToken(VFSContainer container, String relFilePath) { + return HTMLEditorController.createLockToken(container, relFilePath); + } } diff --git a/src/main/java/org/olat/core/util/coordinate/Locker.java b/src/main/java/org/olat/core/util/coordinate/Locker.java index 9d777b3c08a8f75ff76f709896f92af9e54562b3..8b7423cae2006b39b85bfbfec261806ff7f675c4 100644 --- a/src/main/java/org/olat/core/util/coordinate/Locker.java +++ b/src/main/java/org/olat/core/util/coordinate/Locker.java @@ -66,6 +66,14 @@ public interface Locker { * someone (returns true even if locked by "myself") */ public boolean isLocked(OLATResourceable ores, String locksubkey); + + /** + * + * @param ores + * @param locksubkey + * @return The identity which lock the resource or null. + */ + public Identity getLockedBy(OLATResourceable ores, String locksubkey); /** diff --git a/src/main/java/org/olat/course/nodes/gta/GTAManager.java b/src/main/java/org/olat/course/nodes/gta/GTAManager.java index 9c8acb2737c97cbbebdf4063004ce0962758a3bc..f7143b53f7882fd595f505610512fdee78496d04 100644 --- a/src/main/java/org/olat/course/nodes/gta/GTAManager.java +++ b/src/main/java/org/olat/course/nodes/gta/GTAManager.java @@ -31,7 +31,7 @@ import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.model.Membership; import org.olat.course.nodes.gta.model.Solution; import org.olat.course.nodes.gta.model.TaskDefinition; -import org.olat.course.nodes.gta.ui.SubmitEvent; +import org.olat.course.nodes.gta.ui.events.SubmitEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupRef; diff --git a/src/main/java/org/olat/course/nodes/gta/TaskHelper.java b/src/main/java/org/olat/course/nodes/gta/TaskHelper.java index 5b1fe6c4ac6cae4d1bad381df03785e9f1512a78..789657f66decb2b2ff6baf50f928edaf1e512e40 100644 --- a/src/main/java/org/olat/course/nodes/gta/TaskHelper.java +++ b/src/main/java/org/olat/course/nodes/gta/TaskHelper.java @@ -24,8 +24,16 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Locale; +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.editor.htmleditor.WysiwygFactory; import org.olat.core.gui.control.generic.iframe.DeliveryOptions; +import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; +import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.io.SystemFileFilter; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.user.UserManager; /** * @@ -88,6 +96,36 @@ public class TaskHelper { return false; } - + public static String getDocumentsLocked(VFSContainer documentsContainer, File[] documents) { + StringBuilder sb = new StringBuilder(); + + boolean locked = false; + for(File submittedDocument:documents) { + VFSLeaf fileLeaf = (VFSLeaf)documentsContainer.resolve(submittedDocument.getName()); + OLATResourceable lockResourceable = WysiwygFactory.createLockResourceable(fileLeaf); + String lockTocken = WysiwygFactory.createLockToken(documentsContainer, submittedDocument.getName()); + if(CoordinatorManager.getInstance().getCoordinator().getLocker() + .isLocked(lockResourceable, lockTocken)) { + + locked |= true; + Identity lockedBy = CoordinatorManager.getInstance().getCoordinator() + .getLocker().getLockedBy(lockResourceable, lockTocken); + + String fullname = "???"; + if(lockedBy != null) { + fullname = CoreSpringFactory.getImpl(UserManager.class).getUserDisplayName(lockedBy); + } + if(sb.length() > 0) { + sb.append(", "); + } + sb.append(fullname); + } + } + + if(locked) { + return sb.toString(); + } + return null; + } } diff --git a/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java b/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java index 443f385e4c5c1c30ebd5c300e29b068f4f9262f5..683f87fb937a10f09b81a4767cc5ba7560b18826 100644 --- a/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java +++ b/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java @@ -64,7 +64,7 @@ import org.olat.course.nodes.gta.model.TaskDefinition; import org.olat.course.nodes.gta.model.TaskDefinitionList; import org.olat.course.nodes.gta.model.TaskImpl; import org.olat.course.nodes.gta.model.TaskListImpl; -import org.olat.course.nodes.gta.ui.SubmitEvent; +import org.olat.course.nodes.gta.ui.events.SubmitEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupRef; 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 97821d45233f574e063dc3d1f1392bfe00bb90c8..b287b95de8fe5e09dbbd531b9871f56d6ae71bb4 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 @@ -34,8 +34,12 @@ import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; +import org.olat.core.util.resource.OresHelper; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.assessment.AssessmentHelper; @@ -48,6 +52,7 @@ import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskList; import org.olat.course.nodes.gta.TaskProcess; +import org.olat.course.nodes.gta.ui.events.TaskMultiUserEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; @@ -64,7 +69,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public abstract class GTAAbstractController extends BasicController { +public abstract class GTAAbstractController extends BasicController implements GenericEventListener { protected VelocityContainer mainVC; @@ -87,6 +92,8 @@ public abstract class GTAAbstractController extends BasicController { protected final boolean businessGroupTask; + protected final OLATResourceable taskListEventResource; + protected GTAStepPreferences stepPreferences; private ContextualSubscriptionController contextualSubscriptionCtr; @@ -146,15 +153,34 @@ public abstract class GTAAbstractController extends BasicController { stepPreferences = new GTAStepPreferences(); } + taskListEventResource = OresHelper.createOLATResourceableInstance("GTaskList", taskList.getKey()); + CoordinatorManager.getInstance().getCoordinator() + .getEventBus().registerFor(this, getIdentity(), taskListEventResource); + initContainer(ureq); process(ureq); } @Override protected void doDispose() { - // + CoordinatorManager.getInstance().getCoordinator() + .getEventBus().deregisterFor(this, taskListEventResource); + } + + @Override + public void event(Event event) { + if(event instanceof TaskMultiUserEvent) { + TaskMultiUserEvent ste = (TaskMultiUserEvent)event; + if(!getIdentity().getKey().equals(ste.getEmitterKey()) + && ((assessedGroup != null && assessedGroup.getKey().equals(ste.getForGroupKey())) + || (assessedIdentity != null && assessedIdentity.getKey().equals(ste.getForIdentityKey())))) { + processEvent(ste); + } + } } + protected abstract void processEvent(TaskMultiUserEvent event); + protected abstract void initContainer(UserRequest ureq); protected final void process(UserRequest ureq) { diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAAssessmentDetailsController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAAssessmentDetailsController.java index d609ff76cf3a372d5dae1b0b138d7aaf1ac38b9b..9a24736a4a72427402a6a2a136a5ac4f92a86b45 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAAssessmentDetailsController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAAssessmentDetailsController.java @@ -37,6 +37,7 @@ import org.olat.core.id.Roles; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.GTAType; +import org.olat.course.nodes.gta.ui.events.SelectBusinessGroupEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java index 324842775c4b9d53871bb4ce4158bbc7af163f14..a327e916bea1072ed9b1858844061677b5d4dc5c 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java @@ -37,6 +37,7 @@ import org.olat.core.gui.control.generic.closablewrapper.CloseableModalControlle import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; +import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.mail.ContactList; import org.olat.core.util.mail.ContactMessage; import org.olat.core.util.vfs.VFSContainer; @@ -46,6 +47,8 @@ import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskHelper; import org.olat.course.nodes.gta.TaskProcess; import org.olat.course.nodes.gta.model.TaskDefinition; +import org.olat.course.nodes.gta.ui.events.SubmitEvent; +import org.olat.course.nodes.gta.ui.events.TaskMultiUserEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; @@ -426,6 +429,11 @@ public class GTACoachController extends GTAAbstractController { } } + @Override + protected void processEvent(TaskMultiUserEvent event) { + // + } + @Override protected void event(UserRequest ureq, Component source, Event event) { if(reviewedButton == source) { @@ -600,6 +608,11 @@ public class GTACoachController extends GTAAbstractController { task = gtaManager.updateTask(task, review); showInfo("run.documents.successfully.submitted"); + TaskMultiUserEvent event = new TaskMultiUserEvent(TaskMultiUserEvent.SUMBIT_TASK, + assessedIdentity, assessedGroup, getIdentity()); + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .fireEventToListenersOf(event, taskListEventResource); + gtaManager.log("Collect", "collect documents", task, getIdentity(), assessedIdentity, assessedGroup, courseEnv, gtaNode); cleanUpProcess(); diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachRevisionAndCorrectionsController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachRevisionAndCorrectionsController.java index faf49d23aaf29d7d97eb332e5d9750dfc32295ee..575e39e83844dc23d6037614034c2601630802d6 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachRevisionAndCorrectionsController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachRevisionAndCorrectionsController.java @@ -48,6 +48,7 @@ import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskHelper; import org.olat.course.nodes.gta.TaskProcess; +import org.olat.course.nodes.gta.ui.events.SubmitEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java index ee787002bfa3ba50b52e4492c98dd4d78c85611a..1e2f39962bf03e181a7904e2bd85bb196888256b 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java @@ -40,6 +40,8 @@ import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.GTAType; +import org.olat.course.nodes.gta.ui.events.SelectBusinessGroupEvent; +import org.olat.course.nodes.gta.ui.events.SelectIdentityEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironmentImpl; diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupListController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupListController.java index 225655517d6c4bcef2af532336960cf5dc476642..bd6d46574976fbaa6ad17500f549868b5a1a8179 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupListController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupListController.java @@ -40,6 +40,7 @@ import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.TaskLight; import org.olat.course.nodes.gta.ui.CoachGroupsTableModel.CGCols; +import org.olat.course.nodes.gta.ui.events.SelectBusinessGroupEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.repository.RepositoryEntry; diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java index 881b80389bb5cb314de8766f864cb770990f1b6b..f81ea73778c0c0a84f1fa0504d85f0e4644fcf80 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java @@ -52,6 +52,7 @@ import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.TaskLight; import org.olat.course.nodes.gta.ui.CoachParticipantsTableModel.CGCols; +import org.olat.course.nodes.gta.ui.events.SelectIdentityEvent; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironmentImpl; import org.olat.group.BusinessGroup; 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 f6318833e78ede3c54c3911c57cd88bda3a14f34..6fc760aa88266cc1a2783f78a4a933b4f25b2c0c 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 @@ -41,6 +41,7 @@ import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.io.SystemFilenameFilter; import org.olat.core.util.mail.MailBundle; import org.olat.core.util.mail.MailContext; @@ -57,6 +58,8 @@ import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskHelper; import org.olat.course.nodes.gta.TaskProcess; import org.olat.course.nodes.gta.model.TaskDefinition; +import org.olat.course.nodes.gta.ui.events.SubmitEvent; +import org.olat.course.nodes.gta.ui.events.TaskMultiUserEvent; import org.olat.course.nodes.ms.MSCourseNodeRunController; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; @@ -276,26 +279,35 @@ public class GTAParticipantController extends GTAAbstractController { private void doConfirmSubmit(UserRequest ureq, Task task) { String title = translate("run.submit.button"); String text; + File[] submittedDocuments; + VFSContainer documentsContainer; if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) { + documentsContainer = gtaManager.getSubmitContainer(courseEnv, gtaNode, assessedGroup); File documentsDir = gtaManager.getSubmitDirectory(courseEnv, gtaNode, assessedGroup); - File[] submittedDocuments = documentsDir.listFiles(new SystemFilenameFilter(true, false)); + submittedDocuments = documentsDir.listFiles(new SystemFilenameFilter(true, false)); if(submittedDocuments.length == 0) { text = "<div class='o_warning'>" + translate("run.submit.confirm.warning.group", new String[]{ StringHelper.escapeHtml(assessedGroup.getName()) }) + "</div>"; } else { text = translate("run.submit.confirm.group", new String[]{ StringHelper.escapeHtml(assessedGroup.getName()) }); } } else { + documentsContainer = gtaManager.getSubmitContainer(courseEnv, gtaNode, getIdentity()); File documentsDir = gtaManager.getSubmitDirectory(courseEnv, gtaNode, getIdentity()); - File[] submittedDocuments = documentsDir.listFiles(new SystemFilenameFilter(true, false)); + submittedDocuments = documentsDir.listFiles(new SystemFilenameFilter(true, false)); if(submittedDocuments.length == 0) { text = "<div class='o_warning'>" + translate("run.submit.confirm.warning") + "</div>"; } else { text = translate("run.submit.confirm"); } } - - confirmSubmitDialog = activateOkCancelDialog(ureq, title, text, confirmSubmitDialog); - confirmSubmitDialog.setUserObject(task); + + String lockedBy = TaskHelper.getDocumentsLocked(documentsContainer, submittedDocuments); + if(lockedBy != null) { + showWarning("warning.submit.documents.edited", lockedBy); + } else { + confirmSubmitDialog = activateOkCancelDialog(ureq, title, text, confirmSubmitDialog); + confirmSubmitDialog.setUserObject(task); + } } private void doSubmitDocuments(UserRequest ureq, Task task) { @@ -303,6 +315,11 @@ public class GTAParticipantController extends GTAAbstractController { task = gtaManager.updateTask(task, review); showInfo("run.documents.successfully.submitted"); + TaskMultiUserEvent event = new TaskMultiUserEvent(TaskMultiUserEvent.SUMBIT_TASK, + assessedIdentity, assessedGroup, getIdentity()); + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .fireEventToListenersOf(event, taskListEventResource); + gtaManager.log("Submit", "submit documents", task, getIdentity(), assessedIdentity, assessedGroup, courseEnv, gtaNode); cleanUpProcess(); @@ -315,8 +332,6 @@ public class GTAParticipantController extends GTAAbstractController { } } - - private void doSubmissionEmail() { String body = config.getStringValue(GTACourseNode.GTASK_SUBMISSION_TEXT); if(StringHelper.containsNonWhitespace(body)) { @@ -429,7 +444,7 @@ public class GTAParticipantController extends GTAAbstractController { private void setRevisionsAndCorrections(UserRequest ureq, Task task) { if(task.getRevisionLoop() > 0) { revisionDocumentsCtrl = new GTAParticipantRevisionAndCorrectionsController(ureq, getWindowControl(), - userCourseEnv, task, gtaNode, assessedGroup); + userCourseEnv, task, gtaNode, assessedGroup, taskListEventResource); listenTo(revisionDocumentsCtrl); mainVC.put("revisionDocs", revisionDocumentsCtrl.getInitialComponent()); @@ -588,6 +603,19 @@ public class GTAParticipantController extends GTAAbstractController { // } + @Override + protected void processEvent(TaskMultiUserEvent event) { + if(TaskMultiUserEvent.SUMBIT_TASK.equals(event.getCommand())) { + if(submitDocCtrl != null) { + submitDocCtrl.close(); + } + } else if(TaskMultiUserEvent.SUBMIT_REVISION.equals(event.getCommand())) { + if(revisionDocumentsCtrl != null) { + revisionDocumentsCtrl.close(); + } + } + } + @Override protected void event(UserRequest ureq, Component source, Event event) { if(openGroupButton == source) { @@ -635,6 +663,9 @@ public class GTAParticipantController extends GTAAbstractController { if(event instanceof SubmitEvent) { Task assignedTask = submitDocCtrl.getAssignedTask(); gtaManager.log("Submit", (SubmitEvent)event, assignedTask, getIdentity(), assessedIdentity, assessedGroup, courseEnv, gtaNode); + } else if(event == Event.DONE_EVENT) { + cleanUpProcess(); + process(ureq); } } super.event(ureq, source, event); @@ -644,8 +675,6 @@ public class GTAParticipantController extends GTAAbstractController { * Remove all the stuff in the main velocity template, discard all controllers */ private void cleanUpProcess() { - - if(availableTaskCtrl != null) { mainVC.remove(availableTaskCtrl.getInitialComponent()); } @@ -664,6 +693,7 @@ public class GTAParticipantController extends GTAAbstractController { if(gradingCtrl != null) { mainVC.remove(gradingCtrl.getInitialComponent()); } + removeAsListenerAndDispose(availableTaskCtrl); removeAsListenerAndDispose(assignedTaskCtrl); removeAsListenerAndDispose(correctionsCtrl); diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java index 06278cc82af80ce78ed2745ac4f9882a9e410a31..f39b3cb8056f018253690595c6528ca0b3a0156a 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java @@ -36,7 +36,9 @@ import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.io.SystemFilenameFilter; import org.olat.core.util.vfs.VFSContainer; import org.olat.course.CourseFactory; @@ -49,6 +51,8 @@ import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskHelper; import org.olat.course.nodes.gta.TaskProcess; +import org.olat.course.nodes.gta.ui.events.SubmitEvent; +import org.olat.course.nodes.gta.ui.events.TaskMultiUserEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; @@ -76,6 +80,7 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl private final BusinessGroup assessedGroup; private final CourseEnvironment courseEnv; private final UserCourseEnvironment assessedUserCourseEnv; + private final OLATResourceable taskListEventResource; @Autowired private GTAManager gtaManager; @@ -84,14 +89,15 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl public GTAParticipantRevisionAndCorrectionsController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment assessedUserCourseEnv,Task assignedTask, - GTACourseNode gtaNode, BusinessGroup assessedGroup) { + GTACourseNode gtaNode, BusinessGroup assessedGroup, OLATResourceable taskListEventResource) { super(ureq, wControl); this.gtaNode = gtaNode; courseEnv = assessedUserCourseEnv.getCourseEnvironment(); this.assessedUserCourseEnv = assessedUserCourseEnv; this.assignedTask = assignedTask; this.assessedGroup = assessedGroup; - this.businessGroupTask = GTAType.group.name().equals(gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE)); + this.taskListEventResource = taskListEventResource; + businessGroupTask = GTAType.group.name().equals(gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE)); mainVC = createVelocityContainer("participant_revisions"); putInitialPanel(mainVC); @@ -107,6 +113,12 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl // } + public void close() { + if(uploadRevisionsCtrl != null) { + uploadRevisionsCtrl.close(); + } + } + private void initRevisionProcess(UserRequest ureq) { List<String> revisionStepNames = new ArrayList<>(); mainVC.contextPut("previousRevisions", revisionStepNames); @@ -214,6 +226,8 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl if(event instanceof SubmitEvent) { Task aTask = uploadRevisionsCtrl.getAssignedTask(); gtaManager.log("Revision", (SubmitEvent)event, aTask, getIdentity(), getIdentity(), assessedGroup, courseEnv, gtaNode); + } else if(event == Event.DONE_EVENT) { + fireEvent(ureq, Event.DONE_EVENT); } } else if(confirmSubmitDialog == source) { if(DialogBoxUIFactory.isOkEvent(event) || DialogBoxUIFactory.isYesEvent(event)) { @@ -241,30 +255,45 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl int iteration = assignedTask.getRevisionLoop(); String title = translate("run.submit.revision.button"); String text; + File[] submittedDocuments; + VFSContainer documentsContainer; if(GTAType.group.name().equals(gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE))) { + documentsContainer = gtaManager.getRevisedDocumentsContainer(courseEnv, gtaNode, iteration, assessedGroup); File documentsDir = gtaManager.getRevisedDocumentsDirectory(courseEnv, gtaNode, iteration, assessedGroup); - File[] submittedDocuments = documentsDir.listFiles(new SystemFilenameFilter(true, false)); + submittedDocuments = documentsDir.listFiles(new SystemFilenameFilter(true, false)); if(submittedDocuments.length == 0) { text = "<div class='o_warning'>" + translate("run.submit.revision.confirm.warning.group", new String[]{ StringHelper.escapeHtml(assessedGroup.getName()) }) + "</div>"; } else { text = translate("run.submit.revision.confirm.group", new String[]{ StringHelper.escapeHtml(assessedGroup.getName()) }); } } else { + documentsContainer = gtaManager.getRevisedDocumentsContainer(courseEnv, gtaNode, iteration, getIdentity()); File documentsDir = gtaManager.getRevisedDocumentsDirectory(courseEnv, gtaNode, iteration, getIdentity()); - File[] submittedDocuments = documentsDir.listFiles(new SystemFilenameFilter(true, false)); + submittedDocuments = documentsDir.listFiles(new SystemFilenameFilter(true, false)); if(submittedDocuments.length == 0) { text = "<div class='o_warning'>" + translate("run.submit.revision.confirm.warning") + "</div>"; } else { text = translate("run.submit.revision.confirm"); } } - confirmSubmitDialog = activateOkCancelDialog(ureq, title, text, confirmSubmitDialog); + + String lockedBy = TaskHelper.getDocumentsLocked(documentsContainer, submittedDocuments); + if(lockedBy != null) { + showWarning("warning.submit.documents.edited", lockedBy); + } else { + confirmSubmitDialog = activateOkCancelDialog(ureq, title, text, confirmSubmitDialog); + } } private void doSubmitRevisions() { assignedTask = gtaManager.updateTask(assignedTask, TaskProcess.correction); gtaManager.log("Revision", "revision submitted", assignedTask, getIdentity(), getIdentity(), assessedGroup, courseEnv, gtaNode); - + + TaskMultiUserEvent event = new TaskMultiUserEvent(TaskMultiUserEvent.SUBMIT_REVISION, + assessedGroup == null ? getIdentity() : null, assessedGroup, getIdentity()); + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .fireEventToListenersOf(event, taskListEventResource); + if(businessGroupTask) { List<Identity> identities = businessGroupService.getMembers(assessedGroup, GroupRoles.participant.name()); AssessmentManager assessmentManager = courseEnv.getAssessmentManager(); diff --git a/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java b/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java index 8811ea6840d12d3b208ede56d5e983fa26532743..314c20fe8adeebfe20ae5a908f1ba693244ddf06 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java @@ -65,6 +65,7 @@ import org.olat.core.util.vfs.VFSManager; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.Task; +import org.olat.course.nodes.gta.ui.events.SubmitEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.modules.ModuleConfiguration; import org.olat.user.UserManager; @@ -97,6 +98,8 @@ class SubmitDocumentsController extends FormBasicController { private final ModuleConfiguration config; private final SubscriptionContext subscriptionContext; + private boolean open = true; + @Autowired private UserManager userManager; @Autowired @@ -122,12 +125,14 @@ class SubmitDocumentsController extends FormBasicController { public Task getAssignedTask() { return assignedTask; } - public boolean hasUploadDocuments() { return (model.getRowCount() > 0); } - + + public void close() { + open = false; + } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { @@ -294,11 +299,15 @@ class SubmitDocumentsController extends FormBasicController { @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { if(uploadDocButton == source) { - doOpenDocumentUpload(ureq); + if(checkOpen(ureq)) { + doOpenDocumentUpload(ureq); + } } else if(createDocButton == source) { - doChooseFilename(ureq); + if(checkOpen(ureq)) { + doChooseFilename(ureq); + } } else if(tableEl == source) { - if(event instanceof SelectionEvent) { + if(checkOpen(ureq) && event instanceof SelectionEvent) { SelectionEvent se = (SelectionEvent)event; SubmittedSolution row = model.getObject(se.getIndex()); if("delete".equals(se.getCommand())) { @@ -316,6 +325,13 @@ class SubmitDocumentsController extends FormBasicController { super.formInnerEvent(ureq, source, event); } + private boolean checkOpen(UserRequest ureq) { + if(open) return true; + showWarning("warning.tasks.submitted"); + fireEvent(ureq, Event.DONE_EVENT); + return false; + } + private void doView(UserRequest ureq, String filename) { if(viewDocCtrl != null) return; 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 ff5b11ba2be3e9c4a66ca07b7d324c5b6d9a761d..d48d2022fc74e78678baf8d0727efbfe0adc2c55 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 @@ -260,5 +260,6 @@ warning.tasks.in.process.title=Aufgabenprozess bereits gestartet warning.tasks.in.process.text=Es gibt bereits Benutzer die den Aufgabenprozess gestartet haben. \u00C4nderungen an der Workflow-Konfiguration kann f\u00FCr diese Benutzer zu Problemen f\u00FChren. warning.tasks.in.process.delete.title=$\:warning.tasks.in.process.title warning.tasks.in.process.delete.text=Wollen Sie wirklich dieser Aufgabe l\u00F6schen? Es gibt bereits Benutzer die den Aufgabenprozess gestartet haben. Das kann f\u00FCr diese Benutzer zu Problemen f\u00FChren. - +warning.submit.documents.edited=Sie k\u00F6nnen den Aufgabe nicht abgeben, weil Dokument noch von "{0}" editiert werden. +warning.tasks.submitted=Sie k\u00F6nnen den Aufgabe nicht mehr editieren, es wurde abgegeben. diff --git a/src/main/java/org/olat/course/nodes/gta/ui/SelectBusinessGroupEvent.java b/src/main/java/org/olat/course/nodes/gta/ui/events/SelectBusinessGroupEvent.java similarity index 96% rename from src/main/java/org/olat/course/nodes/gta/ui/SelectBusinessGroupEvent.java rename to src/main/java/org/olat/course/nodes/gta/ui/events/SelectBusinessGroupEvent.java index 515952a574d5999ded9e18932f3ef70063d0cbe8..4b2dc9b0318490d21345a1283a1e46c5f51ec919 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/SelectBusinessGroupEvent.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/events/SelectBusinessGroupEvent.java @@ -17,7 +17,7 @@ * frentix GmbH, http://www.frentix.com * <p> */ -package org.olat.course.nodes.gta.ui; +package org.olat.course.nodes.gta.ui.events; import org.olat.core.gui.control.Event; import org.olat.group.BusinessGroup; diff --git a/src/main/java/org/olat/course/nodes/gta/ui/SelectIdentityEvent.java b/src/main/java/org/olat/course/nodes/gta/ui/events/SelectIdentityEvent.java similarity index 96% rename from src/main/java/org/olat/course/nodes/gta/ui/SelectIdentityEvent.java rename to src/main/java/org/olat/course/nodes/gta/ui/events/SelectIdentityEvent.java index 57d6da1e1fa5c95dae35c03f0ee6c0eafcbbc017..d1d35677f720a4df56ae7020b9089133809e76d2 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/SelectIdentityEvent.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/events/SelectIdentityEvent.java @@ -17,7 +17,7 @@ * frentix GmbH, http://www.frentix.com * <p> */ -package org.olat.course.nodes.gta.ui; +package org.olat.course.nodes.gta.ui.events; import org.olat.core.gui.control.Event; diff --git a/src/main/java/org/olat/course/nodes/gta/ui/SubmitEvent.java b/src/main/java/org/olat/course/nodes/gta/ui/events/SubmitEvent.java similarity index 97% rename from src/main/java/org/olat/course/nodes/gta/ui/SubmitEvent.java rename to src/main/java/org/olat/course/nodes/gta/ui/events/SubmitEvent.java index 5072139d1d8a172506cdc7b0ad8fc056c590b57a..fdd4b7dc67e275aadbc377ed280d776a666418d3 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/SubmitEvent.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/events/SubmitEvent.java @@ -17,7 +17,7 @@ * frentix GmbH, http://www.frentix.com * <p> */ -package org.olat.course.nodes.gta.ui; +package org.olat.course.nodes.gta.ui.events; import org.olat.core.gui.control.Event; diff --git a/src/main/java/org/olat/course/nodes/gta/ui/events/TaskMultiUserEvent.java b/src/main/java/org/olat/course/nodes/gta/ui/events/TaskMultiUserEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..6e14d94b1e5a891e1c2b659d1f1c8c717378b029 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/events/TaskMultiUserEvent.java @@ -0,0 +1,61 @@ +/** + * <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.ui.events; + +import org.olat.core.id.Identity; +import org.olat.core.util.event.MultiUserEvent; +import org.olat.group.BusinessGroup; + +/** + * + * Initial date: 31.05.2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class TaskMultiUserEvent extends MultiUserEvent { + + private static final long serialVersionUID = 3633136669745883215L; + + public static final String SUMBIT_TASK = "submit-task"; + public static final String SUBMIT_REVISION = "submit-revision"; + + private final Long forIdentityKey; + private final Long forGroupKey; + private final Long emitterKey; + + public TaskMultiUserEvent(String cmd, Identity forIdentity, BusinessGroup forGroup, Identity emitter) { + super(cmd); + this.forGroupKey = forGroup == null ? null : forGroup.getKey(); + this.forIdentityKey = forIdentity == null ? null : forIdentity.getKey(); + this.emitterKey = emitter == null ? null : emitter.getKey(); + } + + public Long getForGroupKey() { + return forGroupKey; + } + + public Long getForIdentityKey() { + return forIdentityKey; + } + + public Long getEmitterKey() { + return emitterKey; + } +} diff --git a/src/test/java/org/olat/commons/coordinate/cluster/lock/LockTest.java b/src/test/java/org/olat/commons/coordinate/cluster/lock/LockTest.java index 6b7ee934eb2c175758d678ea60bee9d5d72e1bb2..049c8f290262f8e3baed974446f931c2a64f9e4d 100644 --- a/src/test/java/org/olat/commons/coordinate/cluster/lock/LockTest.java +++ b/src/test/java/org/olat/commons/coordinate/cluster/lock/LockTest.java @@ -150,6 +150,10 @@ public class LockTest extends OlatTestCase { LockResult res3 = cl.acquireLock(ores, ident, "abc"); assertTrue(res3.isSuccess()); dbInstance.closeSession(); + + // make sure it is not locked anymore + boolean lo3 = cl.isLocked(ores, "abc"); + assertTrue(lo3); // test the admin List<LockEntry> entries = cl.adminOnlyGetLockEntries(); diff --git a/src/test/java/org/olat/selenium/page/LoginPage.java b/src/test/java/org/olat/selenium/page/LoginPage.java index e2ad4b00758251dd58086ef6eca94a27b525b9ec..8a89f4564931f26fc87c9480b8aba446210a0928 100644 --- a/src/test/java/org/olat/selenium/page/LoginPage.java +++ b/src/test/java/org/olat/selenium/page/LoginPage.java @@ -173,7 +173,7 @@ public class LoginPage { //wait until the content appears By footerUserBy = By.cssSelector("#o_footer_user #o_username"); - OOGraphene.waitElement(footerUserBy, 10, browser); + OOGraphene.waitElement(footerUserBy, 30, browser); return this; } diff --git a/src/test/java/org/olat/selenium/page/course/GroupTaskToCoachPage.java b/src/test/java/org/olat/selenium/page/course/GroupTaskToCoachPage.java index 9c69a58ac7fbdb5e2cdd179c0b4f2bffca6ccb0e..dfebe77771e60e053dffa828b8934af72d246769 100644 --- a/src/test/java/org/olat/selenium/page/course/GroupTaskToCoachPage.java +++ b/src/test/java/org/olat/selenium/page/course/GroupTaskToCoachPage.java @@ -176,6 +176,7 @@ public class GroupTaskToCoachPage { buttons.get(0).click(); } OOGraphene.waitBusy(browser); + OOGraphene.waitModalDialog(browser); return this; } @@ -186,6 +187,9 @@ public class GroupTaskToCoachPage { * @return */ public GroupTaskToCoachPage groupAssessment(Boolean passed, Float score) { + By groupAssessmentPopupBy = By.cssSelector(".modal-body .o_sel_course_gta_group_assessment_form"); + OOGraphene.waitElement(groupAssessmentPopupBy, 5, browser); + By applyToAllBy = By.cssSelector(".o_sel_course_gta_group_assessment_form .o_sel_course_gta_apply_to_all input[type='checkbox']"); WebElement applyToAllEl = browser.findElement(applyToAllBy); OOGraphene.check(applyToAllEl, Boolean.TRUE); diff --git a/src/test/java/org/olat/selenium/page/portfolio/ArtefactWizardPage.java b/src/test/java/org/olat/selenium/page/portfolio/ArtefactWizardPage.java index 84913a153a67b22eaaeb371387c791d8dc2fb5a6..ac00c6e4656ab6eee4be6a52009240657a8cea58 100644 --- a/src/test/java/org/olat/selenium/page/portfolio/ArtefactWizardPage.java +++ b/src/test/java/org/olat/selenium/page/portfolio/ArtefactWizardPage.java @@ -107,6 +107,7 @@ public class ArtefactWizardPage { */ public ArtefactWizardPage tags(String... tags) { By tagBy = By.cssSelector("div.o_sel_artefact_add_wizard div.bootstrap-tagsinput>input"); + OOGraphene.waitElement(tagBy, 5, browser); WebElement tagEl = browser.findElement(tagBy); if(tags != null && tags.length > 0 && tags[0] != null) { for(String tag:tags) {