From 7367734fca47d1fe36035c3196fca003558cd03e Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 20 Dec 2017 11:28:44 +0100
Subject: [PATCH] OO-2967: refactor the list of assessed identities in
 assessment tools to be delivered by the resource or the course element them
 self

---
 ...va => AssessmentCourseNodeController.java} |  10 +-
 .../tool/AssessmentCourseTreeController.java  |  10 +-
 .../tool/DefaultToolsControllerCreator.java   |  65 ---
 .../IdentityListCourseNodeController.java     | 362 ++++++------
 .../IdentityListCourseNodeTableModel.java     |   4 +-
 .../ui/tool/ToolsControllerCreator.java       |  55 --
 .../tool/_content/identity_courseelement.html |  14 -
 .../groupsandrights/CourseGroupManager.java   |   2 -
 .../course/nodes/AssessableCourseNode.java    |  21 +-
 .../olat/course/nodes/BasicLTICourseNode.java |  27 +-
 .../course/nodes/CheckListCourseNode.java     |  29 +-
 .../org/olat/course/nodes/GTACourseNode.java  |  69 +--
 .../olat/course/nodes/IQTESTCourseNode.java   | 155 ++----
 .../org/olat/course/nodes/MSCourseNode.java   |  42 +-
 .../course/nodes/PortfolioCourseNode.java     |  29 +-
 .../course/nodes/ProjectBrokerCourseNode.java |  33 +-
 .../org/olat/course/nodes/STCourseNode.java   |  17 +-
 .../olat/course/nodes/ScormCourseNode.java    |  28 +-
 .../org/olat/course/nodes/TACourseNode.java   |  42 +-
 .../gta/ui/BulkDownloadToolController.java    |  81 ---
 .../ui/GTACoachedGroupGradingController.java  |   4 +-
 .../ui/GTAGroupAssessmentToolController.java  | 116 ----
 .../GTAIdentityListCourseNodeController.java  | 224 ++++++++
 .../gta/ui/GroupAssessmentController.java     |  18 +-
 .../ui/_content/identity_courseelement.html   |  13 +
 .../nodes/iq/ConfirmResetController.java      | 173 ------
 .../nodes/iq/ExtraTimeCellRenderer.java       |  86 +++
 .../olat/course/nodes/iq/ExtraTimeInfos.java  |  56 ++
 .../IQIdentityListCourseNodeController.java   | 513 ++++++++++++++++++
 .../nodes/iq/QTI21ExtraTimeController.java    | 146 -----
 ...IdentityListCourseNodeToolsController.java |  70 +--
 .../iq/_content/identity_courseelement.html   |  31 ++
 .../nodes/iq/_i18n/LocalStrings_de.properties |   7 +
 .../nodes/iq/_i18n/LocalStrings_en.properties |   6 +
 .../nodes/iq/_i18n/LocalStrings_fr.properties |   3 +-
 .../nodes/iq/_i18n/LocalStrings_it.properties |   1 +
 .../iq/_i18n/LocalStrings_pt_BR.properties    |   1 +
 .../iq/_i18n/LocalStrings_zh_CN.properties    |   1 +
 .../MSIdentityListCourseNodeController.java   |  59 ++
 .../ms/_content/identity_courseelement.html   |  15 +
 ...rokerIdentityListCourseNodeController.java |  65 +++
 .../_content/identity_courseelement.html      |   8 +
 .../STIdentityListCourseNodeController.java   | 134 +++++
 .../st/_content/identity_courseelement.html   |   2 +
 .../nodes/ta/BulkDownloadToolController.java  |  80 ---
 .../TAIdentityListCourseNodeController.java   |  95 ++++
 .../ta/_content/identity_courseelement.html   |  10 +
 .../QTI12ExportResultsReportController.java   | 100 ----
 .../ui/QTI12PullTestsToolController.java      | 133 ++---
 .../ui/QTI12StatisticsToolController.java     |  69 +--
 .../ui/_content/retrieve_tests.html           |   5 +
 .../java/org/olat/ims/qti21/QTI21Service.java |   3 +-
 .../manager/AssessmentTestSessionDAO.java     |   8 +
 .../QTI21ExportResultsReportController.java   |  96 ----
 .../ims/qti21/ui/QTI21AssessableResource.java |  18 +-
 .../QTI21AssessedIdentityListController.java  | 158 ++++++
 .../ui/QTI21AssessmentDetailsController.java  |  58 +-
 ...ler.java => QTI21ResetDataController.java} | 252 ++++-----
 .../ui/QTI21RetrieveTestsController.java      | 220 ++++++++
 .../ui/QTI21RetrieveTestsToolController.java  | 239 --------
 .../qti21/ui/_content/assessment_details.html |   4 +-
 .../qti21/ui/_content/identity_element.html   |   8 +
 .../ims/qti21/ui/_content/retrieve_tests.html |   5 +
 .../QTI21CorrectionToolController.java        | 141 -----
 .../QTI21ValidationToolController.java        |  96 ----
 .../QTI21StatisticsToolController.java        |  71 +--
 .../assessment/ui/AssessableResource.java     |   7 +-
 .../ui/AssessedIdentityElementRow.java        |  20 +-
 .../ui/AssessedIdentityListController.java    |  65 +--
 .../ui/AssessmentToolController.java          |  16 +-
 .../ui/_content/identity_element.html         |   8 -
 .../ui/model/AssessableBinderResource.java    |  13 +-
 72 files changed, 2416 insertions(+), 2429 deletions(-)
 rename src/main/java/org/olat/course/assessment/ui/tool/{IdentityListCourseNodeProvider.java => AssessmentCourseNodeController.java} (80%)
 delete mode 100644 src/main/java/org/olat/course/assessment/ui/tool/DefaultToolsControllerCreator.java
 delete mode 100644 src/main/java/org/olat/course/assessment/ui/tool/ToolsControllerCreator.java
 delete mode 100644 src/main/java/org/olat/course/nodes/gta/ui/BulkDownloadToolController.java
 delete mode 100644 src/main/java/org/olat/course/nodes/gta/ui/GTAGroupAssessmentToolController.java
 create mode 100644 src/main/java/org/olat/course/nodes/gta/ui/GTAIdentityListCourseNodeController.java
 create mode 100644 src/main/java/org/olat/course/nodes/gta/ui/_content/identity_courseelement.html
 delete mode 100644 src/main/java/org/olat/course/nodes/iq/ConfirmResetController.java
 create mode 100644 src/main/java/org/olat/course/nodes/iq/ExtraTimeCellRenderer.java
 create mode 100644 src/main/java/org/olat/course/nodes/iq/ExtraTimeInfos.java
 create mode 100644 src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java
 delete mode 100644 src/main/java/org/olat/course/nodes/iq/QTI21ExtraTimeController.java
 create mode 100644 src/main/java/org/olat/course/nodes/iq/_content/identity_courseelement.html
 create mode 100644 src/main/java/org/olat/course/nodes/ms/MSIdentityListCourseNodeController.java
 create mode 100644 src/main/java/org/olat/course/nodes/ms/_content/identity_courseelement.html
 create mode 100644 src/main/java/org/olat/course/nodes/projectbroker/ProjectBrokerIdentityListCourseNodeController.java
 create mode 100644 src/main/java/org/olat/course/nodes/projectbroker/_content/identity_courseelement.html
 create mode 100644 src/main/java/org/olat/course/nodes/st/STIdentityListCourseNodeController.java
 create mode 100644 src/main/java/org/olat/course/nodes/st/_content/identity_courseelement.html
 delete mode 100644 src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java
 create mode 100644 src/main/java/org/olat/course/nodes/ta/TAIdentityListCourseNodeController.java
 create mode 100644 src/main/java/org/olat/course/nodes/ta/_content/identity_courseelement.html
 delete mode 100644 src/main/java/org/olat/ims/qti/resultexport/QTI12ExportResultsReportController.java
 create mode 100644 src/main/java/org/olat/ims/qti/statistics/ui/_content/retrieve_tests.html
 delete mode 100644 src/main/java/org/olat/ims/qti21/resultexport/QTI21ExportResultsReportController.java
 create mode 100644 src/main/java/org/olat/ims/qti21/ui/QTI21AssessedIdentityListController.java
 rename src/main/java/org/olat/ims/qti21/ui/{QTI21ResetToolController.java => QTI21ResetDataController.java} (56%)
 create mode 100644 src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsController.java
 delete mode 100644 src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsToolController.java
 create mode 100644 src/main/java/org/olat/ims/qti21/ui/_content/identity_element.html
 create mode 100644 src/main/java/org/olat/ims/qti21/ui/_content/retrieve_tests.html
 delete mode 100644 src/main/java/org/olat/ims/qti21/ui/assessment/QTI21CorrectionToolController.java
 delete mode 100644 src/main/java/org/olat/ims/qti21/ui/assessment/QTI21ValidationToolController.java

diff --git a/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeProvider.java b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseNodeController.java
similarity index 80%
rename from src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeProvider.java
rename to src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseNodeController.java
index a6f9ff9c0af..249d6ec752d 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeProvider.java
+++ b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseNodeController.java
@@ -22,16 +22,18 @@ package org.olat.course.assessment.ui.tool;
 import java.util.List;
 
 import org.olat.basesecurity.IdentityRef;
+import org.olat.core.gui.control.Controller;
+import org.olat.modules.assessment.ui.AssessedIdentityListState;
 
 /**
  * 
- * 
- * 
- * Initial date: 4 déc. 2017<br>
+ * Initial date: 18 déc. 2017<br>
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
-public interface IdentityListCourseNodeProvider {
+public interface AssessmentCourseNodeController extends Controller {
+	
+	public AssessedIdentityListState getListState();
 	
 	public List<IdentityRef> getSelectedIdentities();
 
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseTreeController.java b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseTreeController.java
index 644b3d5216b..c3ad56f86f1 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseTreeController.java
+++ b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentCourseTreeController.java
@@ -69,7 +69,7 @@ public class AssessmentCourseTreeController extends BasicController implements A
 
 	private Controller currentCtrl;
 	private Controller businessGroupListCtrl;
-	private IdentityListCourseNodeController identityListCtrl; 
+	private AssessmentCourseNodeController identityListCtrl; 
 	
 	private View view = View.users;
 	private TreeNode selectedNodeChanged;
@@ -288,8 +288,14 @@ public class AssessmentCourseTreeController extends BasicController implements A
 		WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(oresUsers, null, getWindowControl());
 		OLATResourceable oresNode = OresHelper.createOLATResourceableInstance("Node", new Long(courseNode.getIdent()));
 		WindowControl bbwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(oresNode, null, bwControl);
-		identityListCtrl = new IdentityListCourseNodeController(ureq, bbwControl, stackPanel,
+		if(courseNode instanceof AssessableCourseNode) {
+			identityListCtrl = ((AssessableCourseNode)courseNode).getIdentityListController(ureq, getWindowControl(), stackPanel,
+					courseEntry, null, coachCourseEnv, toolContainer, assessmentCallback);
+		} else {
+			identityListCtrl = new IdentityListCourseNodeController(ureq, bbwControl, stackPanel,
 				courseEntry, null, courseNode, coachCourseEnv, toolContainer, assessmentCallback);
+		}
+		
 		return identityListCtrl;
 	}
 	
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/DefaultToolsControllerCreator.java b/src/main/java/org/olat/course/assessment/ui/tool/DefaultToolsControllerCreator.java
deleted file mode 100644
index e33a8ad1c5d..00000000000
--- a/src/main/java/org/olat/course/assessment/ui/tool/DefaultToolsControllerCreator.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * <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.course.assessment.ui.tool;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.stack.TooledStackedPanel;
-import org.olat.core.gui.control.Controller;
-import org.olat.core.gui.control.WindowControl;
-import org.olat.core.id.Identity;
-import org.olat.course.run.userview.UserCourseEnvironment;
-import org.olat.modules.assessment.AssessmentToolOptions;
-
-/**
- * 
- * Default implementation without any tools.
- * 
- * Initial date: 4 déc. 2017<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class DefaultToolsControllerCreator implements ToolsControllerCreator {
-
-	@Override
-	public boolean hasCalloutTools() {
-		return false;
-	}
-
-	@Override
-	public Controller createCalloutController(UserRequest ureq, WindowControl wControl,
-			UserCourseEnvironment coachCourseEnv, Identity assessedIdentity) {
-		return null;
-	}
-
-	@Override
-	public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl,
-			TooledStackedPanel stackPanel, UserCourseEnvironment coachCourseEnv, AssessmentToolOptions options) {
-		return null;
-	}
-
-	@Override
-	public List<Controller> createMultiSelectionTools(UserRequest ureq, WindowControl wControl,
-			UserCourseEnvironment coachCourseEnv, IdentityListCourseNodeProvider provider) {
-		return Collections.emptyList();
-	}
-}
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java b/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java
index e631efcdf99..07a88ffac28 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java
+++ b/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java
@@ -21,13 +21,10 @@ package org.olat.course.assessment.ui.tool;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 
 import org.olat.basesecurity.BaseSecurity;
 import org.olat.basesecurity.BaseSecurityModule;
@@ -46,7 +43,6 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink;
 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.DateFlexiCellRenderer;
 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;
@@ -54,7 +50,6 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTable
 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.stack.BreadcrumbPanelAware;
 import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.components.stack.TooledStackedPanel.Align;
 import org.olat.core.gui.control.Controller;
@@ -79,18 +74,15 @@ import org.olat.course.ICourse;
 import org.olat.course.assessment.AssessmentModule;
 import org.olat.course.assessment.AssessmentToolManager;
 import org.olat.course.assessment.bulk.PassedCellRenderer;
-import org.olat.course.assessment.manager.UserCourseInformationsManager;
 import org.olat.course.assessment.model.SearchAssessedIdentityParams;
 import org.olat.course.assessment.ui.tool.IdentityListCourseNodeTableModel.IdentityCourseElementCols;
 import org.olat.course.assessment.ui.tool.event.ShowDetailsEvent;
-import org.olat.course.certificate.CertificateLight;
-import org.olat.course.certificate.CertificatesManager;
-import org.olat.course.certificate.ui.DownloadCertificateCellRenderer;
 import org.olat.course.nodes.AssessableCourseNode;
 import org.olat.course.nodes.CalculatedAssessableCourseNode;
 import org.olat.course.nodes.CourseNode;
 import org.olat.course.nodes.CourseNodeFactory;
 import org.olat.course.nodes.STCourseNode;
+import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.course.run.userview.UserCourseEnvironmentImpl;
@@ -116,34 +108,38 @@ import org.olat.user.propertyhandlers.UserPropertyHandler;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
+ * This is the "abstract" class of the assessed identities list. If you want
+ * to inherit from it, don't forget to copy the velocity template and adapt
+ * it to your need.
  * 
  * Initial date: 06.10.2015<br>
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
-public class IdentityListCourseNodeController extends FormBasicController implements Activateable2, GenericEventListener, IdentityListCourseNodeProvider {
+public class IdentityListCourseNodeController extends FormBasicController
+	implements Activateable2, GenericEventListener, AssessmentCourseNodeController {
 
 	private int counter = 0;
-	private final BusinessGroup group;
-	private final CourseNode courseNode;
+	protected final BusinessGroup group;
+	protected final CourseNode courseNode;
 	private final RepositoryEntry courseEntry;
 	private final RepositoryEntry referenceEntry;
+	private final CourseEnvironment courseEnv;
 	private final boolean isAdministrativeUser;
-	private final UserCourseEnvironment coachCourseEnv;
+	protected final UserCourseEnvironment coachCourseEnv;
 	private final List<UserPropertyHandler> userPropertyHandlers;
-	private final AssessmentToolSecurityCallback assessmentCallback;
+	protected final AssessmentToolSecurityCallback assessmentCallback;
 	
 	private Link nextLink, previousLink;
-	private FlexiTableElement tableEl;
+	protected FlexiTableElement tableEl;
 	private FormLink bulkDoneButton, bulkVisibleButton;
-	private final TooledStackedPanel stackPanel;
+	protected final TooledStackedPanel stackPanel;
 	private final AssessmentToolContainer toolContainer;
-	private IdentityListCourseNodeTableModel usersTableModel;
+	protected IdentityListCourseNodeTableModel usersTableModel;
 	
 	private Controller toolsCtrl;
-	private CloseableModalController cmc;
+	protected CloseableModalController cmc;
 	private List<Controller> bulkToolsList;
-	private ToolsControllerCreator toolsCtrlCreator;
 	private AssessedIdentityController currentIdentityCtrl;
 	private CloseableCalloutWindowController toolsCalloutCtrl;
 	private ConfirmUserVisibilityController changeUserVisibilityCtrl;
@@ -159,16 +155,13 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 	@Autowired
 	private CoordinatorManager coordinatorManager;
 	@Autowired
-	private CertificatesManager certificatesManager;
-	@Autowired
-	private UserCourseInformationsManager userInfosMgr;
-	@Autowired
 	private AssessmentToolManager assessmentToolManager;
 	
 	public IdentityListCourseNodeController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
 			RepositoryEntry courseEntry, BusinessGroup group, CourseNode courseNode, UserCourseEnvironment coachCourseEnv,
 			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
 		super(ureq, wControl, "identity_courseelement");
+		setTranslator(Util.createPackageTranslator(IdentityListCourseNodeController.class, getLocale(), getTranslator()));
 		setTranslator(Util.createPackageTranslator(AssessmentModule.class, getLocale(), getTranslator()));
 		setTranslator(userManager.getPropertyHandlerTranslator(getTranslator()));
 		
@@ -179,15 +172,13 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 		this.toolContainer = toolContainer;
 		this.coachCourseEnv = coachCourseEnv;
 		this.assessmentCallback = assessmentCallback;
+		courseEnv = CourseFactory.loadCourse(courseEntry).getCourseEnvironment();
 		
 		if(courseNode.needsReferenceToARepositoryEntry()) {
 			referenceEntry = courseNode.getReferencedRepositoryEntry();
 		} else {
 			referenceEntry = null;
 		}
-		if(courseNode instanceof AssessableCourseNode) {
-			toolsCtrlCreator = ((AssessableCourseNode)courseNode).getAssessmentToolsCreator();
-		}
 		
 		isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles());
 		userPropertyHandlers = userManager.getUserPropertyHandlersFor(AssessmentToolConstants.usageIdentifyer, isAdministrativeUser);
@@ -199,6 +190,23 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 			.registerFor(this, getIdentity(), courseEntry.getOlatResource());
 	}
 	
+	public RepositoryEntry getCourseRepositoryEntry() {
+		return courseEntry;
+	}
+	
+	public RepositoryEntry getReferencedRepositoryEntry() {
+		return referenceEntry;
+	}
+	
+	public CourseEnvironment getCourseEnvironment() {
+		return courseEnv;
+	}
+	
+	public AssessmentToolContainer getToolContainer() {
+		return toolContainer;
+	}
+
+	@Override
 	public AssessedIdentityListState getListState() {
 		List<FlexiTableFilter> filters = tableEl.getSelectedFilters();
 		String filter = null;
@@ -243,9 +251,7 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 		}
 		
 		ICourse course = CourseFactory.loadCourse(courseEntry);
-		String select = (courseNode instanceof AssessableCourseNode
-				&& (courseNode.getParent() == null || !(courseNode instanceof STCourseNode)))
-				? "select" : null;
+		String select = isSelectable() ? "select" : null;
 
 		//add the table
 		FlexiTableSortOptions options = new FlexiTableSortOptions();
@@ -265,59 +271,13 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 			}
 			colIndex++;
 		}
-		AssessableCourseNode assessableNode = null;
-		if(courseNode instanceof AssessableCourseNode) {
-			assessableNode = (AssessableCourseNode)courseNode;
-			
-			if(assessableNode.hasAttemptsConfigured()) {
-				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.attempts));
-			}
-			if(!(courseNode instanceof CalculatedAssessableCourseNode)) {
-				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.userVisibility,
-						new UserVisibilityCellRenderer(getTranslator())));
-			}
-			if(assessableNode.hasScoreConfigured()) {
-				if(!(assessableNode instanceof STCourseNode)) {
-					if(assessableNode.getMinScoreConfiguration() != null) {
-						columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.min, new ScoreCellRenderer()));
-					}
-					if(assessableNode.getMaxScoreConfiguration() != null) {
-						columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.max, new ScoreCellRenderer()));
-					}
-				}
-				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.score, new ScoreCellRenderer()));
-			}
-			if(assessableNode.hasPassedConfigured()) {
-				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.passed, new PassedCellRenderer()));
-			}
-			if(assessableNode.hasIndividualAsssessmentDocuments()) {
-				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.numOfAssessmentDocs));
-			}
-		}
-
-		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.assessmentStatus, new AssessmentStatusCellRenderer(getLocale())));
-		if(assessableNode != null && assessableNode.hasCompletion()) {
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.currentCompletion));
-		}
-		
-		if(courseNode.getParent() == null) {
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.initialLaunchDate, select));
-		}
 		
-		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, IdentityCourseElementCols.lastModified, select));
-		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.lastUserModified, select));
-		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, IdentityCourseElementCols.lastCoachModified, select));
-		
-		if(course.getCourseConfig().isCertificateEnabled()) {
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.certificate, new DownloadCertificateCellRenderer(getLocale())));
-			if(course.getCourseConfig().isRecertificationEnabled()) {
-				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.recertification, new DateFlexiCellRenderer(getLocale())));
-			}
-		}
-		if(toolsCtrlCreator != null && toolsCtrlCreator.hasCalloutTools()) {
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.tools));
-		}
+		initAssessmentColumns(columnsModel);
+		initStatusColumns(columnsModel);
+		initModificationDatesColumns(columnsModel);
+		initCalloutColumns(columnsModel);
 
+		AssessableCourseNode assessableNode = courseNode instanceof AssessableCourseNode ? (AssessableCourseNode)courseNode : null;
 		usersTableModel = new IdentityListCourseNodeTableModel(columnsModel, assessableNode, getLocale()); 
 		tableEl = uifactory.addTableElement(getWindowControl(), "table", usersTableModel, 20, false, getTranslator(), formLayout);
 		tableEl.setExportEnabled(true);
@@ -355,73 +315,92 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 			}
 		}
 
-		tableEl.setAndLoadPersistedPreferences(ureq, "assessment-tool-identity-list");
-		
-		
+		tableEl.setAndLoadPersistedPreferences(ureq, getTableId());
+	}
+	
+	protected String getTableId() {
+		return "assessment-tool-identity-list";
+	}
+	
+	protected void initAssessmentColumns(FlexiTableColumnModel columnsModel) {
+		if(courseNode instanceof AssessableCourseNode) {
+			AssessableCourseNode assessableNode = (AssessableCourseNode)courseNode;
+			
+			if(assessableNode.hasAttemptsConfigured()) {
+				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.attempts));
+			}
+			if(!(courseNode instanceof CalculatedAssessableCourseNode)) {
+				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.userVisibility,
+						new UserVisibilityCellRenderer(getTranslator())));
+			}
+			if(assessableNode.hasScoreConfigured()) {
+				if(!(assessableNode instanceof STCourseNode)) {
+					if(assessableNode.getMinScoreConfiguration() != null) {
+						columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.min, new ScoreCellRenderer()));
+					}
+					if(assessableNode.getMaxScoreConfiguration() != null) {
+						columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.max, new ScoreCellRenderer()));
+					}
+				}
+				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.score, new ScoreCellRenderer()));
+			}
+			if(assessableNode.hasPassedConfigured()) {
+				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.passed, new PassedCellRenderer()));
+			}
+			if(assessableNode.hasIndividualAsssessmentDocuments()) {
+				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.numOfAssessmentDocs));
+			}
+		}
+	}
+	
+	protected void initStatusColumns(FlexiTableColumnModel columnsModel) {
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.assessmentStatus, new AssessmentStatusCellRenderer(getLocale())));
+	}
+	
+	protected void initModificationDatesColumns(FlexiTableColumnModel columnsModel) {
+		String select = getSelectAction();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, IdentityCourseElementCols.lastModified, select));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.lastUserModified, select));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, IdentityCourseElementCols.lastCoachModified, select));
+	}
+	
+	protected void initCalloutColumns(FlexiTableColumnModel columnsModel) {
+		if(courseNode instanceof AssessableCourseNode) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.tools));
+		}
+	}
+	
+	protected String getSelectAction() {
+		return isSelectable() ? "select" : null;
+	}
+	
+	protected boolean isSelectable() {
+		return courseNode instanceof AssessableCourseNode;
 	}
 	
-	private void initMultiSelectionTools(UserRequest ureq, FormItemContainer formLayout) {
-		if(courseNode instanceof AssessableCourseNode && !(courseNode instanceof CalculatedAssessableCourseNode)) {
+	protected void initMultiSelectionTools(@SuppressWarnings("unused") UserRequest ureq, FormLayoutContainer formLayout) {
+		if(courseNode instanceof AssessableCourseNode) {
 			bulkDoneButton = uifactory.addFormLink("bulk.done", formLayout, Link.BUTTON);
 			bulkDoneButton.setVisible(!coachCourseEnv.isCourseReadOnly());
 			
 			bulkVisibleButton = uifactory.addFormLink("bulk.visible", formLayout, Link.BUTTON);
 			bulkVisibleButton.setVisible(!coachCourseEnv.isCourseReadOnly());
 		}
-		
-		List<String> cmpNames = new ArrayList<>();
-		if(toolsCtrlCreator != null) {
-			List<Controller> tools = toolsCtrlCreator.createMultiSelectionTools(ureq, getWindowControl(), coachCourseEnv, this);
-			cmpNames = putToolsInContainer(tools);
-		}
-		flc.contextPut("multiSelectionCmpNames", cmpNames);
 	}
 	
-	private void updateModel(UserRequest ureq, String searchString, List<FlexiTableFilter> filters, List<FlexiTableFilter> extendedFilters) {
-		SearchAssessedIdentityParams params = new SearchAssessedIdentityParams(courseEntry, courseNode.getIdent(), referenceEntry, assessmentCallback);
-		
-		List<AssessmentEntryStatus> assessmentStatus = null;
-		if(filters != null && filters.size() > 0) {
-			assessmentStatus = new ArrayList<>(filters.size());
-			for(FlexiTableFilter filter:filters) {
-				if("passed".equals(filter.getFilter())) {
-					params.setPassed(true);
-				} else if("failed".equals(filter.getFilter())) {
-					params.setFailed(true);
-				} else if(AssessmentEntryStatus.isValueOf(filter.getFilter())){
-					assessmentStatus.add(AssessmentEntryStatus.valueOf(filter.getFilter()));
-				}
-			}
-		}
-		params.setAssessmentStatus(assessmentStatus);
-		
-		List<Long> businessGroupKeys = null;
-		if(group != null) {
-			businessGroupKeys = Collections.singletonList(group.getKey());
-		} else if(extendedFilters != null && extendedFilters.size() > 0) {
-			businessGroupKeys = new ArrayList<>(extendedFilters.size());
-			for(FlexiTableFilter extendedFilter:extendedFilters) {
-				if(StringHelper.isLong(extendedFilter.getFilter())) {
-					businessGroupKeys.add(Long.parseLong(extendedFilter.getFilter()));
-				}
-			}
-		}
-		params.setBusinessGroupKeys(businessGroupKeys);
-		params.setSearchString(searchString);
-		
+	protected void loadModel(@SuppressWarnings("unused") UserRequest ureq) {
+		SearchAssessedIdentityParams params = getSearchParameters();
 		List<Identity> assessedIdentities = assessmentToolManager.getAssessedIdentities(getIdentity(), params);
 		List<AssessmentEntry> assessmentEntries = assessmentToolManager.getAssessmentEntries(getIdentity(), params, null);
 		Map<Long,AssessmentEntry> entryMap = new HashMap<>();
 		assessmentEntries.stream()
 			.filter(entry -> entry.getIdentity() != null)
 			.forEach((entry) -> entryMap.put(entry.getIdentity().getKey(), entry));
-		Map<Long,Date> initialLaunchDates = userInfosMgr.getInitialLaunchDates(courseEntry.getOlatResource());
 
 		List<AssessedIdentityElementRow> rows = new ArrayList<>(assessedIdentities.size());
 		for(Identity assessedIdentity:assessedIdentities) {
 			AssessmentEntry entry = entryMap.get(assessedIdentity.getKey());
-			Date initialLaunchDate = initialLaunchDates.get(assessedIdentity.getKey());
-
+			
 			CompletionItem currentCompletion = new CompletionItem("current-completion-" + (++counter), getLocale());
 			if(entry != null) {
 				currentCompletion.setCompletion(entry.getCurrentRunCompletion());
@@ -432,69 +411,74 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 			FormLink toolsLink = uifactory.addFormLink("tools_" + (++counter), "tools", "", null, null, Link.NONTRANSLATED);
 			toolsLink.setIconLeftCSS("o_icon o_icon_actions o_icon-lg");
 		
-			AssessedIdentityElementRow row = new AssessedIdentityElementRow(assessedIdentity, entry, initialLaunchDate,
+			AssessedIdentityElementRow row = new AssessedIdentityElementRow(assessedIdentity, entry,
 					currentCompletion, toolsLink, userPropertyHandlers, getLocale());
 			toolsLink.setUserObject(row);
 			rows.add(row);
 		}
-		
-		if(toolContainer.getCertificateMap() == null) {
-			List<CertificateLight> certificates = certificatesManager.getLastCertificates(courseEntry.getOlatResource());
-			ConcurrentMap<Long, CertificateLight> certificateMap = new ConcurrentHashMap<>();
-			for(CertificateLight certificate:certificates) {
-				certificateMap.put(certificate.getIdentityKey(), certificate);
-			}
-			toolContainer.setCertificateMap(certificateMap);
-		}
-		usersTableModel.setCertificateMap(toolContainer.getCertificateMap());
+
 		usersTableModel.setObjects(rows);
+		List<FlexiTableFilter> filters = tableEl.getSelectedFilters();
 		if(filters != null && filters.size() > 0 && filters.get(0) != null) {
 			usersTableModel.filter(Collections.singletonList(filters.get(0)));
 		}
 		tableEl.reset();
 		tableEl.reloadData();
-
-		initTopBulkTools(ureq, params, assessedIdentities);
-	}
-	
-	private void initTopBulkTools(UserRequest ureq, SearchAssessedIdentityParams params, List<Identity> assessedIdentities) {
-		List<String> toolCmpNames = new ArrayList<>();
-		if(toolsCtrlCreator != null) {
-			AssessmentToolOptions options = new AssessmentToolOptions();
-			options.setAdmin(assessmentCallback.isAdmin());
-			if(group == null) {
-				if(assessmentCallback.isAdmin()) {
-					options.setNonMembers(params.isNonMembers());
-				} else {
-					options.setIdentities(assessedIdentities);
-					fillAlternativeToAssessableIdentityList(options, params);
+	}
+	
+	protected SearchAssessedIdentityParams getSearchParameters() {
+		SearchAssessedIdentityParams params = new SearchAssessedIdentityParams(courseEntry, courseNode.getIdent(), referenceEntry, assessmentCallback);
+		
+		List<FlexiTableFilter> filters = tableEl.getSelectedFilters();
+		List<FlexiTableFilter> extendedFilters = tableEl.getSelectedExtendedFilters();
+		
+		List<AssessmentEntryStatus> assessmentStatus = null;
+		if(filters != null && filters.size() > 0) {
+			assessmentStatus = new ArrayList<>(filters.size());
+			for(FlexiTableFilter filter:filters) {
+				if("passed".equals(filter.getFilter())) {
+					params.setPassed(true);
+				} else if("failed".equals(filter.getFilter())) {
+					params.setFailed(true);
+				} else if(AssessmentEntryStatus.isValueOf(filter.getFilter())){
+					assessmentStatus.add(AssessmentEntryStatus.valueOf(filter.getFilter()));
 				}
-			} else {
-				options.setGroup(group);
 			}
-			
-			//TODO qti filter by group?
-			List<Controller> tools = toolsCtrlCreator.createAssessmentTools(ureq, getWindowControl(), stackPanel, coachCourseEnv, options);
-			toolCmpNames = putToolsInContainer(tools);
-			bulkToolsList = tools;
-		}
-		flc.contextPut("toolCmpNames", toolCmpNames);
-	}
-	
-	private List<String> putToolsInContainer(List<Controller> tools) {
-		List<String> toolCmpNames = new ArrayList<>();
-		if(tools != null && !tools.isEmpty()) {
-			for(Controller tool:tools) {
-				listenTo(tool);
-				String toolCmpName = "ctrl_" + (counter++);
-				flc.put(toolCmpName, tool.getInitialComponent());
-				toolCmpNames.add(toolCmpName);
-				if(tool instanceof BreadcrumbPanelAware) {
-					((BreadcrumbPanelAware)tool).setBreadcrumbPanel(stackPanel);
+		}
+		params.setAssessmentStatus(assessmentStatus);
+		
+		List<Long> businessGroupKeys = null;
+		if(group != null) {
+			businessGroupKeys = Collections.singletonList(group.getKey());
+		} else if(extendedFilters != null && extendedFilters.size() > 0) {
+			businessGroupKeys = new ArrayList<>(extendedFilters.size());
+			for(FlexiTableFilter extendedFilter:extendedFilters) {
+				if(StringHelper.isLong(extendedFilter.getFilter())) {
+					businessGroupKeys.add(Long.parseLong(extendedFilter.getFilter()));
 				}
 			}
 		}
-		return toolCmpNames;
+		params.setBusinessGroupKeys(businessGroupKeys);
+		params.setSearchString(tableEl.getQuickSearchString());
+		return params;
+	}
+	
+	protected AssessmentToolOptions getOptions() {
+		SearchAssessedIdentityParams params = getSearchParameters();
+		AssessmentToolOptions options = new AssessmentToolOptions();
+		options.setAdmin(assessmentCallback.isAdmin());
+		if(group == null) {
+			if(assessmentCallback.isAdmin()) {
+				options.setNonMembers(params.isNonMembers());
+			} else {
+				List<Identity> assessedIdentities = assessmentToolManager.getAssessedIdentities(getIdentity(), params);
+				options.setIdentities(assessedIdentities);
+				fillAlternativeToAssessableIdentityList(options, params);
+			}
+		} else {
+			options.setGroup(group);
+		}
+		return options;
 	}
 	
 	private void fillAlternativeToAssessableIdentityList(AssessmentToolOptions options, SearchAssessedIdentityParams params) {
@@ -545,7 +529,7 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 		if(extendedFilters != null) {
 			tableEl.setSelectedExtendedFilters(extendedFilters);
 		}
-		updateModel(ureq, null, tableEl.getSelectedFilters(), tableEl.getSelectedExtendedFilters());
+		loadModel(ureq);
 		
 		if(entries != null && entries.size() > 0) {
 			ContextEntry entry = entries.get(0);
@@ -586,18 +570,18 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 		if(currentIdentityCtrl == source) {
 			if(event instanceof AssessmentFormEvent) {
 				AssessmentFormEvent aee = (AssessmentFormEvent)event;
-				updateModel(ureq, null, null, null);
+				loadModel(ureq);
 				if(aee.isClose()) {
 					stackPanel.popController(currentIdentityCtrl);
 				}
 			} else if(event == Event.CHANGED_EVENT) {
-				updateModel(ureq, null, null, null);
+				loadModel(ureq);
 			} else if(event == Event.CANCELLED_EVENT) {
 				stackPanel.popController(currentIdentityCtrl);
 			}
 		} else if(bulkToolsList != null && bulkToolsList.contains(source)) {
 			if(event == Event.CHANGED_EVENT) {
-				updateModel(ureq, null, null, null);
+				loadModel(ureq);
 			}
 		} else if(changeUserVisibilityCtrl == source) {
 			if(event == Event.DONE_EVENT) {
@@ -611,7 +595,7 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 				toolsCalloutCtrl.deactivate();
 				cleanUp();
 			} else if(event == Event.CHANGED_EVENT) {
-				updateModel(ureq, null, null, null);
+				loadModel(ureq);
 				toolsCalloutCtrl.deactivate();
 				cleanUp();
 			} else if(event == Event.CLOSE_EVENT) {
@@ -624,7 +608,7 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 		super.event(ureq, source, event);
 	}
 	
-	private void cleanUp() {
+	protected void cleanUp() {
 		removeAsListenerAndDispose(changeUserVisibilityCtrl);
 		removeAsListenerAndDispose(toolsCalloutCtrl);
 		removeAsListenerAndDispose(toolsCtrl);
@@ -646,8 +630,7 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 					doSelect(ureq, row);
 				}
 			} else if(event instanceof FlexiTableSearchEvent) {
-				FlexiTableSearchEvent ftse = (FlexiTableSearchEvent)event;
-				updateModel(ureq, ftse.getSearch(), ftse.getFilters(), ftse.getExtendedFilters());
+				loadModel(ureq);
 			}
 		} else if(bulkDoneButton == source) {
 			doSetDone(ureq);
@@ -664,13 +647,13 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 	}
 	
 	private void doOpenTools(UserRequest ureq, AssessedIdentityElementRow row, FormLink link) {
-		if(toolsCtrlCreator == null) return;
+		if(toolsCtrl != null) return;
 		
 		removeAsListenerAndDispose(toolsCtrl);
 		removeAsListenerAndDispose(toolsCalloutCtrl);
 
 		Identity assessedIdentity = securityManager.loadIdentityByKey(row.getIdentityKey());
-		toolsCtrl = toolsCtrlCreator.createCalloutController(ureq, getWindowControl(), coachCourseEnv, assessedIdentity);
+		toolsCtrl = createCalloutController(ureq, assessedIdentity);
 		listenTo(toolsCtrl);
 
 		toolsCalloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(),
@@ -679,6 +662,11 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 		toolsCalloutCtrl.activate();
 	}
 	
+	protected Controller createCalloutController(UserRequest ureq, Identity assessedIdentity) {
+		return new IdentityListCourseNodeToolsController(ureq, getWindowControl(),
+				(AssessableCourseNode)courseNode, assessedIdentity, coachCourseEnv);
+	}
+	
 	private void doNext(UserRequest ureq) {
 		stackPanel.popController(currentIdentityCtrl);
 		
@@ -798,7 +786,7 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 					scoreEval.getCurrentRunCompletion(), scoreEval.getCurrentRunStatus(), scoreEval.getAssessmentID());
 			assessableCourseNode.updateUserScoreEvaluation(doneEval, assessedUserCourseEnv, getIdentity(), false, Role.coach);
 		}
-		updateModel(ureq, null, null, null);
+		loadModel(ureq);
 	}
 	
 	private void doSetDone(UserRequest ureq) {
@@ -833,7 +821,7 @@ public class IdentityListCourseNodeController extends FormBasicController implem
 				assessableCourseNode.updateUserScoreEvaluation(doneEval, assessedUserCourseEnv, getIdentity(), false, Role.coach);
 			}
 			
-			updateModel(ureq, null, null, null);
+			loadModel(ureq);
 		}
 	}
 	
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeTableModel.java b/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeTableModel.java
index 1e009e54a10..0aad3aa841b 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeTableModel.java
+++ b/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeTableModel.java
@@ -161,6 +161,7 @@ public class IdentityListCourseNodeTableModel extends DefaultFlexiTableDataModel
 				case lastUserModified: return row.getLastUserModified();
 				case lastCoachModified: return row.getLastCoachModified();
 				case tools: return row.getToolsLink();
+				case details: return row.getDetails();
 			}
 		}
 		int propPos = col - AssessmentToolConstants.USER_PROPS_OFFSET;
@@ -190,7 +191,8 @@ public class IdentityListCourseNodeTableModel extends DefaultFlexiTableDataModel
 		lastCoachModified("table.header.lastCoachModificationDate"),
 		numOfAssessmentDocs("table.header.num.assessmentDocs"),
 		currentCompletion("table.header.completion"),
-		tools("table.header.tools");
+		tools("table.header.tools"),
+		details("table.header.details");
 		
 		private final String i18nKey;
 		
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/ToolsControllerCreator.java b/src/main/java/org/olat/course/assessment/ui/tool/ToolsControllerCreator.java
deleted file mode 100644
index a9979aac45a..00000000000
--- a/src/main/java/org/olat/course/assessment/ui/tool/ToolsControllerCreator.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * <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.course.assessment.ui.tool;
-
-import java.util.List;
-
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.stack.TooledStackedPanel;
-import org.olat.core.gui.control.Controller;
-import org.olat.core.gui.control.WindowControl;
-import org.olat.core.id.Identity;
-import org.olat.course.run.userview.UserCourseEnvironment;
-import org.olat.modules.assessment.AssessmentToolOptions;
-
-/**
- * 
- * Initial date: 23 nov. 2017<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public interface ToolsControllerCreator {
-	
-	public boolean hasCalloutTools();
-	
-	public Controller createCalloutController(UserRequest ureq, WindowControl wControl,
-			UserCourseEnvironment coachCourseEnv, Identity assessedIdentity);
-	
-	/**
-	 *  Factory method to launch course element assessment tools. limitToGroup is optional to skip he the group choose step
-	 */
-	public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
-			UserCourseEnvironment coachCourseEnv, AssessmentToolOptions options);
-	
-	
-	public List<Controller> createMultiSelectionTools(UserRequest ureq, WindowControl wControl,
-			UserCourseEnvironment coachCourseEnv, IdentityListCourseNodeProvider provider);
-
-}
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/_content/identity_courseelement.html b/src/main/java/org/olat/course/assessment/ui/tool/_content/identity_courseelement.html
index 1e37f4d0da0..5f498487a26 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/_content/identity_courseelement.html
+++ b/src/main/java/org/olat/course/assessment/ui/tool/_content/identity_courseelement.html
@@ -1,13 +1,4 @@
 <h2><i class="o_icon $courseNodeCssClass"> </i> $r.escapeHtml($courseNodeTitle)#if($r.isNotEmpty($businessGroupName)) <small><i class="o_icon o_icon_group"> </i> $r.escapeHtml($businessGroupName)</small>#end</h2>
-
-#if ($r.isNotEmpty($toolCmpNames))
-	<div class="o_button_group o_button_group_right">
-	#foreach($toolCmpName in $toolCmpNames)
-		$r.render($toolCmpName)
-	#end
-	</div>
-#end
-
 $r.render("table")
 <div class="o_button_group">
 	#if($r.available("bulk.done"))
@@ -16,9 +7,4 @@ $r.render("table")
 	#if($r.available("bulk.visible"))
 		$r.render("bulk.visible")
 	#end
-	#if ($r.isNotEmpty($multiSelectionCmpNames))
-		#foreach($multiSelectionCmpName in $multiSelectionCmpNames)
-			$r.render($multiSelectionCmpName)
-		#end
-	#end
 </div>
diff --git a/src/main/java/org/olat/course/groupsandrights/CourseGroupManager.java b/src/main/java/org/olat/course/groupsandrights/CourseGroupManager.java
index ef5ab2196ca..0c859e142ce 100644
--- a/src/main/java/org/olat/course/groupsandrights/CourseGroupManager.java
+++ b/src/main/java/org/olat/course/groupsandrights/CourseGroupManager.java
@@ -56,8 +56,6 @@ public interface CourseGroupManager {
 	public OLATResource getCourseResource();
 	
 	public RepositoryEntry getCourseEntry();
-	
-	//public void refreshRepositoryEntry(RepositoryEntry entry);
 
 	/**
 	 * Checks users course rights in any of the available right group context of
diff --git a/src/main/java/org/olat/course/nodes/AssessableCourseNode.java b/src/main/java/org/olat/course/nodes/AssessableCourseNode.java
index 44ee041f6c1..abed1a7f88c 100644
--- a/src/main/java/org/olat/course/nodes/AssessableCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/AssessableCourseNode.java
@@ -31,15 +31,20 @@ import java.util.List;
 
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.stack.BreadcrumbPanel;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.id.Identity;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
 import org.olat.course.run.scoring.AssessmentEvaluation;
 import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroup;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
+import org.olat.repository.RepositoryEntry;
 
 
 /**
@@ -189,14 +194,22 @@ public interface AssessableCourseNode extends CourseNode {
 			UserCourseEnvironment coachCourseEnv, UserCourseEnvironment assessedUserCourseEnvironment);
 	
 	/**
-	 * Method to get a factory to create the tools / actions controller
+	 * Returns the controller with the list of assessed identities for
+	 * a specific course node.
+	 * 
 	 * @param ureq
 	 * @param wControl
+	 * @param stackPanel
+	 * @param courseEntry
+	 * @param group
 	 * @param coachCourseEnv
-	 * @param assessedIdentity
+	 * @param toolContainer
+	 * @param assessmentCallback
 	 * @return
 	 */
-	public ToolsControllerCreator getAssessmentToolsCreator();
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback);
 	
 	/**
 	 * 
diff --git a/src/main/java/org/olat/course/nodes/BasicLTICourseNode.java b/src/main/java/org/olat/course/nodes/BasicLTICourseNode.java
index 85d4a0e8ac7..b463bc32b4e 100644
--- a/src/main/java/org/olat/course/nodes/BasicLTICourseNode.java
+++ b/src/main/java/org/olat/course/nodes/BasicLTICourseNode.java
@@ -31,6 +31,7 @@ import java.util.List;
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.stack.BreadcrumbPanel;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.iframe.DeliveryOptions;
@@ -44,9 +45,8 @@ import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
 import org.olat.course.ICourse;
 import org.olat.course.assessment.AssessmentManager;
-import org.olat.course.assessment.ui.tool.DefaultToolsControllerCreator;
-import org.olat.course.assessment.ui.tool.IdentityListCourseNodeToolsController;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeController;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.editor.CourseEditorEnv;
 import org.olat.course.editor.NodeEditController;
@@ -59,6 +59,7 @@ import org.olat.course.run.scoring.AssessmentEvaluation;
 import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroup;
 import org.olat.ims.lti.LTIDisplayOptions;
 import org.olat.ims.lti.LTIManager;
 import org.olat.ims.lti.ui.LTIResultDetailsController;
@@ -66,6 +67,8 @@ import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.repository.RepositoryEntry;
 import org.olat.resource.OLATResource;
 
@@ -449,19 +452,11 @@ public class BasicLTICourseNode extends AbstractAccessableCourseNode implements
 	}
 	
 	@Override
-	public ToolsControllerCreator getAssessmentToolsCreator() {
-		return new DefaultToolsControllerCreator() {
-			@Override
-			public boolean hasCalloutTools() {
-				return true;
-			}
-
-			@Override
-			public Controller createCalloutController(UserRequest ureq, WindowControl wControl,
-					UserCourseEnvironment coachCourseEnv, Identity assessedIdentity) {
-				return new IdentityListCourseNodeToolsController(ureq, wControl, BasicLTICourseNode.this, assessedIdentity, coachCourseEnv);
-			}
-		};
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		return new IdentityListCourseNodeController(ureq, wControl, stackPanel,
+				courseEntry, group, this, coachCourseEnv, toolContainer, assessmentCallback);
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/course/nodes/CheckListCourseNode.java b/src/main/java/org/olat/course/nodes/CheckListCourseNode.java
index 37e7b03d82e..5b8ac41f6e0 100644
--- a/src/main/java/org/olat/course/nodes/CheckListCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/CheckListCourseNode.java
@@ -34,6 +34,7 @@ import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.persistence.DBFactory;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.stack.BreadcrumbPanel;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.messages.MessageUIFactory;
@@ -55,9 +56,8 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.course.ICourse;
 import org.olat.course.assessment.AssessmentManager;
-import org.olat.course.assessment.ui.tool.DefaultToolsControllerCreator;
-import org.olat.course.assessment.ui.tool.IdentityListCourseNodeToolsController;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeController;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.editor.CourseEditorEnv;
 import org.olat.course.editor.NodeEditController;
@@ -78,10 +78,13 @@ import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.course.run.userview.UserCourseEnvironmentImpl;
+import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.repository.RepositoryEntry;
 
 /**
@@ -511,19 +514,11 @@ public class CheckListCourseNode extends AbstractAccessableCourseNode implements
 	}
 	
 	@Override
-	public ToolsControllerCreator getAssessmentToolsCreator() {
-		return new DefaultToolsControllerCreator() {
-			@Override
-			public boolean hasCalloutTools() {
-				return true;
-			}
-
-			@Override
-			public Controller createCalloutController(UserRequest ureq, WindowControl wControl,
-					UserCourseEnvironment coachCourseEnv, Identity assessedIdentity) {
-				return new IdentityListCourseNodeToolsController(ureq, wControl, CheckListCourseNode.this, assessedIdentity, coachCourseEnv);
-			}
-		};
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		return new IdentityListCourseNodeController(ureq, wControl, stackPanel,
+				courseEntry, group, this, coachCourseEnv, toolContainer, assessmentCallback);
 	}
 	
 	/**
@@ -531,7 +526,7 @@ public class CheckListCourseNode extends AbstractAccessableCourseNode implements
 	 */
 	@Override
 	public String getDetailsListView(UserCourseEnvironment userCourseEnvironment) {
-		return "checklist";
+		return null;
 	}
 
 	/**
diff --git a/src/main/java/org/olat/course/nodes/GTACourseNode.java b/src/main/java/org/olat/course/nodes/GTACourseNode.java
index 8f5f607abd8..4b9bc346e85 100644
--- a/src/main/java/org/olat/course/nodes/GTACourseNode.java
+++ b/src/main/java/org/olat/course/nodes/GTACourseNode.java
@@ -61,10 +61,7 @@ import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
 import org.olat.course.archiver.ScoreAccountingHelper;
 import org.olat.course.assessment.AssessmentManager;
-import org.olat.course.assessment.bulk.BulkAssessmentToolController;
-import org.olat.course.assessment.ui.tool.DefaultToolsControllerCreator;
-import org.olat.course.assessment.ui.tool.IdentityListCourseNodeToolsController;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.editor.CourseEditorEnv;
 import org.olat.course.editor.NodeEditController;
@@ -78,13 +75,11 @@ import org.olat.course.nodes.gta.Task;
 import org.olat.course.nodes.gta.TaskHelper;
 import org.olat.course.nodes.gta.TaskList;
 import org.olat.course.nodes.gta.model.TaskDefinition;
-import org.olat.course.nodes.gta.ui.BulkDownloadToolController;
 import org.olat.course.nodes.gta.ui.GTAAssessmentDetailsController;
 import org.olat.course.nodes.gta.ui.GTACoachedGroupListController;
 import org.olat.course.nodes.gta.ui.GTAEditController;
-import org.olat.course.nodes.gta.ui.GTAGroupAssessmentToolController;
+import org.olat.course.nodes.gta.ui.GTAIdentityListCourseNodeController;
 import org.olat.course.nodes.gta.ui.GTARunController;
-import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.run.navigation.NodeRunConstructionResult;
 import org.olat.course.run.scoring.AssessmentEvaluation;
 import org.olat.course.run.scoring.ScoreEvaluation;
@@ -93,9 +88,10 @@ import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
-import org.olat.modules.assessment.AssessmentToolOptions;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.repository.RepositoryEntry;
 import org.olat.user.UserManager;
 
@@ -888,59 +884,12 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Persi
 		return new GTACoachedGroupListController(ureq, wControl, stackPanel, coachCourseEnv, this, groups);
 	}
 
-
-	
 	@Override
-	public ToolsControllerCreator getAssessmentToolsCreator() {
-		return new DefaultToolsControllerCreator() {
-			@Override
-			public boolean hasCalloutTools() {
-				return true;
-			}
-
-			@Override
-			public Controller createCalloutController(UserRequest ureq, WindowControl wControl,
-					UserCourseEnvironment coachCourseEnv, Identity assessedIdentity) {
-				return new IdentityListCourseNodeToolsController(ureq, wControl, GTACourseNode.this, assessedIdentity, coachCourseEnv);
-			}
-
-			@Override
-			public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl,
-					TooledStackedPanel stackPanel, UserCourseEnvironment coachCourseEnv,
-					AssessmentToolOptions options) {
-				return createAssessmentToolList(ureq, wControl, coachCourseEnv, options);
-			}
-		};
-	}
-
-	private List<Controller> createAssessmentToolList(UserRequest ureq, WindowControl wControl,
-			UserCourseEnvironment coachCourseEnv, AssessmentToolOptions options) {
-		ModuleConfiguration config =  getModuleConfiguration();
-		CourseEnvironment courseEnv = coachCourseEnv.getCourseEnvironment();
-		List<Controller> tools = new ArrayList<>(2);
-		if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))
-			&& (config.getBooleanSafe(GTASK_ASSIGNMENT)
-				|| config.getBooleanSafe(GTASK_SUBMIT)
-				|| config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION)
-				|| config.getBooleanSafe(GTASK_REVISION_PERIOD))) {
-			
-			if(options.getGroup() != null && !coachCourseEnv.isCourseReadOnly()) {
-				tools.add(new GTAGroupAssessmentToolController(ureq, wControl, courseEnv, options.getGroup(), this));
-			}
-			tools.add(new BulkDownloadToolController(ureq, wControl, courseEnv, options, this));
-		} else if(GTAType.individual.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) {
-			if(!coachCourseEnv.isCourseReadOnly() && (config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION) || config.getBooleanSafe(GTASK_GRADING))){
-				tools.add(new BulkAssessmentToolController(ureq, wControl, courseEnv, this));
-			}
-			
-			if(config.getBooleanSafe(GTASK_ASSIGNMENT)
-					|| config.getBooleanSafe(GTASK_SUBMIT)
-					|| config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION)
-					|| config.getBooleanSafe(GTASK_REVISION_PERIOD)) {
-				tools.add(new BulkDownloadToolController(ureq, wControl, courseEnv, options, this));
-			}
-		}
-		return tools;
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		return new GTAIdentityListCourseNodeController(ureq, wControl, stackPanel,
+				courseEntry, group, this, coachCourseEnv, toolContainer, assessmentCallback);
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java b/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
index 3523bf598ad..96ce9b104fe 100644
--- a/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
@@ -28,7 +28,6 @@ package org.olat.course.nodes;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -57,21 +56,17 @@ import org.olat.core.util.resource.OresHelper;
 import org.olat.course.ICourse;
 import org.olat.course.archiver.ScoreAccountingHelper;
 import org.olat.course.assessment.AssessmentManager;
-import org.olat.course.assessment.ui.tool.DefaultToolsControllerCreator;
-import org.olat.course.assessment.ui.tool.IdentityListCourseNodeProvider;
-import org.olat.course.assessment.ui.tool.IdentityListCourseNodeToolsController;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.editor.CourseEditorEnv;
 import org.olat.course.editor.NodeEditController;
 import org.olat.course.editor.StatusDescription;
 import org.olat.course.nodes.iq.CourseIQSecurityCallback;
-import org.olat.course.nodes.iq.QTI21ExtraTimeController;
 import org.olat.course.nodes.iq.IQEditController;
+import org.olat.course.nodes.iq.IQIdentityListCourseNodeController;
 import org.olat.course.nodes.iq.IQPreviewController;
 import org.olat.course.nodes.iq.IQRunController;
 import org.olat.course.nodes.iq.QTI21AssessmentRunController;
-import org.olat.course.nodes.iq.QTI21IdentityListCourseNodeToolsController;
 import org.olat.course.properties.CoursePropertyManager;
 import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.run.navigation.NodeRunConstructionResult;
@@ -83,6 +78,7 @@ import org.olat.course.statistic.StatisticResourceOption;
 import org.olat.course.statistic.StatisticResourceResult;
 import org.olat.fileresource.FileResourceManager;
 import org.olat.fileresource.types.ImsQTI21Resource;
+import org.olat.group.BusinessGroup;
 import org.olat.ims.qti.QTI12ResultDetailsController;
 import org.olat.ims.qti.QTIResultManager;
 import org.olat.ims.qti.QTIResultSet;
@@ -99,35 +95,27 @@ import org.olat.ims.qti.export.QTIExportSCQItemFormatConfig;
 import org.olat.ims.qti.fileresource.TestFileResource;
 import org.olat.ims.qti.process.AssessmentInstance;
 import org.olat.ims.qti.process.FilePersister;
-import org.olat.ims.qti.resultexport.QTI12ExportResultsReportController;
 import org.olat.ims.qti.resultexport.QTI12ResultsExportMediaResource;
 import org.olat.ims.qti.statistics.QTIStatisticResourceResult;
 import org.olat.ims.qti.statistics.QTIStatisticSearchParams;
 import org.olat.ims.qti.statistics.QTIType;
-import org.olat.ims.qti.statistics.ui.QTI12PullTestsToolController;
-import org.olat.ims.qti.statistics.ui.QTI12StatisticsToolController;
 import org.olat.ims.qti21.AssessmentTestSession;
 import org.olat.ims.qti21.QTI21DeliveryOptions;
 import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.manager.AssessmentTestSessionDAO;
 import org.olat.ims.qti21.manager.archive.QTI21ArchiveFormat;
 import org.olat.ims.qti21.model.QTI21StatisticSearchParams;
-import org.olat.ims.qti21.resultexport.QTI21ExportResultsReportController;
 import org.olat.ims.qti21.resultexport.QTI21ResultsExportMediaResource;
 import org.olat.ims.qti21.ui.QTI21AssessmentDetailsController;
-import org.olat.ims.qti21.ui.QTI21ResetToolController;
-import org.olat.ims.qti21.ui.QTI21RetrieveTestsToolController;
-import org.olat.ims.qti21.ui.assessment.QTI21CorrectionToolController;
-import org.olat.ims.qti21.ui.assessment.QTI21ValidationToolController;
 import org.olat.ims.qti21.ui.statistics.QTI21StatisticResourceResult;
 import org.olat.ims.qti21.ui.statistics.QTI21StatisticsSecurityCallback;
-import org.olat.ims.qti21.ui.statistics.QTI21StatisticsToolController;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
-import org.olat.modules.assessment.AssessmentToolOptions;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentEntryStatus;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.modules.iq.IQSecurityCallback;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryImportExport;
@@ -238,20 +226,49 @@ public class IQTESTCourseNode extends AbstractAccessableCourseNode implements Pe
 	 */
 	public boolean hasQTI21TimeLimit(RepositoryEntry testEntry) {
 		boolean timeLimit = false;
-		
-		ModuleConfiguration config = getModuleConfiguration();
-		boolean configRef = config.getBooleanSafe(IQEditController.CONFIG_KEY_CONFIG_REF, false);
-		if(!configRef && config.getIntegerSafe(IQEditController.CONFIG_KEY_TIME_LIMIT, -1) > 0) {
-			timeLimit = true;
-		} else {
-			File unzippedDirRoot = FileResourceManager.getInstance().unzipFileResource(testEntry.getOlatResource());
-			ResolvedAssessmentTest resolvedAssessmentTest = CoreSpringFactory.getImpl(QTI21Service.class)
-					.loadAndResolveAssessmentTest(unzippedDirRoot, false, false);
-			AssessmentTest assessmentTest = resolvedAssessmentTest.getRootNodeLookup().extractIfSuccessful();
-			if(assessmentTest.getTimeLimits() != null && assessmentTest.getTimeLimits().getMaximum() != null) {
+		if(ImsQTI21Resource.TYPE_NAME.equals(testEntry.getOlatResource().getResourceableTypeName())) {
+			ModuleConfiguration config = getModuleConfiguration();
+			boolean configRef = config.getBooleanSafe(IQEditController.CONFIG_KEY_CONFIG_REF, false);
+			if(!configRef && config.getIntegerSafe(IQEditController.CONFIG_KEY_TIME_LIMIT, -1) > 0) {
 				timeLimit = true;
+			} else {
+				File unzippedDirRoot = FileResourceManager.getInstance().unzipFileResource(testEntry.getOlatResource());
+				ResolvedAssessmentTest resolvedAssessmentTest = CoreSpringFactory.getImpl(QTI21Service.class)
+						.loadAndResolveAssessmentTest(unzippedDirRoot, false, false);
+				AssessmentTest assessmentTest = resolvedAssessmentTest.getRootNodeLookup().extractIfSuccessful();
+				if(assessmentTest.getTimeLimits() != null && assessmentTest.getTimeLimits().getMaximum() != null) {
+					timeLimit = true;
+				}
 			}
-		}	
+		}
+		return timeLimit;
+	}
+	
+	/**
+	 * If the course element override the test configuration, the value is from
+	 * the course element's configuration. Else, the value is from the assessment
+	 * test.
+	 * 
+	 * @param testEntry The test repository entry
+	 * @return the maximum time limit in seconds or -1 if no time limit is configured
+	 */
+	public int getQTI21TimeLimitMaxInSeconds(RepositoryEntry testEntry) {
+		int timeLimit = -1;
+		if(ImsQTI21Resource.TYPE_NAME.equals(testEntry.getOlatResource().getResourceableTypeName())) {
+			ModuleConfiguration config = getModuleConfiguration();
+			boolean configRef = config.getBooleanSafe(IQEditController.CONFIG_KEY_CONFIG_REF, false);
+			if(!configRef && config.getIntegerSafe(IQEditController.CONFIG_KEY_TIME_LIMIT, -1) > 0) {
+				timeLimit = config.getIntegerSafe(IQEditController.CONFIG_KEY_TIME_LIMIT, -1);
+			} else {
+				File unzippedDirRoot = FileResourceManager.getInstance().unzipFileResource(testEntry.getOlatResource());
+				ResolvedAssessmentTest resolvedAssessmentTest = CoreSpringFactory.getImpl(QTI21Service.class)
+						.loadAndResolveAssessmentTest(unzippedDirRoot, false, false);
+				AssessmentTest assessmentTest = resolvedAssessmentTest.getRootNodeLookup().extractIfSuccessful();
+				if(assessmentTest.getTimeLimits() != null && assessmentTest.getTimeLimits().getMaximum() != null) {
+					timeLimit = assessmentTest.getTimeLimits().getMaximum().intValue();
+				}
+			}
+		}
 		return timeLimit;
 	}
 
@@ -275,81 +292,11 @@ public class IQTESTCourseNode extends AbstractAccessableCourseNode implements Pe
 	}
 	
 	@Override
-	public ToolsControllerCreator getAssessmentToolsCreator() {
-		return new DefaultToolsControllerCreator() {
-			@Override
-			public boolean hasCalloutTools() {
-				return true;
-			}
-
-			@Override
-			public Controller createCalloutController(UserRequest ureq, WindowControl wControl,
-					UserCourseEnvironment coachCourseEnv, Identity assessedIdentity) {
-				boolean qti21 = IQEditController.CONFIG_VALUE_QTI21.equals(getModuleConfiguration().get(IQEditController.CONFIG_KEY_TYPE_QTI));
-				if (qti21) {
-					return new QTI21IdentityListCourseNodeToolsController(ureq, wControl, IQTESTCourseNode.this, assessedIdentity, coachCourseEnv);
-				}
-				return  new IdentityListCourseNodeToolsController(ureq, wControl, IQTESTCourseNode.this, assessedIdentity, coachCourseEnv);
-			}
-
-			@Override
-			public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl,
-					TooledStackedPanel stackPanel, UserCourseEnvironment coachCourseEnv,
-					AssessmentToolOptions options) {
-				return createAssessmentToolList(ureq, wControl, stackPanel, coachCourseEnv, options);
-			}
-
-			@Override
-			public List<Controller> createMultiSelectionTools(UserRequest ureq, WindowControl wControl,
-					UserCourseEnvironment coachCourseEnv, IdentityListCourseNodeProvider provider) {
-				return createMultiSelectionToolList(ureq, wControl, coachCourseEnv, provider);
-			}
-		};
-	}
-	
-	private List<Controller> createMultiSelectionToolList(UserRequest ureq, WindowControl wControl,
-			UserCourseEnvironment coachCourseEnv, IdentityListCourseNodeProvider provider) {
-		List<Controller> tools = new ArrayList<>(2);
-		RepositoryEntry qtiTestEntry = getReferencedRepositoryEntry();
-		if(ImsQTI21Resource.TYPE_NAME.equals(qtiTestEntry.getOlatResource().getResourceableTypeName()) && hasQTI21TimeLimit(qtiTestEntry)) {
-			tools.add(new QTI21ExtraTimeController(ureq, wControl, coachCourseEnv.getCourseEnvironment(), this, provider));
-		}
-		return tools;
-	}
-
-	private List<Controller> createAssessmentToolList(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
-			UserCourseEnvironment coachCourseEnv, AssessmentToolOptions options) {
-		List<Controller> tools = new ArrayList<>();
-		
-		CourseEnvironment courseEnv = coachCourseEnv.getCourseEnvironment();
-		
-		RepositoryEntry qtiTestEntry = getReferencedRepositoryEntry();
-		if(ImsQTI21Resource.TYPE_NAME.equals(qtiTestEntry.getOlatResource().getResourceableTypeName())) {
-			tools.add(new QTI21StatisticsToolController(ureq, wControl, stackPanel, courseEnv, options, this));
-			if(!coachCourseEnv.isCourseReadOnly()) {
-				QTI21Service qtiService = CoreSpringFactory.getImpl(QTI21Service.class);
-				tools.add(new QTI21RetrieveTestsToolController(ureq, wControl, courseEnv, options, this));
-				if(options.isAdmin()) {
-					tools.add(new QTI21ResetToolController(ureq, wControl, courseEnv, options, this));
-				}
-				if(qtiService.needManualCorrection(qtiTestEntry)
-						|| IQEditController.CORRECTION_MANUAL.equals(getModuleConfiguration().getStringValue(IQEditController.CONFIG_CORRECTION_MODE))) {
-					tools.add(new QTI21CorrectionToolController(ureq, wControl, courseEnv, options, this));
-				}
-				if(getModuleConfiguration().getBooleanSafe(IQEditController.CONFIG_DIGITAL_SIGNATURE, false)) {
-					tools.add(new QTI21ValidationToolController(ureq, wControl));
-				}
-			}
-			tools.add(new QTI21ExportResultsReportController(ureq, wControl, courseEnv, options, this));
-
-		} else {
-			tools.add(new QTI12StatisticsToolController(ureq, wControl, stackPanel, courseEnv, options, this));
-			if(!coachCourseEnv.isCourseReadOnly()) {
-				tools.add(new QTI12PullTestsToolController(ureq, wControl, courseEnv, options, this));
-			}
-			tools.add(new QTI12ExportResultsReportController(ureq, wControl, courseEnv, options, this));
-		}
-		return tools;
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		return new IQIdentityListCourseNodeController(ureq, wControl, stackPanel,
+				courseEntry, group, this, coachCourseEnv, toolContainer, assessmentCallback);
 	}
 
 	public boolean isQTI12TestRunning(Identity assessedIdentity, CourseEnvironment courseEnv) {
diff --git a/src/main/java/org/olat/course/nodes/MSCourseNode.java b/src/main/java/org/olat/course/nodes/MSCourseNode.java
index b1f4b679fd4..629641a2ba2 100644
--- a/src/main/java/org/olat/course/nodes/MSCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/MSCourseNode.java
@@ -26,7 +26,6 @@
 package org.olat.course.nodes;
 
 import java.io.File;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
@@ -47,10 +46,7 @@ import org.olat.core.logging.OLATRuntimeException;
 import org.olat.core.util.Util;
 import org.olat.course.ICourse;
 import org.olat.course.assessment.AssessmentManager;
-import org.olat.course.assessment.bulk.BulkAssessmentToolController;
-import org.olat.course.assessment.ui.tool.DefaultToolsControllerCreator;
-import org.olat.course.assessment.ui.tool.IdentityListCourseNodeToolsController;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.editor.CourseEditorEnv;
 import org.olat.course.editor.NodeEditController;
@@ -58,19 +54,21 @@ import org.olat.course.editor.StatusDescription;
 import org.olat.course.nodes.ms.MSCourseNodeEditController;
 import org.olat.course.nodes.ms.MSCourseNodeRunController;
 import org.olat.course.nodes.ms.MSEditFormController;
+import org.olat.course.nodes.ms.MSIdentityListCourseNodeController;
 import org.olat.course.properties.CoursePropertyManager;
 import org.olat.course.properties.PersistingCoursePropertyManager;
-import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.run.navigation.NodeRunConstructionResult;
 import org.olat.course.run.scoring.AssessmentEvaluation;
 import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
-import org.olat.modules.assessment.AssessmentToolOptions;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.properties.Property;
 import org.olat.repository.RepositoryEntry;
 import org.olat.resource.OLATResource;
@@ -526,31 +524,11 @@ public class MSCourseNode extends AbstractAccessableCourseNode implements Persis
 	}
 	
 	@Override
-	public ToolsControllerCreator getAssessmentToolsCreator() {
-		return new DefaultToolsControllerCreator() {
-			@Override
-			public boolean hasCalloutTools() {
-				return true;
-			}
-
-			@Override
-			public Controller createCalloutController(UserRequest ureq, WindowControl wControl,
-					UserCourseEnvironment coachCourseEnv, Identity assessedIdentity) {
-				return new IdentityListCourseNodeToolsController(ureq, wControl, MSCourseNode.this, assessedIdentity, coachCourseEnv);
-			}
-
-			@Override
-			public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl,
-					TooledStackedPanel stackPanel, UserCourseEnvironment coachCourseEnv,
-					AssessmentToolOptions options) {
-				List<Controller> tools = new ArrayList<>(1);
-				if(!coachCourseEnv.isCourseReadOnly()) {
-					CourseEnvironment courseEnv = coachCourseEnv.getCourseEnvironment();
-					tools.add(new BulkAssessmentToolController(ureq, wControl, courseEnv, MSCourseNode.this));
-				}
-				return tools;
-			}
-		};
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		return new MSIdentityListCourseNodeController(ureq, wControl, stackPanel,
+				courseEntry, group, this, coachCourseEnv, toolContainer, assessmentCallback);
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/course/nodes/PortfolioCourseNode.java b/src/main/java/org/olat/course/nodes/PortfolioCourseNode.java
index 718c1b54325..fb583f15ca3 100644
--- a/src/main/java/org/olat/course/nodes/PortfolioCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/PortfolioCourseNode.java
@@ -29,6 +29,7 @@ import java.util.Locale;
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.stack.BreadcrumbPanel;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.messages.MessageUIFactory;
@@ -44,9 +45,8 @@ import org.olat.core.util.Util;
 import org.olat.core.util.ValidationStatus;
 import org.olat.course.ICourse;
 import org.olat.course.assessment.AssessmentManager;
-import org.olat.course.assessment.ui.tool.DefaultToolsControllerCreator;
-import org.olat.course.assessment.ui.tool.IdentityListCourseNodeToolsController;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeController;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.condition.Condition;
 import org.olat.course.condition.interpreter.ConditionInterpreter;
@@ -64,10 +64,13 @@ import org.olat.course.run.scoring.AssessmentEvaluation;
 import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.modules.portfolio.PortfolioService;
 import org.olat.modules.portfolio.handler.BinderTemplateResource;
 import org.olat.modules.portfolio.ui.PortfolioAssessmentDetailsController;
@@ -169,19 +172,11 @@ public class PortfolioCourseNode extends AbstractAccessableCourseNode implements
 	}
 	
 	@Override
-	public ToolsControllerCreator getAssessmentToolsCreator() {
-		return new DefaultToolsControllerCreator() {
-			@Override
-			public boolean hasCalloutTools() {
-				return true;
-			}
-
-			@Override
-			public Controller createCalloutController(UserRequest ureq, WindowControl wControl,
-					UserCourseEnvironment coachCourseEnv, Identity assessedIdentity) {
-				return new IdentityListCourseNodeToolsController(ureq, wControl, PortfolioCourseNode.this, assessedIdentity, coachCourseEnv);
-			}
-		};
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		return new IdentityListCourseNodeController(ureq, wControl, stackPanel,
+				courseEntry, group, this, coachCourseEnv, toolContainer, assessmentCallback);
 	}
 	
 	/**
@@ -476,7 +471,7 @@ public class PortfolioCourseNode extends AbstractAccessableCourseNode implements
 
 	@Override
 	public String getDetailsListViewHeaderKey() {
-		return "table.header.details.ta";
+		return null;
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/course/nodes/ProjectBrokerCourseNode.java b/src/main/java/org/olat/course/nodes/ProjectBrokerCourseNode.java
index d8c193ea398..e5b32279356 100644
--- a/src/main/java/org/olat/course/nodes/ProjectBrokerCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/ProjectBrokerCourseNode.java
@@ -78,9 +78,7 @@ import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.xml.XStreamHelper;
 import org.olat.course.ICourse;
 import org.olat.course.assessment.AssessmentManager;
-import org.olat.course.assessment.bulk.BulkAssessmentToolController;
-import org.olat.course.assessment.ui.tool.DefaultToolsControllerCreator;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.condition.Condition;
 import org.olat.course.condition.interpreter.ConditionExpression;
@@ -92,6 +90,7 @@ import org.olat.course.export.CourseEnvironmentMapper;
 import org.olat.course.nodes.ms.MSEditFormController;
 import org.olat.course.nodes.projectbroker.ProjectBrokerControllerFactory;
 import org.olat.course.nodes.projectbroker.ProjectBrokerCourseEditorController;
+import org.olat.course.nodes.projectbroker.ProjectBrokerIdentityListCourseNodeController;
 import org.olat.course.nodes.projectbroker.ProjectListController;
 import org.olat.course.nodes.projectbroker.datamodel.Project;
 import org.olat.course.nodes.projectbroker.datamodel.ProjectBroker;
@@ -103,7 +102,6 @@ import org.olat.course.nodes.ta.ReturnboxController;
 import org.olat.course.nodes.ta.TaskController;
 import org.olat.course.properties.CoursePropertyManager;
 import org.olat.course.properties.PersistingCoursePropertyManager;
-import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.run.navigation.NodeRunConstructionResult;
 import org.olat.course.run.scoring.AssessmentEvaluation;
 import org.olat.course.run.scoring.ScoreEvaluation;
@@ -114,9 +112,10 @@ import org.olat.group.BusinessGroupService;
 import org.olat.group.model.BusinessGroupReference;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
-import org.olat.modules.assessment.AssessmentToolOptions;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.properties.Property;
 import org.olat.repository.RepositoryEntry;
 import org.olat.resource.OLATResource;
@@ -745,25 +744,13 @@ public class ProjectBrokerCourseNode extends GenericCourseNode implements Persis
 	}
 	
 	@Override
-	public ToolsControllerCreator getAssessmentToolsCreator() {
-		return new DefaultToolsControllerCreator() {
-			@Override
-			public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl,
-					TooledStackedPanel stackPanel, UserCourseEnvironment coachCourseEnv,
-					AssessmentToolOptions options) {
-				List<Controller> tools = new ArrayList<>(1);
-				if(!coachCourseEnv.isCourseReadOnly()) {
-					CourseEnvironment courseEnv = coachCourseEnv.getCourseEnvironment();
-					tools.add(new BulkAssessmentToolController(ureq, wControl, courseEnv, ProjectBrokerCourseNode.this));
-				}
-				return tools;
-			}
-		};
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		return new ProjectBrokerIdentityListCourseNodeController(ureq, wControl, stackPanel,
+				courseEntry, group, this, coachCourseEnv, toolContainer, assessmentCallback);
 	}
 
-	/**
-	 * @see org.olat.course.nodes.AssessableCourseNode#getDetailsListView(org.olat.course.run.userview.UserCourseEnvironment)
-	 */
 	@Override
 	public String getDetailsListView(UserCourseEnvironment userCourseEnvironment) {
 		Identity identity = userCourseEnvironment.getIdentityEnvironment().getIdentity();
@@ -775,7 +762,7 @@ public class ProjectBrokerCourseNode extends GenericCourseNode implements Persis
 
 	@Override
 	public String getDetailsListViewHeaderKey() {
-		return "table.header.details.ta";
+		return null;
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/course/nodes/STCourseNode.java b/src/main/java/org/olat/course/nodes/STCourseNode.java
index 05b28cbfdbe..7e6583749c6 100644
--- a/src/main/java/org/olat/course/nodes/STCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/STCourseNode.java
@@ -36,6 +36,7 @@ import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory;
 import org.olat.core.commons.modules.singlepage.SinglePageController;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.stack.BreadcrumbPanel;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.creator.ControllerCreator;
@@ -56,8 +57,7 @@ import org.olat.core.util.resource.OresHelper;
 import org.olat.course.CourseFactory;
 import org.olat.course.CourseModule;
 import org.olat.course.ICourse;
-import org.olat.course.assessment.ui.tool.DefaultToolsControllerCreator;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
 import org.olat.course.condition.Condition;
 import org.olat.course.condition.KeyAndNameConverter;
 import org.olat.course.condition.interpreter.ConditionExpression;
@@ -72,6 +72,7 @@ import org.olat.course.nodes.sp.SPEditController;
 import org.olat.course.nodes.sp.SPPeekviewController;
 import org.olat.course.nodes.st.STCourseNodeEditController;
 import org.olat.course.nodes.st.STCourseNodeRunController;
+import org.olat.course.nodes.st.STIdentityListCourseNodeController;
 import org.olat.course.nodes.st.STPeekViewController;
 import org.olat.course.run.navigation.NodeRunConstructionResult;
 import org.olat.course.run.scoring.AssessmentEvaluation;
@@ -81,10 +82,13 @@ import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.course.tree.CourseInternalLinkTreeModel;
+import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.repository.RepositoryEntry;
 import org.olat.util.logging.activity.LoggingResourceable;
 
@@ -653,10 +657,13 @@ public class STCourseNode extends AbstractAccessableCourseNode implements Calcul
 	public String getDetailsListViewHeaderKey() {
 		throw new OLATRuntimeException(STCourseNode.class, "Details not available in ST nodes", null);
 	}
-
+	
 	@Override
-	public ToolsControllerCreator getAssessmentToolsCreator() {
-		return new DefaultToolsControllerCreator();
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		return new STIdentityListCourseNodeController(ureq, wControl, stackPanel,
+				courseEntry, group, this, coachCourseEnv, toolContainer, assessmentCallback);
 	}
 
 	/**
diff --git a/src/main/java/org/olat/course/nodes/ScormCourseNode.java b/src/main/java/org/olat/course/nodes/ScormCourseNode.java
index 789c0673ac7..faea014f590 100644
--- a/src/main/java/org/olat/course/nodes/ScormCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/ScormCourseNode.java
@@ -36,6 +36,7 @@ import java.util.zip.ZipOutputStream;
 import org.apache.commons.io.IOUtils;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.stack.BreadcrumbPanel;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.iframe.DeliveryOptions;
@@ -50,9 +51,8 @@ import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
 import org.olat.course.ICourse;
 import org.olat.course.assessment.AssessmentManager;
-import org.olat.course.assessment.ui.tool.DefaultToolsControllerCreator;
-import org.olat.course.assessment.ui.tool.IdentityListCourseNodeToolsController;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeController;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.editor.CourseEditorEnv;
 import org.olat.course.editor.NodeEditController;
@@ -67,10 +67,13 @@ import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.fileresource.types.ScormCPFileResource;
+import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.modules.scorm.ScormMainManager;
 import org.olat.modules.scorm.ScormPackageConfig;
 import org.olat.modules.scorm.archiver.ScormExportManager;
@@ -148,20 +151,11 @@ public class ScormCourseNode extends AbstractAccessableCourseNode implements Per
 	}
 	
 	@Override
-	public ToolsControllerCreator getAssessmentToolsCreator() {
-		return new DefaultToolsControllerCreator() {
-
-			@Override
-			public boolean hasCalloutTools() {
-				return true;
-			}
-
-			@Override
-			public Controller createCalloutController(UserRequest ureq, WindowControl wControl,
-					UserCourseEnvironment coachCourseEnv, Identity assessedIdentity) {
-				return new IdentityListCourseNodeToolsController(ureq, wControl, ScormCourseNode.this, assessedIdentity, coachCourseEnv);
-			}
-		};
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		return new IdentityListCourseNodeController(ureq, wControl, stackPanel,
+				courseEntry, group, this, coachCourseEnv, toolContainer, assessmentCallback);
 	}
 
 	/**
diff --git a/src/main/java/org/olat/course/nodes/TACourseNode.java b/src/main/java/org/olat/course/nodes/TACourseNode.java
index 6674c612430..d047e2acc08 100644
--- a/src/main/java/org/olat/course/nodes/TACourseNode.java
+++ b/src/main/java/org/olat/course/nodes/TACourseNode.java
@@ -68,10 +68,7 @@ import org.olat.core.util.vfs.VFSManager;
 import org.olat.course.ICourse;
 import org.olat.course.archiver.ScoreAccountingHelper;
 import org.olat.course.assessment.AssessmentManager;
-import org.olat.course.assessment.bulk.BulkAssessmentToolController;
-import org.olat.course.assessment.ui.tool.DefaultToolsControllerCreator;
-import org.olat.course.assessment.ui.tool.IdentityListCourseNodeToolsController;
-import org.olat.course.assessment.ui.tool.ToolsControllerCreator;
+import org.olat.course.assessment.ui.tool.AssessmentCourseNodeController;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.condition.Condition;
 import org.olat.course.condition.interpreter.ConditionExpression;
@@ -81,13 +78,13 @@ import org.olat.course.editor.NodeEditController;
 import org.olat.course.editor.StatusDescription;
 import org.olat.course.export.CourseEnvironmentMapper;
 import org.olat.course.nodes.ms.MSEditFormController;
-import org.olat.course.nodes.ta.BulkDownloadToolController;
 import org.olat.course.nodes.ta.ConvertToGTACourseNode;
 import org.olat.course.nodes.ta.DropboxController;
 import org.olat.course.nodes.ta.DropboxScoringViewController;
 import org.olat.course.nodes.ta.ReturnboxController;
 import org.olat.course.nodes.ta.TACourseNodeEditController;
 import org.olat.course.nodes.ta.TACourseNodeRunController;
+import org.olat.course.nodes.ta.TAIdentityListCourseNodeController;
 import org.olat.course.nodes.ta.TaskController;
 import org.olat.course.properties.CoursePropertyManager;
 import org.olat.course.properties.PersistingCoursePropertyManager;
@@ -97,11 +94,13 @@ import org.olat.course.run.scoring.AssessmentEvaluation;
 import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
-import org.olat.modules.assessment.AssessmentToolOptions;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentRunStatus;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.properties.Property;
 import org.olat.repository.RepositoryEntry;
 import org.olat.resource.OLATResource;
@@ -786,32 +785,11 @@ public class TACourseNode extends GenericCourseNode implements PersistentAssessa
 	}
 	
 	@Override
-	public ToolsControllerCreator getAssessmentToolsCreator() {
-		return new DefaultToolsControllerCreator() {
-			@Override
-			public boolean hasCalloutTools() {
-				return true;
-			}
-
-			@Override
-			public Controller createCalloutController(UserRequest ureq, WindowControl wControl,
-					UserCourseEnvironment coachCourseEnv, Identity assessedIdentity) {
-				return new IdentityListCourseNodeToolsController(ureq, wControl, TACourseNode.this, assessedIdentity, coachCourseEnv);
-			}
-
-			@Override
-			public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl,
-					TooledStackedPanel stackPanel, UserCourseEnvironment coachCourseEnv,
-					AssessmentToolOptions options) {
-				List<Controller> tools = new ArrayList<Controller>(1);
-				CourseEnvironment courseEnv = coachCourseEnv.getCourseEnvironment();
-				if(!coachCourseEnv.isCourseReadOnly()) {
-					tools.add(new BulkAssessmentToolController(ureq, wControl, courseEnv, TACourseNode.this));
-				}
-				tools.add(new BulkDownloadToolController(ureq, wControl, courseEnv, options, TACourseNode.this));
-				return tools;
-			}
-		};
+	public AssessmentCourseNodeController getIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		return new TAIdentityListCourseNodeController(ureq, wControl, stackPanel,
+				courseEntry, group, this, coachCourseEnv, toolContainer, assessmentCallback);
 	}
 
 	/**
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/BulkDownloadToolController.java b/src/main/java/org/olat/course/nodes/gta/ui/BulkDownloadToolController.java
deleted file mode 100644
index ad5983b25b0..00000000000
--- a/src/main/java/org/olat/course/nodes/gta/ui/BulkDownloadToolController.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * <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.course.nodes.gta.ui;
-
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
-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.course.archiver.ArchiveResource;
-import org.olat.course.nodes.ArchiveOptions;
-import org.olat.course.nodes.GTACourseNode;
-import org.olat.course.run.environment.CourseEnvironment;
-import org.olat.modules.assessment.AssessmentToolOptions;
-import org.olat.resource.OLATResource;
-
-/**
- * 
- * Initial date: 07.05.2015<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class BulkDownloadToolController extends BasicController {
-	
-	private final Link downloadButton;
-
-	private final ArchiveOptions options;
-	private final OLATResource courseOres;
-	private final GTACourseNode courseNode;
-	
-	public BulkDownloadToolController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv,
-			AssessmentToolOptions asOptions, GTACourseNode courseNode) {
-		super(ureq, wControl);
-		this.options = new ArchiveOptions();
-		this.options.setGroup(asOptions.getGroup());
-		this.options.setIdentities(asOptions.getIdentities());
-		this.courseNode = courseNode;
-		courseOres = courseEnv.getCourseGroupManager().getCourseResource();
-		
-		downloadButton = LinkFactory.createButton("bulk.download.title", null, this);
-		downloadButton.setTranslator(getTranslator());
-		putInitialPanel(downloadButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
-	}
-	
-	@Override
-	protected void doDispose() {
-		//
-	}
-
-	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		if(downloadButton == source) {
-			doDownload(ureq);
-		}
-	}
-	
-	private void doDownload(UserRequest ureq) {
-		ArchiveResource resource = new ArchiveResource(courseNode, courseOres, options, getLocale());
-		ureq.getDispatchResult().setResultingMediaResource(resource);
-	}
-}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupGradingController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupGradingController.java
index 0e83fbaef72..4bf62cf6b9d 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupGradingController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupGradingController.java
@@ -73,6 +73,7 @@ import org.olat.group.BusinessGroupService;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.modules.assessment.Role;
+import org.olat.repository.RepositoryEntry;
 import org.olat.user.UserManager;
 import org.olat.user.propertyhandlers.UserPropertyHandler;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -330,7 +331,8 @@ public class GTACoachedGroupGradingController extends FormBasicController {
 	private void doOpenAssessmentForm(UserRequest ureq) {
 		removeAsListenerAndDispose(assessmentCtrl);
 		
-		assessmentCtrl = new GroupAssessmentController(ureq, getWindowControl(), courseEnv, gtaNode, assessedGroup);
+		RepositoryEntry courseEntry = courseEnv.getCourseGroupManager().getCourseEntry();
+		assessmentCtrl = new GroupAssessmentController(ureq, getWindowControl(), courseEntry, gtaNode, assessedGroup);
 		listenTo(assessmentCtrl);
 		
 		String title = translate("grading");
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAGroupAssessmentToolController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAGroupAssessmentToolController.java
deleted file mode 100644
index 0588a68bece..00000000000
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAGroupAssessmentToolController.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * <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.course.nodes.gta.ui;
-
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
-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.course.nodes.GTACourseNode;
-import org.olat.course.run.environment.CourseEnvironment;
-import org.olat.group.BusinessGroup;
-
-/**
- * 
- * Initial date: 30.03.2015<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class GTAGroupAssessmentToolController extends BasicController {
-
-	private final Link statsButton;
-
-	private CloseableModalController cmc;
-	private GroupAssessmentController assessmentCtrl;
-
-	private final GTACourseNode gtaNode;
-	private final CourseEnvironment courseEnv;
-	private final BusinessGroup assessedGroup;
-	
-	public GTAGroupAssessmentToolController(UserRequest ureq, WindowControl wControl, 
-			CourseEnvironment courseEnv, BusinessGroup assessedGroup, GTACourseNode gtaNode) {
-		super(ureq, wControl);
-		
-		this.gtaNode = gtaNode;
-		this.courseEnv = courseEnv;
-		this.assessedGroup = assessedGroup;
-
-		statsButton = LinkFactory.createButton("assessment.group.tool", null, this);
-		statsButton.setTranslator(getTranslator());
-		putInitialPanel(statsButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
-	}
-
-	@Override
-	protected void doDispose() {
-		//
-	}
-
-	@Override
-	protected void event(UserRequest ureq, Controller source, Event event) {
-		if(assessmentCtrl == source) {
-			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
-				doGrading();
-				fireEvent(ureq, Event.CHANGED_EVENT);
-			}
-			cmc.deactivate();
-			cleanUp();
-		} else if(cmc == source) {
-			cleanUp();
-		}
-		
-		super.event(ureq, source, event);
-	}
-	
-	private void cleanUp() {
-		removeAsListenerAndDispose(assessmentCtrl);
-		removeAsListenerAndDispose(cmc);
-		assessmentCtrl = null;
-		cmc = null;
-	}
-
-	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		if(statsButton == source) {
-			doOpenAssessmentForm(ureq);
-		}
-	}
-	
-	private void doGrading() {
-		//assignedTask = gtaManager.updateTask(assignedTask, TaskProcess.graded);
-	}
-	
-	private void doOpenAssessmentForm(UserRequest ureq) {
-		removeAsListenerAndDispose(assessmentCtrl);
-		
-		assessmentCtrl = new GroupAssessmentController(ureq, getWindowControl(), courseEnv, gtaNode, assessedGroup);
-		listenTo(assessmentCtrl);
-		
-		String title = translate("grading");
-		cmc = new CloseableModalController(getWindowControl(), "close", assessmentCtrl.getInitialComponent(), true, title, true);
-		listenTo(cmc);
-		cmc.activate();
-	}
-}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAIdentityListCourseNodeController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAIdentityListCourseNodeController.java
new file mode 100644
index 00000000000..35ca370aca0
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAIdentityListCourseNodeController.java
@@ -0,0 +1,224 @@
+/**
+ * <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.course.nodes.gta.ui;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+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.link.Link;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
+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.generic.closablewrapper.CloseableModalController;
+import org.olat.core.util.StringHelper;
+import org.olat.course.archiver.ArchiveResource;
+import org.olat.course.assessment.bulk.BulkAssessmentToolController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeTableModel.IdentityCourseElementCols;
+import org.olat.course.nodes.ArchiveOptions;
+import org.olat.course.nodes.AssessableCourseNode;
+import org.olat.course.nodes.GTACourseNode;
+import org.olat.course.nodes.gta.GTAManager;
+import org.olat.course.nodes.gta.GTAType;
+import org.olat.course.nodes.gta.Task;
+import org.olat.course.nodes.gta.TaskList;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.ModuleConfiguration;
+import org.olat.modules.assessment.AssessmentToolOptions;
+import org.olat.modules.assessment.ui.AssessedIdentityElementRow;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
+import org.olat.repository.RepositoryEntry;
+import org.olat.resource.OLATResource;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * This specialized view of the list assessed identities show the chosen task
+ * and some bulk actions if they are configured in the course element.
+ * 
+ * Initial date: 18 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class GTAIdentityListCourseNodeController extends IdentityListCourseNodeController {
+	
+	private FormLink downloadButton, groupAssessmentButton;
+	
+	private GroupAssessmentController assessmentCtrl;
+	
+	@Autowired
+	private GTAManager gtaManager;
+
+	public GTAIdentityListCourseNodeController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, GTACourseNode courseNode, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		super(ureq, wControl, stackPanel, courseEntry, group, courseNode, coachCourseEnv, toolContainer, assessmentCallback);
+	}
+
+	@Override
+	protected String getTableId() {
+		return "gta-assessment-tool-identity-list";
+	}
+
+	@Override
+	protected void initStatusColumns(FlexiTableColumnModel columnsModel) {
+		ModuleConfiguration config =  courseNode.getModuleConfiguration();
+		if(GTAType.individual.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE)) && config.getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT)) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("table.header.details.gta", IdentityCourseElementCols.details.ordinal()));
+		}
+		super.initStatusColumns(columnsModel);
+	}
+
+	@Override
+	protected void initMultiSelectionTools(UserRequest ureq, FormLayoutContainer formLayout) {
+		ModuleConfiguration config =  courseNode.getModuleConfiguration();
+		if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))
+			&& (config.getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT)
+				|| config.getBooleanSafe(GTACourseNode.GTASK_SUBMIT)
+				|| config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)
+				|| config.getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD))) {
+
+			if(!coachCourseEnv.isCourseReadOnly() && group != null) {
+				groupAssessmentButton = uifactory.addFormLink("assessment.group.tool", formLayout, Link.BUTTON);
+			}
+
+			initBulkDownloadController(formLayout);
+		} else if(GTAType.individual.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) {
+			if(!coachCourseEnv.isCourseReadOnly()
+					&& (config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION) || config.getBooleanSafe(GTACourseNode.GTASK_GRADING))){
+				initBulkAsssessmentTool(ureq, formLayout);
+			}
+			
+			if(config.getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT)
+					|| config.getBooleanSafe(GTACourseNode.GTASK_SUBMIT)
+					|| config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)
+					|| config.getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) {
+				initBulkDownloadController(formLayout);
+			}
+		}
+		super.initMultiSelectionTools(ureq, formLayout);
+	}
+	
+	private void initBulkDownloadController(FormLayoutContainer formLayout) {
+		downloadButton = uifactory.addFormLink("bulk.download.title", formLayout, Link.BUTTON);
+	}
+	
+	private void initBulkAsssessmentTool(UserRequest ureq, FormLayoutContainer formLayout) {
+		BulkAssessmentToolController bulkAssessmentTollCtrl = new BulkAssessmentToolController(ureq, getWindowControl(),
+				getCourseEnvironment(), (AssessableCourseNode)courseNode);
+		listenTo(bulkAssessmentTollCtrl);
+		formLayout.put("bulk.assessment", bulkAssessmentTollCtrl.getInitialComponent());	
+	}
+
+	@Override
+	protected void loadModel(UserRequest ureq) {
+		super.loadModel(ureq);
+
+		ModuleConfiguration config =  courseNode.getModuleConfiguration();
+		if(GTAType.individual.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE)) && config.getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT)) {
+			TaskList taskList = gtaManager.getTaskList(getCourseRepositoryEntry(), (GTACourseNode)courseNode);
+			if(taskList != null) {
+				loadTasksInModel(taskList);
+			}
+		}
+	}
+	
+	private void loadTasksInModel(TaskList taskList) {
+		List<Task> tasks = gtaManager.getTasks(taskList, (GTACourseNode)courseNode);
+		Map<Long,Task> identityToTask = new HashMap<>();
+		for(Task task:tasks) {
+			if(task.getIdentity() != null) {
+				identityToTask.put(task.getIdentity().getKey(), task);
+			}
+		}
+		
+		List<AssessedIdentityElementRow> rows = usersTableModel.getObjects();
+		for(AssessedIdentityElementRow row:rows) {
+			Task task = identityToTask.get(row.getIdentityKey());
+			if(task != null && StringHelper.containsNonWhitespace(task.getTaskName())) {
+				row.setDetails(task.getTaskName());
+			}
+		}
+	}
+
+	@Override
+	public void event(UserRequest ureq, Controller source, Event event) {
+		if(assessmentCtrl == source) {
+			if(event == Event.CHANGED_EVENT || event == Event.DONE_EVENT) {
+				loadModel(ureq);
+			}
+			cmc.deactivate();
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(downloadButton == source) {
+			doDownload(ureq);
+		} else if(groupAssessmentButton == source) {
+			doOpenAssessmentForm(ureq);
+		} else {
+			super.formInnerEvent(ureq, source, event);
+		}
+	}
+
+	@Override
+	protected void cleanUp() {
+		removeAsListenerAndDispose(assessmentCtrl);
+		assessmentCtrl = null;
+		super.cleanUp();
+	}
+	
+	private void doDownload(UserRequest ureq) {
+		AssessmentToolOptions asOptions = getOptions();
+		OLATResource courseOres = getCourseRepositoryEntry().getOlatResource();
+		
+		ArchiveOptions options = new ArchiveOptions();
+		options.setGroup(asOptions.getGroup());
+		options.setIdentities(asOptions.getIdentities());
+		
+		ArchiveResource resource = new ArchiveResource(courseNode, courseOres, options, getLocale());
+		ureq.getDispatchResult().setResultingMediaResource(resource);
+	}
+	
+	private void doOpenAssessmentForm(UserRequest ureq) {
+		removeAsListenerAndDispose(assessmentCtrl);
+		
+		assessmentCtrl = new GroupAssessmentController(ureq, getWindowControl(), getCourseRepositoryEntry(), (GTACourseNode)courseNode, group);
+		listenTo(assessmentCtrl);
+		
+		String title = translate("grading");
+		cmc = new CloseableModalController(getWindowControl(), "close", assessmentCtrl.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GroupAssessmentController.java b/src/main/java/org/olat/course/nodes/gta/ui/GroupAssessmentController.java
index ecd684d6dcb..e4783c9b7d1 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GroupAssessmentController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GroupAssessmentController.java
@@ -50,7 +50,6 @@ import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController;
 import org.olat.core.id.Identity;
-import org.olat.core.id.OLATResourceable;
 import org.olat.core.id.Roles;
 import org.olat.core.id.UserConstants;
 import org.olat.core.util.CodeHelper;
@@ -61,7 +60,6 @@ import org.olat.course.assessment.AssessmentHelper;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
 import org.olat.course.nodes.gta.ui.GroupAssessmentModel.Cols;
-import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.group.BusinessGroup;
@@ -69,6 +67,8 @@ import org.olat.group.BusinessGroupService;
 import org.olat.modules.assessment.AssessmentEntry;
 import org.olat.modules.assessment.Role;
 import org.olat.modules.assessment.model.AssessmentEntryStatus;
+import org.olat.repository.RepositoryEntry;
+import org.olat.resource.OLATResource;
 import org.olat.user.UserManager;
 import org.olat.user.propertyhandlers.UserPropertyHandler;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -101,7 +101,7 @@ public class GroupAssessmentController extends FormBasicController {
 	private Float cutValue;
 	private final boolean withScore, withPassed, withComment;
 	private final GTACourseNode gtaNode;
-	private final CourseEnvironment courseEnv;
+	private final RepositoryEntry courseEntry;
 	private final BusinessGroup assessedGroup;
 	
 	@Autowired
@@ -116,10 +116,10 @@ public class GroupAssessmentController extends FormBasicController {
 	private final List<Long> duplicateMemberKeys;
 	
 	public GroupAssessmentController(UserRequest ureq, WindowControl wControl,
-			CourseEnvironment courseEnv, GTACourseNode courseNode, BusinessGroup assessedGroup) {
+			RepositoryEntry courseEntry, GTACourseNode courseNode, BusinessGroup assessedGroup) {
 		super(ureq, wControl, "assessment_per_group");
 		this.gtaNode = courseNode;
-		this.courseEnv = courseEnv;
+		this.courseEntry = courseEntry;
 		this.assessedGroup = assessedGroup;
 
 		withScore = courseNode.hasScoreConfigured();
@@ -293,7 +293,7 @@ public class GroupAssessmentController extends FormBasicController {
 	 */
 	private ModelInfos loadModel() {
 		//load participants, load datas
-		ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId());
+		ICourse course = CourseFactory.loadCourse(courseEntry);
 		List<Identity> identities = businessGroupService.getMembers(assessedGroup, GroupRoles.participant.name());
 		
 		Map<Identity, AssessmentEntry> identityToEntryMap = new HashMap<>();
@@ -525,7 +525,7 @@ public class GroupAssessmentController extends FormBasicController {
 	}
 	
 	private void applyChangesForEveryMemberGroup(List<AssessmentRow> rows, boolean setAsDone, boolean userVisible) {
-		ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId());
+		ICourse course = CourseFactory.loadCourse(courseEntry);
 		
 		for(AssessmentRow row:rows) {
 			UserCourseEnvironment userCourseEnv = row.getUserCourseEnvironment(course);
@@ -565,7 +565,7 @@ public class GroupAssessmentController extends FormBasicController {
 	}
 	
 	private void applyChangesForTheWholeGroup(List<AssessmentRow> rows, boolean setAsDone, boolean userVisible) {
-		ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId());
+		ICourse course = CourseFactory.loadCourse(courseEntry);
 		
 		Float score = null;
 		if(withScore) {
@@ -614,7 +614,7 @@ public class GroupAssessmentController extends FormBasicController {
 	private void doEditComment(UserRequest ureq, AssessmentRow row) {
 		removeAsListenerAndDispose(commentCalloutCtrl);
 		
-		OLATResourceable courseOres = courseEnv.getCourseGroupManager().getCourseResource();
+		OLATResource courseOres = courseEntry.getOlatResource();
 		editCommentCtrl = new EditCommentController(ureq, getWindowControl(), courseOres, gtaNode, row);
 		listenTo(editCommentCtrl);
 		commentCalloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(),
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/identity_courseelement.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/identity_courseelement.html
new file mode 100644
index 00000000000..780df08442b
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/identity_courseelement.html
@@ -0,0 +1,13 @@
+<h2><i class="o_icon o_gta_icon"> </i> $r.escapeHtml($courseNodeTitle)#if($r.isNotEmpty($businessGroupName)) <small><i class="o_icon o_icon_group"> </i> $r.escapeHtml($businessGroupName)</small>#end</h2>
+<div class="o_button_group o_button_group_right">
+	#if($r.available("bulk.assessment"))
+		$r.render("bulk.assessment")
+	#end
+	#if($r.available("bulk.download.title"))
+		$r.render("bulk.download.title")
+	#end
+	#if($r.available("group.assessment"))
+		$r.render("group.assessment")
+	#end
+</div>
+$r.render("table")
diff --git a/src/main/java/org/olat/course/nodes/iq/ConfirmResetController.java b/src/main/java/org/olat/course/nodes/iq/ConfirmResetController.java
deleted file mode 100644
index 53fc59f2263..00000000000
--- a/src/main/java/org/olat/course/nodes/iq/ConfirmResetController.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/**
- * <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.course.nodes.iq;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.zip.ZipOutputStream;
-
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.form.flexible.FormItemContainer;
-import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
-import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
-import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
-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.id.Identity;
-import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
-import org.olat.core.util.Formatter;
-import org.olat.core.util.StringHelper;
-import org.olat.core.util.Util;
-import org.olat.course.CourseFactory;
-import org.olat.course.ICourse;
-import org.olat.course.assessment.AssessmentHelper;
-import org.olat.course.nodes.ArchiveOptions;
-import org.olat.course.nodes.AssessableCourseNode;
-import org.olat.course.run.scoring.ScoreEvaluation;
-import org.olat.course.run.userview.UserCourseEnvironment;
-import org.olat.ims.qti21.QTI21LoggingAction;
-import org.olat.ims.qti21.QTI21Service;
-import org.olat.ims.qti21.ui.AssessmentTestDisplayController;
-import org.olat.modules.assessment.Role;
-import org.olat.modules.assessment.model.AssessmentRunStatus;
-import org.olat.repository.RepositoryEntry;
-import org.olat.user.UserManager;
-import org.springframework.beans.factory.annotation.Autowired;
-
-/**
- * 
- * Initial date: 27 nov. 2017<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class ConfirmResetController extends FormBasicController {
-	
-	private final String[] onKeys = new String[]{ "on" };
-
-	private MultipleSelectionElement acknowledgeEl;
-
-	private final Identity assessedIdentity;
-	private final AssessableCourseNode courseNode;
-	private final RepositoryEntry courseEntry, testEntry;
-	
-	@Autowired
-	private UserManager userManager;
-	@Autowired
-	private QTI21Service qtiService;
-	
-	public ConfirmResetController(UserRequest ureq, WindowControl wControl, RepositoryEntry courseEntry,
-			AssessableCourseNode courseNode, RepositoryEntry testEntry, Identity assessedIdentity) {
-		super(ureq, wControl, "confirm_reset_data");
-		setTranslator(Util.createPackageTranslator(AssessmentTestDisplayController.class, getLocale(), getTranslator()));
-		this.assessedIdentity = assessedIdentity;
-		this.courseEntry = courseEntry;
-		this.courseNode = courseNode;
-		this.testEntry = testEntry;
-		initForm(ureq);
-	}
-
-	@Override
-	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
-		if(formLayout instanceof FormLayoutContainer) {
-			FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
-			String[] args = new String[]{ userManager.getUserDisplayName(assessedIdentity) };
-			String msg = translate("reset.test.data.text.identity", args);
-			layoutCont.contextPut("msg", msg);
-		}
-		
-		FormLayoutContainer confirmCont = FormLayoutContainer.createDefaultFormLayout("confirm", getTranslator());
-		formLayout.add("confirm", confirmCont);
-		confirmCont.setRootForm(mainForm);
-		
-		String[] onValues = new String[]{ translate("reset.test.data.acknowledge") };
-		acknowledgeEl = uifactory.addCheckboxesHorizontal("acknowledge", "confirmation", confirmCont, onKeys, onValues);
-		
-		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
-		buttonsCont.setRootForm(mainForm);
-		confirmCont.add(buttonsCont);
-		uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl());
-		uifactory.addFormSubmitButton("reset.data", buttonsCont);
-	}
-	
-	@Override
-	protected void doDispose() {
-		//
-	}
-
-	@Override
-	protected boolean validateFormLogic(UserRequest ureq) {
-		boolean allOk = true;
-		
-		acknowledgeEl.clearError();
-		if(!acknowledgeEl.isAtLeastSelected(1)) {
-			acknowledgeEl.setErrorKey("form.legende.mandatory", null);
-			allOk &= false;
-		}
-		
-		return allOk & super.validateFormLogic(ureq);
-	}
-
-	@Override
-	protected void formOK(UserRequest ureq) {
-		ICourse course = CourseFactory.loadCourse(courseEntry);
-		UserCourseEnvironment assessedUserCourseEnv = AssessmentHelper
-				.createAndInitUserCourseEnvironment(assessedIdentity, course.getCourseEnvironment());
-
-		List<Identity> identities = Collections.singletonList(assessedIdentity);
-		ArchiveOptions options = new ArchiveOptions();
-		options.setIdentities(identities);
-
-		File exportDirectory = CourseFactory.getOrCreateDataExportDirectory(getIdentity(), course.getCourseTitle());
-		String archiveName = courseNode.getType()
-				+ "_" + StringHelper.transformDisplayNameToFileSystemName(userManager.getUserDisplayName(assessedIdentity))
-				+ "_" + StringHelper.transformDisplayNameToFileSystemName(courseNode.getShortName())
-				+ "_" + Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis())) + ".zip";
-
-		File exportFile = new File(exportDirectory, archiveName);
-		try(FileOutputStream fileStream = new FileOutputStream(exportFile);
-			ZipOutputStream exportStream = new ZipOutputStream(fileStream)) {
-			courseNode.archiveNodeData(getLocale(), course, options, exportStream, "UTF-8");
-		} catch (IOException e) {
-			logError("", e);
-		}
-
-		qtiService.deleteAssessmentTestSession(identities, testEntry, courseEntry, courseNode.getIdent());
-
-		ScoreEvaluation scoreEval = new ScoreEvaluation(null, null);
-		courseNode.updateUserScoreEvaluation(scoreEval, assessedUserCourseEnv, getIdentity(), false, Role.coach);
-		courseNode.updateCurrentCompletion(assessedUserCourseEnv, getIdentity(), null, AssessmentRunStatus.notStarted, Role.coach);
-		
-
-		ThreadLocalUserActivityLogger.log(QTI21LoggingAction.QTI_RESET_IN_COURSE, getClass());
-		
-		fireEvent(ureq, Event.DONE_EVENT);
-	}
-
-	@Override
-	protected void formCancelled(UserRequest ureq) {
-		fireEvent(ureq, Event.CANCELLED_EVENT);
-	}
-
-}
diff --git a/src/main/java/org/olat/course/nodes/iq/ExtraTimeCellRenderer.java b/src/main/java/org/olat/course/nodes/iq/ExtraTimeCellRenderer.java
new file mode 100644
index 00000000000..8537ef32fab
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/iq/ExtraTimeCellRenderer.java
@@ -0,0 +1,86 @@
+/**
+ * <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.course.nodes.iq;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent;
+import org.olat.core.gui.render.Renderer;
+import org.olat.core.gui.render.StringOutput;
+import org.olat.core.gui.render.URLBuilder;
+import org.olat.core.gui.translator.Translator;
+import org.olat.core.util.Formatter;
+
+/**
+ * 
+ * Initial date: 20 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ExtraTimeCellRenderer implements FlexiCellRenderer {
+	
+	private final boolean renderDueDate;
+	private final int timeLimitInSeconds;
+	private final Formatter formatter;
+	
+	public ExtraTimeCellRenderer(boolean renderDueDate, int timeLimitInSeconds, Locale locale) {
+		this.renderDueDate = renderDueDate;
+		this.timeLimitInSeconds = timeLimitInSeconds;
+		formatter = Formatter.getInstance(locale);
+	}
+
+	@Override
+	public void render(Renderer renderer, StringOutput target, Object cellValue, int row,
+			FlexiTableComponent source, URLBuilder ubu, Translator translator) {
+		if(cellValue instanceof ExtraTimeInfos) {
+			ExtraTimeInfos infos = (ExtraTimeInfos)cellValue;
+			Integer extraTimeInSeconds = infos.getExtraTimeInSeconds();
+			
+			if(renderDueDate) {
+				if(infos.getStart() != null) {
+					int totalTime = timeLimitInSeconds;
+					if(extraTimeInSeconds != null) {
+						totalTime += extraTimeInSeconds;
+					}
+					
+					Calendar now = Calendar.getInstance();
+					Calendar cal = Calendar.getInstance();
+					cal.setTime(infos.getStart());
+					cal.add(Calendar.SECOND, totalTime);
+					Date dueDate = cal.getTime();
+
+					boolean sameDay = now.get(Calendar.YEAR) == cal.get(Calendar.YEAR)
+							&& now.get(Calendar.DAY_OF_YEAR) == cal.get(Calendar.DAY_OF_YEAR);
+					if(sameDay) {
+						target.append(formatter.formatTime(dueDate));
+					} else {
+						target.append(formatter.formatDateAndTime(dueDate));
+					}
+				}
+			} else if(extraTimeInSeconds != null) {
+				int extraTimeInMinutes = extraTimeInSeconds.intValue() / 60;
+				target.append("+").append(extraTimeInMinutes).append("m");
+			}
+		}
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/iq/ExtraTimeInfos.java b/src/main/java/org/olat/course/nodes/iq/ExtraTimeInfos.java
new file mode 100644
index 00000000000..aebc107d4a3
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/iq/ExtraTimeInfos.java
@@ -0,0 +1,56 @@
+/**
+ * <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.course.nodes.iq;
+
+import java.util.Date;
+
+/**
+ * 
+ * Initial date: 20 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ExtraTimeInfos implements Comparable<ExtraTimeInfos> {
+
+	private final Integer extraTimeInSeconds;
+	private final Date start;
+	
+	public ExtraTimeInfos(Integer extraTimeInSeconds, Date start) {
+		this.extraTimeInSeconds = extraTimeInSeconds;
+		this.start = start;
+	}
+
+	public Integer getExtraTimeInSeconds() {
+		return extraTimeInSeconds;
+	}
+
+	public Date getStart() {
+		return start;
+	}
+
+	@Override
+	public int compareTo(ExtraTimeInfos o) {
+		if(o == null) return -1;
+		if(extraTimeInSeconds == null && o.extraTimeInSeconds == null) return 0;
+		if(extraTimeInSeconds != null && o.extraTimeInSeconds == null) return -1;
+		if(extraTimeInSeconds == null && o.extraTimeInSeconds != null) return 1;
+		return extraTimeInSeconds.compareTo(o.extraTimeInSeconds);
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java b/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java
new file mode 100644
index 00000000000..a7ce92cad8d
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java
@@ -0,0 +1,513 @@
+/**
+ * <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.course.nodes.iq;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.olat.basesecurity.GroupRoles;
+import org.olat.basesecurity.IdentityRef;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+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.FlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
+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.generic.closablewrapper.CloseableModalController;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.id.Identity;
+import org.olat.course.archiver.ScoreAccountingHelper;
+import org.olat.course.assessment.AssessmentHelper;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeTableModel.IdentityCourseElementCols;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeToolsController;
+import org.olat.course.nodes.IQTESTCourseNode;
+import org.olat.course.nodes.iq.QTI21IdentityListCourseNodeToolsController.AssessmentTestSessionDetailsComparator;
+import org.olat.course.run.environment.CourseEnvironment;
+import org.olat.course.run.scoring.ScoreEvaluation;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.fileresource.types.ImsQTI21Resource;
+import org.olat.group.BusinessGroup;
+import org.olat.group.BusinessGroupService;
+import org.olat.ims.qti.resultexport.QTI12ResultsExportMediaResource;
+import org.olat.ims.qti.statistics.ui.QTI12PullTestsToolController;
+import org.olat.ims.qti.statistics.ui.QTI12StatisticsToolController;
+import org.olat.ims.qti21.AssessmentTestSession;
+import org.olat.ims.qti21.QTI21DeliveryOptions;
+import org.olat.ims.qti21.QTI21Service;
+import org.olat.ims.qti21.model.jpa.AssessmentTestSessionStatistics;
+import org.olat.ims.qti21.resultexport.QTI21ResultsExportMediaResource;
+import org.olat.ims.qti21.ui.QTI21ResetDataController;
+import org.olat.ims.qti21.ui.QTI21RetrieveTestsController;
+import org.olat.ims.qti21.ui.assessment.AssessmentTestCorrection;
+import org.olat.ims.qti21.ui.assessment.IdentitiesAssessmentTestCorrectionController;
+import org.olat.ims.qti21.ui.assessment.ValidationXmlSignatureController;
+import org.olat.ims.qti21.ui.statistics.QTI21StatisticsToolController;
+import org.olat.modules.ModuleConfiguration;
+import org.olat.modules.assessment.AssessmentToolOptions;
+import org.olat.modules.assessment.Role;
+import org.olat.modules.assessment.ui.AssessedIdentityElementRow;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
+import org.olat.modules.assessment.ui.event.CompleteAssessmentTestSessionEvent;
+import org.olat.repository.RepositoryEntry;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import de.bps.onyx.plugin.OnyxModule;
+
+/**
+ * 
+ * Initial date: 18 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class IQIdentityListCourseNodeController extends IdentityListCourseNodeController {
+	
+	private FormLink extraTimeButton;
+	private FormLink exportResultsButton, statsButton, validateButton, correctionButton, pullButton, resetButton;
+
+	private Controller statisticsCtrl;
+	private Controller retrieveConfirmationCtr;
+	private QTI21ResetDataController resetDataCtrl;
+	private ConfirmExtraTimeController extraTimeCtrl;
+	private ValidationXmlSignatureController validationCtrl;
+	private IdentitiesAssessmentTestCorrectionController correctionCtrl;
+
+	@Autowired
+	private QTI21Service qtiService;
+	@Autowired
+	private BusinessGroupService groupService;
+
+	
+	public IQIdentityListCourseNodeController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, IQTESTCourseNode courseNode, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		super(ureq, wControl, stackPanel, courseEntry, group, courseNode, coachCourseEnv, toolContainer, assessmentCallback);
+	}
+	
+	@Override
+	protected String getTableId() {
+		if(isTestQTI21()) {
+			return"qti21-assessment-tool-identity-list";
+		}
+		return "qti-assessment-tool-identity-list";
+	}
+
+	@Override
+	protected void initStatusColumns(FlexiTableColumnModel columnsModel) {
+		super.initStatusColumns(columnsModel);
+		IQTESTCourseNode testCourseNode = (IQTESTCourseNode)courseNode;
+		if(testCourseNode != null && testCourseNode.hasCompletion()) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.currentCompletion));
+		}
+		
+		RepositoryEntry qtiTestEntry = getReferencedRepositoryEntry();
+		if(testCourseNode != null && testCourseNode.hasQTI21TimeLimit(qtiTestEntry)) {
+			int timeLimitInSeconds = testCourseNode.getQTI21TimeLimitMaxInSeconds(qtiTestEntry);
+			boolean suspendEnabled = isSuspendEnable();
+			FlexiCellRenderer renderer = new ExtraTimeCellRenderer(!suspendEnabled, timeLimitInSeconds, getLocale());
+			String header = suspendEnabled ? "table.header.extra.time" : "table.header.end.date";
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(header, IdentityCourseElementCols.details.ordinal(), renderer));
+		}
+	}
+	
+	private boolean isSuspendEnable() {
+		ModuleConfiguration config = courseNode.getModuleConfiguration();
+		QTI21DeliveryOptions testOptions = qtiService.getDeliveryOptions(getReferencedRepositoryEntry());
+		boolean configRef = config.getBooleanSafe(IQEditController.CONFIG_KEY_CONFIG_REF, false);
+		boolean suspendEnabled = false;
+		if(!configRef) {
+			suspendEnabled = config.getBooleanSafe(IQEditController.CONFIG_KEY_ENABLESUSPEND, testOptions.isEnableSuspend());
+		} else {
+			suspendEnabled = testOptions.isEnableSuspend();
+		}
+		return suspendEnabled;
+	}
+
+	@Override
+	protected void initMultiSelectionTools(UserRequest ureq, FormLayoutContainer formLayout) {
+		//bulk
+		super.initMultiSelectionTools(ureq, formLayout);
+		
+		RepositoryEntry testEntry = getReferencedRepositoryEntry();
+		if(((IQTESTCourseNode)courseNode).hasQTI21TimeLimit(testEntry)) {
+			extraTimeButton = uifactory.addFormLink("extra.time", formLayout, Link.BUTTON);
+			extraTimeButton.setIconLeftCSS("o_icon o_icon_extra_time");
+		}
+		boolean qti21 = isTestQTI21();
+		boolean onyx = !qti21 && OnyxModule.isOnyxTest(testEntry.getOlatResource());
+		
+		statsButton = uifactory.addFormLink("button.stats", formLayout, Link.BUTTON);
+		statsButton.setIconLeftCSS("o_icon o_icon-fw o_icon_statistics_tool");
+		
+		if(!coachCourseEnv.isCourseReadOnly()) {
+			if(!onyx) {
+				pullButton = uifactory.addFormLink("retrieve.tests.title", formLayout, Link.BUTTON);
+				pullButton.setIconLeftCSS("o_icon o_icon_pull");
+			}
+
+			if(qti21) {
+				if(assessmentCallback.isAdmin()) {
+					resetButton = uifactory.addFormLink("tool.delete.data", formLayout, Link.BUTTON); 
+					resetButton.setIconLeftCSS("o_icon o_icon_delete_item");
+				}
+				if(qtiService.needManualCorrection(testEntry)
+						|| IQEditController.CORRECTION_MANUAL.equals(courseNode.getModuleConfiguration().getStringValue(IQEditController.CONFIG_CORRECTION_MODE))) {
+					correctionButton = uifactory.addFormLink("correction.test.title", formLayout, Link.BUTTON); 
+					correctionButton.setIconLeftCSS("o_icon o_icon-fw o_icon_correction");
+				}
+				if(courseNode.getModuleConfiguration().getBooleanSafe(IQEditController.CONFIG_DIGITAL_SIGNATURE, false)) {
+					validateButton = uifactory.addFormLink("validate.xml.signature", formLayout, Link.BUTTON);
+					validateButton.setIconLeftCSS("o_icon o_icon-fw o_icon_correction");
+				}
+			}
+		}
+		
+		if(!onyx) {
+			exportResultsButton = uifactory.addFormLink("button.export", formLayout, Link.BUTTON);
+			exportResultsButton.setIconLeftCSS("o_icon o_icon-fw o_icon_export");
+		}
+	}
+	
+	private boolean isTestRunning() {
+		List<Identity> identities = getIdentities();
+		if(isTestQTI21()) {
+			return qtiService.isRunningAssessmentTestSession(getCourseRepositoryEntry(),
+					courseNode.getIdent(), getReferencedRepositoryEntry(), identities);
+		}
+
+		for(Identity assessedIdentity:identities) {
+			if(((IQTESTCourseNode)courseNode).isQTI12TestRunning(assessedIdentity, getCourseEnvironment())) {
+				return true;
+			}
+		}
+		return false;
+	}
+	
+	private boolean isTestQTI21() {
+		return ImsQTI21Resource.TYPE_NAME.equals(getReferencedRepositoryEntry().getOlatResource().getResourceableTypeName());
+	}
+
+	@Override
+	protected void loadModel(UserRequest ureq) {
+		super.loadModel(ureq);
+		
+		if(((IQTESTCourseNode)courseNode).hasQTI21TimeLimit(getReferencedRepositoryEntry())) {
+			Map<Long,ExtraTimeInfos> extraTimeInfos = getExtraTimes();
+			List<AssessedIdentityElementRow> rows = usersTableModel.getObjects();
+			for(AssessedIdentityElementRow row:rows) {
+				row.setDetails(extraTimeInfos.get(row.getIdentityKey()));
+			}
+		}
+		
+		if(pullButton != null) {
+			boolean enabled = isTestRunning();
+			pullButton.setEnabled(enabled);
+		}
+	}
+	
+	/**
+	 * @return A map identity key to extra time
+	 */
+	private Map<Long,ExtraTimeInfos> getExtraTimes() {
+		Map<Long,ExtraTimeInfos> identityToExtraTime = new HashMap<>();
+		List<AssessmentTestSession> sessions = qtiService
+				.getAssessmentTestSessions(getCourseRepositoryEntry(), courseNode.getIdent(), getReferencedRepositoryEntry());
+		//sort by identity, then by creation date
+		Collections.sort(sessions, new AssessmentTestSessionComparator());
+		
+		Long currentIdentityKey = null;
+		for(AssessmentTestSession session:sessions) {
+			Long identityKey = session.getIdentity().getKey();
+			if(currentIdentityKey == null || !currentIdentityKey.equals(identityKey)) {
+				if(session.getFinishTime() == null && session.getExtraTime() != null) {
+					Integer extraTimeInSeconds = session.getExtraTime();
+					Date start = session.getCreationDate();
+					ExtraTimeInfos infos = new ExtraTimeInfos(extraTimeInSeconds, start);
+					identityToExtraTime.put(identityKey, infos);
+				}
+				currentIdentityKey = identityKey;
+			}
+		}
+		return identityToExtraTime;	
+	}
+	
+
+	@Override
+	public void event(UserRequest ureq, Controller source, Event event) {
+		if(validationCtrl == source) {
+			cmc.deactivate();
+			cleanUp();
+		} else if(correctionCtrl == source) {
+			if(event instanceof CompleteAssessmentTestSessionEvent) {
+				CompleteAssessmentTestSessionEvent catse = (CompleteAssessmentTestSessionEvent)event;
+				List<AssessmentTestSession> testSessionsToComplete = catse.getTestSessions();
+				doUpdateCourseNode(correctionCtrl.getTestCorrections(), testSessionsToComplete);
+				loadModel(ureq);				
+				fireEvent(ureq, Event.CHANGED_EVENT);
+			}
+			cmc.deactivate();
+			cleanUp();
+		} else if(retrieveConfirmationCtr == source || resetDataCtrl == source || extraTimeCtrl == source) {
+			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
+				loadModel(ureq);
+			}
+			cmc.deactivate();
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(extraTimeButton == source) {
+			doConfirmExtraTime(ureq);
+		} else if(exportResultsButton == source) {
+			doExportResults(ureq);
+		} else if(statsButton == source) {
+			doLaunchStatistics(ureq);
+		} else if(validateButton == source) {
+			doValidateSignature(ureq);
+		} else if(correctionButton == source) {
+			doStartCorrection(ureq);
+		} else if(pullButton == source) {
+			doConfirmPull(ureq);
+		} else if(resetButton == source) {
+			doConfirmResetData(ureq);
+		} else {
+			super.formInnerEvent(ureq, source, event);
+		}
+	}
+	
+	@Override
+	protected void cleanUp() {
+		removeAsListenerAndDispose(retrieveConfirmationCtr);
+		removeAsListenerAndDispose(validationCtrl);
+		removeAsListenerAndDispose(extraTimeCtrl);
+		removeAsListenerAndDispose(resetDataCtrl);
+		retrieveConfirmationCtr = null;
+		validationCtrl = null;
+		extraTimeCtrl = null;
+		resetDataCtrl = null;
+		super.cleanUp();
+	}
+	
+	@Override
+	protected Controller createCalloutController(UserRequest ureq, Identity assessedIdentity) {
+		if(isTestQTI21()) {
+			return new QTI21IdentityListCourseNodeToolsController(ureq, getWindowControl(),
+					(IQTESTCourseNode)courseNode, assessedIdentity, coachCourseEnv);
+		}
+		return  new IdentityListCourseNodeToolsController(ureq, getWindowControl(),
+				(IQTESTCourseNode)courseNode, assessedIdentity, coachCourseEnv);
+	}
+	
+	private List<Identity> getIdentities() {
+		AssessmentToolOptions asOptions = getOptions();
+		List<Identity> identities = asOptions.getIdentities();
+		if (group != null) {
+			identities = groupService.getMembers(group, GroupRoles.participant.toString());
+		} else if (identities != null) {
+			identities = asOptions.getIdentities();			
+		} else if (asOptions.isAdmin()){
+			identities = ScoreAccountingHelper.loadUsers(getCourseEnvironment());
+		}
+		return identities;
+	}
+	
+	private void doExportResults(UserRequest ureq) {
+		List<Identity> identities = getIdentities();
+		if (identities != null && identities.size() > 0) {
+			MediaResource resource;
+			CourseEnvironment courseEnv = getCourseEnvironment();
+			if(isTestQTI21()) {
+				resource = new QTI21ResultsExportMediaResource(courseEnv, identities, (IQTESTCourseNode)courseNode, getLocale());
+			} else {
+				resource = new QTI12ResultsExportMediaResource(courseEnv, getLocale(), identities, (IQTESTCourseNode)courseNode);
+			}
+			ureq.getDispatchResult().setResultingMediaResource(resource);
+		} else {
+			showWarning("error.no.assessed.users");
+		}
+	}
+	
+	private void doValidateSignature(UserRequest ureq) {
+		if(validationCtrl != null) return;
+		
+		validationCtrl = new ValidationXmlSignatureController(ureq, getWindowControl());
+		listenTo(validationCtrl);
+		cmc = new CloseableModalController(getWindowControl(), "close", validationCtrl.getInitialComponent(),
+				true, translate("validate.xml.signature"));
+		cmc.activate();
+		listenTo(cmc);
+	}
+	
+	public void doStartCorrection(UserRequest ureq) {
+		AssessmentToolOptions asOptions = getOptions();
+		correctionCtrl = new IdentitiesAssessmentTestCorrectionController(ureq, getWindowControl(),
+				getCourseEnvironment(), asOptions, (IQTESTCourseNode)courseNode);
+		if(correctionCtrl.getNumberOfAssessedIdentities() == 0) {
+			showWarning("grade.nobody");
+			correctionCtrl = null;
+		} else {
+			listenTo(correctionCtrl);
+			cmc = new CloseableModalController(getWindowControl(), "close", correctionCtrl.getInitialComponent(),
+					true, translate("correction.test.title"));
+			cmc.activate();
+			listenTo(cmc);
+		}
+	}
+	
+	private void doUpdateCourseNode(AssessmentTestCorrection corrections, List<AssessmentTestSession> testSessionsToComplete) {
+		Set<AssessmentTestSession> selectedSessions = new HashSet<>(testSessionsToComplete);
+		for(AssessmentTestSession testSession:corrections.getTestSessions()) {
+			if(selectedSessions.contains(testSession)) {
+				UserCourseEnvironment assessedUserCourseEnv = AssessmentHelper
+						.createAndInitUserCourseEnvironment(testSession.getIdentity(), getCourseEnvironment());
+				ScoreEvaluation scoreEval = ((IQTESTCourseNode)courseNode).getUserScoreEvaluation(assessedUserCourseEnv);
+				
+				BigDecimal finalScore = testSession.getFinalScore();
+				Float score = finalScore == null ? null : finalScore.floatValue();
+				ScoreEvaluation manualScoreEval = new ScoreEvaluation(score, scoreEval.getPassed(),
+						scoreEval.getAssessmentStatus(), scoreEval.getUserVisible(), scoreEval.getFullyAssessed(),
+						scoreEval.getCurrentRunCompletion(), scoreEval.getCurrentRunStatus(), testSession.getKey());
+				((IQTESTCourseNode)courseNode).updateUserScoreEvaluation(manualScoreEval, assessedUserCourseEnv, getIdentity(), false, Role.coach);
+			}
+		}
+	}
+	
+	private void doConfirmPull(UserRequest ureq) {
+		AssessmentToolOptions asOptions = getOptions();
+		RepositoryEntry testEntry = getReferencedRepositoryEntry();
+		CourseEnvironment courseEnv = getCourseEnvironment();
+		if(ImsQTI21Resource.TYPE_NAME.equals(testEntry.getOlatResource().getResourceableTypeName())) {
+			retrieveConfirmationCtr = new QTI21RetrieveTestsController(ureq, getWindowControl(),
+					courseEnv, asOptions, (IQTESTCourseNode)courseNode);
+		} else {
+			retrieveConfirmationCtr = new QTI12PullTestsToolController(ureq, getWindowControl(),
+					courseEnv, asOptions, (IQTESTCourseNode)courseNode);
+		}
+		listenTo(retrieveConfirmationCtr);
+		
+		String title = translate("tool.pull");
+		cmc = new CloseableModalController(getWindowControl(), null, retrieveConfirmationCtr.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
+	private void doConfirmResetData(UserRequest ureq) {
+		AssessmentToolOptions asOptions = getOptions();
+		CourseEnvironment courseEnv = getCourseEnvironment();
+		resetDataCtrl = new QTI21ResetDataController(ureq, getWindowControl(), courseEnv, asOptions, (IQTESTCourseNode)courseNode);
+		listenTo(resetDataCtrl);
+		
+		String title = translate("tool.reset");
+		cmc = new CloseableModalController(getWindowControl(), null, resetDataCtrl.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
+	private void doLaunchStatistics(UserRequest ureq) {
+		RepositoryEntry testEntry = getReferencedRepositoryEntry();
+		if(ImsQTI21Resource.TYPE_NAME.equals(testEntry.getOlatResource().getResourceableTypeName())) {
+			statisticsCtrl = new QTI21StatisticsToolController(ureq, getWindowControl(), 
+					stackPanel, getCourseEnvironment(), getOptions(), (IQTESTCourseNode)courseNode);
+		} else {
+			statisticsCtrl = new QTI12StatisticsToolController(ureq, getWindowControl(),
+					stackPanel, getCourseEnvironment(), getOptions(), (IQTESTCourseNode)courseNode);
+		}
+		listenTo(statisticsCtrl);
+		stackPanel.pushController(translate("button.stats"), statisticsCtrl);
+	}
+	
+	private void doConfirmExtraTime(UserRequest ureq) {
+		List<IdentityRef> identities = getSelectedIdentities();
+		if(identities == null || identities.isEmpty()) {
+			showWarning("warning.users.extra.time");
+			return;
+		}
+
+		List<AssessmentTestSession> testSessions = new ArrayList<>(identities.size());
+		for(IdentityRef identity:identities) {
+			List<AssessmentTestSessionStatistics> sessionsStatistics = qtiService
+					.getAssessmentTestSessionsStatistics(getCourseRepositoryEntry(), courseNode.getIdent(), identity);
+			if(!sessionsStatistics.isEmpty()) {
+				if(sessionsStatistics.size() > 1) {
+					Collections.sort(sessionsStatistics, new AssessmentTestSessionDetailsComparator());
+				}
+				AssessmentTestSession lastSession = sessionsStatistics.get(0).getTestSession();
+				if(lastSession != null && lastSession.getFinishTime() == null) {
+					testSessions.add(lastSession);
+				}
+			}
+		}
+		
+		if(testSessions == null || testSessions.isEmpty()) {
+			showWarning("warning.users.extra.time");
+			return;
+		}
+		
+		extraTimeCtrl = new ConfirmExtraTimeController(ureq, getWindowControl(), getCourseRepositoryEntry(), testSessions);
+		listenTo(extraTimeCtrl);
+
+		String title = translate("extra.time");
+		cmc = new CloseableModalController(getWindowControl(), null, extraTimeCtrl.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
+	/**
+	 * Sort by identity
+	 * Initial date: 20 déc. 2017<br>
+	 * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+	 *
+	 */
+	private static final class AssessmentTestSessionComparator implements Comparator<AssessmentTestSession> {
+
+		@Override
+		public int compare(AssessmentTestSession session1, AssessmentTestSession session2) {
+			Long id1 = session1.getIdentity().getKey();
+			Long id2 = session2.getIdentity().getKey();
+			
+			int c = id1.compareTo(id2);
+			if(c == 0) {
+				Date start1 = session1.getCreationDate();
+				Date start2 = session2.getCreationDate();
+				c = start2.compareTo(start1);
+			}
+			return c;
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/iq/QTI21ExtraTimeController.java b/src/main/java/org/olat/course/nodes/iq/QTI21ExtraTimeController.java
deleted file mode 100644
index 3e273ad0f6c..00000000000
--- a/src/main/java/org/olat/course/nodes/iq/QTI21ExtraTimeController.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- * <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.course.nodes.iq;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.olat.basesecurity.IdentityRef;
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
-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.course.assessment.ui.tool.IdentityListCourseNodeProvider;
-import org.olat.course.nodes.IQTESTCourseNode;
-import org.olat.course.nodes.iq.QTI21IdentityListCourseNodeToolsController.AssessmentTestSessionDetailsComparator;
-import org.olat.course.run.environment.CourseEnvironment;
-import org.olat.ims.qti21.AssessmentTestSession;
-import org.olat.ims.qti21.QTI21Service;
-import org.olat.ims.qti21.model.jpa.AssessmentTestSessionStatistics;
-import org.olat.repository.RepositoryEntry;
-import org.springframework.beans.factory.annotation.Autowired;
-
-/**
- * 
- * Button for to extend time in a test.
- * 
- * Initial date: 4 déc. 2017<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class QTI21ExtraTimeController extends BasicController {
-	
-	private final Link extraTimeLink;
-	
-	private final IQTESTCourseNode courseNode;
-	private final CourseEnvironment courseEnv;
-	private final IdentityListCourseNodeProvider provider;
-	
-	private CloseableModalController cmc;
-	private ConfirmExtraTimeController extraTimeCtrl; 
-	
-	@Autowired
-	private QTI21Service qtiService;
-	
-	public QTI21ExtraTimeController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv,
-			IQTESTCourseNode courseNode, IdentityListCourseNodeProvider provider) {
-		super(ureq, wControl);
-		this.provider = provider;
-		this.courseEnv = courseEnv;
-		this.courseNode = courseNode;
-		
-		extraTimeLink = LinkFactory.createButton("extra.time", null, this);
-		extraTimeLink.setIconLeftCSS("o_icon o_icon_extra_time");
-		extraTimeLink.setTranslator(getTranslator());
-		putInitialPanel(extraTimeLink);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
-	}
-
-	@Override
-	protected void doDispose() {
-		//
-	}
-
-	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		if(extraTimeLink == source) {
-			doConfirmExtraTime(ureq);
-		}
-	}
-	
-	@Override
-	protected void event(UserRequest ureq, Controller source, Event event) {
-		if(extraTimeCtrl == source) {
-			cmc.deactivate();
-			cleanUp();
-		} else if(cmc == source) {
-			cleanUp();
-		}
-	}
-	
-	private void cleanUp() {
-		removeAsListenerAndDispose(extraTimeCtrl);
-		removeAsListenerAndDispose(cmc);
-		extraTimeCtrl = null;
-		cmc = null;
-	}
-
-	private void doConfirmExtraTime(UserRequest ureq) {
-		List<IdentityRef> identities = provider.getSelectedIdentities();
-		if(identities == null || identities.isEmpty()) {
-			showWarning("warning.users.extra.time");
-			return;
-		}
-		
-		RepositoryEntry courseEntry = courseEnv.getCourseGroupManager().getCourseEntry();
-		List<AssessmentTestSession> testSessions = new ArrayList<>(identities.size());
-		for(IdentityRef identity:identities) {
-			List<AssessmentTestSessionStatistics> sessionsStatistics = qtiService.getAssessmentTestSessionsStatistics(courseEntry, courseNode.getIdent(), identity);
-			if(!sessionsStatistics.isEmpty()) {
-				if(sessionsStatistics.size() > 1) {
-					Collections.sort(sessionsStatistics, new AssessmentTestSessionDetailsComparator());
-				}
-				AssessmentTestSession lastSession = sessionsStatistics.get(0).getTestSession();
-				if(lastSession != null && lastSession.getFinishTime() == null) {
-					testSessions.add(lastSession);
-				}
-			}
-		}
-		
-		if(testSessions == null || testSessions.isEmpty()) {
-			showWarning("warning.users.extra.time");
-			return;
-		}
-		
-		extraTimeCtrl = new ConfirmExtraTimeController(ureq, getWindowControl(), courseEntry, testSessions);
-		listenTo(extraTimeCtrl);
-
-		String title = translate("extra.time");
-		cmc = new CloseableModalController(getWindowControl(), null, extraTimeCtrl.getInitialComponent(), true, title, true);
-		listenTo(cmc);
-		cmc.activate();
-	}
-}
diff --git a/src/main/java/org/olat/course/nodes/iq/QTI21IdentityListCourseNodeToolsController.java b/src/main/java/org/olat/course/nodes/iq/QTI21IdentityListCourseNodeToolsController.java
index 1522b2a748c..d668ba8cb77 100644
--- a/src/main/java/org/olat/course/nodes/iq/QTI21IdentityListCourseNodeToolsController.java
+++ b/src/main/java/org/olat/course/nodes/iq/QTI21IdentityListCourseNodeToolsController.java
@@ -30,27 +30,20 @@ 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.generic.closablewrapper.CloseableModalController;
-import org.olat.core.gui.control.generic.modal.DialogBoxController;
-import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
 import org.olat.core.id.Identity;
 import org.olat.core.util.Util;
-import org.olat.course.CourseFactory;
 import org.olat.course.assessment.ui.tool.tools.AbstractToolsController;
 import org.olat.course.nodes.IQTESTCourseNode;
-import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.ims.qti21.AssessmentTestSession;
-import org.olat.ims.qti21.QTI21DeliveryOptions;
 import org.olat.ims.qti21.QTI21Service;
-import org.olat.ims.qti21.model.DigitalSignatureOptions;
 import org.olat.ims.qti21.model.jpa.AssessmentTestSessionStatistics;
 import org.olat.ims.qti21.ui.AssessmentTestDisplayController;
 import org.olat.ims.qti21.ui.AssessmentTestSessionComparator;
+import org.olat.ims.qti21.ui.QTI21ResetDataController;
+import org.olat.ims.qti21.ui.QTI21RetrieveTestsController;
 import org.olat.ims.qti21.ui.assessment.IdentityAssessmentTestCorrectionController;
-import org.olat.modules.ModuleConfiguration;
-import org.olat.modules.assessment.Role;
 import org.olat.repository.RepositoryEntry;
-import org.olat.user.UserManager;
 import org.springframework.beans.factory.annotation.Autowired;
 
 
@@ -66,9 +59,9 @@ public class QTI21IdentityListCourseNodeToolsController extends AbstractToolsCon
 	
 	private CloseableModalController cmc;
 	private ConfirmReopenController reopenCtrl;
-	private ConfirmResetController confirmResetCtrl;
+	private QTI21ResetDataController resetDataCtrl;
 	private ConfirmExtraTimeController extraTimeCtrl;
-	private DialogBoxController retrieveConfirmationCtr;
+	private QTI21RetrieveTestsController retrieveConfirmationCtr;
 	private IdentityAssessmentTestCorrectionController correctionCtrl;
 	
 	private RepositoryEntry testEntry;
@@ -80,8 +73,6 @@ public class QTI21IdentityListCourseNodeToolsController extends AbstractToolsCon
 	
 	@Autowired
 	private QTI21Service qtiService;
-	@Autowired
-	private UserManager userManager;
 	
 	public QTI21IdentityListCourseNodeToolsController(UserRequest ureq, WindowControl wControl,
 			IQTESTCourseNode courseNode, Identity assessedIdentity, UserCourseEnvironment coachCourseEnv) {
@@ -170,19 +161,20 @@ public class QTI21IdentityListCourseNodeToolsController extends AbstractToolsCon
 	@Override
 	protected void event(UserRequest ureq, Controller source, Event event) {
 		if(retrieveConfirmationCtr == source) {
-			if(DialogBoxUIFactory.isYesEvent(event) || DialogBoxUIFactory.isOkEvent(event)) {
-				doPullSession(lastSession);
+			if(event == Event.DONE_EVENT) {
 				fireEvent(ureq, Event.CHANGED_EVENT);
 			} else {
 				fireEvent(ureq, Event.DONE_EVENT);
 			}
+			cmc.deactivate();
+			cleanUp();
 		} else if(correctionCtrl == source) {
 			if(event == Event.DONE_EVENT || event == Event.CANCELLED_EVENT) {
 				cmc.deactivate();
 				cleanUp();
 				fireEvent(ureq, Event.CHANGED_EVENT);
 			}
-		} else if(confirmResetCtrl == source || extraTimeCtrl == source || reopenCtrl == source) {
+		} else if(resetDataCtrl == source || extraTimeCtrl == source || reopenCtrl == source) {
 			cmc.deactivate();
 			cleanUp();
 			fireAlteredEvent(ureq, event);
@@ -204,13 +196,13 @@ public class QTI21IdentityListCourseNodeToolsController extends AbstractToolsCon
 	}
 	
 	private void cleanUp() {
-		removeAsListenerAndDispose(confirmResetCtrl);
 		removeAsListenerAndDispose(correctionCtrl);
 		removeAsListenerAndDispose(extraTimeCtrl);
+		removeAsListenerAndDispose(resetDataCtrl);
 		removeAsListenerAndDispose(cmc);
-		confirmResetCtrl = null;
 		correctionCtrl = null;
 		extraTimeCtrl = null;
+		resetDataCtrl = null;
 		cmc = null;
 	}
 	
@@ -225,45 +217,21 @@ public class QTI21IdentityListCourseNodeToolsController extends AbstractToolsCon
 	}
 	
 	private void doConfirmPullSession(UserRequest ureq, AssessmentTestSession session) {
-		String title = translate("tool.pull");
-		String fullname = userManager.getUserDisplayName(session.getIdentity());
-		String text = translate("retrievetest.confirm.text", new String[]{ fullname });
-		retrieveConfirmationCtr = activateOkCancelDialog(ureq, title, text, retrieveConfirmationCtr);
-		retrieveConfirmationCtr.setUserObject(session);
-	}
-	
-	private void doPullSession(AssessmentTestSession session) {
-		qtiService.pullSession(session, getSignatureOptions(session), getIdentity());
-		testCourseNode.pullAssessmentTestSession(session, assessedUserCourseEnv, getIdentity(), Role.coach);
-	}
-	
-	private DigitalSignatureOptions getSignatureOptions(AssessmentTestSession session) {
-		RepositoryEntry sessionTestEntry = session.getTestEntry();
-		QTI21DeliveryOptions deliveryOptions = qtiService.getDeliveryOptions(sessionTestEntry);
+		retrieveConfirmationCtr = new QTI21RetrieveTestsController(ureq, getWindowControl(), session, (IQTESTCourseNode)courseNode);
+		listenTo(retrieveConfirmationCtr);
 		
-		boolean digitalSignature = deliveryOptions.isDigitalSignature();
-		boolean sendMail = deliveryOptions.isDigitalSignatureMail();
-
-		ModuleConfiguration config = courseNode.getModuleConfiguration();
-		digitalSignature = config.getBooleanSafe(IQEditController.CONFIG_DIGITAL_SIGNATURE,
-			deliveryOptions.isDigitalSignature());
-		sendMail = config.getBooleanSafe(IQEditController.CONFIG_DIGITAL_SIGNATURE_SEND_MAIL,
-			deliveryOptions.isDigitalSignatureMail());
-
-		DigitalSignatureOptions options = new DigitalSignatureOptions(digitalSignature, sendMail, courseEntry, testEntry);
-		if(digitalSignature) {
-			CourseEnvironment courseEnv = CourseFactory.loadCourse(courseEntry).getCourseEnvironment();
-			QTI21AssessmentRunController.decorateCourseConfirmation(session, options, courseEnv, courseNode, sessionTestEntry, null, getLocale());
-		}
-		return options;
+		String title = translate("tool.pull");
+		cmc = new CloseableModalController(getWindowControl(), null, retrieveConfirmationCtr.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
 	}
 	
 	private void doConfirmDeleteData(UserRequest ureq) {
-		confirmResetCtrl = new ConfirmResetController(ureq, getWindowControl(), courseEntry, courseNode, testEntry, assessedIdentity);
-		listenTo(confirmResetCtrl);
+		resetDataCtrl = new QTI21ResetDataController(ureq, getWindowControl(), courseEntry, (IQTESTCourseNode)courseNode, assessedIdentity);
+		listenTo(resetDataCtrl);
 
 		String title = translate("reset.test.data.title");
-		cmc = new CloseableModalController(getWindowControl(), null, confirmResetCtrl.getInitialComponent(), true, title, true);
+		cmc = new CloseableModalController(getWindowControl(), null, resetDataCtrl.getInitialComponent(), true, title, true);
 		listenTo(cmc);
 		cmc.activate();
 	}
diff --git a/src/main/java/org/olat/course/nodes/iq/_content/identity_courseelement.html b/src/main/java/org/olat/course/nodes/iq/_content/identity_courseelement.html
new file mode 100644
index 00000000000..e9df974738d
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/iq/_content/identity_courseelement.html
@@ -0,0 +1,31 @@
+<h2><i class="o_icon $courseNodeCssClass"> </i> $r.escapeHtml($courseNodeTitle)#if($r.isNotEmpty($businessGroupName)) <small><i class="o_icon o_icon_group"> </i> $r.escapeHtml($businessGroupName)</small>#end</h2>
+<div class="o_button_group o_button_group_right">
+	$r.render("button.stats")
+	#if($r.available("button.export"))
+		$r.render("button.export")
+	#end
+	#if($r.available("retrieve.tests.title"))
+		$r.render("retrieve.tests.title")
+	#end
+	#if($r.available("correction.test.title"))
+		$r.render("correction.test.title")
+	#end
+	#if($r.available("validate.xml.signature"))
+		$r.render("validate.xml.signature")
+	#end
+	#if($r.available("tool.delete.data"))
+		$r.render("tool.delete.data")
+	#end
+</div>
+$r.render("table")
+<div class="o_button_group">
+	#if($r.available("bulk.done"))
+		$r.render("bulk.done")
+	#end
+	#if($r.available("bulk.visible"))
+		$r.render("bulk.visible")
+	#end
+	#if($r.available("extra.time"))
+		$r.render("extra.time")
+	#end
+</div>
diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties
index 97f2f927c91..1f57d8e15e0 100644
--- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties
@@ -5,6 +5,8 @@ Intro.surv=Dr\u00FCcken Sie Start, um mit dem Fragebogen zu beginnen.
 Intro.test=Dr\u00FCcken Sie Start, um den Test zu beginnen.
 attempts.nomoreattempts=Es stehen Ihnen keine weiteren Versuche zur Verf\u00FCgung.
 attempts.yourattempts=Anzahl gemachter Versuche
+button.export=Resultate exportieren
+button.stats=$org.olat.ims.qti.statistics.ui\:menu.title
 changelog.title=\u00C4nderungsverlauf der Ressource
 choosenfile.self=Selbsttest
 choosenfile.surv=Fragebogen
@@ -30,6 +32,7 @@ correction.auto=Auto
 correction.manual=Manuell
 correction.mode=Korrektur
 correction.mode.help=Bei einer Auto Korrektur wird das Resultat sofort angezeigt. Bei der Manuell Korrektur muss die Sichtbarkeit im Bewertungswerkzeug ge\u00E4ndert werden. F\u00FCr die Fragetypen Freitext, Zeichnen und Datei hochladen ist Manuell Korrektur zwingend.
+correction.test.title=Korrigieren
 correcttest=Test korrigieren
 coursefolder=Ablageordner Kurs "{0}"
 digital.signature=Testquittung erstellen
@@ -151,6 +154,8 @@ replace.wizard.title.step1=Neue Lernressource ausw\u00E4hlen
 replace.wizard.title.step2=Informationen zu den Resultaten
 replace.wizard.title.step3=Benutzer informieren
 reporter.unavailable=$de.bps.onyx.plugin.course.nodes.iq\:reporter.unavailable
+reset.test.data.title=Daten von Test zur\u00FCcksetzen
+retrieve.tests.title=$org.olat.ims.qti.statistics.ui\:menu.pull.tests.title
 retrievetest.confirm.text=$org.olat.ims.qti\:retrievetest.confirm.text
 score.cut=Notwendige Punktzahl f\u00FCr "Bestanden"
 score.max=Punktemaximum
@@ -166,6 +171,8 @@ showResults.visibility.future=Die Resultate werden angezeigt sobald die Korrektu
 start=Start
 table.header.lastModified=Last modified
 table.header.results=Resultaten
+table.header.extra.time=Zusatz
+table.header.end.date=Enddatum
 tool.delete.data=Alle Daten zur\u00FCcksetzen
 tool.extra.time=Testzeit verl\u00E4ngern
 tool.pull=Laufender test einziehen
diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties
index 05b84e96240..5875bc220cb 100644
--- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties
@@ -5,6 +5,7 @@ Intro.test=Press the start button to begin with your test.
 assessment.documents.title=Assessment documents
 attempts.nomoreattempts=There are no more attempts at your disposal.
 attempts.yourattempts=Number of attempts
+button.stats=$org.olat.ims.qti.statistics.ui\:menu.title
 changelog.title=Resource change log
 choosenfile.self=Self-test
 choosenfile.surv=Questionnaire
@@ -30,6 +31,7 @@ correction.auto=Auto
 correction.manual=Manual
 correction.mode=Correction
 correction.mode.help=For Auto correction the result is shown immediately. For Manual correction the visibility need to be changed in the assessment tool. For the question types essay, drawing and file upload a manual correction is mandatory.
+correction.test.title=Grade
 correcttest=Correct test
 coursefolder=Storage folder of course "{0}"
 digital.signature=Test receipt
@@ -151,6 +153,8 @@ replace.wizard.title.step1=Select new learning resource
 replace.wizard.title.step2=Information on results
 replace.wizard.title.step3=Notify users
 reporter.unavailable=$de.bps.onyx.plugin.course.nodes.iq\:reporter.unavailable
+reset.test.data.title=Reset data of test
+retrieve.tests.title=$org.olat.ims.qti.statistics.ui\:menu.pull.tests.title
 retrievetest.confirm.text=$org.olat.ims.qti\:retrievetest.confirm.text
 score.cut=Passed cut value
 score.max=Maximum score
@@ -164,6 +168,8 @@ showResults.title=Results
 showResults.visibility=Your results will be displayed from "{0}" until "{1}"
 showResults.visibility.future=Your results will be displayed here as soon as the correction has been completed.
 start=Start
+table.header.end.date=End date
+table.header.extra.time=Extra
 table.header.lastModified=Last modified
 table.header.results=Results
 time.limit.max=Time limit
diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_fr.properties
index 2fbccdc6e2b..7919af21e0b 100644
--- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_fr.properties
@@ -30,7 +30,8 @@ correction.auto=Automatique
 correction.manual=Manuelle
 correction.mode=Correction
 correction.mode.help=Pour la correction automatique, le r\u00E9sultat s'affiche imm\u00E9diatement. Pour la correction manuelle, la visibilit\u00E9 doit \u00EAtre modifi\u00E9e dans l'outil d'\u00E9valuation. Pour les types de questions texte libre, dessin et t\u00E9l\u00E9chargement de fichier, une correction manuelle est obligatoire.
-correcttest=Corriger le Test
+correction.test.title=Corriger
+correcttest=Corriger le test
 coursefolder=Dossier stockage cours "{0}"
 digital.signature=Signature digitale
 digital.signature.download=Signature digitale
diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_it.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_it.properties
index 0aea979b95e..f2b8316a925 100644
--- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_it.properties
+++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_it.properties
@@ -25,6 +25,7 @@ condition.accessibility.title=Accesso
 correction.auto=Automatica
 correction.manual=Manuale
 correction.mode=Correzione
+correction.test.title=Valutazione
 correcttest=Correggi test
 coursefolder=Cartella di deposito del corso "{0}"
 digital.signature=Ricevuta test
diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_pt_BR.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_pt_BR.properties
index 72ef7f15c3a..9cc65d05854 100644
--- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_pt_BR.properties
+++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_pt_BR.properties
@@ -30,6 +30,7 @@ correction.auto=Auto
 correction.manual=Manual
 correction.mode=Corre\u00E7\u00E3o
 correction.mode.help=Para a corre\u00E7\u00E3o autom\u00E1tica, o resultado \u00E9 mostrado imediatamente. Para corre\u00E7\u00E3o manual, a visibilidade precisa ser alterada na ferramenta de avalia\u00E7\u00E3o. Para os tipos de perguntas ensaio, desenho e carregamento de arquivos, \u00E9 obrigat\u00F3ria uma corre\u00E7\u00E3o manual.
+correction.test.title=Grau
 correcttest=Teste correto
 coursefolder=Pasta de armazenamento do curso "{0}"
 digital.signature=Recibo de teste
diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_zh_CN.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_zh_CN.properties
index 0e1dc78d509..9a9788fe8f8 100644
--- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_zh_CN.properties
+++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_zh_CN.properties
@@ -5,6 +5,7 @@ Intro.test=\u70B9\u51FB"\u5F00\u59CB"\u6309\u94AE\u53C2\u4E0E\u6D4B\u8BD5
 attempts.nomoreattempts=\u4F60\u4E0D\u80FD\u518D\u6B21\u542F\u52A8\u8BE5\u6D4B\u8BD5
 attempts.yourattempts=\u5C1D\u8BD5\u6B21\u6570
 changelog.title=\u8D44\u6E90\u53D8\u66F4\u65E5\u5FD7
+correction.test.title=\u8BC4\u5206
 
 
 
diff --git a/src/main/java/org/olat/course/nodes/ms/MSIdentityListCourseNodeController.java b/src/main/java/org/olat/course/nodes/ms/MSIdentityListCourseNodeController.java
new file mode 100644
index 00000000000..6dbd106eb9b
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/ms/MSIdentityListCourseNodeController.java
@@ -0,0 +1,59 @@
+/**
+ * <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.course.nodes.ms;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.course.assessment.bulk.BulkAssessmentToolController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeController;
+import org.olat.course.nodes.MSCourseNode;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * 
+ * Initial date: 18 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class MSIdentityListCourseNodeController extends IdentityListCourseNodeController {
+
+	public MSIdentityListCourseNodeController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, MSCourseNode courseNode, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		super(ureq, wControl, stackPanel, courseEntry, group, courseNode, coachCourseEnv, toolContainer, assessmentCallback);
+	}
+
+	@Override
+	protected void initMultiSelectionTools(UserRequest ureq, FormLayoutContainer formLayout) {
+		if(!coachCourseEnv.isCourseReadOnly()) {
+			BulkAssessmentToolController bulkAssessmentTollCtrl = new BulkAssessmentToolController(ureq, getWindowControl(),
+					coachCourseEnv.getCourseEnvironment(), (MSCourseNode)courseNode);
+			listenTo(bulkAssessmentTollCtrl);
+			formLayout.put("bulk.assessment", bulkAssessmentTollCtrl.getInitialComponent());	
+		}
+		super.initMultiSelectionTools(ureq, formLayout);
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/ms/_content/identity_courseelement.html b/src/main/java/org/olat/course/nodes/ms/_content/identity_courseelement.html
new file mode 100644
index 00000000000..4af4ef27719
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/ms/_content/identity_courseelement.html
@@ -0,0 +1,15 @@
+<h2><i class="o_icon o_st_icon"> </i> $r.escapeHtml($courseNodeTitle)#if($r.isNotEmpty($businessGroupName)) <small><i class="o_icon o_icon_group"> </i> $r.escapeHtml($businessGroupName)</small>#end</h2>
+#if($r.available("bulk.assessment"))
+<div class="o_button_group o_button_group_right">
+	$r.render("bulk.assessment")
+</div>
+#end
+$r.render("table")
+<div class="o_button_group">
+	#if($r.available("bulk.done"))
+		$r.render("bulk.done")
+	#end
+	#if($r.available("bulk.visible"))
+		$r.render("bulk.visible")
+	#end
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/projectbroker/ProjectBrokerIdentityListCourseNodeController.java b/src/main/java/org/olat/course/nodes/projectbroker/ProjectBrokerIdentityListCourseNodeController.java
new file mode 100644
index 00000000000..747bd1112c2
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/projectbroker/ProjectBrokerIdentityListCourseNodeController.java
@@ -0,0 +1,65 @@
+/**
+ * <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.course.nodes.projectbroker;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.course.assessment.bulk.BulkAssessmentToolController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeController;
+import org.olat.course.nodes.ProjectBrokerCourseNode;
+import org.olat.course.nodes.TACourseNode;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * 
+ * This view is not used. The project broker is excluded from
+ * the assessment tool.
+ * 
+ * Initial date: 18 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ProjectBrokerIdentityListCourseNodeController extends IdentityListCourseNodeController {
+	
+	public ProjectBrokerIdentityListCourseNodeController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, ProjectBrokerCourseNode courseNode, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		super(ureq, wControl, stackPanel, courseEntry, group, courseNode, coachCourseEnv, toolContainer, assessmentCallback);
+	}
+
+	@Override
+	protected void initMultiSelectionTools(UserRequest ureq, FormLayoutContainer formLayout) {
+		if(!coachCourseEnv.isCourseReadOnly()) {
+			BulkAssessmentToolController bulkAssessmentTollCtrl = new BulkAssessmentToolController(ureq, getWindowControl(),
+					getCourseEnvironment(), (TACourseNode)courseNode);
+			listenTo(bulkAssessmentTollCtrl);
+			formLayout.put("bulk.assessment", bulkAssessmentTollCtrl.getInitialComponent());	
+		}
+		//no other batch functions
+		tableEl.setMultiSelect(false);
+		tableEl.setSelectAllEnable(false);
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/projectbroker/_content/identity_courseelement.html b/src/main/java/org/olat/course/nodes/projectbroker/_content/identity_courseelement.html
new file mode 100644
index 00000000000..8bac11618b2
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/projectbroker/_content/identity_courseelement.html
@@ -0,0 +1,8 @@
+<h2><i class="o_icon o_st_icon"> </i> $r.escapeHtml($courseNodeTitle)#if($r.isNotEmpty($businessGroupName)) <small><i class="o_icon o_icon_group"> </i> $r.escapeHtml($businessGroupName)</small>#end</h2>
+#if($r.available("bulk.assessment"))
+<div class="o_button_group o_button_right">
+	$r.render("bulk.assessment")
+</div>
+#end
+
+$r.render("table")
diff --git a/src/main/java/org/olat/course/nodes/st/STIdentityListCourseNodeController.java b/src/main/java/org/olat/course/nodes/st/STIdentityListCourseNodeController.java
new file mode 100644
index 00000000000..db6e1182198
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/st/STIdentityListCourseNodeController.java
@@ -0,0 +1,134 @@
+/**
+ * <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.course.nodes.st;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DateFlexiCellRenderer;
+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.stack.TooledStackedPanel;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.assessment.manager.UserCourseInformationsManager;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeTableModel.IdentityCourseElementCols;
+import org.olat.course.certificate.CertificateLight;
+import org.olat.course.certificate.CertificatesManager;
+import org.olat.course.certificate.ui.DownloadCertificateCellRenderer;
+import org.olat.course.nodes.STCourseNode;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.assessment.ui.AssessedIdentityElementRow;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
+import org.olat.repository.RepositoryEntry;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * This specialized list of assessed identities show the certificates
+ * and recertification date if configured.
+ * 
+ * Initial date: 18 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class STIdentityListCourseNodeController extends IdentityListCourseNodeController {
+
+	@Autowired
+	private CertificatesManager certificatesManager;
+	@Autowired
+	private UserCourseInformationsManager userInfosMgr;
+	
+	public STIdentityListCourseNodeController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, STCourseNode courseNode, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		super(ureq, wControl, stackPanel, courseEntry, group, courseNode, coachCourseEnv, toolContainer, assessmentCallback);
+	}
+
+	@Override
+	protected boolean isSelectable() {
+		return courseNode.getParent() == null;
+	}
+
+	@Override
+	protected void initModificationDatesColumns(FlexiTableColumnModel columnsModel) {
+		if(courseNode.getParent() == null) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.initialLaunchDate, getSelectAction()));
+		}
+		super.initModificationDatesColumns(columnsModel);
+	}
+
+	@Override
+	protected void initCalloutColumns(FlexiTableColumnModel columnsModel) {
+		ICourse course = CourseFactory.loadCourse(getCourseRepositoryEntry());
+		if(course.getCourseConfig().isCertificateEnabled()) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.certificate, new DownloadCertificateCellRenderer(getLocale())));
+			if(course.getCourseConfig().isRecertificationEnabled()) {
+				columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.recertification, new DateFlexiCellRenderer(getLocale())));
+			}
+		}
+		//no callout
+	}
+	
+	@Override
+	protected String getTableId() {
+		return "st-assessment-tool-identity-list";
+	}
+
+	@Override
+	protected void initMultiSelectionTools(UserRequest ureq, FormLayoutContainer formLayout) {
+		//no batch tools based on selected identities
+		tableEl.setMultiSelect(false);
+		tableEl.setSelectAllEnable(false);
+	}
+
+	@Override
+	protected void loadModel(UserRequest ureq) {
+		Map<Long, Date> initialLaunchDates = userInfosMgr
+				.getInitialLaunchDates(getCourseRepositoryEntry().getOlatResource());
+		super.loadModel(ureq);
+		
+		List<AssessedIdentityElementRow> rows = usersTableModel.getObjects();
+		for(AssessedIdentityElementRow row:rows) {
+			Date initialLaunchDate = initialLaunchDates.get(row.getIdentityKey());
+			row.setInitialCourseLaunchDate(initialLaunchDate);
+		}
+
+		AssessmentToolContainer toolContainer = getToolContainer();
+		if(toolContainer.getCertificateMap() == null) {
+			List<CertificateLight> certificates = certificatesManager.getLastCertificates(getCourseRepositoryEntry().getOlatResource());
+			ConcurrentMap<Long, CertificateLight> certificateMap = new ConcurrentHashMap<>();
+			for(CertificateLight certificate:certificates) {
+				certificateMap.put(certificate.getIdentityKey(), certificate);
+			}
+			toolContainer.setCertificateMap(certificateMap);
+		}
+		usersTableModel.setCertificateMap(toolContainer.getCertificateMap());
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/st/_content/identity_courseelement.html b/src/main/java/org/olat/course/nodes/st/_content/identity_courseelement.html
new file mode 100644
index 00000000000..a1b4bcfe449
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/st/_content/identity_courseelement.html
@@ -0,0 +1,2 @@
+<h2><i class="o_icon o_st_icon"> </i> $r.escapeHtml($courseNodeTitle)#if($r.isNotEmpty($businessGroupName)) <small><i class="o_icon o_icon_group"> </i> $r.escapeHtml($businessGroupName)</small>#end</h2>
+$r.render("table")
diff --git a/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java b/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java
deleted file mode 100644
index 7a3fa8ba924..00000000000
--- a/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * <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.course.nodes.ta;
-
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
-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.course.archiver.ArchiveResource;
-import org.olat.course.nodes.ArchiveOptions;
-import org.olat.course.nodes.TACourseNode;
-import org.olat.course.run.environment.CourseEnvironment;
-import org.olat.modules.assessment.AssessmentToolOptions;
-import org.olat.resource.OLATResource;
-
-/**
- * 
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class BulkDownloadToolController extends BasicController {
-	
-	private final Link downloadButton;
-
-	private final ArchiveOptions options;
-	private final OLATResource courseOres;
-	private final TACourseNode courseNode;
-	
-	public BulkDownloadToolController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv,
-			AssessmentToolOptions asOptions, TACourseNode courseNode) {
-		super(ureq, wControl);
-		this.options = new ArchiveOptions();
-		this.options.setGroup(asOptions.getGroup());
-		this.options.setIdentities(asOptions.getIdentities());
-		this.courseNode = courseNode;
-		courseOres = courseEnv.getCourseGroupManager().getCourseResource();
-		
-		downloadButton = LinkFactory.createButton("bulk.download.title", null, this);
-		downloadButton.setTranslator(getTranslator());
-		putInitialPanel(downloadButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
-	}
-	
-	@Override
-	protected void doDispose() {
-		//
-	}
-
-	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		if(downloadButton == source) {
-			doDownload(ureq);
-		}
-	}
-	
-	private void doDownload(UserRequest ureq) {
-		ArchiveResource resource = new ArchiveResource(courseNode, courseOres, options, getLocale());
-		ureq.getDispatchResult().setResultingMediaResource(resource);
-	}
-}
diff --git a/src/main/java/org/olat/course/nodes/ta/TAIdentityListCourseNodeController.java b/src/main/java/org/olat/course/nodes/ta/TAIdentityListCourseNodeController.java
new file mode 100644
index 00000000000..de00d34312f
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/ta/TAIdentityListCourseNodeController.java
@@ -0,0 +1,95 @@
+/**
+ * <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.course.nodes.ta;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+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.link.Link;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.course.archiver.ArchiveResource;
+import org.olat.course.assessment.bulk.BulkAssessmentToolController;
+import org.olat.course.assessment.ui.tool.IdentityListCourseNodeController;
+import org.olat.course.nodes.ArchiveOptions;
+import org.olat.course.nodes.TACourseNode;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroup;
+import org.olat.modules.assessment.AssessmentToolOptions;
+import org.olat.modules.assessment.ui.AssessmentToolContainer;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
+import org.olat.repository.RepositoryEntry;
+import org.olat.resource.OLATResource;
+
+/**
+ * This specialized list of assessed identities has 2 bulk actions, download
+ * and bulk assessment. 
+ * 
+ * Initial date: 18 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class TAIdentityListCourseNodeController extends IdentityListCourseNodeController {
+	
+	private FormLink downloadButton;
+	
+	public TAIdentityListCourseNodeController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry courseEntry, BusinessGroup group, TACourseNode courseNode, UserCourseEnvironment coachCourseEnv,
+			AssessmentToolContainer toolContainer, AssessmentToolSecurityCallback assessmentCallback) {
+		super(ureq, wControl, stackPanel, courseEntry, group, courseNode, coachCourseEnv, toolContainer, assessmentCallback);
+	}
+
+	@Override
+	protected void initMultiSelectionTools(UserRequest ureq, FormLayoutContainer formLayout) {
+		if(!coachCourseEnv.isCourseReadOnly()) {
+			BulkAssessmentToolController bulkAssessmentTollCtrl = new BulkAssessmentToolController(ureq, getWindowControl(),
+					getCourseEnvironment(), (TACourseNode)courseNode);
+			listenTo(bulkAssessmentTollCtrl);
+			formLayout.put("bulk.assessment", bulkAssessmentTollCtrl.getInitialComponent());	
+		}
+		
+		downloadButton = uifactory.addFormLink("bulk.download.title", formLayout, Link.BUTTON);
+
+		super.initMultiSelectionTools(ureq, formLayout);
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(downloadButton == source) {
+			doDownload(ureq);
+		} else {
+			super.formInnerEvent(ureq, source, event);
+		}
+	}
+	
+	private void doDownload(UserRequest ureq) {
+		OLATResource courseOres = getCourseRepositoryEntry().getOlatResource();
+		AssessmentToolOptions asOptions = getOptions();
+		
+		ArchiveOptions options = new ArchiveOptions();
+		options.setGroup(asOptions.getGroup());
+		options.setIdentities(asOptions.getIdentities());
+		
+		ArchiveResource resource = new ArchiveResource(courseNode, courseOres, options, getLocale());
+		ureq.getDispatchResult().setResultingMediaResource(resource);
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/ta/_content/identity_courseelement.html b/src/main/java/org/olat/course/nodes/ta/_content/identity_courseelement.html
new file mode 100644
index 00000000000..9faeefebeea
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/ta/_content/identity_courseelement.html
@@ -0,0 +1,10 @@
+<h2><i class="o_icon o_gta_icon"> </i> $r.escapeHtml($courseNodeTitle)#if($r.isNotEmpty($businessGroupName)) <small><i class="o_icon o_icon_group"> </i> $r.escapeHtml($businessGroupName)</small>#end</h2>
+<div class="o_button_group o_button_group_right">
+	#if($r.available("bulk.assessment"))
+		$r.render("bulk.assessment")
+	#end
+	#if($r.available("bulk.download.title"))
+		$r.render("bulk.download.title")
+	#end
+</div>
+$r.render("table")
diff --git a/src/main/java/org/olat/ims/qti/resultexport/QTI12ExportResultsReportController.java b/src/main/java/org/olat/ims/qti/resultexport/QTI12ExportResultsReportController.java
deleted file mode 100644
index fd95f31b42e..00000000000
--- a/src/main/java/org/olat/ims/qti/resultexport/QTI12ExportResultsReportController.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * <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.resultexport;
-
-import java.util.List;
-
-import org.olat.basesecurity.GroupRoles;
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
-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.media.MediaResource;
-import org.olat.core.id.Identity;
-import org.olat.course.archiver.ScoreAccountingHelper;
-import org.olat.course.nodes.QTICourseNode;
-import org.olat.course.run.environment.CourseEnvironment;
-import org.olat.group.BusinessGroup;
-import org.olat.group.BusinessGroupService;
-import org.olat.ims.qti21.QTI21Service;
-import org.olat.modules.assessment.AssessmentToolOptions;
-import org.springframework.beans.factory.annotation.Autowired;
-
-
-public class QTI12ExportResultsReportController extends BasicController {
-	
-	private final Link statsButton;
-	private AssessmentToolOptions asOptions;
-	private QTICourseNode courseNode;
-	private CourseEnvironment courseEnv;
-
-	@Autowired
-	private	BusinessGroupService groupService;
-	@Autowired
-	protected QTI21Service qtiService;
-
-	
-	public QTI12ExportResultsReportController(UserRequest ureq, WindowControl wControl,
-			CourseEnvironment courseEnv, AssessmentToolOptions asOptions, QTICourseNode courseNode) {
-		super(ureq, wControl);
-		this.asOptions = asOptions;
-		this.courseNode = courseNode;
-		this.courseEnv = courseEnv;
-
-		statsButton = LinkFactory.createButton("button.export", null, this);
-		statsButton.setTranslator(getTranslator());
-		putInitialPanel(statsButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
-	}
-	
-
-	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		// 1) calculate my assessed identities
-		List<Identity> identities = asOptions.getIdentities();
-		BusinessGroup group = asOptions.getGroup();
-		if (group != null) {
-			identities = groupService.getMembers(group, GroupRoles.participant.toString());
-		} else if (identities != null) {
-			identities = asOptions.getIdentities();			
-		} else if (asOptions.isAdmin()){
-			identities = ScoreAccountingHelper.loadUsers(courseEnv);
-		}
-		if (identities != null && identities.size() > 0) {
-			// 2) create export resource
-			MediaResource resource = new QTI12ResultsExportMediaResource(courseEnv, ureq.getLocale(), identities, courseNode);
-			// 3) download
-			ureq.getDispatchResult().setResultingMediaResource(resource);
-		} else {
-			showWarning("error.no.assessed.users");
-		}
-		
-	}
-
-	@Override
-	protected void doDispose() {
-
-		
-	}
-
-}
diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12PullTestsToolController.java b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12PullTestsToolController.java
index 216f94a737a..6f2f417aacd 100644
--- a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12PullTestsToolController.java
+++ b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12PullTestsToolController.java
@@ -25,19 +25,13 @@ import java.util.List;
 
 import org.dom4j.Document;
 import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
 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.dtabs.Activateable2;
-import org.olat.core.gui.control.generic.modal.DialogBoxController;
-import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
 import org.olat.core.id.Identity;
-import org.olat.core.id.context.ContextEntry;
-import org.olat.core.id.context.StateEntry;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
 import org.olat.core.util.coordinate.CoordinatorManager;
@@ -70,10 +64,8 @@ import org.springframework.beans.factory.annotation.Autowired;
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
-public class QTI12PullTestsToolController extends BasicController implements Activateable2 {
+public class QTI12PullTestsToolController extends FormBasicController {
 	
-	private final Link pullButton;
-	private DialogBoxController retrieveConfirmationCtr;
 
 	private final IQTESTCourseNode courseNode;
 	private final CourseEnvironment courseEnv;
@@ -88,75 +80,16 @@ public class QTI12PullTestsToolController extends BasicController implements Act
 	
 	public QTI12PullTestsToolController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv,
 			AssessmentToolOptions asOptions, IQTESTCourseNode courseNode) {
-		super(ureq, wControl);
+		super(ureq, wControl, "retrieve_tests");
 		setTranslator(Util.createPackageTranslator(QTIResultManager.class, getLocale(), getTranslator()));
 		this.courseEnv = courseEnv;
 		this.courseNode = courseNode;
 		this.asOptions = asOptions;
-
-		boolean enabled = false;
-		for(Identity assessedIdentity:getIdentities()) {
-			if(courseNode.isQTI12TestRunning(assessedIdentity, courseEnv)) {
-				enabled = true;
-				break;
-			}
-		}
-		
-		pullButton = LinkFactory.createButton("menu.pull.tests.title", null, this);
-		pullButton.setIconLeftCSS("o_icon o_icon_pull");
-		pullButton.setTranslator(getTranslator());
-		pullButton.setEnabled(enabled);
-		putInitialPanel(pullButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
-	}
-	
-	private List<Identity> getIdentities() {
-		List<Identity> identities;
-		if(asOptions.getGroup() == null && asOptions.getIdentities() == null) {
-			if(courseEnv != null) {
-				identities = ScoreAccountingHelper.loadUsers(courseEnv);
-			} else {
-				identities = Collections.emptyList();
-			}
-		} else if (asOptions.getIdentities() != null) {
-			identities = asOptions.getIdentities();
-		} else {
-			identities = businessGroupService.getMembers(asOptions.getGroup());
-		}
-		return identities;
-	}
-
-	@Override
-	protected void doDispose() {
-		//
-	}
-
-	@Override
-	public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
-		//
-	}
-
-	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		if(pullButton == source) {
-			confirmPull(ureq);
-		}
+		initForm(ureq);
 	}
 	
 	@Override
-	protected void event(UserRequest ureq, Controller source, Event event) {
-		if(retrieveConfirmationCtr == source) {
-			if(DialogBoxUIFactory.isYesEvent(event)) {
-				@SuppressWarnings("unchecked")
-				List<Identity> assessedIdentities = (List<Identity>)retrieveConfirmationCtr.getUserObject();
-				doRetrieveTests(assessedIdentities);
-			}
-			removeAsListenerAndDispose(retrieveConfirmationCtr);
-			retrieveConfirmationCtr = null;
-		}
-	}
-	
-	private void confirmPull(UserRequest ureq) {
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
 		int count = 0;
 		StringBuilder fullnames = new StringBuilder(256);
 		List<Identity> assessedIdentities = getIdentities();
@@ -171,19 +104,53 @@ public class QTI12PullTestsToolController extends BasicController implements Act
 			}
 		}
 		
+		String msg;
 		if(count == 0) {
-			showInfo("retrievetest.nothing.todo");
+			msg = translate("retrievetest.nothing.todo");
 		} else if(count == 1) {
-			String title = translate("retrievetest.confirm.title");
-			String text = translate("retrievetest.confirm.text", new String[]{ fullnames.toString() });
-			retrieveConfirmationCtr = activateYesNoDialog(ureq, title, text, retrieveConfirmationCtr);
-			retrieveConfirmationCtr.setUserObject(assessedIdentities);
+			msg = translate("retrievetest.confirm.text", new String[]{ fullnames.toString() });
 		} else  {
-			String title = translate("retrievetest.confirm.title");
-			String text = translate("retrievetest.confirm.text.plural", new String[]{ fullnames.toString() });
-			retrieveConfirmationCtr = activateYesNoDialog(ureq, title, text, retrieveConfirmationCtr);
-			retrieveConfirmationCtr.setUserObject(assessedIdentities);
+			msg = translate("retrievetest.confirm.text.plural", new String[]{ fullnames.toString() });
+		}
+		if(formLayout instanceof FormLayoutContainer) {
+			FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
+			layoutCont.contextPut("msg", msg);
 		}
+		
+		uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("menu.pull.tests.title", formLayout);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		doRetrieveTests(getIdentities());
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+
+	private List<Identity> getIdentities() {
+		List<Identity> identities;
+		if(asOptions.getGroup() == null && asOptions.getIdentities() == null) {
+			if(courseEnv != null) {
+				identities = ScoreAccountingHelper.loadUsers(courseEnv);
+			} else {
+				identities = Collections.emptyList();
+			}
+		} else if (asOptions.getIdentities() != null) {
+			identities = asOptions.getIdentities();
+		} else {
+			identities = businessGroupService.getMembers(asOptions.getGroup());
+		}
+		return identities;
 	}
 	
 	private void doRetrieveTests(List<Identity> assessedIdentities) {
diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12StatisticsToolController.java b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12StatisticsToolController.java
index b4a8ab9cfe8..08a6b998e35 100644
--- a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12StatisticsToolController.java
+++ b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12StatisticsToolController.java
@@ -27,8 +27,6 @@ import org.olat.basesecurity.Group;
 import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
 import org.olat.core.gui.components.panel.Panel;
 import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.components.tree.GenericTreeModel;
@@ -44,7 +42,6 @@ import org.olat.core.id.context.ContextEntry;
 import org.olat.core.id.context.StateEntry;
 import org.olat.core.util.nodes.INode;
 import org.olat.core.util.resource.OresHelper;
-import org.olat.course.nodes.ArchiveOptions;
 import org.olat.course.nodes.QTICourseNode;
 import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.statistic.StatisticResourceNode;
@@ -62,14 +59,11 @@ import org.olat.resource.OLATResource;
 public class QTI12StatisticsToolController extends BasicController implements Activateable2 {
 
 	private MenuTree courseTree;
-	private final Link statsButton;
 	private Controller currentCtrl;
 	private final TooledStackedPanel stackPanel;
 	private LayoutMain3ColsController layoutCtr;
 
-	private final ArchiveOptions options;
 	private final OLATResource courseRes;
-	private final QTICourseNode courseNode;
 	private QTIStatisticResourceResult result;
 
 	private final QTIStatisticSearchParams searchParams;
@@ -79,10 +73,6 @@ public class QTI12StatisticsToolController extends BasicController implements Ac
 			AssessmentToolOptions asOptions, QTICourseNode courseNode) {
 		super(ureq, wControl);
 		this.stackPanel = stackPanel;
-		this.options = new ArchiveOptions();
-		this.options.setGroup(asOptions.getGroup());
-		this.options.setIdentities(asOptions.getIdentities());
-		this.courseNode = courseNode;
 		courseRes = courseEnv.getCourseGroupManager().getCourseResource();
 		
 		searchParams = new QTIStatisticSearchParams(courseRes.getResourceableId(), courseNode.getIdent());
@@ -94,10 +84,29 @@ public class QTI12StatisticsToolController extends BasicController implements Ac
 			searchParams.setLimitToGroups(asOptions.getGroups());
 		}
 		
-		statsButton = LinkFactory.createButton("menu.title", null, this);
-		statsButton.setTranslator(getTranslator());
-		putInitialPanel(statsButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
+		RepositoryEntry testEntry = courseNode.getReferencedRepositoryEntry();
+		result = new QTIStatisticResourceResult(courseRes, courseNode, testEntry, searchParams);
+		
+		GenericTreeModel treeModel = new GenericTreeModel();
+		StatisticResourceNode rootTreeNode = new StatisticResourceNode(courseNode, result);
+		treeModel.setRootNode(rootTreeNode);
+		
+		TreeNode subRootNode = result.getSubTreeModel().getRootNode();
+		List<INode> subNodes = new ArrayList<>();
+		for(int i=0; i<subRootNode.getChildCount(); i++) {
+			subNodes.add(subRootNode.getChildAt(i));
+		}
+		for(INode subNode:subNodes) {
+			rootTreeNode.addChild(subNode);
+		}
+
+		courseTree = new MenuTree("qtiStatisticsTree");
+		courseTree.setTreeModel(treeModel);
+		courseTree.addListener(this);
+		
+		layoutCtr = new LayoutMain3ColsController(ureq, wControl, courseTree, new Panel("empty"), null);
+		putInitialPanel(layoutCtr.getInitialComponent());
+		doSelectNode(ureq, courseTree.getTreeModel().getRootNode());
 	}
 
 	@Override
@@ -123,10 +132,7 @@ public class QTI12StatisticsToolController extends BasicController implements Ac
 
 	@Override
 	protected void event(UserRequest ureq, Component source, Event event) {
-		if(statsButton == source) {
-			doLaunchStatistics(ureq, getWindowControl());
-			doSelectNode(ureq, courseTree.getTreeModel().getRootNode());
-		} else if(courseTree == source) {
+		if(courseTree == source) {
 			if(event instanceof TreeEvent) {
 				TreeEvent te = (TreeEvent)event;
 				if(MenuTree.COMMAND_TREENODE_CLICKED.equals(te.getCommand())) {
@@ -149,31 +155,4 @@ public class QTI12StatisticsToolController extends BasicController implements Ac
 			layoutCtr.setCol3(new Panel("empty"));
 		}
 	}
-
-	private void doLaunchStatistics(UserRequest ureq, WindowControl wControl) {
-		if(result == null) {
-			RepositoryEntry testEntry = courseNode.getReferencedRepositoryEntry();
-			result = new QTIStatisticResourceResult(courseRes, courseNode, testEntry, searchParams);
-		}
-		
-		GenericTreeModel treeModel = new GenericTreeModel();
-		StatisticResourceNode rootTreeNode = new StatisticResourceNode(courseNode, result);
-		treeModel.setRootNode(rootTreeNode);
-		
-		TreeNode subRootNode = result.getSubTreeModel().getRootNode();
-		List<INode> subNodes = new ArrayList<>();
-		for(int i=0; i<subRootNode.getChildCount(); i++) {
-			subNodes.add(subRootNode.getChildAt(i));
-		}
-		for(INode subNode:subNodes) {
-			rootTreeNode.addChild(subNode);
-		}
-
-		courseTree = new MenuTree("qtiStatisticsTree");
-		courseTree.setTreeModel(treeModel);
-		courseTree.addListener(this);
-		
-		layoutCtr = new LayoutMain3ColsController(ureq, wControl, courseTree, new Panel("empty"), null);
-		stackPanel.pushController("Stats", layoutCtr);
-	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/retrieve_tests.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/retrieve_tests.html
new file mode 100644
index 00000000000..394bef64351
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/retrieve_tests.html
@@ -0,0 +1,5 @@
+<div>$msg</div>
+<div class="o_button_group">
+	$r.render("cancel")
+	$r.render("menu.pull.tests.title")
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/QTI21Service.java b/src/main/java/org/olat/ims/qti21/QTI21Service.java
index 19300db2049..356d869fdb1 100644
--- a/src/main/java/org/olat/ims/qti21/QTI21Service.java
+++ b/src/main/java/org/olat/ims/qti21/QTI21Service.java
@@ -314,7 +314,8 @@ public interface QTI21Service {
 	public AssessmentTestSession getLastAssessmentTestSessions(RepositoryEntryRef courseEntry, String subIdent, RepositoryEntry testEntry, IdentityRef identity);
 	
 	/**
-	 * Retrieve the sessions for a test.
+	 * Retrieve the sessions for a test. It returns only the sessions of authenticated users (fetched).
+	 * The anonymous ones are not included.
 	 * 
 	 * @param courseEntry
 	 * @param subIdent
diff --git a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java
index 9bfbc656bc0..841e6ffbb77 100644
--- a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java
+++ b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java
@@ -315,6 +315,14 @@ public class AssessmentTestSessionDAO {
 				.getResultList();
 	}
 	
+	/**
+	 * The assessment test sessions of authenticated users (fetched in the query).
+	 * 
+	 * @param courseEntry
+	 * @param courseSubIdent
+	 * @param testEntry
+	 * @return
+	 */
 	public List<AssessmentTestSession> getTestSessions(RepositoryEntryRef courseEntry, String courseSubIdent, RepositoryEntry testEntry) {
 		StringBuilder sb = new StringBuilder();
 		sb.append("select session from qtiassessmenttestsession session")
diff --git a/src/main/java/org/olat/ims/qti21/resultexport/QTI21ExportResultsReportController.java b/src/main/java/org/olat/ims/qti21/resultexport/QTI21ExportResultsReportController.java
deleted file mode 100644
index 4d5d19f164f..00000000000
--- a/src/main/java/org/olat/ims/qti21/resultexport/QTI21ExportResultsReportController.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * <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.resultexport;
-
-import java.util.List;
-
-import org.olat.basesecurity.GroupRoles;
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
-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.media.MediaResource;
-import org.olat.core.id.Identity;
-import org.olat.course.archiver.ScoreAccountingHelper;
-import org.olat.course.nodes.QTICourseNode;
-import org.olat.course.run.environment.CourseEnvironment;
-import org.olat.group.BusinessGroup;
-import org.olat.group.BusinessGroupService;
-import org.olat.modules.assessment.AssessmentToolOptions;
-import org.springframework.beans.factory.annotation.Autowired;
-
-
-public class QTI21ExportResultsReportController extends BasicController {
-	
-	private final Link statsButton;
-	private AssessmentToolOptions asOptions;
-	private QTICourseNode courseNode;
-	private CourseEnvironment courseEnv;
-
-	@Autowired
-	private	BusinessGroupService groupService;
-	
-	public QTI21ExportResultsReportController(UserRequest ureq, WindowControl wControl,
-			CourseEnvironment courseEnv, AssessmentToolOptions asOptions, QTICourseNode courseNode) {
-		super(ureq, wControl);
-		this.asOptions = asOptions;
-		this.courseNode = courseNode;
-		this.courseEnv = courseEnv;
-		
-		statsButton = LinkFactory.createButton("button.export", null, this);
-		statsButton.setTranslator(getTranslator());
-		statsButton.setIconLeftCSS("o_icon o_icon-fw o_icon_export");
-		putInitialPanel(statsButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
-	}
-	
-
-	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		// 1) calculate my assessed identities
-		List<Identity> identities = asOptions.getIdentities();
-		BusinessGroup group = asOptions.getGroup();
-		if (group != null) {
-			identities = groupService.getMembers(group, GroupRoles.participant.toString());
-		} else if (identities != null) {
-			identities = asOptions.getIdentities();			
-		} else if (asOptions.isAdmin()){
-			identities = ScoreAccountingHelper.loadUsers(courseEnv);
-		}
-		if (identities != null && identities.size() > 0) {
-			// 2) create export resource
-			MediaResource resource = new QTI21ResultsExportMediaResource(courseEnv, identities, courseNode, getLocale());
-			// 3) download
-			ureq.getDispatchResult().setResultingMediaResource(resource);
-
-		} else {
-			showWarning("error.no.assessed.users");
-		}
-		
-	}
-
-	@Override
-	protected void doDispose() {
-		//
-	}
-}
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessableResource.java b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessableResource.java
index ce773a7f4e8..5d7ab63656f 100644
--- a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessableResource.java
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessableResource.java
@@ -19,15 +19,12 @@
  */
 package org.olat.ims.qti21.ui;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
-import org.olat.modules.assessment.AssessmentToolOptions;
 import org.olat.modules.assessment.ui.AssessableResource;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.repository.RepositoryEntry;
 
 /**
@@ -44,15 +41,8 @@ public class QTI21AssessableResource extends AssessableResource {
 	}
 
 	@Override
-	public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl,
-			TooledStackedPanel stackPanel, RepositoryEntry entry, AssessmentToolOptions options) {
-
-		Controller resetToolCtrl = new QTI21ResetToolController(ureq, wControl, entry, options);
-		List<Controller> toolsCtrl = new ArrayList<>(1);
-		toolsCtrl.add(resetToolCtrl);
-		
-		Controller retrieveToolCtrl = new QTI21RetrieveTestsToolController(ureq, wControl, entry, options);
-		toolsCtrl.add(retrieveToolCtrl);
-		return toolsCtrl;
+	public Controller createIdentityList(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry entry, AssessmentToolSecurityCallback assessmentCallback) {
+		return new QTI21AssessedIdentityListController(ureq, wControl, stackPanel, entry, this, assessmentCallback);
 	}
 }
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessedIdentityListController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessedIdentityListController.java
new file mode 100644
index 00000000000..60f45310a65
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessedIdentityListController.java
@@ -0,0 +1,158 @@
+/**
+ * <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;
+
+import java.util.List;
+
+import org.olat.core.gui.UserRequest;
+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.FlexiTableFilter;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
+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.generic.closablewrapper.CloseableModalController;
+import org.olat.core.id.Identity;
+import org.olat.ims.qti21.QTI21Service;
+import org.olat.modules.assessment.AssessmentToolOptions;
+import org.olat.modules.assessment.ui.AssessableResource;
+import org.olat.modules.assessment.ui.AssessedIdentityListController;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
+import org.olat.repository.RepositoryEntry;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 19 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class QTI21AssessedIdentityListController extends AssessedIdentityListController {
+	
+	private FormLink resetButton, pullButton;
+	
+	private CloseableModalController cmc;
+	private QTI21ResetDataController resetDataCtrl;
+	private QTI21RetrieveTestsController pullSessionCtrl;
+	
+	@Autowired
+	private QTI21Service qtiService;
+	
+	public QTI21AssessedIdentityListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry testEntry, AssessableResource element, AssessmentToolSecurityCallback assessmentCallback) {
+		super(ureq, wControl, stackPanel, testEntry, element, assessmentCallback);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		super.initForm(formLayout, listener, ureq);
+		
+		resetButton = uifactory.addFormLink("reset.test.data.title", formLayout, Link.BUTTON);
+		resetButton.setIconLeftCSS("o_icon o_icon_delete_item");
+		
+		pullButton = uifactory.addFormLink("menu.retrieve.tests.title", formLayout, Link.BUTTON);
+		pullButton.setIconLeftCSS("o_icon o_icon_pull");
+	}
+	
+	@Override
+	protected void updateModel(String searchString, List<FlexiTableFilter> filters, List<FlexiTableFilter> extendedFilters) {
+		super.updateModel(searchString, filters, extendedFilters);
+	}
+	
+	@Override
+	protected void updateTools(List<Identity> assessedIdentities) {
+		RepositoryEntry assessedEntry = getRepositoryEntry();
+		boolean enabled = false;
+		if(assessedIdentities != null && !assessedIdentities.isEmpty()) {
+			enabled = qtiService.isRunningAssessmentTestSession(assessedEntry, null, assessedEntry, assessedIdentities);
+		}
+		pullButton.setEnabled(enabled);
+	}
+
+	@Override
+	public void event(UserRequest ureq, Controller source, Event event) {
+		if(resetDataCtrl == source || pullSessionCtrl == source) {
+			cmc.deactivate();
+			cleanUp();
+		} else if(cmc == source) {
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(resetDataCtrl);
+		removeAsListenerAndDispose(cmc);
+		resetDataCtrl = null;
+		cmc = null;
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(resetButton == source) {
+			doResetData(ureq);
+		} else if(pullButton == source) {
+			doPullSessions(ureq);
+		} else {
+			super.formInnerEvent(ureq, source, event);
+		}
+	}
+	
+	private void doResetData(UserRequest ureq) {
+		if(resetDataCtrl != null) return;
+		
+		/*
+		if(identities == null || identities.isEmpty()) {
+			showWarning("warning.reset.test.data.nobody");
+		} */
+	
+		AssessmentToolOptions asOptions = getOptions();
+		resetDataCtrl = new QTI21ResetDataController(ureq, getWindowControl(), this.getRepositoryEntry(), asOptions);
+		listenTo(resetDataCtrl);
+		
+		String title = translate("reset.test.data.title");
+		cmc = new CloseableModalController(getWindowControl(), null, resetDataCtrl.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
+	private void doPullSessions(UserRequest ureq) {
+		AssessmentToolOptions asOptions = getOptions();
+		pullSessionCtrl = new QTI21RetrieveTestsController(ureq, getWindowControl(), getRepositoryEntry(), asOptions);
+		listenTo(pullSessionCtrl);
+		
+		String title = translate("retrievetest.confirm.title");
+		cmc = new CloseableModalController(getWindowControl(), null, pullSessionCtrl.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
+	private AssessmentToolOptions getOptions() {
+		AssessmentToolOptions asOptions = new AssessmentToolOptions();
+		asOptions.setAdmin(assessmentCallback.isAdmin());
+		List<Identity> assessedIdentities = assessmentToolManager.getAssessedIdentities(getIdentity(), getSearchParameters());
+		asOptions.setIdentities(assessedIdentities);
+		return asOptions;
+	}
+}
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 249a1e2ea73..09a16dcb690 100644
--- a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
@@ -30,11 +30,11 @@ import java.util.List;
 import javax.servlet.http.HttpServletResponse;
 
 import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.EscapeMode;
 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.elements.FormLink;
 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.elements.table.BooleanCellRenderer;
@@ -44,6 +44,7 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTable
 import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.TextFlexiCellRenderer;
+import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
@@ -99,7 +100,7 @@ import uk.ac.ed.ph.jqtiplus.state.TestSessionState;
  */
 public class QTI21AssessmentDetailsController extends FormBasicController {
 
-	private Component resetToolCmp;
+	private FormLink resetButton;
 	private FlexiTableElement tableEl;
 	private QTI21AssessmentTestSessionTableModel tableModel;
 	
@@ -116,7 +117,7 @@ public class QTI21AssessmentDetailsController extends FormBasicController {
 	
 	private CloseableModalController cmc;
 	private AssessmentResultController resultCtrl;
-	private QTI21ResetToolController resetToolCtrl;
+	private QTI21ResetDataController resetToolCtrl;
 	private DialogBoxController retrieveConfirmationCtr;
 	private IdentityAssessmentTestCorrectionController correctionCtrl;
 	
@@ -216,17 +217,8 @@ public class QTI21AssessmentDetailsController extends FormBasicController {
 		tableEl.setEmtpyTableMessageKey("results.empty");
 
 		if(reSecurity.isEntryAdmin() && !readOnly) {
-			AssessmentToolOptions asOptions = new AssessmentToolOptions();
-			asOptions.setAdmin(reSecurity.isEntryAdmin());
-			asOptions.setIdentities(Collections.singletonList(assessedIdentity));
-			if(courseNode != null) {
-				resetToolCtrl = new QTI21ResetToolController(ureq, getWindowControl(),
-						assessedUserCourseEnv.getCourseEnvironment(), asOptions, courseNode);
-			} else {
-				resetToolCtrl = new QTI21ResetToolController(ureq, getWindowControl(), entry, asOptions);
-			}
-			listenTo(resetToolCtrl);
-			resetToolCmp = resetToolCtrl.getInitialComponent();	
+			resetButton = uifactory.addFormLink("menu.reset.title", formLayout, Link.BUTTON);
+			resetButton.setIconLeftCSS("o_icon o_icon_delete_item"); 	
 		}
 	} 
 
@@ -269,13 +261,9 @@ public class QTI21AssessmentDetailsController extends FormBasicController {
 		tableModel.setObjects(infos);
 		tableEl.reloadData();
 		tableEl.reset();
-			
-		if(resetToolCmp != null) {
-			if(sessionsStatistics.size() > 0) {
-				flc.getFormItemComponent().put("reset.tool", resetToolCmp);
-			} else {
-				flc.getFormItemComponent().remove(resetToolCmp);
-			}
+		
+		if(resetButton != null) {
+			resetButton.setVisible(!sessionsStatistics.isEmpty());
 		}
 	}
 
@@ -301,26 +289,32 @@ public class QTI21AssessmentDetailsController extends FormBasicController {
 				updateModel();
 			}
 		} else if(resetToolCtrl == source) {
-			if(event == Event.DONE_EVENT) {
+			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
 				updateModel();
 				fireEvent(ureq, Event.CHANGED_EVENT);
 			}
+			cmc.deactivate();
+			cleanUp();
 		}
 		super.event(ureq, source, event);
 	}
 	
 	private void cleanUp() {
 		removeAsListenerAndDispose(correctionCtrl);
+		removeAsListenerAndDispose(resetToolCtrl);
 		removeAsListenerAndDispose(resultCtrl);
 		removeAsListenerAndDispose(cmc);
 		correctionCtrl = null;
+		resetToolCtrl = null;
 		resultCtrl = null;
 		cmc = null;
 	}
 
 	@Override
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
-		if(tableEl == source) {
+		if(resetButton == source) {
+			doResetData(ureq);
+		} else if(tableEl == source) {
 			if(event instanceof SelectionEvent) {
 				SelectionEvent se = (SelectionEvent)event;
 				String cmd = se.getCommand();
@@ -373,6 +367,24 @@ public class QTI21AssessmentDetailsController extends FormBasicController {
 		assessmentService.updateAssessmentEntry(assessmentEntry);
 	}
 	
+	private void doResetData(UserRequest ureq) {
+		AssessmentToolOptions asOptions = new AssessmentToolOptions();
+		asOptions.setAdmin(reSecurity.isEntryAdmin());
+		asOptions.setIdentities(Collections.singletonList(assessedIdentity));
+		
+		if(courseNode != null) {
+			resetToolCtrl = new QTI21ResetDataController(ureq, getWindowControl(),
+					assessedUserCourseEnv.getCourseEnvironment(), asOptions, courseNode);
+		} else {
+			resetToolCtrl = new QTI21ResetDataController(ureq, getWindowControl(), entry, asOptions);
+		}
+		listenTo(resetToolCtrl);
+
+		cmc = new CloseableModalController(getWindowControl(), "close", resetToolCtrl.getInitialComponent(),
+				true, translate("table.header.results"));
+		cmc.activate();
+		listenTo(cmc);
+	}
 
 	private void doConfirmPullSession(UserRequest ureq, AssessmentTestSession session) {
 		String title = translate("pull");
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21ResetToolController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21ResetDataController.java
similarity index 56%
rename from src/main/java/org/olat/ims/qti21/ui/QTI21ResetToolController.java
rename to src/main/java/org/olat/ims/qti21/ui/QTI21ResetDataController.java
index 764f147bb0b..156d5f4c6c1 100644
--- a/src/main/java/org/olat/ims/qti21/ui/QTI21ResetToolController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21ResetDataController.java
@@ -24,6 +24,7 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.zip.ZipOutputStream;
@@ -31,18 +32,13 @@ import java.util.zip.ZipOutputStream;
 import org.olat.basesecurity.GroupRoles;
 import org.olat.core.commons.modules.bc.FolderConfig;
 import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
 import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
 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.id.Identity;
 import org.olat.core.id.IdentityEnvironment;
 import org.olat.core.id.Roles;
@@ -76,18 +72,20 @@ import org.springframework.beans.factory.annotation.Autowired;
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
-public class QTI21ResetToolController extends BasicController {
+public class QTI21ResetDataController extends FormBasicController {
 	
 	private final Roles studentRoles = new Roles(false, false, false, false, false, false, false, false);
-	private Link resetButton;
+
+	private final String[] onKeys = new String[]{ "on" };
+
+	private MultipleSelectionElement acknowledgeEl;
 	
-	private CloseableModalController cmc;
-	private ConfirmResetController confirmResetCtrl;
+	private final ArchiveOptions options;
+	private final List<Identity> identities;
 	
 	private QTICourseNode courseNode;
 	private CourseEnvironment courseEnv;
 	private RepositoryEntry assessedEntry;
-	private final AssessmentToolOptions asOptions;
 	
 	@Autowired
 	private QTI21Service qtiService;
@@ -96,100 +94,107 @@ public class QTI21ResetToolController extends BasicController {
 	@Autowired
 	private RepositoryService repositoryService;
 
-	public QTI21ResetToolController(UserRequest ureq, WindowControl wControl, 
+	public QTI21ResetDataController(UserRequest ureq, WindowControl wControl, 
 			CourseEnvironment courseEnv, AssessmentToolOptions asOptions, QTICourseNode courseNode) {
-		super(ureq, wControl);
+		super(ureq, wControl, "confirm_reset_data");
 		this.courseNode = courseNode;
 		this.courseEnv = courseEnv;
-		this.asOptions = asOptions;
-		initButton();
+		
+		options = new ArchiveOptions();
+		if(asOptions.getGroup() == null && asOptions.getIdentities() == null) {
+			identities = ScoreAccountingHelper.loadUsers(courseEnv);
+			options.setIdentities(identities);
+		} else if (asOptions.getIdentities() != null) {
+			identities = asOptions.getIdentities();
+			options.setIdentities(identities);
+		} else {
+			identities = businessGroupService.getMembers(asOptions.getGroup());
+			options.setGroup(asOptions.getGroup());
+		}
+		
+		initForm(ureq);
+	}
+	
+	public QTI21ResetDataController(UserRequest ureq, WindowControl wControl, RepositoryEntry courseEntry,
+			IQTESTCourseNode courseNode, Identity assessedIdentity) {
+		super(ureq, wControl, "confirm_reset_data");
+		this.courseNode = courseNode;
+		courseEnv = CourseFactory.loadCourse(courseEntry).getCourseEnvironment();
+		
+		options = new ArchiveOptions();
+		identities = Collections.singletonList(assessedIdentity);
+		options.setIdentities(identities);
+		initForm(ureq);
 	}
 	
-	public QTI21ResetToolController(UserRequest ureq, WindowControl wControl, 
+	public QTI21ResetDataController(UserRequest ureq, WindowControl wControl, 
 			RepositoryEntry assessedEntry, AssessmentToolOptions asOptions) {
-		super(ureq, wControl);
+		super(ureq, wControl, "confirm_reset_data");
 		this.assessedEntry = assessedEntry;
-		this.asOptions = asOptions;
-		initButton();
+		
+		options = new ArchiveOptions();
+		if(asOptions.getGroup() == null && asOptions.getIdentities() == null) {
+			identities = repositoryService.getMembers(assessedEntry, GroupRoles.participant.name());
+			options.setIdentities(identities);
+		} else if (asOptions.getIdentities() != null) {
+			identities = asOptions.getIdentities();
+			options.setIdentities(identities);
+		} else {
+			identities = businessGroupService.getMembers(asOptions.getGroup());
+			options.setGroup(asOptions.getGroup());
+		}
+
+		initForm(ureq);
 	}
 	
-	private void initButton() {
-		resetButton = LinkFactory.createButton("reset.test.data.title", null, this);
-		resetButton.setIconLeftCSS("o_icon o_icon_delete_item");
-		resetButton.setTranslator(getTranslator());
-		putInitialPanel(resetButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		if(formLayout instanceof FormLayoutContainer) {
+			FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
+			String[] args = new String[]{ Integer.toString(identities.size()) };
+			String msg = translate("reset.test.data.text", args);
+			layoutCont.contextPut("msg", msg);
+		}
+		
+		FormLayoutContainer confirmCont = FormLayoutContainer.createDefaultFormLayout("confirm", getTranslator());
+		formLayout.add("confirm", confirmCont);
+		confirmCont.setRootForm(mainForm);
+		
+		String[] onValues = new String[]{ translate("reset.test.data.acknowledge") };
+		acknowledgeEl = uifactory.addCheckboxesHorizontal("acknowledge", "confirmation", confirmCont, onKeys, onValues);
+		
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		buttonsCont.setRootForm(mainForm);
+		confirmCont.add(buttonsCont);
+		uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("reset.data", buttonsCont);
 	}
-	
+
 	@Override
 	protected void doDispose() {
 		//
 	}
-
+	
 	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		if(resetButton == source) {
-			doConfirmReset(ureq);
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+		
+		acknowledgeEl.clearError();
+		if(!acknowledgeEl.isAtLeastSelected(1)) {
+			acknowledgeEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
 		}
+		
+		return allOk & super.validateFormLogic(ureq);
 	}
 
 	@Override
-	protected void event(UserRequest ureq, Controller source, Event event) {
-		if(confirmResetCtrl == source) {
-			if(event == Event.DONE_EVENT) {
-				doReset(ureq, confirmResetCtrl.getOptions(), confirmResetCtrl.getIdentities());
-				fireEvent(ureq, Event.DONE_EVENT);
-			}
-			cmc.deactivate();
-			cleanUp();
-		} else if (cmc == source) {
-			cleanUp();
-		}
-		super.event(ureq, source, event);
-	}
-	
-	private void cleanUp() {
-		removeAsListenerAndDispose(confirmResetCtrl);
-		removeAsListenerAndDispose(cmc);
-		confirmResetCtrl = null;
-		cmc = null;
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
 	}
 
-	private void doConfirmReset(UserRequest ureq) {
-		if(confirmResetCtrl != null) return;
-		
-		ArchiveOptions options = new ArchiveOptions();
-		List<Identity> identities = null;
-		if(asOptions.getGroup() == null && asOptions.getIdentities() == null) {
-			if(courseEnv != null) {
-				identities = ScoreAccountingHelper.loadUsers(courseEnv);
-				options.setIdentities(identities);
-			} else {
-				identities = repositoryService.getMembers(assessedEntry, GroupRoles.participant.name());
-				options.setIdentities(identities);
-			}
-		} else if (asOptions.getIdentities() != null) {
-			identities = asOptions.getIdentities();
-			options.setIdentities(identities);
-		} else {
-			identities = businessGroupService.getMembers(asOptions.getGroup());
-			options.setGroup(asOptions.getGroup());
-		}
-		
-		if(identities == null || identities.isEmpty()) {
-			showWarning("warning.reset.test.data.nobody");
-		} else {
-			confirmResetCtrl = new ConfirmResetController(ureq, getWindowControl(), options, identities);
-			listenTo(confirmResetCtrl);
-	
-			String title = translate("reset.test.data.title");
-			cmc = new CloseableModalController(getWindowControl(), null, confirmResetCtrl.getInitialComponent(), true, title, true);
-			listenTo(cmc);
-			cmc.activate();
-		}
-	}
-	
-	private void doReset(UserRequest ureq, ArchiveOptions options, List<Identity> identities) {
+	@Override
+	protected void formOK(UserRequest ureq) {
 		if(courseNode instanceof IQTESTCourseNode) {
 			IQTESTCourseNode testCourseNode = (IQTESTCourseNode)courseNode;
 			RepositoryEntry testEntry = courseNode.getReferencedRepositoryEntry();
@@ -204,6 +209,7 @@ public class QTI21ResetToolController extends BasicController {
 				IdentityEnvironment ienv = new IdentityEnvironment(identity, studentRoles);
 				UserCourseEnvironment uce = new UserCourseEnvironmentImpl(ienv, courseEnv);
 				testCourseNode.updateUserScoreEvaluation(scoreEval, uce, getIdentity(), false, Role.coach);
+				testCourseNode.updateCurrentCompletion(uce, getIdentity(), null, AssessmentRunStatus.notStarted, Role.coach);
 			}
 		} else if(assessedEntry != null) {
 			archiveData(assessedEntry);
@@ -213,7 +219,7 @@ public class QTI21ResetToolController extends BasicController {
 		fireEvent(ureq, Event.CHANGED_EVENT);
 	}
 	
-	private void archiveData(ICourse course, ArchiveOptions options) {
+	private void archiveData(ICourse course, ArchiveOptions archiveOptions) {
 		File exportDirectory = CourseFactory.getOrCreateDataExportDirectory(getIdentity(), course.getCourseTitle());
 		String archiveName = courseNode.getType() + "_"
 				+ StringHelper.transformDisplayNameToFileSystemName(courseNode.getShortName())
@@ -223,7 +229,7 @@ public class QTI21ResetToolController extends BasicController {
 		try(FileOutputStream fileStream = new FileOutputStream(exportFile);
 			ZipOutputStream exportStream = new ZipOutputStream(fileStream)) {
 			
-			courseNode.archiveNodeData(getLocale(), course, options, exportStream, "UTF-8");
+			courseNode.archiveNodeData(getLocale(), course, archiveOptions, exportStream, "UTF-8");
 		} catch (IOException e) {
 			logError("", e);
 		}
@@ -249,80 +255,4 @@ public class QTI21ResetToolController extends BasicController {
 		}
 		
 	}
-	
-	private class ConfirmResetController extends FormBasicController {
-		
-		private final String[] onKeys = new String[]{ "on" };
-
-		private MultipleSelectionElement acknowledgeEl;
-		
-		private final ArchiveOptions options;
-		private final List<Identity> identities;
-		
-		public ConfirmResetController(UserRequest ureq, WindowControl wControl, ArchiveOptions options, List<Identity> identities) {
-			super(ureq, wControl, "confirm_reset_data");
-			this.options = options;
-			this.identities = identities;
-			initForm(ureq);
-		}
-		
-		public ArchiveOptions getOptions() {
-			return options;
-		}
-		
-		public List<Identity> getIdentities() {
-			return identities;
-		}
-
-		@Override
-		protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
-			if(formLayout instanceof FormLayoutContainer) {
-				FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
-				String[] args = new String[]{ Integer.toString(identities.size()) };
-				String msg = translate("reset.test.data.text", args);
-				layoutCont.contextPut("msg", msg);
-			}
-			
-			FormLayoutContainer confirmCont = FormLayoutContainer.createDefaultFormLayout("confirm", getTranslator());
-			formLayout.add("confirm", confirmCont);
-			confirmCont.setRootForm(mainForm);
-			
-			String[] onValues = new String[]{ translate("reset.test.data.acknowledge") };
-			acknowledgeEl = uifactory.addCheckboxesHorizontal("acknowledge", "confirmation", confirmCont, onKeys, onValues);
-			
-			FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
-			buttonsCont.setRootForm(mainForm);
-			confirmCont.add(buttonsCont);
-			uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl());
-			uifactory.addFormSubmitButton("reset.data", buttonsCont);
-		}
-		
-		@Override
-		protected void doDispose() {
-			//
-		}
-
-		@Override
-		protected boolean validateFormLogic(UserRequest ureq) {
-			boolean allOk = true;
-			
-			acknowledgeEl.clearError();
-			if(!acknowledgeEl.isAtLeastSelected(1)) {
-				acknowledgeEl.setErrorKey("form.legende.mandatory", null);
-				allOk &= false;
-			}
-			
-			return allOk & super.validateFormLogic(ureq);
-		}
-
-		@Override
-		protected void formOK(UserRequest ureq) {
-			fireEvent(ureq, Event.DONE_EVENT);
-		}
-
-		@Override
-		protected void formCancelled(UserRequest ureq) {
-			fireEvent(ureq, Event.CANCELLED_EVENT);
-		}
-	}
 }
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsController.java
new file mode 100644
index 00000000000..c2128956bdd
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsController.java
@@ -0,0 +1,220 @@
+/**
+ * <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;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.olat.basesecurity.GroupRoles;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+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.id.Identity;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.Util;
+import org.olat.course.CourseFactory;
+import org.olat.course.archiver.ScoreAccountingHelper;
+import org.olat.course.assessment.AssessmentHelper;
+import org.olat.course.nodes.IQTESTCourseNode;
+import org.olat.course.nodes.iq.IQEditController;
+import org.olat.course.nodes.iq.QTI21AssessmentRunController;
+import org.olat.course.run.environment.CourseEnvironment;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.group.BusinessGroupService;
+import org.olat.ims.qti.QTIResultManager;
+import org.olat.ims.qti21.AssessmentTestSession;
+import org.olat.ims.qti21.QTI21DeliveryOptions;
+import org.olat.ims.qti21.QTI21Service;
+import org.olat.ims.qti21.model.DigitalSignatureOptions;
+import org.olat.modules.ModuleConfiguration;
+import org.olat.modules.assessment.AssessmentToolOptions;
+import org.olat.modules.assessment.Role;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryService;
+import org.olat.user.UserManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 19 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class QTI21RetrieveTestsController extends FormBasicController {
+
+	private RepositoryEntry assessedEntry;
+	private IQTESTCourseNode courseNode;
+	
+	private List<Identity> identities;
+	private List<AssessmentTestSession> sessions;
+	
+	@Autowired
+	private UserManager userManager;
+	@Autowired
+	private QTI21Service qtiService;
+	@Autowired
+	private RepositoryService repositoryService;
+	@Autowired
+	private BusinessGroupService businessGroupService;
+	
+	public QTI21RetrieveTestsController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv,
+			AssessmentToolOptions asOptions, IQTESTCourseNode courseNode) {
+		super(ureq, wControl, "retrieve_tests");
+		setTranslator(Util.createPackageTranslator(QTIResultManager.class, getLocale(), getTranslator()));
+
+		this.courseNode = courseNode;
+		identities = getIdentities(asOptions, courseEnv);
+		sessions = qtiService
+				.getRunningAssessmentTestSession(courseEnv.getCourseGroupManager().getCourseEntry(), courseNode.getIdent(), courseNode.getReferencedRepositoryEntry());
+		
+		initForm(ureq);
+	}
+	
+	public QTI21RetrieveTestsController(UserRequest ureq, WindowControl wControl,
+			AssessmentTestSession session, IQTESTCourseNode courseNode) {
+		super(ureq, wControl, "retrieve_tests");
+		setTranslator(Util.createPackageTranslator(QTIResultManager.class, getLocale(), getTranslator()));
+		this.courseNode = courseNode;
+		identities = Collections.singletonList(session.getIdentity());
+		sessions = Collections.singletonList(session);
+		initForm(ureq);
+	}
+	
+	public QTI21RetrieveTestsController(UserRequest ureq, WindowControl wControl, RepositoryEntry  assessedEntry,
+			AssessmentToolOptions asOptions) {
+		super(ureq, wControl, "retrieve_tests");
+		setTranslator(Util.createPackageTranslator(QTIResultManager.class, getLocale(), getTranslator()));
+		this.assessedEntry = assessedEntry;
+		identities = getIdentities(asOptions, null);
+		sessions = qtiService.getRunningAssessmentTestSession(assessedEntry, null, assessedEntry);
+		initForm(ureq);
+	}
+	
+	private List<Identity> getIdentities(AssessmentToolOptions asOptions, CourseEnvironment courseEnv) {
+		List<Identity> identityList;
+		if(asOptions.getGroup() == null && asOptions.getIdentities() == null) {
+			if(courseEnv != null) {
+				identityList = ScoreAccountingHelper.loadUsers(courseEnv);
+			} else {
+				identityList = repositoryService.getMembers(assessedEntry, GroupRoles.participant.name());
+			}
+		} else if (asOptions.getIdentities() != null) {
+			identityList = asOptions.getIdentities();
+		} else {
+			identityList = businessGroupService.getMembers(asOptions.getGroup());
+		}
+		return identityList;
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		StringBuilder fullnames = new StringBuilder(256);
+
+		List<AssessmentTestSession> sessionsToRetrieve = new ArrayList<>();
+		Set<Identity> assessedIdentites = new HashSet<>(identities);
+		for(AssessmentTestSession session:sessions) {
+			if(assessedIdentites.contains(session.getIdentity())) {
+				if(fullnames.length() > 0) fullnames.append(", ");
+				String name = userManager.getUserDisplayName(session.getIdentity());
+				if(StringHelper.containsNonWhitespace(name)) {
+					fullnames.append(name);
+					sessionsToRetrieve.add(session);
+				}
+			}
+		}
+		
+		String msg;
+		if(sessionsToRetrieve.size() == 0) {
+			msg = translate("retrievetest.nothing.todo");
+		} else if(sessionsToRetrieve.size() == 1) {
+			msg = translate("retrievetest.confirm.text", new String[]{ fullnames.toString() });
+		} else  {
+			msg = translate("retrievetest.confirm.text.plural", new String[]{ fullnames.toString() });
+		}
+		if(formLayout instanceof FormLayoutContainer) {
+			FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
+			layoutCont.contextPut("msg", msg);
+		}
+		
+		uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("menu.retrieve.tests.title", formLayout);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		for(AssessmentTestSession session:sessions) {
+			doRetrieveTest(session);
+		}
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+
+	private void doRetrieveTest(AssessmentTestSession session) {
+		session = qtiService.getAssessmentTestSession(session.getKey());
+		session = qtiService.pullSession(session, getSignatureOptions(session), getIdentity());
+		if(courseNode != null) {
+			RepositoryEntry courseEntry = session.getRepositoryEntry();
+			CourseEnvironment courseEnv = CourseFactory.loadCourse(courseEntry).getCourseEnvironment();
+			UserCourseEnvironment assessedUserCourseEnv = AssessmentHelper
+					.createAndInitUserCourseEnvironment(session.getIdentity(), courseEnv);
+			courseNode.pullAssessmentTestSession(session, assessedUserCourseEnv, getIdentity(), Role.coach);
+		}
+	}
+	
+	private DigitalSignatureOptions getSignatureOptions(AssessmentTestSession session) {
+		if(courseNode == null) return null;
+		
+		RepositoryEntry testEntry = session.getTestEntry();
+		RepositoryEntry courseEntry = session.getRepositoryEntry();
+		QTI21DeliveryOptions deliveryOptions = qtiService.getDeliveryOptions(testEntry);
+		
+		boolean digitalSignature = deliveryOptions.isDigitalSignature();
+		boolean sendMail = deliveryOptions.isDigitalSignatureMail();
+
+		ModuleConfiguration config = courseNode.getModuleConfiguration();
+		digitalSignature = config.getBooleanSafe(IQEditController.CONFIG_DIGITAL_SIGNATURE,
+			deliveryOptions.isDigitalSignature());
+		sendMail = config.getBooleanSafe(IQEditController.CONFIG_DIGITAL_SIGNATURE_SEND_MAIL,
+			deliveryOptions.isDigitalSignatureMail());
+
+		DigitalSignatureOptions options = new DigitalSignatureOptions(digitalSignature, sendMail, courseEntry, testEntry);
+		if(digitalSignature) {
+			CourseEnvironment courseEnv = CourseFactory.loadCourse(courseEntry).getCourseEnvironment();
+			QTI21AssessmentRunController.decorateCourseConfirmation(session, options, courseEnv, courseNode, testEntry, null, getLocale());
+		}
+		return options;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsToolController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsToolController.java
deleted file mode 100644
index 7b0561251c5..00000000000
--- a/src/main/java/org/olat/ims/qti21/ui/QTI21RetrieveTestsToolController.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/**
- * <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;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.olat.basesecurity.GroupRoles;
-import org.olat.core.commons.persistence.DB;
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
-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.dtabs.Activateable2;
-import org.olat.core.gui.control.generic.modal.DialogBoxController;
-import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
-import org.olat.core.id.Identity;
-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.StringHelper;
-import org.olat.core.util.Util;
-import org.olat.core.util.coordinate.CoordinatorManager;
-import org.olat.core.util.resource.OresHelper;
-import org.olat.course.archiver.ScoreAccountingHelper;
-import org.olat.course.nodes.IQTESTCourseNode;
-import org.olat.course.run.environment.CourseEnvironment;
-import org.olat.group.BusinessGroupService;
-import org.olat.ims.qti.QTIResultManager;
-import org.olat.ims.qti21.AssessmentSessionAuditLogger;
-import org.olat.ims.qti21.AssessmentTestSession;
-import org.olat.ims.qti21.QTI21Service;
-import org.olat.ims.qti21.ui.event.RetrieveAssessmentTestSessionEvent;
-import org.olat.modules.assessment.AssessmentToolOptions;
-import org.olat.repository.RepositoryEntry;
-import org.olat.repository.RepositoryService;
-import org.olat.user.UserManager;
-import org.springframework.beans.factory.annotation.Autowired;
-
-/**
- * 
- * Initial date: 07.07.2015<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class QTI21RetrieveTestsToolController extends BasicController implements Activateable2 {
-	
-	private Link pullButton;
-	private DialogBoxController retrieveConfirmationCtr;
-	
-	/** Not used, tool not implemented */
-	private RepositoryEntry assessedEntry;
-	private IQTESTCourseNode courseNode;
-	private CourseEnvironment courseEnv;
-	private final AssessmentToolOptions asOptions;
-	
-	@Autowired
-	private DB dbInstance;
-	@Autowired
-	private UserManager userManager;
-	@Autowired
-	private QTI21Service qtiService;
-	@Autowired
-	private RepositoryService repositoryService;
-	@Autowired
-	private BusinessGroupService businessGroupService;
-	
-	public QTI21RetrieveTestsToolController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv,
-			AssessmentToolOptions asOptions, IQTESTCourseNode courseNode) {
-		super(ureq, wControl);
-		setTranslator(Util.createPackageTranslator(QTIResultManager.class, getLocale(), getTranslator()));
-		
-		this.courseEnv = courseEnv;
-		this.courseNode = courseNode;
-		this.asOptions = asOptions;
-		initButton();
-	}
-	
-	public QTI21RetrieveTestsToolController(UserRequest ureq, WindowControl wControl, RepositoryEntry  assessedEntry,
-			AssessmentToolOptions asOptions) {
-		super(ureq, wControl);
-		setTranslator(Util.createPackageTranslator(QTIResultManager.class, getLocale(), getTranslator()));
-		this.assessedEntry = assessedEntry;
-		this.asOptions = asOptions;
-		initButton();
-	}
-	
-	private List<Identity> getIdentities() {
-		List<Identity> identities;
-		if(asOptions.getGroup() == null && asOptions.getIdentities() == null) {
-			if(courseEnv != null) {
-				identities = ScoreAccountingHelper.loadUsers(courseEnv);
-			} else {
-				identities = repositoryService.getMembers(assessedEntry, GroupRoles.participant.name());
-			}
-		} else if (asOptions.getIdentities() != null) {
-			identities = asOptions.getIdentities();
-		} else {
-			identities = businessGroupService.getMembers(asOptions.getGroup());
-		}
-		return identities;
-	}
-	
-	private void initButton() {
-		boolean enabled;
-		List<Identity> identities = getIdentities();
-		if(identities == null || identities.isEmpty()) {
-			enabled = false;
-		} else if(courseEnv != null) {
-			enabled = qtiService.isRunningAssessmentTestSession(courseEnv.getCourseGroupManager().getCourseEntry(),
-					courseNode.getIdent(), courseNode.getReferencedRepositoryEntry(), getIdentities());
-		} else {
-			enabled = qtiService.isRunningAssessmentTestSession(assessedEntry, null, assessedEntry, getIdentities());
-		}
-		
-		pullButton = LinkFactory.createButton("menu.retrieve.tests.title", null, this);
-		pullButton.setIconLeftCSS("o_icon o_icon_pull");
-		pullButton.setTranslator(getTranslator());
-		pullButton.setEnabled(enabled);
-		putInitialPanel(pullButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
-	}
-
-	@Override
-	protected void doDispose() {
-		//
-	}
-
-	@Override
-	public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
-		//
-	}
-
-	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		if(pullButton == source) {
-			confirmPull(ureq);
-		}
-	}
-	
-	@Override
-	protected void event(UserRequest ureq, Controller source, Event event) {
-		if(retrieveConfirmationCtr == source) {
-			if(DialogBoxUIFactory.isYesEvent(event)) {
-				@SuppressWarnings("unchecked")
-				List<AssessmentTestSession> sessionsToRetrieve = (List<AssessmentTestSession>)retrieveConfirmationCtr.getUserObject();
-				doRetrieveTests(sessionsToRetrieve);
-			}
-			removeAsListenerAndDispose(retrieveConfirmationCtr);
-			retrieveConfirmationCtr = null;
-		}
-	}
-	
-	private void confirmPull(UserRequest ureq) {
-		StringBuilder fullnames = new StringBuilder(256);
-		
-		List<AssessmentTestSession> sessions;
-		if(courseEnv != null) {
-			sessions = qtiService
-				.getRunningAssessmentTestSession(courseEnv.getCourseGroupManager().getCourseEntry(), courseNode.getIdent(), courseNode.getReferencedRepositoryEntry());
-		} else {
-			sessions = qtiService.getRunningAssessmentTestSession(assessedEntry, null, assessedEntry);
-		}
-		
-		List<Identity> identities = getIdentities();
-		List<AssessmentTestSession> sessionsToRetrieve = new ArrayList<>();
-		Set<Identity> assessedIdentites = new HashSet<>(identities);
-		for(AssessmentTestSession session:sessions) {
-			if(assessedIdentites.contains(session.getIdentity())) {
-				if(fullnames.length() > 0) fullnames.append(", ");
-				String name = userManager.getUserDisplayName(session.getIdentity());
-				if(StringHelper.containsNonWhitespace(name)) {
-					fullnames.append(name);
-					sessionsToRetrieve.add(session);
-				}
-			}
-		}
-		
-		if(sessionsToRetrieve.size() == 0) {
-			showInfo("retrievetest.nothing.todo");
-		} else if(sessionsToRetrieve.size() == 1) {
-			String title = translate("retrievetest.confirm.title");
-			String text = translate("retrievetest.confirm.text", new String[]{ fullnames.toString() });
-			retrieveConfirmationCtr = activateYesNoDialog(ureq, title, text, retrieveConfirmationCtr);
-			retrieveConfirmationCtr.setUserObject(sessionsToRetrieve);
-		} else  {
-			String title = translate("retrievetest.confirm.title");
-			String text = translate("retrievetest.confirm.text.plural", new String[]{ fullnames.toString() });
-			retrieveConfirmationCtr = activateYesNoDialog(ureq, title, text, retrieveConfirmationCtr);
-			retrieveConfirmationCtr.setUserObject(sessionsToRetrieve);
-		}
-	}
-	
-	private void doRetrieveTests(List<AssessmentTestSession> sessionsToRetrieve) {
-		for(AssessmentTestSession sessionToRetrieve:sessionsToRetrieve) {
-			doRetrieveTest(sessionToRetrieve);
-		}
-	}
-
-	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
-		
-		AssessmentSessionAuditLogger candidateAuditLogger = qtiService.getAssessmentSessionAuditLogger(session, false);
-		candidateAuditLogger.logTestRetrieved(session, getIdentity());
-		
-		OLATResourceable sessionOres = OresHelper.createOLATResourceableInstance(AssessmentTestSession.class, session.getKey());
-		CoordinatorManager.getInstance().getCoordinator().getEventBus()
-			.fireEventToListenersOf(new RetrieveAssessmentTestSessionEvent(session.getKey()), sessionOres);
-	}
-}
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/ui/_content/assessment_details.html b/src/main/java/org/olat/ims/qti21/ui/_content/assessment_details.html
index 06f44a101c1..268c4971709 100644
--- a/src/main/java/org/olat/ims/qti21/ui/_content/assessment_details.html
+++ b/src/main/java/org/olat/ims/qti21/ui/_content/assessment_details.html
@@ -1,6 +1,6 @@
 $r.render("sessions")
-#if($r.available("reset.tool"))
+#if($r.available("menu.reset.title"))
 	<div class="o_button_group">
-		$r.render("reset.tool")
+		$r.render("menu.reset.title")
 	</div>
 #end
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/ui/_content/identity_element.html b/src/main/java/org/olat/ims/qti21/ui/_content/identity_element.html
new file mode 100644
index 00000000000..db6110d6743
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/ui/_content/identity_element.html
@@ -0,0 +1,8 @@
+<h2><i class="o_icon $cssClass"> </i> $r.escapeHtml($title)</h2>
+<div class="o_button_group o_button_group_right">
+#if($r.available("menu.retrieve.tests.title"))
+	$r.render("menu.retrieve.tests.title")
+#end
+$r.render("reset.test.data.title")
+</div>
+$r.render("table")
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/ui/_content/retrieve_tests.html b/src/main/java/org/olat/ims/qti21/ui/_content/retrieve_tests.html
new file mode 100644
index 00000000000..7392881f148
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/ui/_content/retrieve_tests.html
@@ -0,0 +1,5 @@
+<div>$msg</div>
+<div class="o_button_group">
+	$r.render("cancel")
+	$r.render("menu.retrieve.tests.title")
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/ui/assessment/QTI21CorrectionToolController.java b/src/main/java/org/olat/ims/qti21/ui/assessment/QTI21CorrectionToolController.java
deleted file mode 100644
index d312a73f476..00000000000
--- a/src/main/java/org/olat/ims/qti21/ui/assessment/QTI21CorrectionToolController.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/**
- * <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.assessment;
-
-import java.math.BigDecimal;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
-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.course.assessment.AssessmentHelper;
-import org.olat.course.nodes.IQTESTCourseNode;
-import org.olat.course.run.environment.CourseEnvironment;
-import org.olat.course.run.scoring.ScoreEvaluation;
-import org.olat.course.run.userview.UserCourseEnvironment;
-import org.olat.ims.qti21.AssessmentTestSession;
-import org.olat.modules.assessment.AssessmentToolOptions;
-import org.olat.modules.assessment.Role;
-import org.olat.modules.assessment.ui.event.CompleteAssessmentTestSessionEvent;
-
-/**
- * 
- * Initial date: 08.08.2016<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class QTI21CorrectionToolController extends BasicController {
-	
-	private final Link correctionButton;
-
-	private CloseableModalController cmc;
-	private IdentitiesAssessmentTestCorrectionController correctionCtrl;
-	
-	private final IQTESTCourseNode courseNode;
-	private final CourseEnvironment courseEnv;
-	private final AssessmentToolOptions asOptions;
-
-	public QTI21CorrectionToolController(UserRequest ureq, WindowControl wControl, 
-			CourseEnvironment courseEnv, AssessmentToolOptions asOptions, IQTESTCourseNode courseNode) {
-		super(ureq, wControl);
-		this.courseNode = courseNode;
-		this.courseEnv = courseEnv;
-		this.asOptions = asOptions;
-		
-		correctionButton = LinkFactory.createButton("correction.test.title", null, this);
-		correctionButton.setIconLeftCSS("o_icon o_icon-fw o_icon_correction");
-		correctionButton.setTranslator(getTranslator());
-		putInitialPanel(correctionButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
-	}
-	
-	@Override
-	protected void doDispose() {
-		//
-	}
-
-	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		if(correctionButton == source) {
-			doStartCorrection(ureq);
-		}
-	}
-	
-	@Override
-	protected void event(UserRequest ureq, Controller source, Event event) {
-		if(correctionCtrl == source) {
-			if(event instanceof CompleteAssessmentTestSessionEvent) {
-				CompleteAssessmentTestSessionEvent catse = (CompleteAssessmentTestSessionEvent)event;
-				List<AssessmentTestSession> testSessionsToComplete = catse.getTestSessions();
-				doUpdateCourseNode(correctionCtrl.getTestCorrections(), testSessionsToComplete);
-				fireEvent(ureq, Event.CHANGED_EVENT);
-			}
-			cmc.deactivate();
-			cleanUp();
-		}
-	}
-	
-	private void cleanUp() {
-		removeAsListenerAndDispose(correctionCtrl);
-		removeAsListenerAndDispose(cmc);
-		correctionCtrl = null;
-		cmc = null;
-	}
-	
-	public void doStartCorrection(UserRequest ureq) {
-		correctionCtrl = new IdentitiesAssessmentTestCorrectionController(ureq, getWindowControl(), courseEnv, asOptions, courseNode);
-		if(correctionCtrl.getNumberOfAssessedIdentities() == 0) {
-			showWarning("grade.nobody");
-			correctionCtrl = null;
-		} else {
-			listenTo(correctionCtrl);
-			cmc = new CloseableModalController(getWindowControl(), "close", correctionCtrl.getInitialComponent(),
-					true, translate("correction"));
-			cmc.activate();
-			listenTo(cmc);
-		}
-	}
-	
-	private void doUpdateCourseNode(AssessmentTestCorrection corrections, List<AssessmentTestSession> testSessionsToComplete) {
-		Set<AssessmentTestSession> selectedSessions = new HashSet<>(testSessionsToComplete);
-		for(AssessmentTestSession testSession:corrections.getTestSessions()) {
-			if(selectedSessions.contains(testSession)) {
-				UserCourseEnvironment assessedUserCourseEnv = AssessmentHelper
-						.createAndInitUserCourseEnvironment(testSession.getIdentity(), courseEnv);
-				ScoreEvaluation scoreEval = courseNode.getUserScoreEvaluation(assessedUserCourseEnv);
-				
-				BigDecimal finalScore = testSession.getFinalScore();
-				Float score = finalScore == null ? null : finalScore.floatValue();
-				ScoreEvaluation manualScoreEval = new ScoreEvaluation(score, scoreEval.getPassed(),
-						scoreEval.getAssessmentStatus(), scoreEval.getUserVisible(), scoreEval.getFullyAssessed(),
-						scoreEval.getCurrentRunCompletion(), scoreEval.getCurrentRunStatus(), testSession.getKey());
-				courseNode.updateUserScoreEvaluation(manualScoreEval, assessedUserCourseEnv, getIdentity(), false, Role.coach);
-			}
-		}
-	}
-}
diff --git a/src/main/java/org/olat/ims/qti21/ui/assessment/QTI21ValidationToolController.java b/src/main/java/org/olat/ims/qti21/ui/assessment/QTI21ValidationToolController.java
deleted file mode 100644
index 8c0f5be219f..00000000000
--- a/src/main/java/org/olat/ims/qti21/ui/assessment/QTI21ValidationToolController.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * <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.assessment;
-
-import org.olat.core.gui.UserRequest;
-import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
-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.util.Util;
-import org.olat.ims.qti21.ui.QTI21RuntimeController;
-
-/**
- * 
- * Initial date: 28 févr. 2017<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class QTI21ValidationToolController extends BasicController {
-	
-	private CloseableModalController cmc;
-	private ValidationXmlSignatureController validationCtrl;
-	
-	private final Link validateButton;
-	
-	public QTI21ValidationToolController(UserRequest ureq, WindowControl wControl) {
-		super(ureq, wControl, Util.createPackageTranslator(QTI21RuntimeController.class, ureq.getLocale()));
-		
-		validateButton = LinkFactory.createButton("validate.xml.signature", null, this);
-		validateButton.setIconLeftCSS("o_icon o_icon-fw o_icon_correction");
-		validateButton.setTranslator(getTranslator());
-		putInitialPanel(validateButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
-	}
-
-	@Override
-	protected void doDispose() {
-		//
-	}
-	
-	@Override
-	protected void event(UserRequest ureq, Component source, Event event) {
-		if(validateButton == source) {
-			doValidate(ureq);
-		}
-	}
-	
-	@Override
-	protected void event(UserRequest ureq, Controller source, Event event) {
-		if(validationCtrl == source) {
-			cmc.deactivate();
-			cleanUp();
-		} else if(cmc == source) {
-			cleanUp();
-		}
-	}
-	
-	private void cleanUp() {
-		removeAsListenerAndDispose(validationCtrl);
-		removeAsListenerAndDispose(cmc);
-		validationCtrl = null;
-		cmc = null;
-	}
-
-	private void doValidate(UserRequest ureq) {
-		if(validationCtrl != null) return;
-		
-		validationCtrl = new ValidationXmlSignatureController(ureq, getWindowControl());
-		listenTo(validationCtrl);
-		cmc = new CloseableModalController(getWindowControl(), "close", validationCtrl.getInitialComponent(),
-				true, translate("validate.xml.signature"));
-		cmc.activate();
-		listenTo(cmc);
-	}
-}
diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticsToolController.java b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticsToolController.java
index e6e8deedc55..6e353990746 100644
--- a/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticsToolController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticsToolController.java
@@ -27,8 +27,6 @@ import org.olat.basesecurity.Group;
 import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.link.Link;
-import org.olat.core.gui.components.link.LinkFactory;
 import org.olat.core.gui.components.panel.Panel;
 import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.components.tree.GenericTreeModel;
@@ -64,13 +62,11 @@ import org.springframework.beans.factory.annotation.Autowired;
 public class QTI21StatisticsToolController extends BasicController implements Activateable2 {
 
 	private MenuTree courseTree;
-	private final Link statsButton;
 	private Controller currentCtrl;
 	private final TooledStackedPanel stackPanel;
 	private LayoutMain3ColsController layoutCtr;
 
 	private final ArchiveOptions options;
-	private final QTICourseNode courseNode;
 	private final RepositoryEntry testEntry;
 	private final RepositoryEntry courseEntry;
 	private QTI21StatisticResourceResult result;
@@ -96,10 +92,9 @@ public class QTI21StatisticsToolController extends BasicController implements Ac
 			AssessmentToolOptions asOptions, QTICourseNode courseNode) {
 		super(ureq, wControl);
 		this.stackPanel = stackPanel;
-		this.options = new ArchiveOptions();
-		this.options.setGroup(asOptions.getGroup());
-		this.options.setIdentities(asOptions.getIdentities());
-		this.courseNode = courseNode;
+		options = new ArchiveOptions();
+		options.setGroup(asOptions.getGroup());
+		options.setIdentities(asOptions.getIdentities());
 		courseEntry = courseEnv.getCourseGroupManager().getCourseEntry();
 		testEntry = courseNode.getReferencedRepositoryEntry();
 		
@@ -119,11 +114,29 @@ public class QTI21StatisticsToolController extends BasicController implements Ac
 			searchParams.setViewNonMembers(asOptions.isNonMembers());
 		}
 		
-		statsButton = LinkFactory.createButton("menu.title", null, this);
-		statsButton.setIconLeftCSS("o_icon o_icon-fw o_icon_statistics_tool");
-		statsButton.setTranslator(getTranslator());
-		putInitialPanel(statsButton);
-		getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout 
+		result = new QTI21StatisticResourceResult(testEntry, courseEntry, courseNode, searchParams, secCallback);
+		result.setWithFilter(false);
+		
+		GenericTreeModel treeModel = new GenericTreeModel();
+		StatisticResourceNode rootTreeNode = new StatisticResourceNode(courseNode, result);
+		treeModel.setRootNode(rootTreeNode);
+		
+		TreeNode subRootNode = result.getSubTreeModel().getRootNode();
+		List<INode> subNodes = new ArrayList<>();
+		for(int i=0; i<subRootNode.getChildCount(); i++) {
+			subNodes.add(subRootNode.getChildAt(i));
+		}
+		for(INode subNode:subNodes) {
+			rootTreeNode.addChild(subNode);
+		}
+
+		courseTree = new MenuTree("qti21StatisticsTree");
+		courseTree.setTreeModel(treeModel);
+		courseTree.addListener(this);
+		
+		layoutCtr = new LayoutMain3ColsController(ureq, wControl, courseTree, new Panel("empty"), null);
+		putInitialPanel(layoutCtr.getInitialComponent());
+		doSelectNode(ureq, courseTree.getTreeModel().getRootNode());
 	}
 
 	@Override
@@ -149,10 +162,7 @@ public class QTI21StatisticsToolController extends BasicController implements Ac
 
 	@Override
 	protected void event(UserRequest ureq, Component source, Event event) {
-		if(statsButton == source) {
-			doLaunchStatistics(ureq, getWindowControl());
-			doSelectNode(ureq, courseTree.getTreeModel().getRootNode());
-		} else if(courseTree == source) {
+		if(courseTree == source) {
 			if(event instanceof TreeEvent) {
 				TreeEvent te = (TreeEvent)event;
 				if(MenuTree.COMMAND_TREENODE_CLICKED.equals(te.getCommand())) {
@@ -175,31 +185,4 @@ public class QTI21StatisticsToolController extends BasicController implements Ac
 			layoutCtr.setCol3(new Panel("empty"));
 		}
 	}
-
-	private void doLaunchStatistics(UserRequest ureq, WindowControl wControl) {
-		if(result == null) {
-			result = new QTI21StatisticResourceResult(testEntry, courseEntry, courseNode, searchParams, secCallback);
-			result.setWithFilter(false);
-		}
-		
-		GenericTreeModel treeModel = new GenericTreeModel();
-		StatisticResourceNode rootTreeNode = new StatisticResourceNode(courseNode, result);
-		treeModel.setRootNode(rootTreeNode);
-		
-		TreeNode subRootNode = result.getSubTreeModel().getRootNode();
-		List<INode> subNodes = new ArrayList<>();
-		for(int i=0; i<subRootNode.getChildCount(); i++) {
-			subNodes.add(subRootNode.getChildAt(i));
-		}
-		for(INode subNode:subNodes) {
-			rootTreeNode.addChild(subNode);
-		}
-
-		courseTree = new MenuTree("qti21StatisticsTree");
-		courseTree.setTreeModel(treeModel);
-		courseTree.addListener(this);
-		
-		layoutCtr = new LayoutMain3ColsController(ureq, wControl, courseTree, new Panel("empty"), null);
-		stackPanel.pushController("Stats", layoutCtr);
-	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/assessment/ui/AssessableResource.java b/src/main/java/org/olat/modules/assessment/ui/AssessableResource.java
index 6bce24068d3..4a1002b20a2 100644
--- a/src/main/java/org/olat/modules/assessment/ui/AssessableResource.java
+++ b/src/main/java/org/olat/modules/assessment/ui/AssessableResource.java
@@ -19,13 +19,10 @@
  */
 package org.olat.modules.assessment.ui;
 
-import java.util.List;
-
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
-import org.olat.modules.assessment.AssessmentToolOptions;
 import org.olat.repository.RepositoryEntry;
 
 /**
@@ -84,7 +81,7 @@ public abstract class AssessableResource {
 		return hasComments;
 	}
 	
-	public abstract List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
-			RepositoryEntry entry, AssessmentToolOptions options);
+	public abstract Controller createIdentityList(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry entry, AssessmentToolSecurityCallback assessmentCallback);
 
 }
diff --git a/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityElementRow.java b/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityElementRow.java
index b7485712e2c..ba8487545bc 100644
--- a/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityElementRow.java
+++ b/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityElementRow.java
@@ -44,18 +44,19 @@ public class AssessedIdentityElementRow extends UserPropertiesRow {
 	private final Boolean userVisibility;
 	private final BigDecimal score;
 	private final Boolean passed;
-	private final Date initialCourseLaunchDate;
 	private final Date lastModified, lastUserModified, lastCoachModified;
 	private final int numOfAssessmentDocs;
 	private final AssessmentEntryStatus status;
 	
+	private Object details;
+	private Date initialCourseLaunchDate;
+	
 	private FormLink toolsLink;
 	private CompletionItem currentCompletion;
 	
-	public AssessedIdentityElementRow(Identity identity, AssessmentEntry entry, Date initialCourseLaunchDate,
+	public AssessedIdentityElementRow(Identity identity, AssessmentEntry entry,
 			CompletionItem currentCompletion, FormLink toolsLink, List<UserPropertyHandler> userPropertyHandlers, Locale locale) {
 		super(identity, userPropertyHandlers, locale);
-		this.initialCourseLaunchDate = initialCourseLaunchDate;
 		this.currentCompletion = currentCompletion;
 		this.toolsLink = toolsLink;
 		
@@ -96,7 +97,10 @@ public class AssessedIdentityElementRow extends UserPropertiesRow {
 		return initialCourseLaunchDate;
 	}
 	
-
+	public void setInitialCourseLaunchDate(Date initialCourseLaunchDate) {
+		this.initialCourseLaunchDate = initialCourseLaunchDate;
+	}
+	
 	public Date getLastModified() {
 		return lastModified;
 	}
@@ -128,4 +132,12 @@ public class AssessedIdentityElementRow extends UserPropertiesRow {
 	public Boolean getUserVisibility() {
 		return userVisibility;
 	}
+
+	public Object getDetails() {
+		return details;
+	}
+
+	public void setDetails(Object details) {
+		this.details = details;
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityListController.java b/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityListController.java
index 77bf1f953a0..3652bb0415b 100644
--- a/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityListController.java
+++ b/src/main/java/org/olat/modules/assessment/ui/AssessedIdentityListController.java
@@ -45,7 +45,6 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTable
 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.stack.BreadcrumbPanelAware;
 import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.components.stack.TooledStackedPanel.Align;
 import org.olat.core.gui.control.Controller;
@@ -93,7 +92,7 @@ public class AssessedIdentityListController extends FormBasicController implemen
 	private final boolean isAdministrativeUser;
 	private SearchAssessedIdentityParams searchParams;
 	private final List<UserPropertyHandler> userPropertyHandlers;
-	private final AssessmentToolSecurityCallback assessmentCallback;
+	protected final AssessmentToolSecurityCallback assessmentCallback;
 	
 	private Link nextLink, previousLink;
 	private FlexiTableElement tableEl;
@@ -114,7 +113,7 @@ public class AssessedIdentityListController extends FormBasicController implemen
 	@Autowired
 	private UserCourseInformationsManager userInfosMgr;
 	@Autowired
-	private AssessmentToolManager assessmentToolManager;
+	protected AssessmentToolManager assessmentToolManager;
 	@Autowired
 	private RepositoryHandlerFactory repositoryHandlerFactory;
 	
@@ -122,6 +121,7 @@ public class AssessedIdentityListController extends FormBasicController implemen
 			RepositoryEntry testEntry, AssessableResource element, AssessmentToolSecurityCallback assessmentCallback) {
 		super(ureq, wControl, "identity_element");
 		setTranslator(Util.createPackageTranslator(AssessmentModule.class, getLocale(), getTranslator()));
+		setTranslator(Util.createPackageTranslator(AssessedIdentityListController.class, getLocale(), getTranslator()));
 		setTranslator(userManager.getPropertyHandlerTranslator(getTranslator()));
 		
 		this.element = element;
@@ -134,6 +134,14 @@ public class AssessedIdentityListController extends FormBasicController implemen
 		
 		initForm(ureq);
 	}
+	
+	public RepositoryEntry getRepositoryEntry() {
+		return testEntry;
+	}
+	
+	public SearchAssessedIdentityParams getSearchParameters() {
+		return searchParams;
+	}
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
@@ -209,7 +217,6 @@ public class AssessedIdentityListController extends FormBasicController implemen
 				tableEl.setExtendedFilterButton(translate("filter.groups"), groupFilters);
 			}
 		}
-		
 	}
 	
 	public class AToolsOptions extends AssessmentToolOptions {
@@ -220,7 +227,7 @@ public class AssessedIdentityListController extends FormBasicController implemen
 		}
 	}
 	
-	private void updateModel(UserRequest ureq, String searchString, List<FlexiTableFilter> filters, List<FlexiTableFilter> extendedFilters) {
+	protected void updateModel(String searchString, List<FlexiTableFilter> filters, List<FlexiTableFilter> extendedFilters) {
 		SearchAssessedIdentityParams params = new SearchAssessedIdentityParams(testEntry, null, testEntry, assessmentCallback);
 		
 		List<AssessmentEntryStatus> assessmentStatus = null;
@@ -261,8 +268,9 @@ public class AssessedIdentityListController extends FormBasicController implemen
 		List<AssessedIdentityElementRow> rows = new ArrayList<>(assessedIdentities.size());
 		for(Identity assessedIdentity:assessedIdentities) {
 			AssessmentEntry entry = entryMap.get(assessedIdentity.getKey());
-			Date initialLaunchDate = initialLaunchDates.get(assessedIdentity.getKey());
-			rows.add(new AssessedIdentityElementRow(assessedIdentity, entry, initialLaunchDate, null, null, userPropertyHandlers, getLocale()));
+			AssessedIdentityElementRow row = new AssessedIdentityElementRow(assessedIdentity, entry, null, null, userPropertyHandlers, getLocale());
+			row.setInitialCourseLaunchDate(initialLaunchDates.get(assessedIdentity.getKey()));
+			rows.add(row);
 		}
 
 		usersTableModel.setObjects(rows);
@@ -271,34 +279,13 @@ public class AssessedIdentityListController extends FormBasicController implemen
 		}
 		tableEl.reloadData();
 		searchParams = params;
-		
-		List<String> toolCmpNames = new ArrayList<>();
-		AssessmentToolOptions asOptions = new AssessmentToolOptions();
-		asOptions.setAdmin(assessmentCallback.isAdmin());
-		asOptions.setIdentities(assessedIdentities);
-		List<Controller> tools = element.createAssessmentTools(ureq, getWindowControl(), stackPanel,
-				testEntry, asOptions);
-		int count = 0;
-		if(tools.size() > 0) {
-			for(Controller tool:tools) {
-				listenTo(tool);
-				String toolCmpName = "ctrl_" + (count++);
-				flc.put(toolCmpName, tool.getInitialComponent());
-				toolCmpNames.add(toolCmpName);
-				if(tool instanceof BreadcrumbPanelAware) {
-					((BreadcrumbPanelAware)tool).setBreadcrumbPanel(stackPanel);
-				}
-			}
-		}
-		
-		if(toolsCtrl != null) {
-			for(Controller toolCtrl:toolsCtrl) {
-				removeAsListenerAndDispose(toolCtrl);
-			}
-		}
-		toolsCtrl = tools;
-		flc.contextPut("toolCmpNames", toolCmpNames);
+		updateTools(assessedIdentities);
 	}
+	
+	protected void updateTools(@SuppressWarnings("unused") List<Identity> assessedIdentities) {
+		//to override
+	}
+	
 	@Override
 	protected void doDispose() {
 		//
@@ -315,7 +302,7 @@ public class AssessedIdentityListController extends FormBasicController implemen
 		}
 
 		tableEl.setSelectedFilterKey(filter);
-		updateModel(ureq, null, tableEl.getSelectedFilters(), null);
+		updateModel(null, tableEl.getSelectedFilters(), null);
 		
 		if(entries != null && entries.size() > 0) {
 			String resourceType = entries.get(0).getOLATResourceable().getResourceableTypeName();
@@ -350,16 +337,16 @@ public class AssessedIdentityListController extends FormBasicController implemen
 	public void event(UserRequest ureq, Controller source, Event event) {
 		if(currentIdentityCtrl == source) {
 			if(event == Event.CHANGED_EVENT) {
-				updateModel(ureq, null, null, null);
+				updateModel(null, null, null);
 			} else if(event == Event.DONE_EVENT) {
-				updateModel(ureq, null, null, null);
+				updateModel(null, null, null);
 				stackPanel.popController(currentIdentityCtrl);
 			} else if(event == Event.CANCELLED_EVENT) {
 				stackPanel.popController(currentIdentityCtrl);
 			}
 		} else if(toolsCtrl != null && toolsCtrl.contains(source)) {
 			if(event == Event.CHANGED_EVENT) {
-				updateModel(ureq, null, null, null);
+				updateModel(null, null, null);
 			}
 		}
 		super.event(ureq, source, event);
@@ -377,7 +364,7 @@ public class AssessedIdentityListController extends FormBasicController implemen
 				}
 			} else if(event instanceof FlexiTableSearchEvent) {
 				FlexiTableSearchEvent ftse = (FlexiTableSearchEvent)event;
-				updateModel(ureq, ftse.getSearch(), ftse.getFilters(), ftse.getExtendedFilters());
+				updateModel(ftse.getSearch(), ftse.getFilters(), ftse.getExtendedFilters());
 			}
 		}
 		
diff --git a/src/main/java/org/olat/modules/assessment/ui/AssessmentToolController.java b/src/main/java/org/olat/modules/assessment/ui/AssessmentToolController.java
index bd6ae32bea2..3b2ce524a4e 100644
--- a/src/main/java/org/olat/modules/assessment/ui/AssessmentToolController.java
+++ b/src/main/java/org/olat/modules/assessment/ui/AssessmentToolController.java
@@ -58,7 +58,7 @@ public class AssessmentToolController extends MainLayoutBasicController implemen
 	private Link usersLink;
 	private final TooledStackedPanel stackPanel;
 	
-	private AssessedIdentityListController currentCtl;
+	private Controller currentCtl;
 	private AssessmentOverviewController overviewCtrl;
 	
 	public AssessmentToolController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
@@ -115,13 +115,16 @@ public class AssessmentToolController extends MainLayoutBasicController implemen
 				UserSelectionEvent use = (UserSelectionEvent)event;
 				OLATResourceable resource = OresHelper.createOLATResourceableInstance("Identity", use.getIdentityKey());
 				List<ContextEntry> entries = BusinessControlFactory.getInstance().createCEListFromString(resource);
-				doSelectUsersView(ureq, "Users", null).activate(ureq, entries, null);
+				Controller userViewCtrl = doSelectUsersView(ureq, "Users", null);
+				if(userViewCtrl instanceof Activateable2) {
+					((Activateable2)userViewCtrl).activate(ureq, entries, null);
+				}
 			}
 		}
 		super.event(ureq, source, event);
 	}
 	
-	private Activateable2 doSelectUsersView(UserRequest ureq, String resName, AssessedIdentityListState state) {
+	private Controller doSelectUsersView(UserRequest ureq, String resName, AssessedIdentityListState state) {
 		if(currentCtl != null) {
 			stackPanel.popController(currentCtl);
 		}
@@ -129,12 +132,13 @@ public class AssessmentToolController extends MainLayoutBasicController implemen
 		OLATResourceable ores = OresHelper.createOLATResourceableInstance(resName, 0l);
 		WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ores, null, getWindowControl());
 		addToHistory(ureq, bwControl);
-		AssessedIdentityListController treeCtrl = new AssessedIdentityListController(ureq, bwControl, stackPanel,
-				testEntry, element, assessmentCallback);
+		Controller treeCtrl = element.createIdentityList(ureq, bwControl, stackPanel, testEntry, assessmentCallback);
 		listenTo(treeCtrl);
 		stackPanel.pushController(translate("users"), treeCtrl);
 		currentCtl = treeCtrl;
-		treeCtrl.activate(ureq, null, state);
+		if(treeCtrl instanceof Activateable2) {
+			((Activateable2)treeCtrl).activate(ureq, null, state);
+		}
 		return currentCtl;
 	}
 }
diff --git a/src/main/java/org/olat/modules/assessment/ui/_content/identity_element.html b/src/main/java/org/olat/modules/assessment/ui/_content/identity_element.html
index 552eea214ec..74005495897 100644
--- a/src/main/java/org/olat/modules/assessment/ui/_content/identity_element.html
+++ b/src/main/java/org/olat/modules/assessment/ui/_content/identity_element.html
@@ -1,10 +1,2 @@
 <h2><i class="o_icon $cssClass"> </i> $r.escapeHtml($title)</h2>
-#if($r.isNotEmpty($toolCmpNames))
-	<div class="o_button_group o_button_group_right">
-	#foreach($toolCmpName in $toolCmpNames)
-		$r.render($toolCmpName)
-	#end
-	</div>
-#end
-
 $r.render("table")
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/portfolio/ui/model/AssessableBinderResource.java b/src/main/java/org/olat/modules/portfolio/ui/model/AssessableBinderResource.java
index 6dd0d9255cf..4f5f321ce78 100644
--- a/src/main/java/org/olat/modules/portfolio/ui/model/AssessableBinderResource.java
+++ b/src/main/java/org/olat/modules/portfolio/ui/model/AssessableBinderResource.java
@@ -19,15 +19,13 @@
  */
 package org.olat.modules.portfolio.ui.model;
 
-import java.util.Collections;
-import java.util.List;
-
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
-import org.olat.modules.assessment.AssessmentToolOptions;
 import org.olat.modules.assessment.ui.AssessableResource;
+import org.olat.modules.assessment.ui.AssessedIdentityListController;
+import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback;
 import org.olat.repository.RepositoryEntry;
 
 /**
@@ -44,8 +42,9 @@ public class AssessableBinderResource extends AssessableResource {
 	}
 
 	@Override
-	public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl,
-			TooledStackedPanel stackPanel, RepositoryEntry entry, AssessmentToolOptions options) {
-		return Collections.emptyList();
+	public Controller createIdentityList(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
+			RepositoryEntry entry, AssessmentToolSecurityCallback assessmentCallback) {
+		return new AssessedIdentityListController(ureq, wControl, stackPanel,
+				entry, this, assessmentCallback);
 	}
 }
-- 
GitLab