From 1a61f3da9a3fdc8705c8d0d5da72e9eb2b3540cb Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Tue, 23 Aug 2016 14:45:55 +0200
Subject: [PATCH] OO-1593: rework small statistics panel of assessment tool,
 anonymize selft test archive, rs, wording

---
 .../assessment/AssessmentToolManager.java     |  3 +-
 .../manager/AssessmentToolManagerImpl.java    | 69 +++++++++++------
 ...ssmentCourseStatisticsSmallController.java | 32 +++++---
 .../ui/tool/_content/course_stats_small.html  | 14 +++-
 .../ui/tool/_i18n/LocalStrings_de.properties  |  1 +
 .../ui/tool/_i18n/LocalStrings_en.properties  |  1 +
 .../nodes/iq/IQConfigurationController.java   | 14 +++-
 .../iq/QTI21AssessmentRunController.java      |  2 +-
 .../export/Archive_2_UserSelectionStep.java   | 19 +++++
 .../manager/archive/QTI21ArchiveFormat.java   | 76 +++++++++++++------
 .../qti21/model/InMemoryOutcomeListener.java  | 41 ++++++++++
 .../handlers/QTI21AssessmentTestHandler.java  |  2 +-
 .../ui/AssessmentTestDisplayController.java   |  8 +-
 .../ui/QTI21AssessmentDetailsController.java  |  3 +
 .../ui/QTI21RetrieveTestsToolController.java  |  3 +
 .../QTI21StatisticsSecurityCallback.java      | 19 +++++
 .../model/AssessmentMembersStatistics.java    | 61 +++++++++++++++
 .../AssessmentStatisticsSmallController.java  | 24 ++++--
 .../ui/_content/test_stats_small.html         | 12 ++-
 .../ui/_i18n/LocalStrings_de.properties       |  1 +
 .../ui/_i18n/LocalStrings_en.properties       |  1 +
 21 files changed, 321 insertions(+), 85 deletions(-)
 create mode 100644 src/main/java/org/olat/ims/qti21/model/InMemoryOutcomeListener.java
 create mode 100644 src/main/java/org/olat/modules/assessment/model/AssessmentMembersStatistics.java

diff --git a/src/main/java/org/olat/course/assessment/AssessmentToolManager.java b/src/main/java/org/olat/course/assessment/AssessmentToolManager.java
index 3cd610db434..321223ad2bd 100644
--- a/src/main/java/org/olat/course/assessment/AssessmentToolManager.java
+++ b/src/main/java/org/olat/course/assessment/AssessmentToolManager.java
@@ -28,6 +28,7 @@ import org.olat.course.assessment.model.AssessmentStatistics;
 import org.olat.course.assessment.model.SearchAssessedIdentityParams;
 import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.modules.assessment.model.AssessmentEntryStatus;
+import org.olat.modules.assessment.model.AssessmentMembersStatistics;
 
 /**
  * The manager taylored for the assessment tool.
@@ -59,7 +60,7 @@ public interface AssessmentToolManager {
 	 * @param params
 	 * @return
 	 */
-	public int getNumberOfParticipants(Identity coach, SearchAssessedIdentityParams params);
+	public AssessmentMembersStatistics getNumberOfParticipants(Identity coach, SearchAssessedIdentityParams params);
 	
 	/**
 	 * The number of user who launched the course / resource
diff --git a/src/main/java/org/olat/course/assessment/manager/AssessmentToolManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/AssessmentToolManagerImpl.java
index 57cd72a5e75..0c99e15b13a 100644
--- a/src/main/java/org/olat/course/assessment/manager/AssessmentToolManagerImpl.java
+++ b/src/main/java/org/olat/course/assessment/manager/AssessmentToolManagerImpl.java
@@ -20,7 +20,9 @@
 package org.olat.course.assessment.manager;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import javax.persistence.TypedQuery;
 
@@ -37,6 +39,7 @@ import org.olat.course.assessment.model.AssessmentStatistics;
 import org.olat.course.assessment.model.SearchAssessedIdentityParams;
 import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.modules.assessment.model.AssessmentEntryStatus;
+import org.olat.modules.assessment.model.AssessmentMembersStatistics;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -69,56 +72,74 @@ public class AssessmentToolManagerImpl implements AssessmentToolManager {
 
 
 	@Override
-	public int getNumberOfParticipants(Identity coach, SearchAssessedIdentityParams params) {
+	public AssessmentMembersStatistics getNumberOfParticipants(Identity coach, SearchAssessedIdentityParams params) {
 		RepositoryEntry courseEntry = params.getEntry();
 		
+		int loggedIn = 0;
+		int numOfOtherUsers = 0;
 		int numOfParticipants = 0;
+		int participantLoggedIn = 0;
+		
+		StringBuilder sc = new StringBuilder();
+		sc.append("select infos.identity.key from usercourseinfos as infos ")
+		  .append(" inner join infos.resource as infosResource on (infosResource.key=:resourceKey)");
+		List<Long> allKeys = dbInstance.getCurrentEntityManager()
+			.createQuery(sc.toString(), Long.class)
+			.setParameter("resourceKey", courseEntry.getOlatResource().getKey())
+			.getResultList();
+		
 		if(params.isAdmin()) {
 			StringBuilder sb = new StringBuilder();
-			sb.append("select count(participant.identity.key) from repoentrytogroup as rel")
+			sb.append("select participant.identity.key from repoentrytogroup as rel")
 	          .append("  inner join rel.group as bGroup")
 	          .append("  inner join bGroup.members as participant on (participant.role='").append(GroupRoles.participant.name()).append("')")
 	          .append("  where rel.entry.key=:repoEntryKey)");
 			
-			List<Number> count = dbInstance.getCurrentEntityManager()
-				.createQuery(sb.toString(), Number.class)
+			List<Long> keys = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Long.class)
 				.setParameter("repoEntryKey", courseEntry.getKey())
 				.getResultList();
-			numOfParticipants = count == null || count.isEmpty() || count.get(0) == null ? 0 : count.get(0).intValue();
+			Set<Long> participantKeys = new HashSet<>(keys);
+			numOfParticipants = participantKeys.size();
 			
+			Set<Long> participantLoggedInKeys = new HashSet<>(allKeys);
+			participantLoggedInKeys.retainAll(participantKeys);
+			participantLoggedIn = participantLoggedInKeys.size();
+
 			//count the users which login but are not members of the course
 			if(params.isNonMembers()) {
-				StringBuilder sc = new StringBuilder();
-				sc.append("select count(infos.key) from usercourseinfos as infos ")
-				  .append(" inner join infos.resource as infosResource on (infosResource.key=:resourceKey)")
-				  .append(" where not exists (select membership.identity from repoentrytogroup as rel, bgroupmember as membership")
-		          .append("   where rel.entry.key=:repoEntryKey and rel.group.key=membership.group.key and membership.identity.key=infos.identity.key")
-		          .append("  )");
-				
-				List<Number> countAlt = dbInstance.getCurrentEntityManager()
-					.createQuery(sc.toString(), Number.class)
-					.setParameter("repoEntryKey", courseEntry.getKey())
-					.setParameter("resourceKey", courseEntry.getOlatResource().getKey())
-					.getResultList();
-				numOfParticipants += countAlt == null || countAlt.isEmpty() || countAlt.get(0) == null ? 0 : countAlt.get(0).intValue();
+				Set<Long> allLoggedInKeys = new HashSet<>(allKeys);
+				allLoggedInKeys.removeAll(participantLoggedInKeys);
+				loggedIn = allLoggedInKeys.size();
+				allLoggedInKeys.removeAll(participantKeys);
+				numOfOtherUsers = allLoggedInKeys.size();
+			} else {
+				loggedIn = participantLoggedIn;
 			}
-			
 		} else if(params.isBusinessGroupCoach() || params.isRepositoryEntryCoach()) {
 			StringBuilder sb = new StringBuilder();
-			sb.append("select count(participant.identity.key) from repoentrytogroup as rel")
+			sb.append("select participant.identity.key from repoentrytogroup as rel")
 	          .append("  inner join rel.group as bGroup")
 	          .append("  inner join bGroup.members as coach on (coach.identity.key=:identityKey and coach.role='").append(GroupRoles.coach.name()).append("')")
 	          .append("  inner join bGroup.members as participant on (participant.role='").append(GroupRoles.participant.name()).append("')")
 	          .append("  where rel.entry.key=:repoEntryKey)");
 			
-			List<Number> count = dbInstance.getCurrentEntityManager()
-				.createQuery(sb.toString(), Number.class)
+			List<Long> keys = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Long.class)
 				.setParameter("identityKey", coach.getKey())
 				.setParameter("repoEntryKey", courseEntry.getKey())
 				.getResultList();
-			numOfParticipants = count == null || count.isEmpty() || count.get(0) == null ? 0 : count.get(0).intValue();
+
+			Set<Long> participantKeys = new HashSet<>(keys);
+			numOfParticipants = participantKeys.size();
+			
+			Set<Long> participantLoggedInKeys = new HashSet<>(allKeys);
+			participantLoggedInKeys.retainAll(participantKeys);
+			participantLoggedIn = participantLoggedInKeys.size();
+
+			loggedIn = participantLoggedIn;
 		}
-		return numOfParticipants;
+		return new AssessmentMembersStatistics(numOfParticipants, participantLoggedIn, numOfOtherUsers, loggedIn);
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseStatisticsSmallController.java b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseStatisticsSmallController.java
index ea7e34e0f47..c53ccf8d03f 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseStatisticsSmallController.java
+++ b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseStatisticsSmallController.java
@@ -31,6 +31,7 @@ import org.olat.course.assessment.AssessmentHelper;
 import org.olat.course.assessment.AssessmentToolManager;
 import org.olat.course.assessment.model.AssessmentStatistics;
 import org.olat.course.assessment.model.SearchAssessedIdentityParams;
+import org.olat.modules.assessment.model.AssessmentMembersStatistics;
 import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -50,8 +51,8 @@ public class AssessmentCourseStatisticsSmallController extends BasicController {
 	
 	private int numOfPassed;
 	private int numOfFailed;
-	private int numOfParticipants;
 	private int numOfAssessedIdentities;
+	private AssessmentMembersStatistics memberStatistics;
 	
 	@Autowired
 	private AssessmentToolManager assessmentToolManager;
@@ -79,8 +80,8 @@ public class AssessmentCourseStatisticsSmallController extends BasicController {
 		return numOfAssessedIdentities;
 	}
 	
-	public int getNumOfParticipants() {
-		return numOfParticipants;
+	public AssessmentMembersStatistics getMemberStatistics() {
+		return memberStatistics;
 	}
 	
 	public void updateStatistics() {
@@ -91,24 +92,33 @@ public class AssessmentCourseStatisticsSmallController extends BasicController {
 		numOfAssessedIdentities = assessmentToolManager.getNumberOfAssessedIdentities(getIdentity(), params);
 		mainVC.contextPut("numOfAssessedIdentities", numOfAssessedIdentities);
 		
-		numOfParticipants = assessmentToolManager.getNumberOfParticipants(getIdentity(), params);
-		mainVC.contextPut("numOfParticipants", numOfParticipants);
+		memberStatistics = assessmentToolManager.getNumberOfParticipants(getIdentity(), params);
+		mainVC.contextPut("numOfParticipants", memberStatistics.getNumOfParticipants());
+		if(assessmentCallback.canAssessNonMembers()) {
+			mainVC.contextPut("numOfOtherUsers", memberStatistics.getNumOfOtherUsers());
+		}
 		
 		AssessmentStatistics stats = assessmentToolManager.getStatistics(getIdentity(), params);
 		mainVC.contextPut("scoreAverage", AssessmentHelper.getRoundedScore(stats.getAverageScore()));
 		numOfPassed = stats.getCountPassed();
 		mainVC.contextPut("numOfPassed", numOfPassed);
-		int percentPassed = numOfParticipants == 0 ? 0 :
-				Math.round(100.0f * (stats.getCountPassed() / numOfParticipants));
+		
+		int total = memberStatistics.getTotal();
+		int percentPassed = total == 0 ? 0 :
+				Math.round(100.0f * (stats.getCountPassed() / total));
 		mainVC.contextPut("percentPassed", percentPassed);
 		numOfFailed = stats.getCountFailed();
 		mainVC.contextPut("numOfFailed", numOfFailed);
-		int percentFailed = numOfParticipants == 0 ? 0 :
-				Math.round(100.0f * (stats.getCountFailed() / numOfParticipants));
+		int percentFailed = total == 0 ? 0 :
+				Math.round(100.0f * (stats.getCountFailed() / total));
 		mainVC.contextPut("percentFailed", percentFailed);
 		
-		int numOfLaunches = assessmentToolManager.getNumberOfInitialLaunches(getIdentity(), params);
-		mainVC.contextPut("numOfInitialLaunch", numOfLaunches);
+		int numOfParticipantLaunches = memberStatistics.getNumOfParticipantsLoggedIn();
+		mainVC.contextPut("numOfParticipantLaunches", numOfParticipantLaunches);
+		if(assessmentCallback.canAssessNonMembers()) {
+			int numOfOtherUserLaunches = memberStatistics.getLoggedIn();
+			mainVC.contextPut("numOfOtherUserLaunches", numOfOtherUserLaunches);
+		}
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/_content/course_stats_small.html b/src/main/java/org/olat/course/assessment/ui/tool/_content/course_stats_small.html
index 2f044dd1b81..a1e23d2d87e 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/_content/course_stats_small.html
+++ b/src/main/java/org/olat/course/assessment/ui/tool/_content/course_stats_small.html
@@ -7,17 +7,23 @@
 			<th>$r.translate("table.header.numOfParticipants")</th>
 			<td>$numOfParticipants</td>
 		</tr>
+		#if($r.isNotNull($numOfOtherUsers))
+		<tr>
+			<th>$r.translate("table.header.numOfOtherUsers")</th>
+			<td>$numOfOtherUsers</td>
+		</tr>
+		#end
 		<tr>
 			<th>$r.translate("table.header.numOfInitialLaunch")</th>
 			<td>
-			#if($numOfInitialLaunch && $numOfInitialLaunch == 0)
-				<i class="o_icon o_green_led"> </i>
-			#elseif($numOfInitialLaunch && $numOfInitialLaunch == $numOfParticipants)
+			#if($r.isNotNull($numOfParticipantLaunches) && $numOfParticipantLaunches == 0)
 				<i class="o_icon o_red_led"> </i>
+			#elseif($r.isNotNull($numOfParticipantLaunches) && $numOfParticipantLaunches == $numOfParticipants)
+				<i class="o_icon o_green_led"> </i>
 			#else
 				<i class="o_icon o_yellow_led"> </i>
 			#end
-			$numOfInitialLaunch</td>
+			$numOfParticipantLaunches #if($r.isNotNull($numOfOtherUserLaunches))/ $numOfOtherUserLaunches#end</td>
 		</tr>
 		<tr>
 			<th>$r.translate("table.header.scoreAverage")</th>
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_de.properties
index 9152de27ffd..c4373f11906 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_de.properties
@@ -45,6 +45,7 @@ table.header.numOfAssessedIdentities=$org.olat.modules.assessment.ui\:table.head
 table.header.numOfInitialLaunch=$org.olat.modules.assessment.ui\:table.header.numOfInitialLaunch
 table.header.numOfParticipants=$org.olat.modules.assessment.ui\:table.header.numOfParticipants
 table.header.numOfPassed=$org.olat.modules.assessment.ui\:table.header.numOfPassed
+table.header.numOfOtherUsers=# andere Benutzer
 table.header.passed=Bestanden
 table.header.scoreAverage=$org.olat.modules.assessment.ui\:table.header.scoreAverage
 tooltip.of={0} von {1}
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_en.properties
index 14425cadf6c..1569c6c11b1 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_en.properties
@@ -45,6 +45,7 @@ table.header.numOfAssessedIdentities=$org.olat.modules.assessment.ui\:table.head
 table.header.numOfInitialLaunch=$org.olat.modules.assessment.ui\:table.header.numOfInitialLaunch
 table.header.numOfParticipants=$org.olat.modules.assessment.ui\:table.header.numOfParticipants
 table.header.numOfPassed=$org.olat.modules.assessment.ui\:table.header.numOfPassed
+table.header.numOfOtherUsers=# other users
 table.header.passed=Passed
 table.header.scoreAverage=$org.olat.modules.assessment.ui\:table.header.scoreAverage
 tooltip.of={0} of {1}
diff --git a/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java b/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java
index 01cb2f0f580..6242f5f49ce 100644
--- a/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java
+++ b/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java
@@ -376,11 +376,19 @@ public class IQConfigurationController extends BasicController {
 		
 		RepositoryEntry re = getIQReference(moduleConfiguration, false);
 		if(re != null && !OnyxModule.isOnyxTest(re.getOlatResource())) {
+			Controller previewController;
 			if(ImsQTI21Resource.TYPE_NAME.equals(re.getOlatResource().getResourceableTypeName())) {
-				//TODO
+				//TODO qti
+				/* need to clean up the assessment test session
+				QTI21DeliveryOptions deliveryOptions = qti21service.getDeliveryOptions(re);
+				RepositoryEntry courseEntry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+				previewController = new AssessmentTestDisplayController(ureq, getWindowControl(), new InMemoryOutcomeListener(),
+						re, courseEntry, courseNode.getIdent(),
+						deliveryOptions, true, true, true);
+				*/
 			} else {
 				long courseResId = course.getResourceableId().longValue();
-				Controller previewController = iqManager.createIQDisplayController(moduleConfiguration, new IQPreviewSecurityCallback(), ureq, getWindowControl(), courseResId, courseNode.getIdent(), null);
+				previewController = iqManager.createIQDisplayController(moduleConfiguration, new IQPreviewSecurityCallback(), ureq, getWindowControl(), courseResId, courseNode.getIdent(), null);
 				previewLayoutCtr = new LayoutMain3ColsController(ureq, getWindowControl(), previewController);
 				stackPanel.pushController(translate("preview"), previewLayoutCtr);
 			}
@@ -407,7 +415,7 @@ public class IQConfigurationController extends BasicController {
 		removeAsListenerAndDispose(cmc);
 		removeAsListenerAndDispose(searchController);
 		
-		String[] types = new String[]{ TestFileResource.TYPE_NAME };
+		String[] types = new String[]{ TestFileResource.TYPE_NAME, ImsQTI21Resource.TYPE_NAME };
 		searchController = new ReferencableEntriesSearchController(getWindowControl(), ureq, types, translate("command.chooseTest"));
 		listenTo(searchController);
 		
diff --git a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
index 9f58c130167..056b7ef967f 100644
--- a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
+++ b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
@@ -298,7 +298,7 @@ public class QTI21AssessmentRunController extends BasicController implements Gen
 		WindowControl bwControl = addToHistory(ureq, ores, null);
 		
 		RepositoryEntry courseRe = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
-		displayCtrl = new AssessmentTestDisplayController(ureq, bwControl, this, testEntry, courseRe, courseNode.getIdent(), deliveryOptions, true, false);
+		displayCtrl = new AssessmentTestDisplayController(ureq, bwControl, this, testEntry, courseRe, courseNode.getIdent(), deliveryOptions, true, false, false);
 		listenTo(displayCtrl);
 		if(displayCtrl.isTerminated()) {
 			//do nothing
diff --git a/src/main/java/org/olat/ims/qti/export/Archive_2_UserSelectionStep.java b/src/main/java/org/olat/ims/qti/export/Archive_2_UserSelectionStep.java
index 65483709267..77e2ef49d85 100644
--- a/src/main/java/org/olat/ims/qti/export/Archive_2_UserSelectionStep.java
+++ b/src/main/java/org/olat/ims/qti/export/Archive_2_UserSelectionStep.java
@@ -1,3 +1,22 @@
+/**
+ * <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.ims.qti.export;
 
 import org.olat.core.gui.UserRequest;
diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java b/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java
index 772beff1a8a..0d72f4521f8 100644
--- a/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java
+++ b/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java
@@ -51,10 +51,12 @@ import org.olat.core.util.openxml.OpenXMLWorksheet;
 import org.olat.core.util.openxml.OpenXMLWorksheet.Row;
 import org.olat.core.util.openxml.workbookstyle.CellStyle;
 import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
 import org.olat.course.nodes.CourseNode;
 import org.olat.fileresource.FileResourceManager;
 import org.olat.ims.qti.export.QTIArchiver;
 import org.olat.ims.qti.export.QTIExportFormatter;
+import org.olat.ims.qti.export.helper.IdentityAnonymizerCallback;
 import org.olat.ims.qti21.AssessmentItemSession;
 import org.olat.ims.qti21.AssessmentResponse;
 import org.olat.ims.qti21.AssessmentTestSession;
@@ -129,6 +131,7 @@ public class QTI21ArchiveFormat {
 	
 	private ResolvedAssessmentTest resolvedAssessmentTest;
 	private List<UserPropertyHandler> userPropertyHandlers;
+	private IdentityAnonymizerCallback anonymizerCallback;
 
 	private boolean participants;
 	private boolean allUsers;
@@ -193,12 +196,17 @@ public class QTI21ArchiveFormat {
 		File unzippedDirRoot = frm.unzipFileResource(testEntry.getOlatResource());
 		resolvedAssessmentTest = qtiService.loadAndResolveAssessmentTest(unzippedDirRoot, false);
 		
-		CourseNode courseNode = CourseFactory.loadCourse(courseEntry).getRunStructure().getNode(subIdent);
+		ICourse course = CourseFactory.loadCourse(courseEntry);
+		CourseNode courseNode = course.getRunStructure().getNode(subIdent);
 		String label = courseNode.getType() + "_"
 				+ StringHelper.transformDisplayNameToFileSystemName(courseNode.getShortName())
 				+ "_" + Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis()))
 				+ ".xlsx";
 		
+		if("iqself".equals(courseNode.getType())) {
+			anonymizerCallback = course.getCourseEnvironment().getCoursePropertyManager();
+		}
+		
 		export(courseEntry, subIdent, testEntry, label, exportStream);
 	}
 	
@@ -213,7 +221,7 @@ public class QTI21ArchiveFormat {
 		export(null, null, testEntry, archiveName, exportStream);
 	}
 	
-	private void export(RepositoryEntry courseEntry, String subIdent, RepositoryEntry testEntry, String filename, ZipOutputStream exportStream) {
+	private void export(RepositoryEntry courseEntry, String subIdent, RepositoryEntry testEntry, String filename,  ZipOutputStream exportStream) {
 		//content
 		final List<AssessmentResponse> responses = responseDao.getResponse(courseEntry, subIdent, testEntry, participants, allUsers, anonymUsers);
 		try {
@@ -240,12 +248,17 @@ public class QTI21ArchiveFormat {
 		File unzippedDirRoot = frm.unzipFileResource(testEntry.getOlatResource());
 		resolvedAssessmentTest = qtiService.loadAndResolveAssessmentTest(unzippedDirRoot, false);
 		
-		CourseNode courseNode = CourseFactory.loadCourse(courseEntry).getRunStructure().getNode(subIdent);
+		ICourse course = CourseFactory.loadCourse(courseEntry);
+		CourseNode courseNode = course.getRunStructure().getNode(subIdent);
 		String label = courseNode.getType() + "_"
 				+ StringHelper.transformDisplayNameToFileSystemName(courseNode.getShortName())
 				+ "_" + Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis()))
 				+ ".xlsx";
 		
+		if("iqself".equals(courseNode.getType())) {
+			anonymizerCallback = course.getCourseEnvironment().getCoursePropertyManager();
+		}
+		
 		//content
 		final List<AssessmentResponse> responses = responseDao.getResponse(courseEntry, subIdent, testEntry, participants, allUsers, anonymUsers);
 
@@ -271,12 +284,16 @@ public class QTI21ArchiveFormat {
 		//first header
 		Row header1Row = exportSheet.newRow();
 		int col = 1;
-		for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
-			if (userPropertyHandler != null) {
-				col++;
+		if(anonymizerCallback != null) {
+			col += 5;// anonymized name -> test duration
+		} else {
+			for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
+				if (userPropertyHandler != null) {
+					col++;
+				}
 			}
+			col += 5;// homepage -> test duration
 		}
-		col += 5;// homepage -> test duration
 		
 		List<ItemInfos> infos = getItemInfos();
 		for(int i=0; i<infos.size(); i++) {
@@ -298,20 +315,24 @@ public class QTI21ArchiveFormat {
 		Row header2Row = exportSheet.newRow();
 		String sequentialNumber = translator.translate("column.header.seqnum");
 		header2Row.addCell(col++, sequentialNumber, headerStyle);
-	
-		for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
-			if (userPropertyHandler == null) {
-				continue;
+
+		if(anonymizerCallback != null) {
+			col++;
+		} else {
+			for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
+				if (userPropertyHandler == null) {
+					continue;
+				}
+				String header = translator.translate(userPropertyHandler.i18nFormElementLabelKey());
+				header2Row.addCell(col++, header, headerStyle);			
 			}
-			String header = translator.translate(userPropertyHandler.i18nFormElementLabelKey());
-			header2Row.addCell(col++, header, headerStyle);			
+			
+			// add other user and session information
+			header2Row.addCell(col++, translator.translate("column.header.homepage"), headerStyle);
 		}
 		
-		// add other user and session information
-		header2Row.addCell(col++, translator.translate("column.header.homepage"), headerStyle);
 		header2Row.addCell(col++, translator.translate("column.header.assesspoints"), headerStyle);
 		header2Row.addCell(col++, translator.translate("column.header.passed"), headerStyle);
-		//header2Row.addCell(col++, translator.translate("column.header.ipaddress"), headerStyle);
 		header2Row.addCell(col++, translator.translate("column.header.date"), headerStyle);
 		header2Row.addCell(col++, translator.translate("column.header.duration"), headerStyle);
 
@@ -370,6 +391,9 @@ public class QTI21ArchiveFormat {
 					}	
 				}	
 			}
+		} else if(anonymizerCallback != null) {
+			String anonymizedName = anonymizerCallback.getAnonymizedUserName(entry.getIdentity());
+			dataRow.addCell(col++, anonymizedName, null);
 		} else {
 			User assessedUser = entry.getIdentity().getUser();
 			for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
@@ -380,16 +404,19 @@ public class QTI21ArchiveFormat {
 			}
 		}
 		
-		//homepage, assesspoints, passed, ipaddress, date, duration
-		String homepage;
-		if(entry.getIdentity() == null) {
-			homepage = "";
-		} else {
-			ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(entry.getIdentity());
-			homepage = BusinessControlFactory.getInstance().getAsURIString(Collections.singletonList(ce), false);
+		//homepage
+		if(anonymizerCallback == null) {
+			String homepage;
+			if(entry.getIdentity() == null) {
+				homepage = "";
+			} else {
+				ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(entry.getIdentity());
+				homepage = BusinessControlFactory.getInstance().getAsURIString(Collections.singletonList(ce), false);
+			}
+			dataRow.addCell(col++, homepage, null);
 		}
 		
-		dataRow.addCell(col++, homepage, null);
+		//assesspoints, passed, ipaddress, date, duration
 		if(testSession.getScore() != null) {
 			dataRow.addCell(col++, testSession.getScore(), null);
 		} else {
@@ -400,7 +427,6 @@ public class QTI21ArchiveFormat {
 		} else {
 			col++;
 		}
-		//dataRow.addCell(col++, "0.0.0.1", null);
 		dataRow.addCell(col++, testSession.getCreationDate(), workbook.getStyles().getDateStyle());
 		dataRow.addCell(col++, toDurationInMilliseconds(testSession.getDuration()), null);
 
diff --git a/src/main/java/org/olat/ims/qti21/model/InMemoryOutcomeListener.java b/src/main/java/org/olat/ims/qti21/model/InMemoryOutcomeListener.java
new file mode 100644
index 00000000000..a5b7cab32cb
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/model/InMemoryOutcomeListener.java
@@ -0,0 +1,41 @@
+/**
+ * <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.ims.qti21.model;
+
+import org.olat.ims.qti21.OutcomesListener;
+
+/**
+ * 
+ * Initial date: 23.08.2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class InMemoryOutcomeListener implements OutcomesListener {
+
+	@Override
+	public void updateOutcomes(Float score, Boolean pass) {
+		//
+	}
+
+	@Override
+	public void submit(Float score, Boolean pass, Long assessmentId) {
+		//
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java
index 2a6e7697f17..f24b2d95141 100644
--- a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java
+++ b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java
@@ -359,7 +359,7 @@ public class QTI21AssessmentTestHandler extends FileHandler {
 						boolean authorMode = reSecurity.isEntryAdmin();
 						CoreSpringFactory.getImpl(UserCourseInformationsManager.class)
 							.updateUserCourseInformations(entry.getOlatResource(), uureq.getIdentity());
-						return new AssessmentTestDisplayController(uureq, wwControl, null, entry, entry, null, options, false, authorMode);
+						return new AssessmentTestDisplayController(uureq, wwControl, null, entry, entry, null, options, false, authorMode, false);
 					}
 				});
 	}
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 c0411bfe803..ca35367be3e 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
@@ -197,7 +197,7 @@ public class AssessmentTestDisplayController extends BasicController implements
 	 */
 	public AssessmentTestDisplayController(UserRequest ureq, WindowControl wControl, OutcomesListener listener,
 			RepositoryEntry testEntry, RepositoryEntry entry, String subIdent, QTI21DeliveryOptions deliveryOptions,
-			boolean showCloseResults, boolean authorMode) {
+			boolean showCloseResults, boolean authorMode, boolean anonym) {
 		super(ureq, wControl);
 
 		this.entry = entry;
@@ -208,12 +208,12 @@ public class AssessmentTestDisplayController extends BasicController implements
 		this.showCloseResults = showCloseResults;
 		
 		UserSession usess = ureq.getUserSession();
-		if(usess.getRoles().isGuestOnly()) {
-			anonym = true;
+		if(usess.getRoles().isGuestOnly() || anonym) {
+			this.anonym = anonym;
 			assessedIdentity = null;
 			anonymousIdentifier = getAnonymousIdentifier(usess);
 		} else {
-			anonym = false;
+			this.anonym = anonym;
 			assessedIdentity = getIdentity();
 			anonymousIdentifier = null;
 		}
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
index 85202d5b450..96b1b920093 100644
--- a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
@@ -265,6 +265,9 @@ public class QTI21AssessmentDetailsController extends FormBasicController {
 	}
 	
 	private void doPullSession(AssessmentTestSession session) {
+		if(session.getFinishTime() == null) {
+			session.setFinishTime(new Date());
+		}
 		session.setTerminationTime(new Date());
 		session = qtiService.updateAssessmentTestSession(session);
 		dbInstance.commit();//make sure that the changes committed before sending the event
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsToolController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsToolController.java
index 340b6839159..c6d6bae4b04 100644
--- a/src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsToolController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsToolController.java
@@ -161,6 +161,9 @@ public class QTI21RetrieveTestsToolController extends BasicController implements
 	}
 
 	private void doRetrieveTest(AssessmentTestSession session) {
+		if(session.getFinishTime() == null) {
+			session.setFinishTime(new Date());
+		}
 		session.setTerminationTime(new Date());
 		session = qtiService.updateAssessmentTestSession(session);
 		dbInstance.commit();//make sure that the changes committed before sending the event
diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticsSecurityCallback.java b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticsSecurityCallback.java
index 31a05ab29a5..332ecadcd4b 100644
--- a/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticsSecurityCallback.java
+++ b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticsSecurityCallback.java
@@ -1,3 +1,22 @@
+/**
+ * <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.ims.qti21.ui.statistics;
 
 /**
diff --git a/src/main/java/org/olat/modules/assessment/model/AssessmentMembersStatistics.java b/src/main/java/org/olat/modules/assessment/model/AssessmentMembersStatistics.java
new file mode 100644
index 00000000000..035413ea65a
--- /dev/null
+++ b/src/main/java/org/olat/modules/assessment/model/AssessmentMembersStatistics.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.modules.assessment.model;
+
+/**
+ * 
+ * Initial date: 23.08.2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class AssessmentMembersStatistics {
+	
+	private final int numOfParticipants;
+	private final int numOfOtherUsers;
+	private final int loggedIn;
+	private final int numOfParticipantsLoggedIn; 
+	
+	public AssessmentMembersStatistics(int numOfParticipants, int numOfParticipantsLoggedIn, int numOfOtherUsers, int loggedIn) {
+		this.numOfParticipants = numOfParticipants;
+		this.numOfParticipantsLoggedIn = numOfParticipantsLoggedIn;
+		this.numOfOtherUsers = numOfOtherUsers;
+		this.loggedIn = loggedIn;
+	}
+
+	public int getNumOfParticipants() {
+		return numOfParticipants;
+	}
+
+	public int getNumOfOtherUsers() {
+		return numOfOtherUsers;
+	}
+	
+	public int getLoggedIn() {
+		return loggedIn;
+	}
+
+	public int getNumOfParticipantsLoggedIn() {
+		return numOfParticipantsLoggedIn;
+	}
+
+	public int getTotal() {
+		return numOfOtherUsers + numOfParticipants;
+	}
+}
diff --git a/src/main/java/org/olat/modules/assessment/ui/AssessmentStatisticsSmallController.java b/src/main/java/org/olat/modules/assessment/ui/AssessmentStatisticsSmallController.java
index ca344da089e..e4f47cd73e9 100644
--- a/src/main/java/org/olat/modules/assessment/ui/AssessmentStatisticsSmallController.java
+++ b/src/main/java/org/olat/modules/assessment/ui/AssessmentStatisticsSmallController.java
@@ -29,7 +29,7 @@ import org.olat.course.assessment.AssessmentHelper;
 import org.olat.course.assessment.AssessmentToolManager;
 import org.olat.course.assessment.model.AssessmentStatistics;
 import org.olat.course.assessment.model.SearchAssessedIdentityParams;
-import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
+import org.olat.modules.assessment.model.AssessmentMembersStatistics;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -48,8 +48,8 @@ public class AssessmentStatisticsSmallController extends BasicController {
 	
 	private int numOfPassed;
 	private int numOfFailed;
-	private int numOfParticipants;
 	private int numOfAssessedIdentities;
+	private AssessmentMembersStatistics membersStatistics;
 	
 	@Autowired
 	private AssessmentToolManager assessmentToolManager;
@@ -82,22 +82,30 @@ public class AssessmentStatisticsSmallController extends BasicController {
 		numOfAssessedIdentities = assessmentToolManager.getNumberOfAssessedIdentities(getIdentity(), params);
 		mainVC.contextPut("numOfAssessedIdentities", numOfAssessedIdentities);
 		
-		numOfParticipants = assessmentToolManager.getNumberOfParticipants(getIdentity(), params);
-		mainVC.contextPut("numOfParticipants", numOfParticipants);
+		membersStatistics = assessmentToolManager.getNumberOfParticipants(getIdentity(), params);
+		mainVC.contextPut("numOfParticipants", membersStatistics.getNumOfParticipants());
+		if(assessmentCallback.canAssessNonMembers()) {
+			mainVC.contextPut("numOfOtherUsers", membersStatistics.getNumOfOtherUsers());
+		}
 		
 		AssessmentStatistics stats = assessmentToolManager.getStatistics(getIdentity(), params);
 		mainVC.contextPut("scoreAverage", AssessmentHelper.getRoundedScore(stats.getAverageScore()));
 		numOfPassed = stats.getCountPassed();
 		mainVC.contextPut("numOfPassed", numOfPassed);
-		int percentPassed = numOfParticipants <= 0 ? 0 : Math.round(100.0f * (stats.getCountPassed() / numOfParticipants));
+		
+		int total = membersStatistics.getTotal();
+		int percentPassed = total <= 0 ? 0 : Math.round(100.0f * (stats.getCountPassed() / total));
 		mainVC.contextPut("percentPassed", percentPassed);
 		numOfFailed = stats.getCountFailed();
 		mainVC.contextPut("numOfFailed", numOfFailed);
-		int percentFailed = numOfParticipants <= 0 ? 0 : Math.round(100.0f * (stats.getCountFailed() / numOfParticipants));
+		int percentFailed = total <= 0 ? 0 : Math.round(100.0f * (stats.getCountFailed() / total));
 		mainVC.contextPut("percentFailed", percentFailed);
 		
-		int numOfLaunches = assessmentToolManager.getNumberOfInitialLaunches(getIdentity(), params);
-		mainVC.contextPut("numOfInitialLaunch", numOfLaunches);
+		int numOfParticipantLaunches = membersStatistics.getNumOfParticipantsLoggedIn();
+		mainVC.contextPut("numOfParticipantLaunches", numOfParticipantLaunches);
+		if(assessmentCallback.canAssessNonMembers()) {
+			mainVC.contextPut("numOfOtherUserLaunches", membersStatistics.getLoggedIn());
+		}
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/modules/assessment/ui/_content/test_stats_small.html b/src/main/java/org/olat/modules/assessment/ui/_content/test_stats_small.html
index 15ec159336d..3b7711d6bc2 100644
--- a/src/main/java/org/olat/modules/assessment/ui/_content/test_stats_small.html
+++ b/src/main/java/org/olat/modules/assessment/ui/_content/test_stats_small.html
@@ -7,17 +7,23 @@
 			<th>$r.translate("table.header.numOfParticipants")</th>
 			<td>$numOfParticipants</td>
 		</tr>
+		#if($r.isNotNull($numOfOtherUsers))
+		<tr>
+			<th>$r.translate("table.header.numOfOtherUsers")</th>
+			<td>$numOfOtherUsers</td>
+		</tr>
+		#end
 		<tr>
 			<th>$r.translate("table.header.numOfInitialLaunch")</th>
 			<td>
-			#if($numOfInitialLaunch && $numOfInitialLaunch == 0)
+			#if($r.isNotNull($numOfParticipantLaunches) && $numOfParticipantLaunches == 0)
 				<i class="o_icon o_red_led"> </i>
-			#elseif($numOfInitialLaunch && $numOfInitialLaunch == $numOfParticipants)
+			#elseif($r.isNotNull($numOfParticipantLaunches) && $numOfParticipantLaunches == $numOfParticipantLaunches)
 				<i class="o_icon o_green_led"> </i>
 			#else
 				<i class="o_icon o_yellow_led"> </i>
 			#end
-			$numOfInitialLaunch</td>
+			$numOfParticipantLaunches #if($r.isNotNull($numOfOtherUserLaunches))/ $numOfOtherUserLaunches#end</td>
 		</tr>
 		<tr>
 			<th>$r.translate("table.header.scoreAverage")</th>
diff --git a/src/main/java/org/olat/modules/assessment/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/assessment/ui/_i18n/LocalStrings_de.properties
index 08f8e423294..5385ec008f8 100644
--- a/src/main/java/org/olat/modules/assessment/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/modules/assessment/ui/_i18n/LocalStrings_de.properties
@@ -31,6 +31,7 @@ table.header.numOfParticipants=\# Teilnehmer
 table.header.numOfAssessedIdentities=\# Teilnehmer
 table.header.numOfInitialLaunch=Eingeloggt
 table.header.numOfPassed=Bestanden
+table.header.numOfOtherUsers=# andere Benutzer
 table.header.scoreAverage=Durchschnitt
 users=Benutzer
 waiting.review=Anstehende Bewertungen
diff --git a/src/main/java/org/olat/modules/assessment/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/assessment/ui/_i18n/LocalStrings_en.properties
index ec9bd1a5248..829c8acc440 100644
--- a/src/main/java/org/olat/modules/assessment/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/modules/assessment/ui/_i18n/LocalStrings_en.properties
@@ -31,6 +31,7 @@ table.header.numOfParticipants=\# participants
 table.header.numOfAssessedIdentities=\# participants
 table.header.numOfInitialLaunch=Logged in
 table.header.numOfPassed=Passed
+table.header.numOfOtherUsers=# other users
 table.header.scoreAverage=Average
 users=Users
 waiting.review=Pending reviews
-- 
GitLab