From 079057d10df1e9e53303ff0fb0265b3db866f87a Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Fri, 3 Jun 2016 09:53:19 +0200
Subject: [PATCH] OO-2046: fix selenium, fix rs in coaching tool, add table
 search in coaching tool

---
 .../basesecurity/BaseSecurityManager.java     |   4 +
 .../table/DefaultFlexiColumnModel.java        |  10 ++
 .../org/olat/ims/qti/QTIResultManager.java    |   1 -
 .../modules/coach/manager/CoachingDAO.java    | 142 ++++++++-------
 .../modules/coach/ui/CourseController.java    |   8 +-
 ...fficiencyStatementEntryTableDataModel.java |  19 +-
 .../modules/coach/ui/GroupController.java     |   6 +-
 .../coach/ui/StudentCoursesController.java    | 170 ++++++++++--------
 .../coach/ui/StudentListController.java       |  14 +-
 .../modules/coach/ui/StudentListProvider.java |  79 ++++++++
 .../coach/ui/StudentsTableDataModel.java      |  63 ++++++-
 .../modules/coach/ui/UserListController.java  |   5 +-
 .../ui/_content/student_course_list.html      |  12 +-
 .../modules/reminder/manager/ReminderDAO.java |   4 +-
 .../manager/RepositoryEntryRelationDAO.java   |  16 +-
 src/main/java/org/olat/user/UserImpl.java     |   5 +
 .../basesecurity/BaseSecurityManagerTest.java |  12 ++
 17 files changed, 397 insertions(+), 173 deletions(-)
 create mode 100644 src/main/java/org/olat/modules/coach/ui/StudentListProvider.java

diff --git a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
index 48ea10f339e..6e798d65f6e 100644
--- a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
+++ b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java
@@ -72,6 +72,7 @@ import org.olat.portfolio.manager.InvitationDAO;
 import org.olat.resource.OLATResource;
 import org.olat.resource.OLATResourceManager;
 import org.olat.user.ChangePasswordController;
+import org.olat.user.UserImpl;
 import org.olat.user.UserManager;
 
 /**
@@ -694,6 +695,7 @@ public class BaseSecurityManager implements BaseSecurity {
 	public Identity createAndPersistIdentity(String username, User user, String provider, String authusername, String credential) {
 		IdentityImpl iimpl = new IdentityImpl(username, user);
 		dbInstance.getCurrentEntityManager().persist(iimpl);
+		((UserImpl)user).setIdentity(iimpl);
 		if (provider != null) { 
 			createAndPersistAuthenticationIntern(iimpl, provider, authusername, credential, loginModule.getDefaultHashAlgorithm());
 		}
@@ -714,6 +716,7 @@ public class BaseSecurityManager implements BaseSecurity {
 		IdentityImpl iimpl = new IdentityImpl(username, user);
 		iimpl.setExternalId(externalId);
 		dbInstance.getCurrentEntityManager().persist(iimpl);
+		((UserImpl)user).setIdentity(iimpl);
 		if (provider != null) { 
 			createAndPersistAuthenticationIntern(iimpl, provider, authusername, null, null);
 		}
@@ -737,6 +740,7 @@ public class BaseSecurityManager implements BaseSecurity {
 		IdentityImpl iimpl = new IdentityImpl(username, user);
 		iimpl.setExternalId(externalId);
 		dbInstance.getCurrentEntityManager().persist(iimpl);
+		((UserImpl)user).setIdentity(iimpl);
 		if (provider != null) { 
 			createAndPersistAuthenticationIntern(iimpl, provider, authusername, credential, loginModule.getDefaultHashAlgorithm());
 		}
diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/DefaultFlexiColumnModel.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/DefaultFlexiColumnModel.java
index fe5569d7e55..3eba71c2052 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/DefaultFlexiColumnModel.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/DefaultFlexiColumnModel.java
@@ -63,11 +63,21 @@ public class DefaultFlexiColumnModel implements FlexiColumnModel {
 				new StaticFlexiCellRenderer(action, new TextFlexiCellRenderer()));
 	}
 	
+	public DefaultFlexiColumnModel(boolean defVisible, FlexiColumnDef def, String action) {
+		this(defVisible, false, def.i18nHeaderKey(), def.ordinal(), action, false, null, FlexiColumnModel.ALIGNMENT_LEFT, 
+				new StaticFlexiCellRenderer(action, new TextFlexiCellRenderer()));
+	}
+	
 	public DefaultFlexiColumnModel(FlexiSortableColumnDef def, String action) {
 		this(true, false, def.i18nHeaderKey(), def.ordinal(), action, def.sortable(), def.sortKey(), FlexiColumnModel.ALIGNMENT_LEFT, 
 				new StaticFlexiCellRenderer(action, new TextFlexiCellRenderer()));
 	}
 	
+	public DefaultFlexiColumnModel(boolean defVisible, FlexiSortableColumnDef def, String action) {
+		this(defVisible, false, def.i18nHeaderKey(), def.ordinal(), action, def.sortable(), def.sortKey(), FlexiColumnModel.ALIGNMENT_LEFT, 
+				new StaticFlexiCellRenderer(action, new TextFlexiCellRenderer()));
+	}
+	
 	public DefaultFlexiColumnModel(FlexiColumnDef def, FlexiCellRenderer renderer) {
 		this(true, false, def.i18nHeaderKey(), def.ordinal(), null, false, null, FlexiColumnModel.ALIGNMENT_LEFT, renderer);
 	}
diff --git a/src/main/java/org/olat/ims/qti/QTIResultManager.java b/src/main/java/org/olat/ims/qti/QTIResultManager.java
index 57f9c4b3bc7..e7217678bb8 100644
--- a/src/main/java/org/olat/ims/qti/QTIResultManager.java
+++ b/src/main/java/org/olat/ims/qti/QTIResultManager.java
@@ -37,7 +37,6 @@ import javax.persistence.TypedQuery;
 import org.olat.basesecurity.Group;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.Identity;
-import org.olat.core.id.UserConstants;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.user.UserDataDeletable;
diff --git a/src/main/java/org/olat/modules/coach/manager/CoachingDAO.java b/src/main/java/org/olat/modules/coach/manager/CoachingDAO.java
index d73030e5ab8..0bc6c5bf97e 100644
--- a/src/main/java/org/olat/modules/coach/manager/CoachingDAO.java
+++ b/src/main/java/org/olat/modules/coach/manager/CoachingDAO.java
@@ -40,7 +40,6 @@ import org.olat.core.commons.persistence.PersistenceHelper;
 import org.olat.core.id.Identity;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
-import org.olat.core.util.CodeHelper;
 import org.olat.core.util.StringHelper;
 import org.olat.course.assessment.UserEfficiencyStatement;
 import org.olat.course.assessment.model.UserEfficiencyStatementLight;
@@ -620,9 +619,7 @@ public class CoachingDAO {
 	
 	protected List<StudentStatEntry> getStudentsStatisticsNative(Identity coach, List<UserPropertyHandler> userPropertyHandlers) {
 		Map<Long, StudentStatEntry> map = new HashMap<>();
-		long start = System.nanoTime();
 		boolean hasCoachedStudents = getStudentsStastisticInfosForCoach(coach, map, userPropertyHandlers);
-		CodeHelper.printNanoTime(start, "Coach stats infos");
 		boolean hasOwnedStudents = getStudentsStastisticInfosForOwner(coach, map, userPropertyHandlers);
 		if(hasOwnedStudents || hasCoachedStudents) {
 			for(StudentStatEntry entry:map.values()) {
@@ -631,7 +628,7 @@ public class CoachingDAO {
 				entry.setInitialLaunch(entry.getLaunchIds().size());
 				entry.setLaunchIds(null);
 			}
-			getStudentsStatisticStatement(coach, map);
+			getStudentsStatisticStatement(coach, hasCoachedStudents, hasOwnedStudents, map);
 			for(StudentStatEntry entry:map.values()) {
 				int notAttempted = entry.getCountRepo() - entry.getCountPassed() - entry.getCountFailed();
 				entry.setCountNotAttempted(notAttempted);
@@ -641,39 +638,6 @@ public class CoachingDAO {
 	}
 	
 	private boolean getStudentsStastisticInfosForCoach(IdentityRef coach, Map<Long, StudentStatEntry> map, List<UserPropertyHandler> userPropertyHandlers) {
-		/*
-		NativeQueryBuilder sb = new NativeQueryBuilder(1024, dbInstance);
-		sb.append("select")
-		  .append("  sg_participant.fk_identity_id as part_id,")
-		  .append("  ").appendToArray("sg_re.repositoryentry_id").append(" as re_ids,")
-		  .append("  ").appendToArray("pg_initial_launch.id").append(" as pg_ids")
-		  .append(" from o_repositoryentry sg_re")
-		  .append(" inner join o_olatresource sg_res on (sg_res.resource_id = sg_re.fk_olatresource and sg_res.resname = 'CourseModule') ")
-		  .append(" inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id)")
-		  .append(" inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role = 'coach')")
-		  .append(" inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant')")
-		  .append(" left join o_as_user_course_infos pg_initial_launch")
-		  .append("   on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = sg_participant.fk_identity_id)")
-		  .append(" where sg_coach.fk_identity_id=:coachKey and ( ")
-		  .append("   (sg_re.accesscode >= ").append(RepositoryEntry.ACC_USERS).append(" and sg_coach.g_role = 'coach') ")//BAR
-		  .append("   or ")
-		  .append("   (sg_re.accesscode = ").append(RepositoryEntry.ACC_OWNERS).append(" and sg_re.membersonly=").appendTrue().append(")) ")
-		  .append(" group by sg_participant.fk_identity_id");
-
-		List<?> rawList = dbInstance.getCurrentEntityManager()
-				.createNativeQuery(sb.toString())
-				.setParameter("coachKey", coach.getKey())
-				.getResultList();
-
-		for(Object rawObject:rawList) {
-			Object[] rawStat = (Object[])rawObject;
-			StudentStatEntry entry = new StudentStatEntry(((Number)rawStat[0]).longValue());
-			appendArrayToSet(rawStat[1], entry.getRepoIds());
-			appendArrayToSet(rawStat[2], entry.getLaunchIds());
-			map.put(entry.getStudentKey(), entry);
-		}
-		*/
-		
 		NativeQueryBuilder sb = new NativeQueryBuilder(1024, dbInstance);
 		sb.append("select")
 		  .append("  sg_participant_id.id as part_id,")
@@ -818,7 +782,7 @@ public class CoachingDAO {
 		}
 	}
 	
-	private boolean getStudentsStatisticStatement(IdentityRef coach, Map<Long,StudentStatEntry> stats) {
+	private boolean getStudentsStatisticStatement(IdentityRef coach, boolean hasCoached, boolean hasOwned, Map<Long,StudentStatEntry> stats) {
 		NativeQueryBuilder sb = new NativeQueryBuilder(1024, dbInstance);
 		sb.append("select ")
 		  .append(" fin_statement.fk_identity, ")
@@ -826,29 +790,40 @@ public class CoachingDAO {
 		  .append("  sum(case when fin_statement.passed=").appendTrue().append(" then 1 else 0 end) as num_of_passed, ")
 		  .append("  sum(case when fin_statement.passed=").appendFalse().append(" then 1 else 0 end) as num_of_failed ")
 		  .append(" from o_as_eff_statement fin_statement ")
-		  .append(" where fin_statement.id in ( select ")
-		  .append("   distinct sg_statement.id as st_id ")
-		  .append("  from o_repositoryentry sg_re ")
-		  .append("  inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) ")
-		  .append("  inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role = 'coach') ")
-		  .append("  inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') ")
-		  .append("  inner join o_as_eff_statement sg_statement ")
-		  .append("    on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) ")
-		  .append("  where sg_coach.fk_identity_id=:coachKey and ( ")
-		  .append("    (sg_re.accesscode>2 and sg_coach.g_role = 'coach') ")
-		  .append("    or ")
-		  .append("    (sg_re.accesscode=1 and sg_re.membersonly=").appendTrue().append(")) ")
-		  .append(" ) or fin_statement.id in ( select  ")
-		  .append("    distinct sg_statement.id as st_id ")
-		  .append("  from o_repositoryentry sg_re ")
-		  .append("  inner join o_re_to_group owngroup on (owngroup.fk_entry_id = sg_re.repositoryentry_id and owngroup.r_defgroup=").appendTrue().append(") ")
-		  .append("  inner join o_bs_group_member sg_owner on (sg_owner.fk_group_id=owngroup.fk_group_id and sg_owner.g_role = 'owner') ")
-		  .append("  inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) ")
-		  .append("  inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=togroup.fk_group_id and sg_participant.g_role='participant') ")
-		  .append("  inner join o_as_eff_statement sg_statement ")
-		  .append("    on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) ")
-		  .append("  where sg_owner.fk_identity_id=:coachKey and sg_re.accesscode>=").append(RepositoryEntry.ACC_OWNERS).append(") ")
-		  .append(" group by fin_statement.fk_identity");
+		  .append(" where ");
+		if(hasCoached) {
+			sb.append(" fin_statement.id in ( select ")
+			  .append("   distinct sg_statement.id as st_id ")
+			  .append("  from o_repositoryentry sg_re ")
+			  .append("  inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) ")
+			  .append("  inner join o_bs_group_member sg_coach on (sg_coach.fk_group_id=togroup.fk_group_id and sg_coach.g_role = 'coach') ")
+			  .append("  inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=sg_coach.fk_group_id and sg_participant.g_role='participant') ")
+			  .append("  inner join o_as_eff_statement sg_statement ")
+			  .append("    on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) ")
+			  .append("  where sg_coach.fk_identity_id=:coachKey and ( ")
+			  .append("    (sg_re.accesscode>2 and sg_coach.g_role = 'coach') ")
+			  .append("    or ")
+			  .append("    (sg_re.accesscode=1 and sg_re.membersonly=").appendTrue().append(")) ")
+			  .append(" )");
+		}
+		if(hasOwned) {
+			if(hasCoached) {
+				sb.append(" or ");
+			}
+		
+			sb.append("  fin_statement.id in ( select  ")
+			  .append("    distinct sg_statement.id as st_id ")
+			  .append("  from o_repositoryentry sg_re ")
+			  .append("  inner join o_re_to_group owngroup on (owngroup.fk_entry_id = sg_re.repositoryentry_id and owngroup.r_defgroup=").appendTrue().append(") ")
+			  .append("  inner join o_bs_group_member sg_owner on (sg_owner.fk_group_id=owngroup.fk_group_id and sg_owner.g_role = 'owner') ")
+			  .append("  inner join o_re_to_group togroup on (togroup.fk_entry_id = sg_re.repositoryentry_id) ")
+			  .append("  inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=togroup.fk_group_id and sg_participant.g_role='participant') ")
+			  .append("  inner join o_as_eff_statement sg_statement ")
+			  .append("    on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) ")
+			  .append("  where sg_owner.fk_identity_id=:coachKey and sg_re.accesscode>=").append(RepositoryEntry.ACC_OWNERS).append(") ");
+		  
+		}
+		sb.append(" group by fin_statement.fk_identity");
 
 		List<?> rawList = dbInstance.getCurrentEntityManager()
 				.createNativeQuery(sb.toString())
@@ -902,8 +877,8 @@ public class CoachingDAO {
 		  .append(" inner join o_bs_identity sg_participant_id on (sg_participant_id.id=sg_participant.fk_identity_id)")
 		  .append(" inner join o_user sg_participant_user on (sg_participant_user.user_id=sg_participant_id.fk_user_id)")
 		  .append(" left join o_as_user_course_infos pg_initial_launch ")
-		  .append("   on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = id_participant.id) ");
-		appendUsersStatisticsJoins(params, queryParams, sb)
+		  .append("   on (pg_initial_launch.fk_resource_id = sg_re.fk_olatresource and pg_initial_launch.fk_identity = id_participant.id) ")
+		  .append(" inner join o_user user_participant on (user_participant.user_id=id_participant.fk_user_id)")
 		  .append(" where sg_re.accesscode >= ").append(RepositoryEntry.ACC_OWNERS).append(" ");
 		appendUsersStatisticsSearchParams(params, queryParams, sb)
 		  .append(" group by sg_participant_id.id, sg_participant_user.user_id");
@@ -951,7 +926,7 @@ public class CoachingDAO {
 		  .append(" inner join o_bs_group_member sg_participant on (sg_participant.fk_group_id=togroup.fk_group_id and sg_participant.g_role='participant') ")
 		  .append(" inner join o_as_eff_statement sg_statement on (sg_statement.fk_identity = sg_participant.fk_identity_id and sg_statement.fk_resource_id = sg_re.fk_olatresource) ")
 		  .append(" inner join o_bs_identity id_participant on (sg_participant.fk_identity_id = id_participant.id) ");
-		appendUsersStatisticsJoins(params, queryParams, sb)
+		appendUsersStatisticsJoins(params, sb)
 		  .append(" where  sg_re.accesscode>0 ");
 		appendUsersStatisticsSearchParams(params, queryParams, sb)
 		  .append(") ")
@@ -980,7 +955,7 @@ public class CoachingDAO {
 	}
 	
 
-	private NativeQueryBuilder appendUsersStatisticsJoins(SearchCoachedIdentityParams params, Map<String,Object> queryParams, NativeQueryBuilder sb) {
+	private NativeQueryBuilder appendUsersStatisticsJoins(SearchCoachedIdentityParams params, NativeQueryBuilder sb) {
 		if(params != null && params.getUserProperties() != null && params.getUserProperties().size() > 0) {
 			sb.append(" inner join o_user user_participant on (user_participant.user_id=id_participant.fk_user_id)");
 		}
@@ -1042,6 +1017,43 @@ public class CoachingDAO {
 		return sb;
 	}
 	
+	public List<Identity> getStudents(Identity coach) {
+		StringBuilder sc = new StringBuilder();
+		sc.append("select participantIdent from repositoryentry as re")
+		  .append(" inner join re.groups as ownedRelGroup on ownedRelGroup.defaultGroup=true")
+		  .append(" inner join ownedRelGroup.group as ownedGroup")
+		  .append(" inner join ownedGroup.members as owner on owner.role='owner'")
+		  .append(" inner join re.groups as relGroup")
+		  .append(" inner join relGroup.group as baseGroup")
+		  .append(" inner join baseGroup.members as participant on participant.role='participant'")
+		  .append(" inner join participant.identity as participantIdent")
+		  .append(" inner join fetch participantIdent.user as participantUser")
+          .append(" where owner.identity.key=:coachKey and re.key=:repoKey");
+
+		List<Identity> identityKeys = dbInstance.getCurrentEntityManager()
+				.createQuery(sc.toString(), Identity.class)
+				.setParameter("coachKey", coach.getKey())
+				.getResultList();
+		
+		//owner see all participants
+		if(identityKeys.isEmpty()) {
+			StringBuilder sb = new StringBuilder();
+			sb.append("select participantIdent from repoentrytogroup as relGroup ")
+			  .append(" inner join relGroup.group as baseGroup")
+			  .append(" inner join baseGroup.members as coach on coach.role = 'coach'")
+			  .append(" inner join baseGroup.members as participant on participant.role='participant'")
+			  .append(" inner join participant.identity as participantIdent")
+			  .append(" inner join fetch participantIdent.user as participantUser")
+	          .append(" where coach.identity.key=:coachKey and relGroup.entry.key=:repoKey");
+	
+			identityKeys = dbInstance.getCurrentEntityManager()
+					.createQuery(sb.toString(), Identity.class)
+					.setParameter("coachKey", coach.getKey())
+					.getResultList();
+		}
+		return new ArrayList<>(new HashSet<>(identityKeys));
+	}
+	
 	public List<Identity> getStudents(Identity coach, RepositoryEntry entry) {
 		StringBuilder sc = new StringBuilder();
 		sc.append("select participantIdent from repositoryentry as re")
diff --git a/src/main/java/org/olat/modules/coach/ui/CourseController.java b/src/main/java/org/olat/modules/coach/ui/CourseController.java
index 5466fda9067..e70068e39ce 100644
--- a/src/main/java/org/olat/modules/coach/ui/CourseController.java
+++ b/src/main/java/org/olat/modules/coach/ui/CourseController.java
@@ -54,7 +54,7 @@ import org.olat.core.util.StringHelper;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.event.GenericEventListener;
 import org.olat.core.util.resource.OresHelper;
-import org.olat.course.assessment.ui.tool.AssessmentToolConstants;
+import org.olat.course.assessment.bulk.PassedCellRenderer;
 import org.olat.course.certificate.CertificateEvent;
 import org.olat.course.certificate.CertificateLight;
 import org.olat.course.certificate.CertificatesManager;
@@ -189,22 +189,20 @@ public class CourseController extends FormBasicController implements Activateabl
 		for (int i = 0; i < userPropertyHandlers.size(); i++) {
 			UserPropertyHandler userPropertyHandler	= userPropertyHandlers.get(i);
 			boolean visible = userManager.isMandatoryUserProperty(UserListController.usageIdentifyer , userPropertyHandler);
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, "select", false, null));
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, "select", true, null));
 		}
 		
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.repoName));
-		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.passed));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.passed, new PassedCellRenderer()));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.certificate, new DownloadCertificateCellRenderer()));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.score, new ScoreCellRenderer()));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.lastModification));
 		
-		
 		model = new EfficiencyStatementEntryTableDataModel(columnsModel);
 		tableEl = uifactory.addTableElement(getWindowControl(), "table", model, 20, false, getTranslator(), formLayout);
 		tableEl.setExportEnabled(true);
 		tableEl.setEmtpyTableMessageKey("error.no.found");
 		tableEl.setAndLoadPersistedPreferences(ureq, "fCourseController");
-		
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementEntryTableDataModel.java b/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementEntryTableDataModel.java
index 96fbab0d385..01de0398cb9 100644
--- a/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementEntryTableDataModel.java
+++ b/src/main/java/org/olat/modules/coach/ui/EfficiencyStatementEntryTableDataModel.java
@@ -24,9 +24,10 @@ import java.util.concurrent.ConcurrentMap;
 
 import org.olat.core.commons.persistence.SortKey;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
-import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnDef;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate;
 import org.olat.course.assessment.UserEfficiencyStatement;
 import org.olat.course.certificate.CertificateLight;
 import org.olat.modules.coach.model.EfficiencyStatementEntry;
@@ -62,8 +63,8 @@ public class EfficiencyStatementEntryTableDataModel extends DefaultFlexiTableDat
 	}
 
 	@Override
-	public void sort(SortKey sortKey) {
-		//
+	public void sort(SortKey orderBy) {
+		setObjects(new SortableFlexiTableModelDelegate<>(orderBy, this, null).sort());
 	}
 	@Override
 	public Object getValueAt(int row, int col) {
@@ -132,7 +133,7 @@ public class EfficiencyStatementEntryTableDataModel extends DefaultFlexiTableDat
 		return new EfficiencyStatementEntryTableDataModel(getTableColumnModel());
 	}
 	
-	public static enum Columns implements FlexiColumnDef {
+	public static enum Columns implements FlexiSortableColumnDef {
 		name("student.name"), 
 		repoName("table.header.course.name"),
 		score("table.header.score"),
@@ -152,6 +153,16 @@ public class EfficiencyStatementEntryTableDataModel extends DefaultFlexiTableDat
 			return i18nKey;
 		}
 
+		@Override
+		public boolean sortable() {
+			return true;
+		}
+
+		@Override
+		public String sortKey() {
+			return name();
+		}
+
 		public static Columns getValueAt(int ordinal) {
 			if(ordinal >= 0 && ordinal < values().length) {
 				return values()[ordinal];
diff --git a/src/main/java/org/olat/modules/coach/ui/GroupController.java b/src/main/java/org/olat/modules/coach/ui/GroupController.java
index 8002804b2a1..3fe1b8bb902 100644
--- a/src/main/java/org/olat/modules/coach/ui/GroupController.java
+++ b/src/main/java/org/olat/modules/coach/ui/GroupController.java
@@ -55,6 +55,7 @@ import org.olat.core.util.StringHelper;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.event.GenericEventListener;
 import org.olat.core.util.resource.OresHelper;
+import org.olat.course.assessment.bulk.PassedCellRenderer;
 import org.olat.course.certificate.CertificateEvent;
 import org.olat.course.certificate.CertificateLight;
 import org.olat.course.certificate.CertificatesManager;
@@ -190,16 +191,15 @@ public class GroupController extends FormBasicController implements Activateable
 		for (int i = 0; i < userPropertyHandlers.size(); i++) {
 			UserPropertyHandler userPropertyHandler	= userPropertyHandlers.get(i);
 			boolean visible = userManager.isMandatoryUserProperty(UserListController.usageIdentifyer , userPropertyHandler);
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, "select", false, null));
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, "select", true, null));
 		}
 		
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.repoName));
-		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.passed));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.passed, new PassedCellRenderer()));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.certificate, new DownloadCertificateCellRenderer()));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.score, new ScoreCellRenderer()));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.lastModification));
 		
-		
 		model = new EfficiencyStatementEntryTableDataModel(columnsModel);
 		tableEl = uifactory.addTableElement(getWindowControl(), "table", model, 20, false, getTranslator(), formLayout);
 		tableEl.setExportEnabled(true);
diff --git a/src/main/java/org/olat/modules/coach/ui/StudentCoursesController.java b/src/main/java/org/olat/modules/coach/ui/StudentCoursesController.java
index 312a07fb0a7..b3570ea3493 100644
--- a/src/main/java/org/olat/modules/coach/ui/StudentCoursesController.java
+++ b/src/main/java/org/olat/modules/coach/ui/StudentCoursesController.java
@@ -28,21 +28,22 @@ import org.olat.NewControllerFactory;
 import org.olat.basesecurity.BaseSecurityModule;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
 import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.components.link.LinkFactory;
-import org.olat.core.gui.components.table.BooleanColumnDescriptor;
-import org.olat.core.gui.components.table.ColumnDescriptor;
-import org.olat.core.gui.components.table.CustomRenderColumnDescriptor;
-import org.olat.core.gui.components.table.DefaultColumnDescriptor;
-import org.olat.core.gui.components.table.TableController;
-import org.olat.core.gui.components.table.TableEvent;
-import org.olat.core.gui.components.table.TableGuiConfiguration;
 import org.olat.core.gui.components.text.TextComponent;
-import org.olat.core.gui.components.velocity.VelocityContainer;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
-import org.olat.core.gui.control.controller.BasicController;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
 import org.olat.core.gui.control.generic.dtabs.Activateable2;
 import org.olat.core.id.Identity;
@@ -57,6 +58,7 @@ import org.olat.core.util.event.GenericEventListener;
 import org.olat.core.util.mail.ContactList;
 import org.olat.core.util.mail.ContactMessage;
 import org.olat.core.util.resource.OresHelper;
+import org.olat.course.assessment.bulk.PassedCellRenderer;
 import org.olat.course.certificate.CertificateEvent;
 import org.olat.course.certificate.CertificateLight;
 import org.olat.course.certificate.CertificatesManager;
@@ -83,15 +85,14 @@ import org.springframework.beans.factory.annotation.Autowired;
  *
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  */
-public class StudentCoursesController extends BasicController implements Activateable2, GenericEventListener {
+public class StudentCoursesController extends FormBasicController implements Activateable2, GenericEventListener {
 
 	private final Link backLink, next, previous;
 	private final Link nextStudent, previousStudent;
 	private final Link homeLink, contactLink;
 	private final TextComponent detailsCmp, detailsStudentCmp;
-	private final TableController tableCtr;
-	private final VelocityContainer mainVC;
-	private final VelocityContainer detailsVC;
+
+	private FlexiTableElement tableEl;
 	private EfficiencyStatementEntryTableDataModel model;
 	
 	private CloseableModalController cmc;
@@ -119,7 +120,7 @@ public class StudentCoursesController extends BasicController implements Activat
 	
 	public StudentCoursesController(UserRequest ureq, WindowControl wControl, StudentStatEntry statEntry,
 			Identity student, int index, int numOfStudents, boolean fullAccess) {
-		super(ureq, wControl);
+		super(ureq, wControl, "student_course_list");
 		setTranslator(userManager.getPropertyHandlerTranslator(getTranslator()));
 		isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles());
 		userPropertyHandlers = userManager.getUserPropertyHandlersFor(UserListController.usageIdentifyer, isAdministrativeUser);
@@ -127,40 +128,16 @@ public class StudentCoursesController extends BasicController implements Activat
 		this.student = student;
 		this.statEntry = statEntry;
 		this.fullAccess = fullAccess;
-
-		TableGuiConfiguration tableConfig = new TableGuiConfiguration();
-		tableConfig.setTableEmptyMessage(translate("error.no.found"));
-		tableConfig.setDownloadOffered(true);
-		tableConfig.setPreferencesOffered(true, "studentCourseListController");
 		
-		tableCtr = new TableController(tableConfig, ureq, getWindowControl(), null, null, null, null, true, getTranslator());
-		tableCtr.addColumnDescriptor(false, new DefaultColumnDescriptor("student.name", Columns.name.ordinal(), "select", getLocale()));
-		tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.course.name", Columns.repoName.ordinal(), "select", getLocale()));
-		tableCtr.addColumnDescriptor(new BooleanColumnDescriptor("table.header.passed", Columns.passed.ordinal(), translate("passed.true"), translate("passed.false")));
-		tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.score", Columns.score.ordinal(), "select", getLocale(),
-				ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer()));
-		tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.certificate", Columns.certificate.ordinal(), null, getLocale(),
-				ColumnDescriptor.ALIGNMENT_LEFT, new DownloadCertificateCellRenderer(student)));
-		tableCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.progress", Columns.progress.ordinal(), null, getLocale(),
-				ColumnDescriptor.ALIGNMENT_LEFT, new ProgressRenderer(true, getTranslator())));
-		tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.lastScoreDate", Columns.lastModification.ordinal(), "select", getLocale()));
+		initForm(ureq);
 
-		listenTo(tableCtr);
 		List<EfficiencyStatementEntry> statements = loadModel();
-
-		mainVC = createVelocityContainer("student_course_list");
-		detailsVC = createVelocityContainer("student_details");
-		
 		String fullName = StringHelper.escapeHtml(userManager.getUserDisplayName(student));
-		
-		detailsVC.contextPut("studentName", fullName);
-		mainVC.put("studentDetails", detailsVC);
-		mainVC.put("studentsTable", tableCtr.getInitialComponent());
-		
+
 		toolbar = new ToolbarController(ureq, wControl, getTranslator());
 		listenTo(toolbar);
+		flc.getFormItemComponent().put("toolbar", toolbar.getInitialComponent());
 		
-		mainVC.put("toolbar", toolbar.getInitialComponent());
 		backLink = toolbar.addToolbarLink("back", this, Position.left);
 		backLink.setIconLeftCSS("o_icon o_icon_back");
 		previous = toolbar.addToolbarLink("previous.course", this, Position.center);
@@ -189,21 +166,54 @@ public class StudentCoursesController extends BasicController implements Activat
 		nextStudent.setCustomDisabledLinkCSS("navbar-text");
 		nextStudent.setEnabled(numOfStudents > 1);
 		
-		contactLink = LinkFactory.createButton("contact.link", detailsVC, this);
+		contactLink = LinkFactory.createButton("contact.link", flc.getFormItemComponent(), this);
 		contactLink.setIconLeftCSS("o_icon o_icon_mail");
-		detailsVC.put("contact", contactLink);
+		flc.getFormItemComponent().put("contact", contactLink);
 		
-		homeLink = LinkFactory.createButton("home.link", detailsVC, this);
+		homeLink = LinkFactory.createButton("home.link", flc.getFormItemComponent(), this);
 		homeLink.setIconLeftCSS("o_icon o_icon_home");
-		detailsVC.put("home", homeLink);
+		flc.getFormItemComponent().put("home", homeLink);
 
 		setDetailsToolbarVisible(false);
-		putInitialPanel(mainVC);
 		
 		CoordinatorManager.getInstance().getCoordinator().getEventBus()
 			.registerFor(this, getIdentity(), CertificatesManager.ORES_CERTIFICATE_EVENT);
 	}
-	
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		if(formLayout instanceof FormLayoutContainer) {
+			FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
+			String fullName = StringHelper.escapeHtml(userManager.getUserDisplayName(student));
+			layoutCont.contextPut("studentName", StringHelper.escapeHtml(fullName));
+		}
+		
+		//add the table
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		if(isAdministrativeUser) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, Columns.name, "select"));
+		}
+		
+		int colIndex = UserListController.USER_PROPS_OFFSET;
+		for (int i = 0; i < userPropertyHandlers.size(); i++) {
+			UserPropertyHandler userPropertyHandler	= userPropertyHandlers.get(i);
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, "select", true, null));
+		}
+		
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.repoName));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.passed, new PassedCellRenderer()));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.score, new ScoreCellRenderer()));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.certificate, new DownloadCertificateCellRenderer()));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.progress, new ProgressRenderer(true, getTranslator())));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.lastModification));
+		
+		model = new EfficiencyStatementEntryTableDataModel(columnsModel);
+		tableEl = uifactory.addTableElement(getWindowControl(), "table", model, 20, false, getTranslator(), formLayout);
+		tableEl.setExportEnabled(true);
+		tableEl.setEmtpyTableMessageKey("error.no.found");
+		tableEl.setAndLoadPersistedPreferences(ureq, "fStudentCourseListController");
+	}
+
 	@Override
 	protected void doDispose() {
 		CoordinatorManager.getInstance().getCoordinator().getEventBus()
@@ -241,8 +251,9 @@ public class StudentCoursesController extends BasicController implements Activat
 			certificateMap.put(key, certificate);
 		}
 
-		//model = new EfficiencyStatementEntryTableDataModel(statements, certificateMap);
-		//tableCtr.setTableDataModel(model);
+		model.setObjects(statements, certificateMap);
+		tableEl.reset();
+		tableEl.reloadData();
 		return statements;
 	}
 	
@@ -255,7 +266,27 @@ public class StudentCoursesController extends BasicController implements Activat
 	}
 
 	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+	
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(tableEl == source) {
+			if(event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent)event;
+				String cmd = se.getCommand();
+				EfficiencyStatementEntry selectedRow = model.getObject(se.getIndex());
+				if("select".equals(cmd)) {
+					selectDetails(ureq, selectedRow);
+				}
+			}
+		} 
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	public void event(UserRequest ureq, Component source, Event event) {
 		if (source == next) {
 			nextEntry(ureq);
 		} else if (source == previous) {
@@ -267,19 +298,12 @@ public class StudentCoursesController extends BasicController implements Activat
 		} else if (source == contactLink) {
 			contact(ureq);
 		}
+		super.event(ureq, source, event);
 	}
 
 	@Override
 	protected void event(UserRequest ureq, Controller source, Event event) {
-		if(source == tableCtr) {
-			if(event instanceof TableEvent) {
-				TableEvent e = (TableEvent) event;
-				if("select".equals(e.getActionId())) {
-					EfficiencyStatementEntry entry = (EfficiencyStatementEntry)tableCtr.getTableDataModel().getObject(e.getRowId());
-					selectDetails(ureq, entry);					
-				}
-			}
-		} else if (source == statementCtrl) {
+		if (source == statementCtrl) {
 			if(event == Event.CHANGED_EVENT) {
 				hasChanged = true;
 				fireEvent(ureq, event);
@@ -324,10 +348,9 @@ public class StudentCoursesController extends BasicController implements Activat
 		ContextEntry ce = entries.get(0);
 		OLATResourceable ores = ce.getOLATResourceable();
 		if("RepositoryEntry".equals(ores.getResourceableTypeName())) {
-			Long identityKey = ores.getResourceableId();
-			for(int i=tableCtr.getRowCount(); i-->0; ) {
-				EfficiencyStatementEntry entry = (EfficiencyStatementEntry)tableCtr.getTableDataModel().getObject(i);
-				if(identityKey.equals(entry.getCourse().getKey())) {
+			Long entryKey = ores.getResourceableId();
+			for(EfficiencyStatementEntry entry:model.getObjects()) {
+				if(entryKey.equals(entry.getCourse().getKey())) {
 					selectDetails(ureq, entry);
 					statementCtrl.activate(ureq, entries.subList(1, entries.size()), ce.getTransientState());
 					break;
@@ -361,7 +384,7 @@ public class StudentCoursesController extends BasicController implements Activat
 	}
 	
 	private void removeDetails(UserRequest ureq) {
-		mainVC.remove(statementCtrl.getInitialComponent());
+		flc.getFormItemComponent().remove(statementCtrl.getInitialComponent());
 		removeAsListenerAndDispose(statementCtrl);
 		statementCtrl = null;
 		setDetailsToolbarVisible(false);
@@ -378,21 +401,21 @@ public class StudentCoursesController extends BasicController implements Activat
 	
 	private void nextEntry(UserRequest ureq) {
 		EfficiencyStatementEntry currentEntry = statementCtrl.getEntry();
-		int nextIndex = tableCtr.getIndexOfSortedObject(currentEntry) + 1;
-		if(nextIndex < 0 || nextIndex >= tableCtr.getRowCount()) {
+		int nextIndex = model.getObjects().indexOf(currentEntry) + 1;
+		if(nextIndex < 0 || nextIndex >= model.getRowCount()) {
 			nextIndex = 0;
 		}
-		EfficiencyStatementEntry nextEntry = (EfficiencyStatementEntry)tableCtr.getSortedObjectAt(nextIndex);
+		EfficiencyStatementEntry nextEntry = model.getObject(nextIndex);
 		selectDetails(ureq, nextEntry);
 	}
 	
 	private void previousEntry(UserRequest ureq) {
 		EfficiencyStatementEntry currentEntry = statementCtrl.getEntry();
-		int previousIndex = tableCtr.getIndexOfSortedObject(currentEntry) - 1;
-		if(previousIndex < 0 || previousIndex >= tableCtr.getRowCount()) {
-			previousIndex = tableCtr.getRowCount() - 1;
+		int previousIndex = model.getObjects().indexOf(currentEntry) - 1;
+		if(previousIndex < 0 || previousIndex >= model.getRowCount()) {
+			previousIndex = model.getRowCount() - 1;
 		}
-		EfficiencyStatementEntry previousEntry = (EfficiencyStatementEntry)tableCtr.getSortedObjectAt(previousIndex);
+		EfficiencyStatementEntry previousEntry = model.getObject(previousIndex);
 		selectDetails(ureq, previousEntry);
 	}
 	
@@ -400,6 +423,7 @@ public class StudentCoursesController extends BasicController implements Activat
 		boolean selectAssessmentTool = false;
 		if(statementCtrl != null) {
 			selectAssessmentTool = statementCtrl.isAssessmentToolSelected();
+			flc.getFormItemComponent().remove(statementCtrl.getInitialComponent());
 			removeAsListenerAndDispose(statementCtrl);
 		}
 		
@@ -409,7 +433,7 @@ public class StudentCoursesController extends BasicController implements Activat
 		listenTo(statementCtrl);
 		detailsCmp.setText(entry.getCourse().getDisplayname());
 
-		mainVC.put("efficiencyDetails", statementCtrl.getInitialComponent());	
+		flc.getFormItemComponent().put("efficiencyDetails", statementCtrl.getInitialComponent());	
 		setDetailsToolbarVisible(true);
 	}
 	
@@ -418,7 +442,7 @@ public class StudentCoursesController extends BasicController implements Activat
 		ces.add(BusinessControlFactory.getInstance().createContextEntry(student));
 
 		BusinessControl bc = BusinessControlFactory.getInstance().createFromContextEntries(ces);
-	  WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
+		WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl());
 		NewControllerFactory.getInstance().launch(ureq, bwControl);
 	}
 }
diff --git a/src/main/java/org/olat/modules/coach/ui/StudentListController.java b/src/main/java/org/olat/modules/coach/ui/StudentListController.java
index 144a6d7515d..f2039c6885d 100644
--- a/src/main/java/org/olat/modules/coach/ui/StudentListController.java
+++ b/src/main/java/org/olat/modules/coach/ui/StudentListController.java
@@ -32,6 +32,7 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableSearchEvent;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
@@ -42,7 +43,6 @@ import org.olat.core.id.OLATResourceable;
 import org.olat.core.id.context.ContextEntry;
 import org.olat.core.id.context.StateEntry;
 import org.olat.core.util.resource.OresHelper;
-import org.olat.course.assessment.ui.tool.AssessmentToolConstants;
 import org.olat.modules.coach.CoachingService;
 import org.olat.modules.coach.model.StudentStatEntry;
 import org.olat.modules.coach.ui.StudentsTableDataModel.Columns;
@@ -99,11 +99,11 @@ public class StudentListController extends FormBasicController implements Activa
 			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.name, "select"));
 		}
 		
-		int colIndex = AssessmentToolConstants.USER_PROPS_OFFSET;
+		int colIndex = UserListController.USER_PROPS_OFFSET;
 		for (int i = 0; i < userPropertyHandlers.size(); i++) {
 			UserPropertyHandler userPropertyHandler	= userPropertyHandlers.get(i);
 			boolean visible = userManager.isMandatoryUserProperty(UserListController.usageIdentifyer , userPropertyHandler);
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, "select", false, null));
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, "select", true, null));
 		}
 		
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.countCourse));
@@ -115,6 +115,8 @@ public class StudentListController extends FormBasicController implements Activa
 		tableEl.setExportEnabled(true);
 		tableEl.setEmtpyTableMessageKey("error.no.found");
 		tableEl.setAndLoadPersistedPreferences(ureq, "fStudentListController");
+		tableEl.setSearchEnabled(new StudentListProvider(model, userManager), ureq.getUserSession());
+		
 	}
 	
 	@Override
@@ -137,6 +139,12 @@ public class StudentListController extends FormBasicController implements Activa
 				if("select".equals(cmd)) {
 					selectStudent(ureq, selectedRow);
 				}
+			} else if(event instanceof FlexiTableSearchEvent) {
+				FlexiTableSearchEvent ftse = (FlexiTableSearchEvent)event;
+				String searchString = ftse.getSearch();
+				model.search(searchString);
+				tableEl.reset();
+				tableEl.reloadData();
 			}
 		} 
 		super.formInnerEvent(ureq, source, event);
diff --git a/src/main/java/org/olat/modules/coach/ui/StudentListProvider.java b/src/main/java/org/olat/modules/coach/ui/StudentListProvider.java
new file mode 100644
index 00000000000..5708150b3e9
--- /dev/null
+++ b/src/main/java/org/olat/modules/coach/ui/StudentListProvider.java
@@ -0,0 +1,79 @@
+/**
+ * <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.coach.ui;
+
+import java.util.Iterator;
+
+import org.olat.core.gui.control.generic.ajax.autocompletion.ListProvider;
+import org.olat.core.gui.control.generic.ajax.autocompletion.ListReceiver;
+import org.olat.core.gui.util.CSSHelper;
+import org.olat.modules.coach.model.StudentStatEntry;
+import org.olat.user.UserManager;
+
+/**
+ * 
+ * Initial date: 02.06.2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class StudentListProvider implements ListProvider {
+	
+	private final StudentsTableDataModel model;
+	private final UserManager userManager;
+	
+	public StudentListProvider(StudentsTableDataModel model, UserManager userManager) {
+		this.model = model;
+		this.userManager = userManager;
+	}
+
+	@Override
+	public void getResult(String searchValue, ListReceiver receiver) {
+		int maxEntries = 10;
+		boolean hasMore = false;
+		for (Iterator<StudentStatEntry> it_res = model.getObjects().iterator(); (hasMore=it_res.hasNext()) && maxEntries > 0;) {
+			StudentStatEntry entry = it_res.next();
+			if(contains(searchValue, entry)) {
+				maxEntries--;
+				String key = entry.getIdentityKey().toString();
+				String displayKey = entry.getIdentityName();
+				String displayText = userManager.getUserDisplayName(entry.getIdentityKey());
+				receiver.addEntry(key, displayKey, displayText, CSSHelper.CSS_CLASS_USER);
+			}
+		}					
+		
+		if(hasMore){
+			receiver.addEntry(".....",".....");
+		}		
+	}
+	
+	public static boolean contains(String searchValue, StudentStatEntry entry) {
+		if(entry.getIdentityName().contains(searchValue)) {
+			return true;
+		}
+		String[] userProperties = entry.getIdentityProps();
+		for(int i=userProperties.length; i-->0; ) {
+			String userProp = userProperties[i];
+			if(userProp != null && userProp.contains(searchValue)) {
+				return true;
+			}
+		}
+		return false;
+	}
+}
diff --git a/src/main/java/org/olat/modules/coach/ui/StudentsTableDataModel.java b/src/main/java/org/olat/modules/coach/ui/StudentsTableDataModel.java
index bc17ffcaea7..79e0d17d801 100644
--- a/src/main/java/org/olat/modules/coach/ui/StudentsTableDataModel.java
+++ b/src/main/java/org/olat/modules/coach/ui/StudentsTableDataModel.java
@@ -19,11 +19,18 @@
  */
 package org.olat.modules.coach.ui;
 
+import java.util.List;
+import java.util.stream.Collectors;
+
 import org.olat.core.commons.persistence.SortKey;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
-import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnDef;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
 import org.olat.modules.coach.model.StudentStatEntry;
 import org.olat.modules.coach.ui.LightedValue.Light;
 
@@ -36,16 +43,44 @@ import org.olat.modules.coach.ui.LightedValue.Light;
  *
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  */
-public class StudentsTableDataModel extends DefaultFlexiTableDataModel<StudentStatEntry> implements SortableFlexiTableDataModel<StudentStatEntry> {
+public class StudentsTableDataModel extends DefaultFlexiTableDataModel<StudentStatEntry>
+	implements SortableFlexiTableDataModel<StudentStatEntry> {
+	
+	private static final OLog log = Tracing.createLoggerFor(StudentsTableDataModel.class);
 
+	private List<StudentStatEntry> backupList;
 	
 	public StudentsTableDataModel(FlexiTableColumnModel columnModel) {
 		super(columnModel);
 	}
+	
+	public void search(final String searchString) {
+		if(StringHelper.containsNonWhitespace(searchString)) {
+			try {
+				List<StudentStatEntry> filteredList;
+				if(StringHelper.isLong(searchString)) {
+					Long identityKey = new Long(searchString);
+					filteredList = backupList.stream()
+						.filter(entry ->  entry.getIdentityKey().equals(identityKey))
+						.collect(Collectors.toList());
+				} else {
+					filteredList = backupList.stream()
+						.filter(entry -> StudentListProvider.contains(searchString, entry))
+						.collect(Collectors.toList());
+				}
+				super.setObjects(filteredList);
+			} catch (Exception e) {
+				log.error("", e);
+				super.setObjects(backupList);
+			}
+		} else {
+			super.setObjects(backupList);
+		}
+	}
 
 	@Override
-	public void sort(SortKey sortKey) {
-		//
+	public void sort(SortKey orderBy) {
+		super.setObjects(new SortableFlexiTableModelDelegate<>(orderBy, this, null).sort());
 	}
 
 	@Override
@@ -104,13 +139,19 @@ public class StudentsTableDataModel extends DefaultFlexiTableDataModel<StudentSt
 		int propPos = col - UserListController.USER_PROPS_OFFSET;
 		return student.getIdentityProp(propPos);
 	}
+	
+	@Override
+	public void setObjects(List<StudentStatEntry> objects) {
+		this.backupList = objects;
+		super.setObjects(objects);
+	}
 
 	@Override
 	public StudentsTableDataModel createCopyWithEmptyList() {
 		return new StudentsTableDataModel(getTableColumnModel());
 	}
 	
-	public static enum Columns implements FlexiColumnDef {
+	public static enum Columns implements FlexiSortableColumnDef {
 		name("student.name"),
 		countCourse("table.header.countCourses"),
 		initialLaunch("table.header.login"),
@@ -127,7 +168,17 @@ public class StudentsTableDataModel extends DefaultFlexiTableDataModel<StudentSt
 		public String i18nHeaderKey() {
 			return i18nKey;
 		}
-		
+
+		@Override
+		public boolean sortable() {
+			return true;
+		}
+
+		@Override
+		public String sortKey() {
+			return name();
+		}
+
 		public static Columns getValueAt(int ordinal) {
 			if(ordinal >= 0 && ordinal < values().length) {
 				return values()[ordinal];
diff --git a/src/main/java/org/olat/modules/coach/ui/UserListController.java b/src/main/java/org/olat/modules/coach/ui/UserListController.java
index 5ef4bc4b467..1d4096ac68d 100644
--- a/src/main/java/org/olat/modules/coach/ui/UserListController.java
+++ b/src/main/java/org/olat/modules/coach/ui/UserListController.java
@@ -44,7 +44,6 @@ import org.olat.core.id.OLATResourceable;
 import org.olat.core.id.context.ContextEntry;
 import org.olat.core.id.context.StateEntry;
 import org.olat.core.util.resource.OresHelper;
-import org.olat.course.assessment.ui.tool.AssessmentToolConstants;
 import org.olat.modules.coach.CoachingService;
 import org.olat.modules.coach.model.SearchCoachedIdentityParams;
 import org.olat.modules.coach.model.StudentStatEntry;
@@ -100,11 +99,11 @@ public class UserListController extends FormBasicController implements Activatea
 			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.name, "select"));
 		}
 		
-		int colIndex = AssessmentToolConstants.USER_PROPS_OFFSET;
+		int colIndex = UserListController.USER_PROPS_OFFSET;
 		for (int i = 0; i < userPropertyHandlers.size(); i++) {
 			UserPropertyHandler userPropertyHandler	= userPropertyHandlers.get(i);
 			boolean visible = userManager.isMandatoryUserProperty(UserListController.usageIdentifyer , userPropertyHandler);
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, "select", false, null));
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, "select", true, null));
 		}
 		
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Columns.countCourse));
diff --git a/src/main/java/org/olat/modules/coach/ui/_content/student_course_list.html b/src/main/java/org/olat/modules/coach/ui/_content/student_course_list.html
index c281965cc1f..df978d038f8 100644
--- a/src/main/java/org/olat/modules/coach/ui/_content/student_course_list.html
+++ b/src/main/java/org/olat/modules/coach/ui/_content/student_course_list.html
@@ -2,6 +2,14 @@ $r.render("toolbar")
 #if($r.available("efficiencyDetails"))
 	$r.render("efficiencyDetails")
 #else
-	$r.render("studentDetails")
-	$r.render("studentsTable")
+	<div class="o_eff_statement_details">
+		<h3><i class="o_icon o_icon_user"> </i> $studentName</h3>
+		#if($r.available("home"))
+			$r.render("home")
+		#end
+		#if($r.available("contact"))
+			$r.render("contact")
+		#end
+	</div>
+	$r.render("table")
 #end
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/reminder/manager/ReminderDAO.java b/src/main/java/org/olat/modules/reminder/manager/ReminderDAO.java
index 466b5a170eb..6eaad9c4c10 100644
--- a/src/main/java/org/olat/modules/reminder/manager/ReminderDAO.java
+++ b/src/main/java/org/olat/modules/reminder/manager/ReminderDAO.java
@@ -193,7 +193,7 @@ public class ReminderDAO {
 	}
 	
 	public List<SentReminder> getSendReminders(Reminder reminder) {
-		String q = "select sent from sentreminder sent inner join fetch sent.identity ident where sent.reminder.key=:reminderKey";
+		String q = "select sent from sentreminder sent inner join fetch sent.identity ident inner join fetch ident.user as identUser where sent.reminder.key=:reminderKey";
 		return dbInstance.getCurrentEntityManager()
 				.createQuery(q, SentReminder.class)
 				.setParameter("reminderKey", reminder.getKey())
@@ -201,7 +201,7 @@ public class ReminderDAO {
 	}
 	
 	public List<SentReminder> getSendReminders(RepositoryEntryRef entry) {
-		String q = "select sent from sentreminder sent inner join fetch sent.reminder rem inner join fetch sent.identity ident where rem.entry.key=:entryKey";
+		String q = "select sent from sentreminder sent inner join fetch sent.reminder rem inner join fetch sent.identity ident inner join fetch ident.user as identUser where rem.entry.key=:entryKey";
 		return dbInstance.getCurrentEntityManager()
 				.createQuery(q, SentReminder.class)
 				.setParameter("entryKey", entry.getKey())
diff --git a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java
index 5da215aa10b..9074a4a14f0 100644
--- a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java
+++ b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java
@@ -269,11 +269,13 @@ public class RepositoryEntryRelationDAO {
 	
 	public List<Identity> getIdentitiesWithRole(String role) {
 		StringBuilder sb = new StringBuilder();
-		sb.append("select distinct members.identity from ").append(RepositoryEntry.class.getName()).append(" as v")
+		sb.append("select distinct ident from ").append(RepositoryEntry.class.getName()).append(" as v")
 		  .append(" inner join v.groups as relGroup")
 		  .append(" inner join relGroup.group as baseGroup")
-		  .append(" inner join baseGroup.members as members")
-		  .append(" where members.role=:role");
+		  .append(" inner join baseGroup.members as memberships")
+		  .append(" inner join memberships.identity as ident")
+		  .append(" inner join fetch ident.user as identUser")
+		  .append(" where memberships.role=:role");
 
 		return dbInstance.getCurrentEntityManager()
 				.createQuery(sb.toString(), Identity.class)
@@ -382,13 +384,15 @@ public class RepositoryEntryRelationDAO {
 		}
 		
 		StringBuilder sb = new StringBuilder();
-		sb.append("select members.identity from ").append(RepositoryEntry.class.getName()).append(" as v")
+		sb.append("select ident from ").append(RepositoryEntry.class.getName()).append(" as v")
 		  .append(" inner join v.groups as relGroup").append(def)
 		  .append(" inner join relGroup.group as baseGroup")
-		  .append(" inner join baseGroup.members as members")
+		  .append(" inner join baseGroup.members as memberships")
+		  .append(" inner join memberships.identity as ident")
+		  .append(" inner join fetch ident.user as identUser")
 		  .append(" where v.key=:repoKey");
 		if(roleList.size() > 0) {
-				sb.append(" and members.role in (:roles)");
+				sb.append(" and memberships.role in (:roles)");
 		}
 			
 		TypedQuery<Identity> query = dbInstance.getCurrentEntityManager()
diff --git a/src/main/java/org/olat/user/UserImpl.java b/src/main/java/org/olat/user/UserImpl.java
index b87a5f58e0c..00902f9f058 100644
--- a/src/main/java/org/olat/user/UserImpl.java
+++ b/src/main/java/org/olat/user/UserImpl.java
@@ -317,6 +317,11 @@ public class UserImpl implements Persistable, User {
 	public Identity getIdentity() {
 		return identity;
 	}
+	
+	public void setIdentity(Identity identity) {
+		this.identity = identity;
+	}
+	
 
 	public String getFirstName() {
 		return firstName;
diff --git a/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java b/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java
index 28199332fbb..b602c06ebab 100644
--- a/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java
+++ b/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java
@@ -825,6 +825,18 @@ public class BaseSecurityManagerTest extends OlatTestCase {
 		dbInstance.commitAndCloseSession();	
 	}
 	
+	@Test
+	public void findAuthenticationName() {
+		Identity ident = JunitTestHelper.createAndPersistIdentityAsRndUser("auth-d-");
+		dbInstance.commitAndCloseSession();
+		
+		Authentication auth = securityManager.findAuthentication(ident, "OLAT");
+		Assert.assertNotNull(auth);
+		
+		String authName = securityManager.findAuthenticationName(ident, "OLAT");
+		Assert.assertNotNull(authName);
+	}
+	
 	@Test
 	public void updateToSaltedAuthentication() {
 		Identity ident = JunitTestHelper.createAndPersistIdentityAsUser("auth-c-" + UUID.randomUUID().toString());
-- 
GitLab