Index: lams_common/src/java/org/lamsfoundation/lams/flux/FluxMap.java =================================================================== diff -u -r4dbd12afbfcb6eb768ceb772768779e7619dc2f8 -r20e62542c73330b5ccd425dc727597660abdd129 --- lams_common/src/java/org/lamsfoundation/lams/flux/FluxMap.java (.../FluxMap.java) (revision 4dbd12afbfcb6eb768ceb772768779e7619dc2f8) +++ lams_common/src/java/org/lamsfoundation/lams/flux/FluxMap.java (.../FluxMap.java) (revision 20e62542c73330b5ccd425dc727597660abdd129) @@ -5,6 +5,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiPredicate; import java.util.function.Function; import org.apache.log4j.Logger; @@ -23,24 +24,28 @@ public class FluxMap { private static Logger log = Logger.getLogger(FluxMap.class.getName()); - public static final int STANDARD_THROTTLE = 1; + public static final int SHORT_THROTTLE = 5; + public static final int STANDARD_THROTTLE = 10; + public static final int LONG_THROTTLE = 30; public static final int STANDARD_TIMEOUT = 5 * 60; private final Map> map; // only for logging purposes private final String name; private final Flux source; + private final BiPredicate itemEqualsPredicate; + private final Function fetchFunction; // default timeout is null, i.e. never expire private final Integer timeoutSeconds; // default throttle time is null, i.e. no throttling private final Integer throttleSeconds; - private final Function fetchFunction; - public FluxMap(String name, Flux source, Function fetchFunction, Integer throttleSeconds, - Integer timeoutSeconds) { + public FluxMap(String name, Flux source, BiPredicate itemEqualsPredicate, Function fetchFunction, + Integer throttleSeconds, Integer timeoutSeconds) { this.name = name; this.source = source; + this.itemEqualsPredicate = itemEqualsPredicate; this.fetchFunction = fetchFunction; this.throttleSeconds = throttleSeconds; this.timeoutSeconds = timeoutSeconds; @@ -61,7 +66,7 @@ } // filter out signals which do not match the key - Flux filteringFlux = source.filter(item -> item.equals(key)); + Flux filteringFlux = source.filter(item -> itemEqualsPredicate.test(item, key)); // do not emit more often than this amount of time if (throttleSeconds != null) { @@ -101,8 +106,7 @@ } if (log.isDebugEnabled()) { - log.debug("Subscribed (" + counter + ") to flux \"" + name + "\" with key " - + key); + log.debug("Subscribed (" + counter + ") to flux \"" + name + "\" with key " + key); } }) @@ -111,8 +115,8 @@ int counter = subscriberCounter.decrementAndGet(); if (log.isDebugEnabled()) { - log.debug("Cancelling (" + counter + ") subscription to flux for \"" + name - + "\" with key " + key); + log.debug("Cancelling (" + counter + ") subscription to flux for \"" + name + "\" with key " + + key); } }); @@ -121,8 +125,7 @@ flux = flux.timeout(Duration.ofSeconds(timeoutSeconds)).onErrorResume(TimeoutException.class, throwable -> { if (log.isDebugEnabled()) { - log.debug( - "Removing timed out flux for \"" + name + "\" with key " + key); + log.debug("Removing timed out flux for \"" + name + "\" with key " + key); } // remove terminated Flux from the map map.remove(key); Index: lams_common/src/java/org/lamsfoundation/lams/flux/FluxRegistry.java =================================================================== diff -u -rf61af352afa0ddbfb791477732cbfe80425af47d -r20e62542c73330b5ccd425dc727597660abdd129 --- lams_common/src/java/org/lamsfoundation/lams/flux/FluxRegistry.java (.../FluxRegistry.java) (revision f61af352afa0ddbfb791477732cbfe80425af47d) +++ lams_common/src/java/org/lamsfoundation/lams/flux/FluxRegistry.java (.../FluxRegistry.java) (revision 20e62542c73330b5ccd425dc727597660abdd129) @@ -3,6 +3,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiPredicate; import java.util.function.Function; import reactor.core.publisher.Flux; @@ -40,17 +41,20 @@ } } - public static void initFluxMap(String fluxName, String sinkName, Function fetchFunction, - Integer throttleSeconds, Integer timeoutSeconds) { + public static void initFluxMap(String fluxName, String sinkName, BiPredicate itemEqualsPredicate, + Function fetchFunction, Integer throttleSeconds, Integer timeoutSeconds) { if (fluxRegistry.containsKey(fluxName)) { throw new IllegalArgumentException("FluxMap for \"" + fluxName + "\" was already initialised"); } SharedSink sink = sinkRegistry.get(sinkName); if (sink == null) { sink = FluxRegistry.getSink(sinkName); } - FluxMap fluxMap = new FluxMap<>(fluxName, sink.getFlux(), fetchFunction, throttleSeconds, - timeoutSeconds); + if (itemEqualsPredicate == null) { + itemEqualsPredicate = (key, item) -> item.equals(key); + } + FluxMap fluxMap = new FluxMap<>(fluxName, sink.getFlux(), itemEqualsPredicate, fetchFunction, + throttleSeconds, timeoutSeconds); fluxRegistry.put(fluxName, fluxMap); } Index: lams_common/src/java/org/lamsfoundation/lams/lesson/util/LearnerActivityCompleteFluxItem.java =================================================================== diff -u -r3e58e9a9a8b4b7f7d94ad65d64376f091d7502dc -r20e62542c73330b5ccd425dc727597660abdd129 --- lams_common/src/java/org/lamsfoundation/lams/lesson/util/LearnerActivityCompleteFluxItem.java (.../LearnerActivityCompleteFluxItem.java) (revision 3e58e9a9a8b4b7f7d94ad65d64376f091d7502dc) +++ lams_common/src/java/org/lamsfoundation/lams/lesson/util/LearnerActivityCompleteFluxItem.java (.../LearnerActivityCompleteFluxItem.java) (revision 20e62542c73330b5ccd425dc727597660abdd129) @@ -1,28 +1,53 @@ package org.lamsfoundation.lams.lesson.util; +import java.util.Objects; + /** * Flux item emitted on learner moving from one activity to another by himself */ public class LearnerActivityCompleteFluxItem { private long lessonId; - private long activityId; private int userId; + private Long activityId; - public LearnerActivityCompleteFluxItem(long lessonId, long activityId, int userId) { + public LearnerActivityCompleteFluxItem(long lessonId, int userId, Long activityId) { this.lessonId = lessonId; - this.activityId = activityId; this.userId = userId; + this.activityId = activityId; } public long getLessonId() { return lessonId; } - public long getActivityId() { + public Long getActivityId() { return activityId; } public int getUserId() { return userId; } + + @Override + public int hashCode() { + return Objects.hash(activityId, lessonId, userId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LearnerActivityCompleteFluxItem)) { + return false; + } + LearnerActivityCompleteFluxItem other = (LearnerActivityCompleteFluxItem) obj; + return Objects.equals(activityId, other.activityId) && lessonId == other.lessonId && userId == other.userId; + } + + @Override + public String toString() { + return "LearnerActivityCompleteFluxItem [lessonId=" + lessonId + ", userId=" + userId + ", activityId=" + + activityId + "]"; + } } \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/web/controller/CompleteActivityController.java =================================================================== diff -u -r3e58e9a9a8b4b7f7d94ad65d64376f091d7502dc -r20e62542c73330b5ccd425dc727597660abdd129 --- lams_learning/src/java/org/lamsfoundation/lams/learning/web/controller/CompleteActivityController.java (.../CompleteActivityController.java) (revision 3e58e9a9a8b4b7f7d94ad65d64376f091d7502dc) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/web/controller/CompleteActivityController.java (.../CompleteActivityController.java) (revision 20e62542c73330b5ccd425dc727597660abdd129) @@ -123,7 +123,7 @@ // notify all event subscribers that a learner finished an activity FluxRegistry.emit(CommonConstants.ACTIVITY_COMPLETED_SINK_NAME, - new LearnerActivityCompleteFluxItem(lessonId, activityId, learnerId)); + new LearnerActivityCompleteFluxItem(lessonId, learnerId, activityId)); return forward; Index: lams_learning/src/java/org/lamsfoundation/lams/learning/web/controller/LearnerController.java =================================================================== diff -u -r3e58e9a9a8b4b7f7d94ad65d64376f091d7502dc -r20e62542c73330b5ccd425dc727597660abdd129 --- lams_learning/src/java/org/lamsfoundation/lams/learning/web/controller/LearnerController.java (.../LearnerController.java) (revision 3e58e9a9a8b4b7f7d94ad65d64376f091d7502dc) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/web/controller/LearnerController.java (.../LearnerController.java) (revision 20e62542c73330b5ccd425dc727597660abdd129) @@ -40,6 +40,7 @@ import javax.servlet.http.HttpSession; import org.apache.log4j.Logger; +import org.lamsfoundation.lams.flux.FluxMap; import org.lamsfoundation.lams.flux.FluxRegistry; import org.lamsfoundation.lams.gradebook.GradebookUserActivity; import org.lamsfoundation.lams.gradebook.service.IGradebookService; @@ -58,6 +59,7 @@ import org.lamsfoundation.lams.lesson.LearnerProgressArchive; import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.lesson.util.LearnerActivityCompleteFluxItem; import org.lamsfoundation.lams.lesson.util.LearnerLessonJoinFluxItem; import org.lamsfoundation.lams.monitoring.service.IMonitoringService; import org.lamsfoundation.lams.tool.ToolSession; @@ -81,9 +83,12 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import reactor.core.publisher.Flux; + /** *

* The action servlet that interacts with learner to start a lams learner module, join a user to the lesson and allows a @@ -134,6 +139,25 @@ "message.lesson.restart.button", "label.learner.progress.notebook", "button.exit", "label.learner.progress.support", "label.my.progress" }; + // flux management + public static final String LEARNER_TIMELINE_FLUX_NAME = "learner timeline updated"; + + public LearnerController() { + FluxRegistry.initFluxMap(LEARNER_TIMELINE_FLUX_NAME, CommonConstants.ACTIVITY_COMPLETED_SINK_NAME, + (LearnerActivityCompleteFluxItem item, + LearnerActivityCompleteFluxItem key) -> item.getLessonId() == key.getLessonId() + && item.getUserId() == key.getUserId(), + (LearnerActivityCompleteFluxItem item) -> { + ObjectNode responseJSON = null; + try { + responseJSON = getLearnerProgress(item.getLessonId(), item.getUserId(), true); + } catch (Exception e) { + log.error("Error while getting learner timeline flux", e); + } + return responseJSON == null ? "" : responseJSON.toString(); + }, FluxMap.STANDARD_THROTTLE, FluxMap.STANDARD_TIMEOUT); + } + @RequestMapping("/redirectToURL") @ResponseBody private String redirectToURL(HttpServletResponse response, String url) throws IOException, ServletException { @@ -267,7 +291,6 @@ /** * Produces necessary data for learner progress bar. */ - @SuppressWarnings("unchecked") @RequestMapping("/getLearnerProgress") @ResponseBody public String getLearnerProgress(HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -284,7 +307,6 @@ monitorId = userId; } - ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); Long lessonId = WebUtil.readLongParam(request, AttributeNames.PARAM_LESSON_ID, true); if (lessonId == null) { // depending on when this is called, there may only be a toolSessionId known, not the lessonId. @@ -293,11 +315,36 @@ lessonId = toolSession.getLesson().getLessonId(); } + ObjectNode responseJSON = getLearnerProgress(lessonId, learnerId, monitorId != null); + if (responseJSON == null) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + return null; + } + responseJSON.set("messages", getProgressBarMessages()); + response.setContentType("application/json;charset=utf-8"); + return responseJSON.toString(); + } + + /** + * Produces necessary data for learner progress bar. + */ + + @RequestMapping("/getLearnerProgressUpdateFlux") + @ResponseBody + public Flux getLearnerProgressUpdateFlux(@RequestParam long lessonId, @RequestParam int userId) + throws JsonProcessingException, IOException { + return FluxRegistry.get(LEARNER_TIMELINE_FLUX_NAME, + new LearnerActivityCompleteFluxItem(lessonId, userId, null)); + } + + @SuppressWarnings("unchecked") + private ObjectNode getLearnerProgress(long lessonId, int learnerId, boolean monitorMode) throws IOException { + ObjectNode responseJSON = JsonNodeFactory.instance.objectNode(); + Object[] ret = learnerService.getStructuredActivityURLs(learnerId, lessonId); if (ret == null) { - response.setStatus(HttpServletResponse.SC_NO_CONTENT); return null; } @@ -308,17 +355,14 @@ // these are support activities for (ActivityURL childActivity : activity.getChildActivities()) { responseJSON.withArray("support") - .add(activityProgressToJSON(childActivity, null, lessonId, learnerId, monitorId)); + .add(activityProgressToJSON(childActivity, null, lessonId, learnerId, monitorMode)); } } else { responseJSON.withArray("activities") - .add(activityProgressToJSON(activity, (Long) ret[1], lessonId, learnerId, monitorId)); + .add(activityProgressToJSON(activity, (Long) ret[1], lessonId, learnerId, monitorMode)); } } - - response.setContentType("application/json;charset=utf-8"); - - return responseJSON.toString(); + return responseJSON; } @RequestMapping("/getPresenceChatActiveUserCount") @@ -375,7 +419,7 @@ * Converts an activity in learner progress to a JSON object. */ private ObjectNode activityProgressToJSON(ActivityURL activity, Long currentActivityId, Long lessonId, - Integer learnerId, Integer monitorId) throws IOException { + Integer learnerId, boolean monitorMode) throws IOException { ObjectNode activityJSON = JsonNodeFactory.instance.objectNode(); activityJSON.put("id", activity.getActivityId()); activityJSON.put("name", activity.getTitle()); @@ -384,7 +428,7 @@ // URL in learner mode String url = activity.getUrl(); - if ((url != null) && (monitorId != null)) { + if ((url != null) && monitorMode) { // URL in monitor mode url = Configuration.get(ConfigurationKeys.SERVER_URL) + "monitoring/monitoring/getLearnerActivityURL.do?lessonID=" + lessonId + "&activityID=" @@ -438,8 +482,8 @@ if (activity.getChildActivities() != null) { for (ActivityURL childActivity : activity.getChildActivities()) { - activityJSON.withArray("childActivities") - .add(activityProgressToJSON(childActivity, currentActivityId, lessonId, learnerId, monitorId)); + activityJSON.withArray("childActivities").add( + activityProgressToJSON(childActivity, currentActivityId, lessonId, learnerId, monitorMode)); } } Index: lams_learning/web/WEB-INF/spring-servlet.xml =================================================================== diff -u -r7ff6d4b34cd71ac45741cb7c8d0c3ef6909eadf4 -r20e62542c73330b5ccd425dc727597660abdd129 --- lams_learning/web/WEB-INF/spring-servlet.xml (.../spring-servlet.xml) (revision 7ff6d4b34cd71ac45741cb7c8d0c3ef6909eadf4) +++ lams_learning/web/WEB-INF/spring-servlet.xml (.../spring-servlet.xml) (revision 20e62542c73330b5ccd425dc727597660abdd129) @@ -11,8 +11,10 @@ http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> - + + Index: lams_learning/web/WEB-INF/web.xml =================================================================== diff -u -r9986a8084b318ccc9c025ec56407dc09a705dd60 -r20e62542c73330b5ccd425dc727597660abdd129 --- lams_learning/web/WEB-INF/web.xml (.../web.xml) (revision 9986a8084b318ccc9c025ec56407dc09a705dd60) +++ lams_learning/web/WEB-INF/web.xml (.../web.xml) (revision 20e62542c73330b5ccd425dc727597660abdd129) @@ -30,42 +30,52 @@ singleSession true + true SystemSessionFilter org.lamsfoundation.lams.web.session.SystemSessionFilter + true LocaleFilter org.lamsfoundation.lams.web.filter.LocaleFilter + true HibernateFilter /* + REQUEST + ASYNC SystemSessionFilter /* + REQUEST + ASYNC LocaleFilter /* + REQUEST + ASYNC spring org.springframework.web.servlet.DispatcherServlet + true 1 @@ -75,13 +85,15 @@ org.lamsfoundation.lams.learning.web.servlet.RepopulateProgressMarksServlet + true LogLessonMarkPushedToIntegrationsServlet org.lamsfoundation.lams.learning.web.servlet.LogLessonMarkPushedToIntegrationsServlet + true Index: lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java =================================================================== diff -u -r3e58e9a9a8b4b7f7d94ad65d64376f091d7502dc -r20e62542c73330b5ccd425dc727597660abdd129 --- lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java (.../MonitoringController.java) (revision 3e58e9a9a8b4b7f7d94ad65d64376f091d7502dc) +++ lams_monitoring/src/java/org/lamsfoundation/lams/monitoring/web/MonitoringController.java (.../MonitoringController.java) (revision 20e62542c73330b5ccd425dc727597660abdd129) @@ -42,7 +42,6 @@ import java.util.Set; import java.util.TreeSet; import java.util.Vector; -import java.util.function.Function; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -132,8 +131,6 @@ private static final int LATEST_LEARNER_PROGRESS_ACTIVITY_DISPLAY_LIMIT = 7; private static final int USER_PAGE_SIZE = 10; - private static final int CANVAS_REFRESH_FLUX_THROTTLE = 10; - @Autowired private ILogEventService logEventService; @Autowired @@ -165,8 +162,8 @@ lessonJoinedFluxItem -> ((LearnerLessonJoinFluxItem) lessonJoinedFluxItem).getLessonId()); FluxRegistry.initFluxMap(MonitoringConstants.CANVAS_REFRESH_FLUX_NAME, - CommonConstants.LESSON_PROGRESSED_SINK_NAME, (Function) lessonId -> "doRefresh", - CANVAS_REFRESH_FLUX_THROTTLE, FluxMap.STANDARD_TIMEOUT); + CommonConstants.LESSON_PROGRESSED_SINK_NAME, null, lessonId -> "doRefresh", FluxMap.STANDARD_THROTTLE, + FluxMap.STANDARD_TIMEOUT); } private Integer getUserId() { Index: lams_monitoring/web/includes/javascript/monitorLesson5.js =================================================================== diff -u -r5576f5aee54bc8139b65159f25a90fd2470a1cec -r20e62542c73330b5ccd425dc727597660abdd129 --- lams_monitoring/web/includes/javascript/monitorLesson5.js (.../monitorLesson5.js) (revision 5576f5aee54bc8139b65159f25a90fd2470a1cec) +++ lams_monitoring/web/includes/javascript/monitorLesson5.js (.../monitorLesson5.js) (revision 20e62542c73330b5ccd425dc727597660abdd129) @@ -2388,8 +2388,7 @@ */ function updateLearnersTab(){ let learnersAccordion = $('#learners-accordion').empty(), - itemTemplate = $('.learners-accordion-item-template').clone().removeClass('learners-accordion-item-template d-none'), - activityEntryTemplate = $('.learners-timeline-entry-template').clone().removeClass('learners-timeline-entry-template d-none'); + itemTemplate = $('.learners-accordion-item-template').clone().removeClass('learners-accordion-item-template d-none'); $.ajax({ 'url' : LAMS_URL + 'monitoring/monitoring/getLearnerProgressPage.do', @@ -2398,14 +2397,16 @@ }, 'dataType' : 'json', 'success' : function(response) { - + let learnerProgressSource = null; + $(response.learners).each(function(){ let learner = this, itemHeaderId = 'learners-accordion-heading-' + learner.id, itemCollapseId = 'learners-accordion-collapse-' + learner.id, - item = itemTemplate.clone().data('user-id', learner.id).appendTo(learnersAccordion), + item = itemTemplate.clone().data('user-id', learner.id).attr('id', 'learners-accordion-item-' + learner.id).appendTo(learnersAccordion), portraitSmall = $(definePortrait(learner.portraitId, learner.id, STYLE_SMALL, true, LAMS_URL)).addClass('me-2'), - portraitLarge = learner.portraitId ? $(definePortrait(learner.portraitId, learner.id, STYLE_LARGE, false, LAMS_URL)) : null; + portraitLarge = learner.portraitId ? $(definePortrait(learner.portraitId, learner.id, STYLE_LARGE, false, LAMS_URL)) : null + $('.accordion-header', item).attr('id', itemHeaderId) .find('.accordion-button').attr('data-bs-target', '#' + itemCollapseId).attr('aria-controls', itemCollapseId) .html('' + learner.firstName + ' ' + learner.lastName + '') @@ -2419,76 +2420,86 @@ $('.accordion-collapse', item).attr('id', itemCollapseId).attr('data-bs-parent', '#learners-accordion') .on('show.bs.collapse', function () { - let learnerId = $(this).closest('.accordion-item').data('user-id'), - timelineContainer = $('.vertical-timeline-container', this), - timeline = $('.vertical-timeline', timelineContainer).empty(), - noProgressLabel = $('.no-progress', this); - - $.ajax({ - 'url' : LAMS_URL + 'learning/learner/getLearnerProgress.do', - 'data': { - lessonID: lessonId, - userID : learnerId - }, - 'dataType' : 'json', - 'success' : function(response) { - if (!response) { - noProgressLabel.show(); - return; - } - noProgressLabel.hide(); - - $(response.activities).each(function(){ - let activity = this, - entry = activityEntryTemplate.clone().appendTo(timeline), - icon = $('.timeline-icon', entry), - iconURL = null, - durationCell = $('.timeline-activity-duration', entry), - markCell = $('.timeline-activity-mark', entry); - - $('.timeline-title', entry).text(activity.name); - - switch(activity.status){ - case 0: icon.addClass('border-primary activity-current');break; - case 1: icon.addClass('border-success activity-complete');break; - } - - if (activity.iconURL) { - iconURL = activity.iconURL; - } else if (activity.type === 'g') { - iconURL = 'images/svg/gateClosed.svg'; - } else if (activity.type === 'o') { - iconURL = 'images/svg/branchingStart.svg'; - } else if (activity.isGrouping) { - iconURL = 'images/svg/grouping.svg'; - } - - if (iconURL) { - $('').attr('src', LAMS_URL + iconURL).appendTo(icon); - } - - if (typeof activity.mark !== 'undefined') { - markCell.text(activity.mark + (activity.maxMark ? ' / ' + activity.maxMark : '')); - } else { - markCell.closest('tr').remove(); - } - - if (activity.duration) { - durationCell.text(activity.duration); - } else { - durationCell.closest('tr').remove(); - } - }); - - timelineContainer.show(); + if (learnerProgressSource) { + try { + learnerProgressSource.close(); + } catch(e) { + console.error(e); } - }); + } + + let learnerId = $(this).closest('.accordion-item').data('user-id'); + learnerProgressSource = new EventSource(LAMS_URL + 'learning/learner/getLearnerProgressUpdateFlux.do?lessonId=' + + lessonId + '&userId=' + learnerId); + + learnerProgressSource.onmessage = function (event) { + drawLearnerTimeline(learnerId, event.data); + } }); }); } }); } +function drawLearnerTimeline(learnerId, data) { + let item = $('#learners-accordion-item-' + learnerId), + timelineContainer = $('.vertical-timeline-container', item), + timeline = $('.vertical-timeline', timelineContainer).empty(), + noProgressLabel = $('.no-progress', item); + + if (!data) { + noProgressLabel.show(); + return; + } + noProgressLabel.hide(); + data = JSON.parse(data); + let activityEntryTemplate = $('.learners-timeline-entry-template').clone().removeClass('learners-timeline-entry-template d-none'); + + $(data.activities).each(function(){ + let activity = this, + entry = activityEntryTemplate.clone().appendTo(timeline), + icon = $('.timeline-icon', entry), + iconURL = null, + durationCell = $('.timeline-activity-duration', entry), + markCell = $('.timeline-activity-mark', entry); + + $('.timeline-title', entry).text(activity.name); + + switch(activity.status){ + case 0: icon.addClass('border-primary activity-current');break; + case 1: icon.addClass('border-success activity-complete');break; + } + + if (activity.iconURL) { + iconURL = activity.iconURL; + } else if (activity.type === 'g') { + iconURL = 'images/svg/gateClosed.svg'; + } else if (activity.type === 'o') { + iconURL = 'images/svg/branchingStart.svg'; + } else if (activity.isGrouping) { + iconURL = 'images/svg/grouping.svg'; + } + + if (iconURL) { + $('').attr('src', LAMS_URL + iconURL).appendTo(icon); + } + + if (typeof activity.mark !== 'undefined') { + markCell.text(activity.mark + (activity.maxMark ? ' / ' + activity.maxMark : '')); + } else { + markCell.closest('tr').remove(); + } + + if (activity.duration) { + durationCell.text(activity.duration); + } else { + durationCell.closest('tr').remove(); + } + }); + + timelineContainer.show(); +} + /** * Clears previous run search for phrase, in Learners tab. */ Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -rc1e3ca12a9ccb265363e2330dd91ce7bedbcfa35 -r20e62542c73330b5ccd425dc727597660abdd129 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision c1e3ca12a9ccb265363e2330dd91ce7bedbcfa35) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 20e62542c73330b5ccd425dc727597660abdd129) @@ -201,9 +201,9 @@ public AssessmentServiceImpl() { FluxRegistry.initFluxMap(AssessmentConstants.COMPLETION_CHARTS_UPDATE_FLUX_NAME, - AssessmentConstants.ANSWERS_UPDATED_SINK_NAME, - (Function) toolContentId -> getCompletionChartsData(toolContentId), - FluxMap.STANDARD_THROTTLE, FluxMap.STANDARD_TIMEOUT); + AssessmentConstants.ANSWERS_UPDATED_SINK_NAME, null, + (Long toolContentId) -> getCompletionChartsData(toolContentId), FluxMap.SHORT_THROTTLE, + FluxMap.STANDARD_TIMEOUT); } // *******************************************************************************