diff --git a/src/main/java/org/olat/ims/qti21/QTI21DeliveryOptions.java b/src/main/java/org/olat/ims/qti21/QTI21DeliveryOptions.java index 149e77bc4b111e82e34b489dcc1068a9dfd48dce..e1abbb4a6efffdc2ca02ca53319f38588e289f66 100644 --- a/src/main/java/org/olat/ims/qti21/QTI21DeliveryOptions.java +++ b/src/main/java/org/olat/ims/qti21/QTI21DeliveryOptions.java @@ -29,10 +29,19 @@ package org.olat.ims.qti21; */ public class QTI21DeliveryOptions { + private Boolean enableCancel; private Boolean enableSuspend; private Boolean displayScoreProgress; private Boolean displayQuestionProgress; + public Boolean getEnableCancel() { + return enableCancel; + } + + public void setEnableCancel(Boolean enableCancel) { + this.enableCancel = enableCancel; + } + public Boolean getEnableSuspend() { return enableSuspend; } @@ -56,8 +65,4 @@ public class QTI21DeliveryOptions { public void setDisplayQuestionProgress(Boolean displayQuestionProgress) { this.displayQuestionProgress = displayQuestionProgress; } - - - - } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/QTI21Service.java b/src/main/java/org/olat/ims/qti21/QTI21Service.java index e30966eaba85154fcbfd21bfafd6f62d4e2c9a51..f10dcf7a60996d2af69ffc7a4f5a08674d96fa89 100644 --- a/src/main/java/org/olat/ims/qti21/QTI21Service.java +++ b/src/main/java/org/olat/ims/qti21/QTI21Service.java @@ -156,6 +156,8 @@ public interface QTI21Service { public AssessmentTestSession finishTestSession(AssessmentTestSession candidateSession, TestSessionState testSessionState, AssessmentResult assessmentResul, Date timestamp); + public void cancelTestSession(AssessmentTestSession candidateSession, TestSessionState testSessionState); + public CandidateEvent recordCandidateTestEvent(AssessmentTestSession candidateSession, CandidateTestEventType textEventType, TestSessionState testSessionState, NotificationRecorder notificationRecorder); diff --git a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java index 030c7efc41c0ab245ea609d3f74a40ccf0c9c8d7..b2a79190437ed7e8cecb33fbfe6116abd1c1f7e5 100644 --- a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java +++ b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java @@ -119,6 +119,28 @@ public class AssessmentTestSessionDAO { .getResultList(); } + public int deleteTestSession(AssessmentTestSession testSession) { + StringBuilder responseSb = new StringBuilder(); + responseSb.append("delete from qtiassessmentresponse response where response.assessmentTestSession.key=:sessionKey"); + int responses = dbInstance.getCurrentEntityManager() + .createQuery(responseSb.toString()) + .setParameter("sessionKey", testSession.getKey()) + .executeUpdate(); + + StringBuilder itemSb = new StringBuilder(); + itemSb.append("delete from qtiassessmentitemsession itemSession where itemSession.assessmentTestSession.key=:sessionKey"); + int itemSessions = dbInstance.getCurrentEntityManager() + .createQuery(itemSb.toString()) + .setParameter("sessionKey", testSession.getKey()) + .executeUpdate(); + + String q = "delete from qtiassessmenttestsession session where session.key=:sessionKey"; + int sessions = dbInstance.getCurrentEntityManager() + .createQuery(q) + .setParameter("sessionKey", testSession.getKey()) + .executeUpdate(); + return itemSessions + sessions + responses; + } public int deleteUserTestSessions(RepositoryEntryRef testEntry) { StringBuilder responseSb = new StringBuilder(); diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java index ed189fc9e693645a74759fc94305639de40f0060..fb8889bc76d91c983c7af9c25a34c6c048bbadd9 100644 --- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java +++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java @@ -456,6 +456,26 @@ public class QTI21ServiceImpl implements QTI21Service, InitializingBean, Disposa //maybeScheduleLtiOutcomes(candidateSession, assessmentResult); return candidateSession; } + + /** + * Cancel delete the test session, related items session and their responses, the + * assessment result file, the test plan file. + * + */ + @Override + public void cancelTestSession(AssessmentTestSession candidateSession, TestSessionState testSessionState) { + final File myStore = storage.getDirectory(candidateSession.getStorage()); + final File sessionState = new File(myStore, "testSessionState.xml"); + final File resultFile = getAssessmentResultFile(candidateSession); + + testSessionDao.deleteTestSession(candidateSession); + if(sessionState != null && sessionState.exists()) { + sessionState.delete(); + } + if(resultFile != null && resultFile.exists()) { + resultFile.delete(); + } + } private void recordOutcomeVariables(AssessmentTestSession candidateSession, AbstractResult resultNode) { for (final ItemVariable itemVariable : resultNode.getItemVariables()) { diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java index cb79b3d7cfb491b74ce89e82c72e16b9f0e28c46..60343c8e9fa4b02111ff5f5e18b3e9db74e61288 100644 --- a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java +++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java @@ -42,6 +42,7 @@ import org.olat.core.gui.components.form.flexible.impl.MultipartFileInfos; import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit; import org.olat.core.gui.components.htmlheader.jscss.JSAndCSSComponent; import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.panel.StackedPanel; import org.olat.core.gui.components.progressbar.ProgressBarItem; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Controller; @@ -133,8 +134,13 @@ public class AssessmentTestDisplayController extends BasicController implements private final QTI21DeliveryOptions deliveryOptions; private VelocityContainer mainVC; + private final StackedPanel mainPanel; private QtiWorksController qtiWorksCtrl; private TestSessionController testSessionController; + + private DialogBoxController advanceTestPartDialog; + private DialogBoxController confirmCancelDialog; + private DialogBoxController confirmSuspendDialog; private CandidateEvent lastEvent; private Date currentRequestTimestamp; @@ -154,6 +160,8 @@ public class AssessmentTestDisplayController extends BasicController implements @Autowired private AssessmentService assessmentService; + private final boolean allowResume; + /** * * @param ureq @@ -185,7 +193,7 @@ public class AssessmentTestDisplayController extends BasicController implements marks = qtiService.getMarks(getIdentity(), entry, subIdent, testEntry); deliveryOptions = qtiService.getDeliveryOptions(testEntry); - boolean allowResume = deliveryOptions.getEnableSuspend() != null + allowResume = deliveryOptions.getEnableSuspend() != null && deliveryOptions.getEnableSuspend().booleanValue(); AssessmentTestSession lastSession = null; @@ -214,11 +222,13 @@ public class AssessmentTestDisplayController extends BasicController implements initQtiWorks(ureq); } - putInitialPanel(mainVC); + mainPanel = putInitialPanel(mainVC); } private void initQtiWorks(UserRequest ureq) { - qtiWorksCtrl = new QtiWorksController(ureq, getWindowControl(), true); + boolean allowCancel = deliveryOptions.getEnableCancel() != null + && deliveryOptions.getEnableCancel().booleanValue(); + qtiWorksCtrl = new QtiWorksController(ureq, getWindowControl(), allowCancel, allowResume); listenTo(qtiWorksCtrl); mainVC.put("qtirun", qtiWorksCtrl.getInitialComponent()); } @@ -260,8 +270,20 @@ public class AssessmentTestDisplayController extends BasicController implements processAdvanceTestPart(ureq); } mainVC.setDirty(true); + } else if(confirmCancelDialog == source) { + if(DialogBoxUIFactory.isOkEvent(event) || DialogBoxUIFactory.isYesEvent(event)) { + doCancel(); + } + } else if(confirmSuspendDialog == source) { + if(DialogBoxUIFactory.isOkEvent(event) || DialogBoxUIFactory.isYesEvent(event)) { + doSuspend(); + } } else if(qtiWorksCtrl == source) { - if(event instanceof QTIWorksAssessmentTestEvent) { + if(event == Event.CANCELLED_EVENT) { + doConfirmCancel(ureq); + } else if("suspend".equals(event.getCommand())) { + doConfirmSuspend(ureq); + } else if(event instanceof QTIWorksAssessmentTestEvent) { processQTIEvent(ureq, (QTIWorksAssessmentTestEvent)event); } } @@ -271,6 +293,31 @@ public class AssessmentTestDisplayController extends BasicController implements private void doExitTest(UserRequest ureq) { fireEvent(ureq, new QTI21Event(QTI21Event.EXIT)); } + + private void doConfirmSuspend(UserRequest ureq) { + String title = translate("suspend.test"); + String text = translate("confirm.suspend.test"); + confirmSuspendDialog = activateOkCancelDialog(ureq, title, text, confirmSuspendDialog); + } + + private void doSuspend() { + VelocityContainer suspendedVC = createVelocityContainer("suspended"); + mainPanel.setContent(suspendedVC); + } + + private void doConfirmCancel(UserRequest ureq) { + String title = translate("cancel.test"); + String text = translate("confirm.cancel.test"); + confirmCancelDialog = activateOkCancelDialog(ureq, title, text, confirmCancelDialog); + } + + private void doCancel() { + VelocityContainer cancelledVC = createVelocityContainer("cancelled"); + mainPanel.setContent(cancelledVC); + TestSessionState testSessionState = testSessionController.getTestSessionState(); + qtiService.cancelTestSession(candidateSession, testSessionState); + //delete database object, file submissions... + } private void processQTIEvent(UserRequest ureq, QTIWorksAssessmentTestEvent qe) { currentRequestTimestamp = ureq.getRequestTimestamp(); @@ -645,8 +692,6 @@ public class AssessmentTestDisplayController extends BasicController implements testSessionController.endCurrentTestPart(requestTimestamp); } - private DialogBoxController advanceTestPartDialog; - private void confirmAdvanceTestPart(UserRequest ureq) { String title = translate("confirm.advance.testpart.title"); String text = translate("confirm.advance.testpart.text"); @@ -972,17 +1017,19 @@ public class AssessmentTestDisplayController extends BasicController implements */ private class QtiWorksController extends AbstractQtiWorksController { - private FormLink endTestPartButton, closeTestButton, cancelTestButton; + private FormLink endTestPartButton, closeTestButton, cancelTestButton, suspendTestButton; private AssessmentTestFormItem qtiEl; private AssessmentTreeFormItem qtiTreeEl; private ProgressBarItem scoreProgress, questionProgress; - private final boolean allowCancel, displayQuestionProgress, displayScoreProgress; + private final boolean allowCancel, allowSuspend; + private final boolean displayQuestionProgress, displayScoreProgress; private final QtiWorksStatus qtiWorksStatus = new QtiWorksStatus(); - public QtiWorksController(UserRequest ureq, WindowControl wControl, boolean allowCancel) { + public QtiWorksController(UserRequest ureq, WindowControl wControl, boolean allowCancel, boolean allowSuspend) { super(ureq, wControl, "at_run"); this.allowCancel = allowCancel; + this.allowSuspend = allowSuspend; displayScoreProgress = deliveryOptions.getDisplayScoreProgress() != null && deliveryOptions.getDisplayScoreProgress().booleanValue(); displayQuestionProgress = deliveryOptions.getDisplayQuestionProgress() != null @@ -1009,7 +1056,10 @@ public class AssessmentTestDisplayController extends BasicController implements endTestPartButton = uifactory.addFormLink("endTest", endName, null, formLayout, Link.BUTTON); closeTestButton = uifactory.addFormLink("closeTest", "assessment.test.close.test", null, formLayout, Link.BUTTON); if(allowCancel) { - cancelTestButton = uifactory.addFormLink("cancelTest", "cancel", null, formLayout, Link.BUTTON); + cancelTestButton = uifactory.addFormLink("cancelTest", "cancel.test", null, formLayout, Link.BUTTON); + } + if(allowSuspend) { + suspendTestButton = uifactory.addFormLink("suspendTest", "suspend.test", null, formLayout, Link.BUTTON); } ResourceLocator fileResourceLocator = new PathResourceLocator(fUnzippedDirRoot.toPath()); @@ -1068,6 +1118,8 @@ public class AssessmentTestDisplayController extends BasicController implements doCloseTest(ureq); } else if(cancelTestButton == source) { doCancelTest(ureq); + } else if(suspendTestButton == source) { + doSuspendTest(ureq); } else if(source == qtiEl || source == qtiTreeEl) { if(event instanceof QTIWorksAssessmentTestEvent) { fireEvent(ureq, event); @@ -1119,7 +1171,11 @@ public class AssessmentTestDisplayController extends BasicController implements } private void doCancelTest(UserRequest ureq) { - + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + private void doSuspendTest(UserRequest ureq) { + fireEvent(ureq, new Event("suspend")); } private void updateGUI() { diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21OptionsController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21DeliveryOptionsController.java similarity index 85% rename from src/main/java/org/olat/ims/qti21/ui/QTI21OptionsController.java rename to src/main/java/org/olat/ims/qti21/ui/QTI21DeliveryOptionsController.java index 782ec0217bad3aa7900c790dded3c63244540a47..9acfa96b410a1bbb4e3914b106ab8f1aac030c70 100644 --- a/src/main/java/org/olat/ims/qti21/ui/QTI21OptionsController.java +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21DeliveryOptionsController.java @@ -43,12 +43,12 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class QTI21OptionsController extends FormBasicController implements Activateable2 { +public class QTI21DeliveryOptionsController extends FormBasicController implements Activateable2 { private static final String[] onKeys = new String[]{ "on" }; private static final String[] onValues = new String[]{ "" }; - private MultipleSelectionElement enableSuspendEl, displayQuestionProgressEl, displayScoreProgressEl; + private MultipleSelectionElement enableCancelEl, enableSuspendEl, displayQuestionProgressEl, displayScoreProgressEl; private final RepositoryEntry testEntry; private final QTI21DeliveryOptions deliveryOptions; @@ -56,7 +56,7 @@ public class QTI21OptionsController extends FormBasicController implements Activ @Autowired private QTI21Service qtiService; - public QTI21OptionsController(UserRequest ureq, WindowControl wControl, RepositoryEntry testEntry) { + public QTI21DeliveryOptionsController(UserRequest ureq, WindowControl wControl, RepositoryEntry testEntry) { super(ureq, wControl); this.testEntry = testEntry; this.deliveryOptions = qtiService.getDeliveryOptions(testEntry); @@ -82,6 +82,11 @@ public class QTI21OptionsController extends FormBasicController implements Activ enableSuspendEl.select(onKeys[0], true); } + enableCancelEl = uifactory.addCheckboxesHorizontal("cancel", "qti.form.enablecancel", formLayout, onKeys, onValues); + if(deliveryOptions.getEnableCancel() != null && deliveryOptions.getEnableCancel().booleanValue()) { + enableCancelEl.select(onKeys[0], true); + } + FormLayoutContainer buttonsLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); buttonsLayout.setRootForm(mainForm); formLayout.add(buttonsLayout); @@ -100,6 +105,12 @@ public class QTI21OptionsController extends FormBasicController implements Activ @Override protected void formOK(UserRequest ureq) { + if(enableCancelEl.isAtLeastSelected(1)) { + deliveryOptions.setEnableCancel(Boolean.TRUE); + } else { + deliveryOptions.setEnableCancel(Boolean.FALSE); + } + if(enableSuspendEl.isAtLeastSelected(1)) { deliveryOptions.setEnableSuspend(Boolean.TRUE); } else { diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21RuntimeController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21RuntimeController.java index 908d9cc5cd657f5f6e5c637b9353739e42bc4bf2..b2b4733224473c38880d0b0b65c26eeb65b28e51 100644 --- a/src/main/java/org/olat/ims/qti21/ui/QTI21RuntimeController.java +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21RuntimeController.java @@ -50,7 +50,7 @@ public class QTI21RuntimeController extends RepositoryEntryRuntimeController { private Link assessmentLink, testStatisticLink, qtiOptionsLink; - private QTI21OptionsController optionsCtrl; + private QTI21DeliveryOptionsController optionsCtrl; private AssessmentOverviewController assessmentToolCtrl; private QTI21AssessmentTestStatisticsController statsToolCtr; @@ -123,7 +123,7 @@ public class QTI21RuntimeController extends RepositoryEntryRuntimeController { WindowControl swControl = addToHistory(ureq, ores, null); if (reSecurity.isEntryAdmin()) { - QTI21OptionsController ctrl = new QTI21OptionsController(ureq, swControl, getRepositoryEntry()); + QTI21DeliveryOptionsController ctrl = new QTI21DeliveryOptionsController(ureq, swControl, getRepositoryEntry()); listenTo(ctrl); optionsCtrl = pushController(ureq, "Options", ctrl); currentToolCtr = optionsCtrl; diff --git a/src/main/java/org/olat/ims/qti21/ui/_content/cancelled.html b/src/main/java/org/olat/ims/qti21/ui/_content/cancelled.html new file mode 100644 index 0000000000000000000000000000000000000000..76a693d7d0ab6b459b8ffd88f457a3a564b6cc13 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/_content/cancelled.html @@ -0,0 +1,3 @@ +<div id="o_qti_container"> + <div class="o_important">$r.translate("assessment.test.cancelled")</div> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/_content/suspended.html b/src/main/java/org/olat/ims/qti21/ui/_content/suspended.html new file mode 100644 index 0000000000000000000000000000000000000000..c0282a636250948871a78ab6a19528c40ab6e539 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/_content/suspended.html @@ -0,0 +1,3 @@ +<div id="o_qti_container"> + <div class="o_important">$r.translate("assessment.test.suspended")</div> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties index e4a007b82a11285c5c2e07f8a317b86c9b1718f0..dafc06c53f7045ee5ccd6ca9763d7190a1f55a42 100644 --- a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties @@ -11,6 +11,8 @@ assessment.item.status.notAnswered=Not answered assessment.item.status.modelSolution=Model solution assessment.item.modal.feedback=Feedback assessment.test.backToTestFeedback=Back to test's feedback +assessment.test.cancelled=The test has been cancelled. +assessment.test.suspended=the test has been suspended. assessment.test.end.test=Finish test assessment.test.end.testPart=Finish test part assessment.test.close.test=Close test @@ -23,14 +25,19 @@ assessment.test.nav.title.multiPartTestMenu=Test part question menu assessment.test.nextQuestion=Next Question assessment.solution.hide=Hide solution assessment.solution.show=Show solution +cancel.test=$org.olat.modules.iq\:cancelAssess command.openassessment=Assessment tool command.openteststatistic=Test statistics +confirm.cancel.test=$org.olat.modules.iq\:confirmCancel +confirmSuspend=$org.olat.modules.iq\:confirmSuspend confirm.advance.testpart.title=Advance test part confirm.advance.testpart.text=Do really want to leave this test part and advance further? +qti.form.enablecancel=$org.olat.course.nodes.iq\:qti.form.enablecancel qti.form.enablesuspend=$org.olat.course.nodes.iq\:qti.form.enablesuspend qti.form.questionprogress=$org.olat.course.nodes.iq\:qti.form.questionprogress qti.form.scoreprogress=$org.olat.course.nodes.iq\:qti.form.scoreprogress question.progress.answered=Answered question.progress.score=$org.olat.modules.iq\:actualPoints question.progress.noMaxScore=$org.olat.modules.iq\:noMaxScore +suspend.test=$org.olat.modules.iq\:suspendAssess tab.options=Options \ No newline at end of file