Index: lams_common/src/java/org/lamsfoundation/lams/web/controller/AbstractTimeLimitWebsocketServer.java =================================================================== diff -u -r4352b39a6fd8ac3ccf9c75d6284179c9f004bf4e -r2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588 --- lams_common/src/java/org/lamsfoundation/lams/web/controller/AbstractTimeLimitWebsocketServer.java (.../AbstractTimeLimitWebsocketServer.java) (revision 4352b39a6fd8ac3ccf9c75d6284179c9f004bf4e) +++ lams_common/src/java/org/lamsfoundation/lams/web/controller/AbstractTimeLimitWebsocketServer.java (.../AbstractTimeLimitWebsocketServer.java) (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -37,18 +37,16 @@ import com.fasterxml.jackson.databind.node.ObjectNode; /** - * Controls activity time limits. - * Can be used in tools to set relative and absolute time limits for learners. + * Controls activity time limits. Can be used in tools to set relative and absolute time limits for learners. * * @author Marcin Cieslak */ public abstract class AbstractTimeLimitWebsocketServer extends ServerEndpointConfig.Configurator { /** - * By default Endpoint instances are created for each request. - * We need persistent data to cache time settings. - * We can not use static fields as each subclass needs to have own set of fields. - * This class registers and retrieves Endpoints as singletons. + * By default Endpoint instances are created for each request. We need persistent data to cache time settings. We + * can not use static fields as each subclass needs to have own set of fields. This class registers and retrieves + * Endpoints as singletons. */ public static class EndpointConfigurator extends ServerEndpointConfig.Configurator { private static final Map TIME_LIMIT_ENDPOINT_INSTANCES = new ConcurrentHashMap<>(); @@ -58,8 +56,8 @@ public T getEndpointInstance(Class endpointClass) throws InstantiationException { // is there an instance already - AbstractTimeLimitWebsocketServer instance = AbstractTimeLimitWebsocketServer - .getInstance(endpointClass.getName()); + AbstractTimeLimitWebsocketServer instance = AbstractTimeLimitWebsocketServer.getInstance( + endpointClass.getName()); if (instance == null) { // every subclass must have a static getInstance() method try { @@ -77,7 +75,8 @@ protected static class TimeCache { public int relativeTimeLimit; - public LocalDateTime absoluteTimeLimit; + public int absoluteTimeLimit; + public LocalDateTime absoluteTimeLimitFinish; // mapping of user ID (not UID) and when the learner entered the activity public final Map timeLimitLaunchedDate = new ConcurrentHashMap<>(); public Map timeLimitAdjustment = new HashMap<>(); @@ -142,8 +141,8 @@ executor.scheduleAtFixedRate(sendWorker, 0, CHECK_INTERVAL, TimeUnit.SECONDS); if (userManagementService == null) { - WebApplicationContext wac = WebApplicationContextUtils - .getRequiredWebApplicationContext(SessionManager.getServletContext()); + WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext( + SessionManager.getServletContext()); userManagementService = (IUserManagementService) wac.getBean("userManagementService"); } } @@ -160,8 +159,8 @@ */ protected static void registerInstance(AbstractTimeLimitWebsocketServer instance) { String endpointClassName = instance.getClass().getName(); - AbstractTimeLimitWebsocketServer existingInstance = EndpointConfigurator.TIME_LIMIT_ENDPOINT_INSTANCES - .get(endpointClassName); + AbstractTimeLimitWebsocketServer existingInstance = EndpointConfigurator.TIME_LIMIT_ENDPOINT_INSTANCES.get( + endpointClassName); if (existingInstance != null) { throw new IllegalStateException("Endpoint " + endpointClassName + " already existing in the pool."); } @@ -198,12 +197,18 @@ timeCache.relativeTimeLimit = existingTimeSettings.relativeTimeLimit; updateAllUsers = true; } - if (timeCache.absoluteTimeLimit == null ? existingTimeSettings.absoluteTimeLimit != null - : !timeCache.absoluteTimeLimit.equals(existingTimeSettings.absoluteTimeLimit)) { + if (timeCache.absoluteTimeLimit != existingTimeSettings.absoluteTimeLimit) { timeCache.absoluteTimeLimit = existingTimeSettings.absoluteTimeLimit; updateAllUsers = true; } + if (timeCache.absoluteTimeLimitFinish == null + ? existingTimeSettings.absoluteTimeLimitFinish != null + : !timeCache.absoluteTimeLimitFinish.equals(existingTimeSettings.absoluteTimeLimitFinish)) { + timeCache.absoluteTimeLimitFinish = existingTimeSettings.absoluteTimeLimitFinish; + updateAllUsers = true; + } + if (!existingTimeSettings.timeLimitAdjustment.equals(timeCache.timeLimitAdjustment)) { timeCache.timeLimitAdjustment = existingTimeSettings.timeLimitAdjustment; updateAllUsers = true; @@ -220,7 +225,8 @@ boolean updateUser = updateAllUsers; // check if there is a point in updating learner launch date - if (timeCache.relativeTimeLimit > 0 || timeCache.absoluteTimeLimit != null) { + if (timeCache.relativeTimeLimit > 0 || timeCache.absoluteTimeLimit > 0 + || timeCache.absoluteTimeLimitFinish != null) { LocalDateTime existingLaunchDate = existingTimeSettings.timeLimitLaunchedDate.get(userId); if (existingLaunchDate == null) { @@ -255,8 +261,8 @@ */ @OnOpen public void registerUser(Session websocket) throws IOException { - Long toolContentId = Long - .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_CONTENT_ID).get(0)); + Long toolContentId = Long.valueOf( + websocket.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_CONTENT_ID).get(0)); String login = websocket.getUserPrincipal().getName(); Integer userId = getUserId(login); if (userId == null) { @@ -289,21 +295,18 @@ */ @OnClose public void unregisterUser(Session websocket, CloseReason reason) { - Long toolContentID = Long - .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_CONTENT_ID).get(0)); + Long toolContentID = Long.valueOf( + websocket.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_CONTENT_ID).get(0)); Set sessionWebsockets = websockets.get(toolContentID); if (sessionWebsockets != null) { websockets.get(toolContentID).remove(websocket); if (log.isDebugEnabled()) { // If there was something wrong with the connection, put it into logs. log.debug("User " + websocket.getUserPrincipal().getName() + " left activity with tool content ID: " - + toolContentID - + (!(reason.getCloseCode().equals(CloseCodes.GOING_AWAY) - || reason.getCloseCode().equals(CloseCodes.NORMAL_CLOSURE)) - ? ". Abnormal close. Code: " + reason.getCloseCode() + ". Reason: " - + reason.getReasonPhrase() - : "")); + + toolContentID + (!(reason.getCloseCode().equals(CloseCodes.GOING_AWAY) + || reason.getCloseCode().equals(CloseCodes.NORMAL_CLOSURE)) ? ". Abnormal close. Code: " + + reason.getCloseCode() + ". Reason: " + reason.getReasonPhrase() : "")); } } } @@ -327,7 +330,7 @@ } protected Long getSecondsLeft(TimeCache timeCache, int userId) { - if (timeCache.relativeTimeLimit == 0 && timeCache.absoluteTimeLimit == null) { + if (timeCache.relativeTimeLimit == 0 && timeCache.absoluteTimeLimitFinish == null) { // no time limit is set at all return null; } @@ -336,9 +339,9 @@ LocalDateTime launchedDate = timeCache.timeLimitLaunchedDate.get(userId); // what is the time limit for him LocalDateTime finish = null; - if (timeCache.absoluteTimeLimit != null) { + if (timeCache.absoluteTimeLimitFinish != null) { // the limit is same for everyone - finish = timeCache.absoluteTimeLimit; + finish = timeCache.absoluteTimeLimitFinish; } else if (launchedDate == null) { return null; } else { Index: lams_monitoring/web/timeLimit.jsp =================================================================== diff -u -r47dddf6db4afaba99d8f3f88fc50b16df9251a76 -r2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588 --- lams_monitoring/web/timeLimit.jsp (.../timeLimit.jsp) (revision 47dddf6db4afaba99d8f3f88fc50b16df9251a76) +++ lams_monitoring/web/timeLimit.jsp (.../timeLimit.jsp) (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -58,7 +58,7 @@ // in minutes since learner entered the activity var relativeTimeLimit = , // in seconds since epoch started - absoluteTimeLimit = ; + absoluteTimeLimitFinish = ; $(document).ready(function(){ let timeLimitWidget = $('#time-limit-widget'), @@ -103,13 +103,13 @@ }); // create counter if absolute time limit is set - if (absoluteTimeLimit) { + if (absoluteTimeLimitFinish) { let timeLimitWidgetOpen = sessionStorage.getItem('lams-time-limit-widget-open') === "true"; showTimeLimitWidget(timeLimitWidgetOpen, timeLimitWidgetOpen); updateAbsoluteTimeLimitCounter(); // expand time limit panel if absolute time limit is set and not expired - if (absoluteTimeLimit > new Date().getTime() / 1000) { + if (absoluteTimeLimitFinish > new Date().getTime() / 1000) { $('#time-limit-collapse').collapse('show'); } } @@ -137,7 +137,7 @@ if (toggle === true && displayedRelativeTimeLimit > 0) { relativeTimeLimit = displayedRelativeTimeLimit; // when teacher enables relative time limit, absolute one gets disabled - absoluteTimeLimit = null; + absoluteTimeLimitFinish = null; updateTimeLimitOnServer(); } return; @@ -182,7 +182,7 @@ // start/stop if (toggle === false) { - absoluteTimeLimit = null; + absoluteTimeLimitFinish = null; updateAbsoluteTimeLimitCounter(); return; } @@ -194,7 +194,7 @@ } if (toggle === 'stop') { - absoluteTimeLimit = Math.round(new Date().getTime() / 1000); + absoluteTimeLimitFinish = Math.round(new Date().getTime() / 1000); updateAbsoluteTimeLimitCounter(); } return; @@ -243,7 +243,7 @@ function updateTimeLimitOnServer() { // absolute time limit has higher priority - if (absoluteTimeLimit != null) { + if (absoluteTimeLimitFinish != null) { relativeTimeLimit = 0; } @@ -254,7 +254,7 @@ 'data': { 'toolContentID' : '', 'relativeTimeLimit' : relativeTimeLimit, - 'absoluteTimeLimit' : absoluteTimeLimit, + 'absoluteTimeLimitFinish' : absoluteTimeLimitFinish, '' : '' }, success : function(){ @@ -273,7 +273,7 @@ $('#relative-time-limit-start').removeClass('hidden').prop('disabled', true); } - if (absoluteTimeLimit === null) { + if (absoluteTimeLimitFinish === null) { // no absolute time limit? destroy the counter $('.absolute-time-limit-counter').countdown('destroy'); $('.absolute-time-limit-value').empty(); @@ -291,7 +291,7 @@ $('#absolute-time-limit-cancel').removeClass('hidden'); $('#absolute-time-limit-enabled').removeClass('hidden'); $('#absolute-time-limit-start').addClass('hidden'); - $('#absolute-time-limit-finish-now').prop('disabled', absoluteTimeLimit <= Math.round(new Date().getTime() / 1000)); + $('#absolute-time-limit-finish-now').prop('disabled', absoluteTimeLimitFinish <= Math.round(new Date().getTime() / 1000)); } } }); @@ -300,22 +300,22 @@ function updateAbsoluteTimeLimitCounter(secondsLeft, start) { var now = Math.round(new Date().getTime() / 1000), // preset means that counter is set just on screen and the time limit is not enforced for learners - preset = start !== true && absoluteTimeLimit == null; + preset = start !== true && absoluteTimeLimitFinish == null; if (secondsLeft) { if (!preset) { // time limit is already enforced on server, so update it there now - absoluteTimeLimit = now + secondsLeft; + absoluteTimeLimitFinish = now + secondsLeft; updateTimeLimitOnServer(); } } else { - if (absoluteTimeLimit == null) { + if (absoluteTimeLimitFinish == null) { // disable the counter updateTimeLimitOnServer(); return; } // counter initialisation on page load or "finish now" - secondsLeft = absoluteTimeLimit - now; + secondsLeft = absoluteTimeLimitFinish - now; if (secondsLeft <= 0) { // finish now updateTimeLimitOnServer(); @@ -505,7 +505,7 @@ } - +
Index: lams_monitoring/web/timeLimit5.jsp =================================================================== diff -u -r9e131a46ef30a0bd2b9407498b3840e0801f064d -r2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588 --- lams_monitoring/web/timeLimit5.jsp (.../timeLimit5.jsp) (revision 9e131a46ef30a0bd2b9407498b3840e0801f064d) +++ lams_monitoring/web/timeLimit5.jsp (.../timeLimit5.jsp) (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -49,7 +49,7 @@ // in minutes since learner entered the activity var relativeTimeLimit = ${param.relativeTimeLimit}, // in seconds since epoch started - absoluteTimeLimit = ${empty param.absoluteTimeLimit ? 'null' : param.absoluteTimeLimit}; + absoluteTimeLimitFinish = ${empty param.absoluteTimeLimitFinish ? 'null' : param.absoluteTimeLimitFinish}; $(document).ready(function(){ let timeLimitWidget = $('#time-limit-widget'), @@ -94,13 +94,13 @@ }); // create counter if absolute time limit is set - if (absoluteTimeLimit) { + if (absoluteTimeLimitFinish) { let timeLimitWidgetOpen = sessionStorage.getItem('lams-time-limit-widget-open') === "true"; showTimeLimitWidget(timeLimitWidgetOpen, timeLimitWidgetOpen); updateAbsoluteTimeLimitCounter(); // expand time limit panel if absolute time limit is set and not expired - if (absoluteTimeLimit > new Date().getTime() / 1000) { + if (absoluteTimeLimitFinish > new Date().getTime() / 1000) { $('#time-limit-collapse').collapse('show'); } } @@ -128,7 +128,7 @@ if (toggle === true && displayedRelativeTimeLimit > 0) { relativeTimeLimit = displayedRelativeTimeLimit; // when teacher enables relative time limit, absolute one gets disabled - absoluteTimeLimit = null; + absoluteTimeLimitFinish = null; updateTimeLimitOnServer(); } return; @@ -173,7 +173,7 @@ // start/stop if (toggle === false) { - absoluteTimeLimit = null; + absoluteTimeLimitFinish = null; updateAbsoluteTimeLimitCounter(); return; } @@ -185,7 +185,7 @@ } if (toggle === 'stop') { - absoluteTimeLimit = Math.round(new Date().getTime() / 1000); + absoluteTimeLimitFinish = Math.round(new Date().getTime() / 1000); updateAbsoluteTimeLimitCounter(); } return; @@ -234,7 +234,7 @@ function updateTimeLimitOnServer() { // absolute time limit has higher priority - if (absoluteTimeLimit != null) { + if (absoluteTimeLimitFinish != null) { relativeTimeLimit = 0; } @@ -245,7 +245,7 @@ 'data': { 'toolContentID' : '${param.toolContentId}', 'relativeTimeLimit' : relativeTimeLimit, - 'absoluteTimeLimit' : absoluteTimeLimit, + 'absoluteTimeLimitFinish' : absoluteTimeLimitFinish, '' : '' }, success : function(){ @@ -264,7 +264,7 @@ $('#relative-time-limit-start').removeClass('d-none').prop('disabled', true); } - if (absoluteTimeLimit === null) { + if (absoluteTimeLimitFinish === null) { // no absolute time limit? destroy the counter $('.absolute-time-limit-counter').countdown('destroy'); $('.absolute-time-limit-value').empty(); @@ -282,7 +282,7 @@ $('#absolute-time-limit-cancel').removeClass('d-none'); $('#absolute-time-limit-enabled').removeClass('d-none'); $('#absolute-time-limit-start').addClass('d-none'); - $('#absolute-time-limit-finish-now').prop('disabled', absoluteTimeLimit <= Math.round(new Date().getTime() / 1000)); + $('#absolute-time-limit-finish-now').prop('disabled', absoluteTimeLimitFinish <= Math.round(new Date().getTime() / 1000)); } } }); @@ -291,22 +291,22 @@ function updateAbsoluteTimeLimitCounter(secondsLeft, start) { var now = Math.round(new Date().getTime() / 1000), // preset means that counter is set just on screen and the time limit is not enforced for learners - preset = start !== true && absoluteTimeLimit == null; + preset = start !== true && absoluteTimeLimitFinish == null; if (secondsLeft) { if (!preset) { // time limit is already enforced on server, so update it there now - absoluteTimeLimit = now + secondsLeft; + absoluteTimeLimitFinish = now + secondsLeft; updateTimeLimitOnServer(); } } else { - if (absoluteTimeLimit == null) { + if (absoluteTimeLimitFinish == null) { // disable the counter updateTimeLimitOnServer(); return; } // counter initialisation on page load or "finish now" - secondsLeft = absoluteTimeLimit - now; + secondsLeft = absoluteTimeLimitFinish - now; if (secondsLeft <= 0) { // finish now updateTimeLimitOnServer(); @@ -497,7 +497,7 @@ } - +
Index: lams_tool_assessment/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -re7765b3cabcfd8709853db351088e54f52fd3fac -r2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588 --- lams_tool_assessment/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision e7765b3cabcfd8709853db351088e54f52fd3fac) +++ lams_tool_assessment/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -77,7 +77,12 @@ label.authoring.essay.add.essay = Add question label.authoring.ordering.add.ordering = Add question label.authoring.advance.allow.students.overall.feedback = Display overall feedback at the end of each attempt -label.authoring.advance.time.limit = Time limit (minutes) +label.authoring.advance.time.limit = Time limit duration (minutes) +label.authoring.advance.time.limit.none = none +label.authoring.advance.time.limit.relative = relative to from student start +label.authoring.advance.time.limit.relative.tooltip = Set the duration in minutes that the learners will have to complete the assessment. The duration will apply from the moment each learner begins the assessment. For example, if set to 5 minutes, all learners will have 5 minutes from the moment each of them start the assessment. +label.authoring.advance.time.limit.absolute = for all learners +label.authoring.advance.time.limit.absolute.tooltip = Set the number of minutes to finish the assessment for all learners. The duration will apply from the moment the first learner starts the assessment. label.authoring.advance.question.distribution = Question distribution label.authoring.advance.questions.per.page = Questions per page label.authoring.advance.all.in.one.page = All in one page @@ -452,4 +457,4 @@ label.learning.page.previous = Previous page label.learning.section.next = Next section label.learning.section.previous = Previous section -label.learning.max.mark = Max mark: {0} +label.learning.max.mark = Max mark: {0} \ No newline at end of file Index: lams_tool_assessment/conf/language/lams/ApplicationResources_en_AU.properties =================================================================== diff -u -r0586f1081f3b7e5b550da93b64463175ea698c28 -r2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588 --- lams_tool_assessment/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 0586f1081f3b7e5b550da93b64463175ea698c28) +++ lams_tool_assessment/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -77,7 +77,12 @@ label.authoring.essay.add.essay = Add question label.authoring.ordering.add.ordering = Add question label.authoring.advance.allow.students.overall.feedback = Display overall feedback at the end of each attempt -label.authoring.advance.time.limit = Time limit (minutes) +label.authoring.advance.time.limit = Time limit duration (minutes) +label.authoring.advance.time.limit.none = none +label.authoring.advance.time.limit.relative = relative to from student start +label.authoring.advance.time.limit.relative.tooltip = Set the duration in minutes that the learners will have to complete the assessment. The duration will apply from the moment each learner begins the assessment. For example, if set to 5 minutes, all learners will have 5 minutes from the moment each of them start the assessment. +label.authoring.advance.time.limit.absolute = for all learners +label.authoring.advance.time.limit.absolute.tooltip = Set the number of minutes to finish the assessment for all learners. The duration will apply from the moment the first learner starts the assessment. label.authoring.advance.question.distribution = Question distribution label.authoring.advance.questions.per.page = Questions per page label.authoring.advance.all.in.one.page = All in one page @@ -98,6 +103,7 @@ label.authoring.advance.grade.boundary = Grade boundary label.authoring.advance.feedback = Feedback label.authoring.advance.add.feedback.field = Add feedback +label.authoring.advance.show.max.mark = Allow learners to see question maximum mark label.authoring.cancel.button = Cancel label.authoring.basic.answer.options = Answer options label.authoring.basic.instruction = Instructions @@ -246,6 +252,7 @@ label.authoring.basic.import.questions = Import label.authoring.basic.export.questions = Export label.authoring.advance.display.summary = Display all questions and answers once the learner finishes. +label.authoring.basic.import.openai = Generative AI label.authoring.basic.import.qti = IMS QTI advanced.reflectOnActivity = Add a notebook at end of Assessment with the following instructions: monitor.summary.td.addNotebook = Add a notebook at end of Assessment @@ -397,6 +404,7 @@ label.comment.textarea.tip = Type your comment here then click on the green tick error.resource.image.comment.blank = Comment can not be blank. label.student.choices = Students' choices +label.groups.choices = Groups' choices message.disclose.correct.answers = Please confirm that you want to disclose the correct answer for this question. Note that after you confirm this, this action cannot be reversed. Are you sure you want to do this? message.disclose.groups.answers = Please confirm that you want to disclose the groups' answers for this question. Note that after you confirm this, this action cannot be reversed. Are you sure you want to do this? message.disclose.all.correct.answers = Please confirm that you want to disclose ALL the correct answers for ALL questions. Note that after you confirm this, this action cannot be reversed. Are you sure you want to do this? @@ -448,4 +456,5 @@ label.learning.page.next = Next page label.learning.page.previous = Previous page label.learning.section.next = Next section -label.learning.section.previous = Previous section \ No newline at end of file +label.learning.section.previous = Previous section +label.learning.max.mark = Max mark: {0} \ No newline at end of file Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dbupdates/patch20230621.sql =================================================================== diff -u --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dbupdates/patch20230621.sql (revision 0) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/dbupdates/patch20230621.sql (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -0,0 +1,15 @@ +-- Turn off autocommit, so nothing is committed if there is an error +SET AUTOCOMMIT = 0; +SET FOREIGN_KEY_CHECKS=0; +-- Put all sql statements below here + +-- LDEV-5401 Add learner-triggered absolute time limit to assessment +ALTER TABLE tl_laasse10_assessment RENAME COLUMN absolute_time_limit TO absolute_time_limit_finish; +ALTER TABLE tl_laasse10_assessment ADD COLUMN absolute_time_limit SMALLINT UNSIGNED NOT NULL DEFAULT '0' AFTER relative_time_limit; + +-- Put all sql statements above here + +-- If there were no errors, commit and restore autocommit to on +COMMIT; +SET AUTOCOMMIT = 1; +SET FOREIGN_KEY_CHECKS=1; \ No newline at end of file Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/model/Assessment.java =================================================================== diff -u -r9ff1812d86a48db3faa6579f8977464d944776f7 -r2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/model/Assessment.java (.../Assessment.java) (revision 9ff1812d86a48db3faa6579f8977464d944776f7) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/model/Assessment.java (.../Assessment.java) (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -89,8 +89,11 @@ private int relativeTimeLimit; @Column(name = "absolute_time_limit") - private LocalDateTime absoluteTimeLimit; + private int absoluteTimeLimit; + @Column(name = "absolute_time_limit_finish") + private LocalDateTime absoluteTimeLimitFinish; + @ElementCollection(fetch = FetchType.LAZY) @CollectionTable(name = "tl_laasse10_time_limit", joinColumns = @JoinColumn(name = "assessment_uid")) @MapKeyColumn(name = "user_id") @@ -156,7 +159,7 @@ @Column(name = "display_max_mark") private boolean displayMaxMark; - + @Column(name = "define_later") private boolean defineLater; @@ -298,7 +301,7 @@ assessment.setCreatedBy((AssessmentUser) createdBy.clone()); } - assessment.setAbsoluteTimeLimit(null); + assessment.setAbsoluteTimeLimitFinish(null); assessment.setTimeLimitAdjustments(new HashMap<>(this.getTimeLimitAdjustments())); } catch (CloneNotSupportedException e) { log.error("When clone " + Assessment.class + " failed"); @@ -366,11 +369,11 @@ // ********************************************************** // get/set methods // ********************************************************** + /** * Returns the object's creation date * * @return date - * */ public Date getCreated() { return created; @@ -389,7 +392,6 @@ * Returns the object's date of last update * * @return date updated - * */ public Date getUpdated() { return updated; @@ -408,7 +410,6 @@ * Returns deadline for learner's submission * * @return submissionDeadline - * */ public Date getSubmissionDeadline() { return submissionDeadline; @@ -432,7 +433,7 @@ /** * @param createdBy - * The userid of the user who created this Share assessment. + * The userid of the user who created this Share assessment. */ public void setCreatedBy(AssessmentUser createdBy) { this.createdBy = createdBy; @@ -455,7 +456,7 @@ /** * @param title - * The title to set. + * The title to set. */ public void setTitle(String title) { this.title = title; @@ -481,22 +482,32 @@ /** * @param timeLimit - * the time limitation, that students have to complete an attempt. + * the time limitation, that students have to complete an attempt. */ public void setRelativeTimeLimit(int timeLimit) { this.relativeTimeLimit = timeLimit; } - public LocalDateTime getAbsoluteTimeLimit() { + public int getAbsoluteTimeLimit() { return absoluteTimeLimit; } + public void setAbsoluteTimeLimit(int absoluteTimeLimit) { + this.absoluteTimeLimit = absoluteTimeLimit; + } + + public LocalDateTime getAbsoluteTimeLimitFinish() { + return absoluteTimeLimitFinish; + } + public Long getAbsoluteTimeLimitSeconds() { - return absoluteTimeLimit == null ? null : absoluteTimeLimit.atZone(ZoneId.systemDefault()).toEpochSecond(); + return absoluteTimeLimitFinish == null + ? null + : absoluteTimeLimitFinish.atZone(ZoneId.systemDefault()).toEpochSecond(); } - public void setAbsoluteTimeLimit(LocalDateTime absoluteTimeLimit) { - this.absoluteTimeLimit = absoluteTimeLimit; + public void setAbsoluteTimeLimitFinish(LocalDateTime absoluteTimeLimitFinish) { + this.absoluteTimeLimitFinish = absoluteTimeLimitFinish; } public Map getTimeLimitAdjustments() { Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -r193e1ddf0d23bb2d90636908378b8dbe4a089dbc -r2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 193e1ddf0d23bb2d90636908378b8dbe4a089dbc) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -63,7 +63,6 @@ import org.lamsfoundation.lams.flux.FluxRegistry; import org.lamsfoundation.lams.learning.service.ILearnerService; import org.lamsfoundation.lams.learningdesign.Group; -import org.lamsfoundation.lams.learningdesign.Group; import org.lamsfoundation.lams.learningdesign.Grouping; import org.lamsfoundation.lams.learningdesign.ToolActivity; import org.lamsfoundation.lams.learningdesign.service.ExportToolContentException; @@ -152,8 +151,6 @@ import reactor.core.publisher.Flux; -import javax.swing.text.AbstractDocument; - /** * @author Andrey Balan */ @@ -351,6 +348,12 @@ @Override public LocalDateTime launchTimeLimit(long toolContentId, int userId) { Assessment assessment = getAssessmentByContentId(toolContentId); + int learnersStarted = assessmentUserDao.getCountLearnersByContentId(toolContentId, null); + if (learnersStarted == 1 && assessment.getRelativeTimeLimit() == 0 && assessment.getAbsoluteTimeLimit() > 0 + && assessment.getAbsoluteTimeLimitFinish() == null) { + assessment.setAbsoluteTimeLimitFinish(LocalDateTime.now().plusMinutes(assessment.getAbsoluteTimeLimit())); + assessmentDao.saveObject(assessment); + } AssessmentResult lastResult = getLastAssessmentResult(assessment.getUid(), Long.valueOf(userId)); if (lastResult == null) { return null; @@ -3170,7 +3173,7 @@ } // do not export time limits if the LD gets exported from a running sequence toolContentObj.setTimeLimitAdjustments(null); - toolContentObj.setAbsoluteTimeLimit(null); + toolContentObj.setAbsoluteTimeLimitFinish(null); try { exportContentService.exportToolContent(toolContentId, toolContentObj, assessmentToolContentHandler, Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningWebsocketServer.java =================================================================== diff -u -r5a579db63172ae32b83984fb7cde499217bbcdd4 -r2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 5a579db63172ae32b83984fb7cde499217bbcdd4) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -31,8 +31,8 @@ public LearningWebsocketServer() { if (assessmentService == null) { - WebApplicationContext wac = WebApplicationContextUtils - .getRequiredWebApplicationContext(SessionManager.getServletContext()); + WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext( + SessionManager.getServletContext()); assessmentService = (IAssessmentService) wac.getBean(AssessmentConstants.ASSESSMENT_SERVICE); } } @@ -50,8 +50,9 @@ Assessment assessment = assessmentService.getAssessmentByContentId(toolContentId); TimeCache existingTimeSettings = new TimeCache(); - existingTimeSettings.absoluteTimeLimit = assessment.getAbsoluteTimeLimit(); + existingTimeSettings.absoluteTimeLimitFinish = assessment.getAbsoluteTimeLimitFinish(); existingTimeSettings.relativeTimeLimit = assessment.getRelativeTimeLimit() * 60; + existingTimeSettings.absoluteTimeLimit = assessment.getAbsoluteTimeLimit() * 60; existingTimeSettings.timeLimitAdjustment = assessment.getTimeLimitAdjustments(); for (Integer userId : userIds) { @@ -81,8 +82,8 @@ } public static LearningWebsocketServer getInstance() { - LearningWebsocketServer result = (LearningWebsocketServer) AbstractTimeLimitWebsocketServer - .getInstance(LearningWebsocketServer.class.getName()); + LearningWebsocketServer result = (LearningWebsocketServer) AbstractTimeLimitWebsocketServer.getInstance( + LearningWebsocketServer.class.getName()); if (result == null) { result = new LearningWebsocketServer(); AbstractTimeLimitWebsocketServer.registerInstance(result); Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java =================================================================== diff -u -rf8f156c0f021d60857760ea60579a41e1380288c -r2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java (.../MonitoringController.java) (revision f8f156c0f021d60857760ea60579a41e1380288c) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/web/controller/MonitoringController.java (.../MonitoringController.java) (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -925,7 +925,7 @@ Assessment assessment = service.getAssessmentByContentId(toolContentId); assessment.setRelativeTimeLimit(relativeTimeLimit); // set time limit as seconds from start of epoch, using current server time zone - assessment.setAbsoluteTimeLimit(absoluteTimeLimit == null + assessment.setAbsoluteTimeLimitFinish(absoluteTimeLimit == null ? null : LocalDateTime.ofEpochSecond(absoluteTimeLimit, 0, OffsetDateTime.now().getOffset())); service.saveOrUpdateAssessment(assessment); Index: lams_tool_assessment/web/pages/authoring/advance.jsp =================================================================== diff -u -r9ff1812d86a48db3faa6579f8977464d944776f7 -r2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588 --- lams_tool_assessment/web/pages/authoring/advance.jsp (.../advance.jsp) (revision 9ff1812d86a48db3faa6579f8977464d944776f7) +++ lams_tool_assessment/web/pages/authoring/advance.jsp (.../advance.jsp) (revision 2bc8f0c2f65f02cb65aa7e90d0bbb612494a4588) @@ -10,13 +10,13 @@ $("#passingMark").prop("disabled", true); $("#attemptsAllowed").prop("disabled", false); }); - + $("#passingMarkRadio").change(function() { $("#attemptsAllowed").val("0"); $("#attemptsAllowed").prop("disabled", true); $("#passingMark").prop("disabled", false); }); - + $("#display-summary").change(function(){ $('#display-summary-area').toggle('slow'); $('#allowQuestionFeedback').prop("checked", false); @@ -34,7 +34,7 @@ } $('#allowRightAnswersAfterQuestion, #allowWrongAnswersAfterQuestion').parent().toggleClass('text-muted'); }); - + $("#useSelectLeaderToolOuput").change(function() { if ($(this).prop('checked')) { $("#display-summary").prop("checked", true).prop("disabled", true); @@ -45,43 +45,43 @@ $("#display-summary").prop("disabled", false); $('#questionEtherpadEnabled').prop("checked", false).closest('.checkbox').hide('slow'); $('#allowDiscloseAnswers').prop("checked", false).prop('disabled', true).change(); - } + } }); $("#enable-confidence-levels").change(function(){ $('#confidence-levels-type-area').toggle('slow'); }); - + $("#passingMark").prop("disabled", true); $("#attemptsAllowed").prop("disabled", true); $("#display-summary").prop("disabled", true); - $('#allowRightAnswersAfterQuestion, #allowWrongAnswersAfterQuestion').prop('disabled', true); - $('#allowRightAnswersAfterQuestion, #allowWrongAnswersAfterQuestion').parent().addClass('text-muted'); + $('#allowRightAnswersAfterQuestion, #allowWrongAnswersAfterQuestion').prop('disabled', true); + $('#allowRightAnswersAfterQuestion, #allowWrongAnswersAfterQuestion').parent().addClass('text-muted'); - $('#confidence-levels-type-area').css('display', 'none'); + $('#confidence-levels-type-area').css('display', 'none'); $('.sectionEntry:last .sectionRemoveButton', sectionsContainer).removeClass('hidden'); - + // sections add/remove functionality let sectionAddButton = $('#sectionAddButton').click(function(){ - let sectionsContainer = $('#sectionsContainer'), + let sectionsContainer = $('#sectionsContainer'), sections = $('.sectionEntry', sectionsContainer), sectionSuffix = sections.length + 1, section = $('#sectionTemplate').clone().attr('id', 'sectionEntry' + sectionSuffix).removeClass('hidden'); $('.sectionRemoveButton', sections).addClass('hidden'); section.appendTo(sectionsContainer); $('.sectionRemoveButton', section).removeClass('hidden'); - + $('input[type="text"]', section).attr('name', 'sectionName' + sectionSuffix); $('select', section).attr('name', 'sectionQuestionCount' + sectionSuffix); }); - + $('input[name="questionDistributionType"]').change(function(){ let distributionType = $('input[name="questionDistributionType"]:checked').val(), - questionsPerPageSelect = $('#questionsPerPage'); + questionsPerPageSelect = $('#questionsPerPage'); if (distributionType === 'all') { questionsPerPageSelect.val(0).prop('disabled', true); sectionAddButton.prop('disabled', true); @@ -98,6 +98,32 @@ return; } }).first().change(); + + + // time limit various options functionality + $('input[name="timeLimit"]').change(function(){ + let timeLimitTypeNone = $('#timeLimitNone'), + timeLimitTypeRelative = $('#timeLimitRelative'), + timeLimitTypeRelativeValue = $('#timeLimitRelativeValue'), + timeLimitTypeAbsolute = $('#timeLimitAbsolute'), + timeLimitTypeAbsoluteValue = $('#timeLimitAbsoluteValue'); + + if (timeLimitTypeNone.prop('checked')) { + timeLimitTypeRelativeValue.prop('disabled', true).val(0); + timeLimitTypeAbsoluteValue.prop('disabled', true).val(0); + return; + } + if (timeLimitTypeRelative.prop('checked')) { + timeLimitTypeRelativeValue.prop('disabled', false); + timeLimitTypeAbsoluteValue.prop('disabled', true).val(0); + return; + } + if (timeLimitTypeAbsolute.prop('checked')) { + timeLimitTypeRelativeValue.prop('disabled', true).val(0); + timeLimitTypeAbsoluteValue.prop('disabled', false); + return; + } + }).first().change(); }); function removeSection(button){ @@ -109,326 +135,362 @@ -
- -
- - -
style="display:none;"> -