From 79274216ed3a786d15d9252a1e0f6539d5fb8f60 Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Tue, 4 Sep 2018 14:35:09 +0200
Subject: [PATCH] OO-3304: First filters in the quality analysis tool

---
 .../modules/curriculum/CurriculumService.java |   4 +-
 .../manager/CurriculumElementDAO.java         |  18 +
 .../manager/CurriculumServiceImpl.java        |   5 +
 .../forms/model/jpa/RubricStatisticImpl.java  |   5 +-
 .../analysis/AnalysisSearchParameter.java     | 106 +++++
 .../quality/analysis/EvaluationFormView.java  |   6 +-
 .../analysis/QualityAnalysisService.java      |  12 +
 .../analysis/manager/AnalysisFilterDAO.java   | 199 ++++++++
 .../manager/QualityAnalysisServiceImpl.java   |  47 ++
 .../model/EvaluationFormViewImpl.java         |  10 +
 .../analysis/ui/AnalysisController.java       | 102 +++++
 .../analysis/ui/AnalysisListController.java   |  34 +-
 .../analysis/ui/AnalysisReportController.java |  72 +++
 .../quality/analysis/ui/AnalysisRow.java      |  19 +-
 .../ui/AnalysisSegmentsController.java        | 138 ++++++
 .../quality/analysis/ui/FilterController.java | 269 +++++++++++
 .../analysis/ui/HeatMapController.java        |  64 +++
 .../analysis/ui/_content/analysis.html        |   4 +
 .../ui/_i18n/LocalStrings_de.properties       |  14 +-
 .../ui/_i18n/LocalStrings_en.properties       |  14 +-
 .../modules/quality/ui/QualityUIFactory.java  |  26 +-
 .../manager/CurriculumElementDAOTest.java     |  24 +
 .../manager/AnalysisFilterDAOTest.java        | 427 ++++++++++++++++++
 .../quality/manager/QualityTestHelper.java    |   4 +-
 .../java/org/olat/test/AllTestsJunit4.java    |   1 +
 25 files changed, 1589 insertions(+), 35 deletions(-)
 create mode 100644 src/main/java/org/olat/modules/quality/analysis/AnalysisSearchParameter.java
 create mode 100644 src/main/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAO.java
 create mode 100644 src/main/java/org/olat/modules/quality/analysis/ui/AnalysisController.java
 create mode 100644 src/main/java/org/olat/modules/quality/analysis/ui/AnalysisReportController.java
 create mode 100644 src/main/java/org/olat/modules/quality/analysis/ui/AnalysisSegmentsController.java
 create mode 100644 src/main/java/org/olat/modules/quality/analysis/ui/FilterController.java
 create mode 100644 src/main/java/org/olat/modules/quality/analysis/ui/HeatMapController.java
 create mode 100644 src/main/java/org/olat/modules/quality/analysis/ui/_content/analysis.html
 create mode 100644 src/test/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAOTest.java

diff --git a/src/main/java/org/olat/modules/curriculum/CurriculumService.java b/src/main/java/org/olat/modules/curriculum/CurriculumService.java
index ce1f32f329c..ed16e1612af 100644
--- a/src/main/java/org/olat/modules/curriculum/CurriculumService.java
+++ b/src/main/java/org/olat/modules/curriculum/CurriculumService.java
@@ -196,7 +196,7 @@ public interface CurriculumService {
 	
 	
 	public CurriculumElement getCurriculumElement(CurriculumElementRef element);
-	
+
 	public List<CurriculumElement> getCurriculumElements(Collection<CurriculumElementRef> elementRefs);
 	
 	public void deleteCurriculumElement(CurriculumElementRef element);
@@ -210,6 +210,8 @@ public interface CurriculumService {
 	 */
 	public List<CurriculumElement> getCurriculumElements(CurriculumRef curriculum, CurriculumElementStatus[] status);
 	
+	public List<CurriculumElement> getCurriculumElementsByCurriculums(Collection<? extends CurriculumRef> curriculumRefs);
+	
 	/**
 	 * Return all the elements of a curriculum, flat, with additional informations
 	 * like the number of resources linked to the elements. List element in state
diff --git a/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java b/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java
index e1963699341..c9abe0b07c4 100644
--- a/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java
+++ b/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java
@@ -19,6 +19,8 @@
  */
 package org.olat.modules.curriculum.manager;
 
+import static java.util.stream.Collectors.toList;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -251,6 +253,21 @@ public class CurriculumElementDAO {
 				.setParameter("entryKey", entry.getKey())
 				.getResultList();
 	}
+
+	public List<CurriculumElement> loadElementsByCurriculums(Collection<? extends CurriculumRef> curriculumRefs) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select el");
+		sb.append("  from curriculumelement el");
+		sb.append("       inner join fetch el.curriculum curriculum");
+		sb.append("       left join el.parent parentEl");
+		sb.and().append("el.curriculum.key in :curriculumKeys");
+		
+		List<Long> curriculumKeys = curriculumRefs.stream().map(CurriculumRef::getKey).collect(toList());
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), CurriculumElement.class)
+				.setParameter("curriculumKeys", curriculumKeys)
+				.getResultList();
+	}
 	
 	public List<CurriculumElement> searchElements(String externalId, String identifier, Long key) {
 		StringBuilder sb = new StringBuilder(256);
@@ -474,4 +491,5 @@ public class CurriculumElementDAO {
 			return len1 - len2;
 		}
 	}
+
 }
diff --git a/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java b/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java
index 7dfd7cc933c..4ab7a4f3058 100644
--- a/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java
+++ b/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java
@@ -326,6 +326,11 @@ public class CurriculumServiceImpl implements CurriculumService {
 		return curriculumElementDao.getChildren(parentElement);
 	}
 
+	@Override
+	public List<CurriculumElement> getCurriculumElementsByCurriculums(Collection<? extends CurriculumRef> curriculumRefs) {
+		return curriculumElementDao.loadElementsByCurriculums(curriculumRefs);
+	}
+
 	@Override
 	public List<CurriculumElement> searchCurriculumElements(String externalId, String identifier, Long key) {
 		return curriculumElementDao.searchElements(externalId, identifier, key);
diff --git a/src/main/java/org/olat/modules/forms/model/jpa/RubricStatisticImpl.java b/src/main/java/org/olat/modules/forms/model/jpa/RubricStatisticImpl.java
index 2e081cc5748..34080dda064 100644
--- a/src/main/java/org/olat/modules/forms/model/jpa/RubricStatisticImpl.java
+++ b/src/main/java/org/olat/modules/forms/model/jpa/RubricStatisticImpl.java
@@ -19,7 +19,6 @@
  */
 package org.olat.modules.forms.model.jpa;
 
-import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -113,9 +112,9 @@ public class RubricStatisticImpl implements RubricStatistic {
 	}
 	
 	private Long getValue(List<CalculatedLong> calculatedLongs, String identifier, int step) {
-		String subidentifier = BigDecimal.valueOf((double)step).toPlainString();
 		for (CalculatedLong calculatedLong: calculatedLongs) {
-			if (calculatedLong.getIdentifier().equals(identifier) && calculatedLong.getSubIdentifier().equals(subidentifier)) {
+			int calculatedStep = Double.valueOf(calculatedLong.getSubIdentifier()).intValue();
+			if (calculatedLong.getIdentifier().equals(identifier) && calculatedStep == step) {
 				return calculatedLong.getValue();
 			}
 		}
diff --git a/src/main/java/org/olat/modules/quality/analysis/AnalysisSearchParameter.java b/src/main/java/org/olat/modules/quality/analysis/AnalysisSearchParameter.java
new file mode 100644
index 00000000000..be5e563aec0
--- /dev/null
+++ b/src/main/java/org/olat/modules/quality/analysis/AnalysisSearchParameter.java
@@ -0,0 +1,106 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.quality.analysis;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import org.olat.core.id.OrganisationRef;
+import org.olat.modules.curriculum.CurriculumElementRef;
+import org.olat.modules.curriculum.CurriculumRef;
+import org.olat.repository.RepositoryEntryRef;
+
+/**
+ * 
+ * Initial date: 04.09.2018<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class AnalysisSearchParameter {
+	
+	private RepositoryEntryRef formEntryRef;
+	private Date dateRangeFrom;
+	private Date dateRangeTo;
+	private List<? extends OrganisationRef> organisationRefs;
+	private Collection<? extends CurriculumRef> curriculumRefs;
+	private List<? extends CurriculumElementRef> curriculumElementRefs;
+
+	public RepositoryEntryRef getFormEntryRef() {
+		return formEntryRef;
+	}
+
+	public void setFormEntryRef(RepositoryEntryRef formEntryRef) {
+		this.formEntryRef = formEntryRef;
+	}
+
+	public Date getDateRangeFrom() {
+		return dateRangeFrom;
+	}
+
+	public void setDateRangeFrom(Date dateRangeFrom) {
+		this.dateRangeFrom = dateRangeFrom;
+	}
+
+	public Date getDateRangeTo() {
+		return dateRangeTo;
+	}
+
+	public void setDateRangeTo(Date dateRangeTo) {
+		this.dateRangeTo = dateRangeTo;
+	}
+
+	public List<? extends OrganisationRef> getOrganisationRefs() {
+		return organisationRefs;
+	}
+
+	public void setOrganisationRefs(List<? extends OrganisationRef> organisationRefs) {
+		this.organisationRefs = organisationRefs;
+	}
+
+	public Collection<? extends CurriculumRef> getCurriculumRefs() {
+		return curriculumRefs;
+	}
+
+	public void setCurriculumRefs(Collection<? extends CurriculumRef> curriculumRefs) {
+		this.curriculumRefs = curriculumRefs;
+	}
+	
+	public List<? extends CurriculumElementRef> getCurriculumElementRefs() {
+		return curriculumElementRefs;
+	}
+
+	public void setCurriculumElementRefs(List<? extends CurriculumElementRef> curriculumElementRefs) {
+		this.curriculumElementRefs = curriculumElementRefs;
+	}
+
+	@Override
+	public AnalysisSearchParameter clone() {
+		AnalysisSearchParameter clone = new AnalysisSearchParameter();
+		clone.formEntryRef = this.formEntryRef;
+		clone.dateRangeFrom = this.dateRangeFrom;
+		clone.organisationRefs = this.organisationRefs != null? new ArrayList<>(this.organisationRefs): null;
+		clone.curriculumRefs = this.curriculumRefs != null? new ArrayList<>(this.curriculumRefs): null;
+		clone.curriculumElementRefs = this.curriculumElementRefs != null? new ArrayList<>(this.curriculumElementRefs): null;
+		return clone;
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/quality/analysis/EvaluationFormView.java b/src/main/java/org/olat/modules/quality/analysis/EvaluationFormView.java
index 9f98da50484..fde6dd3909e 100644
--- a/src/main/java/org/olat/modules/quality/analysis/EvaluationFormView.java
+++ b/src/main/java/org/olat/modules/quality/analysis/EvaluationFormView.java
@@ -21,14 +21,18 @@ package org.olat.modules.quality.analysis;
 
 import java.util.Date;
 
+import org.olat.core.id.OLATResourceable;
+
 /**
  * 
  * Initial date: 03.09.2018<br>
  * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
  *
  */
-public interface EvaluationFormView {
+public interface EvaluationFormView extends OLATResourceable {
 	
+	public String RESOURCEABLE_TYPE = "form";
+
 	public Long getFormEntryKey();
 	
 	public Date getFormCreatedDate();
diff --git a/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java b/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java
index 6712bf132e8..29de9a219a1 100644
--- a/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java
+++ b/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java
@@ -21,6 +21,10 @@ package org.olat.modules.quality.analysis;
 
 import java.util.List;
 
+import org.olat.core.id.Organisation;
+import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumElement;
+
 /**
  * 
  * Initial date: 03.09.2018<br>
@@ -30,5 +34,13 @@ import java.util.List;
 public interface QualityAnalysisService {
 	
 	public List<EvaluationFormView> loadEvaluationForms(EvaluationFormViewSearchParams searchParams);
+	
+	public List<Organisation> loadFilterOrganisations(AnalysisSearchParameter searchParams);
+
+	public List<Curriculum> loadFilterCurriculums(AnalysisSearchParameter searchParams);
+
+	public List<CurriculumElement> loadFilterCurriculumElements(AnalysisSearchParameter searchParams);
+
+	public Long loadFilterDataCollectionCount(AnalysisSearchParameter searchParams);
 
 }
diff --git a/src/main/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAO.java b/src/main/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAO.java
new file mode 100644
index 00000000000..042a9d98d75
--- /dev/null
+++ b/src/main/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAO.java
@@ -0,0 +1,199 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.quality.analysis.manager;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.List;
+
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.persistence.QueryBuilder;
+import org.olat.core.id.Organisation;
+import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumRef;
+import org.olat.modules.quality.QualityDataCollectionLight;
+import org.olat.modules.quality.QualityDataCollectionStatus;
+import org.olat.modules.quality.analysis.AnalysisSearchParameter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 05.09.2018<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class AnalysisFilterDAO {
+	
+	@Autowired
+	private DB dbInstance;
+
+	List<Organisation> loadOrganisations(AnalysisSearchParameter searchParams) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select distinct organisation");
+		appendFrom(sb);
+		appendWhere(sb, searchParams);
+		sb.and().append("organisation.key is not null");
+		
+		TypedQuery<Organisation> query = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Organisation.class);
+		appendParameters(query, searchParams);
+		return query.getResultList();
+	}
+
+	List<Curriculum> loadCurriculums(AnalysisSearchParameter searchParams) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select distinct curriculum");
+		appendFrom(sb);
+		appendWhere(sb, searchParams);
+		sb.and().append("curriculum.key is not null");
+		
+		TypedQuery<Curriculum> query = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Curriculum.class);
+		appendParameters(query, searchParams);
+		return query.getResultList();
+	}
+
+	List<String> loadCurriculumElementPathes(AnalysisSearchParameter searchParams) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select distinct curriculumElement.materializedPathKeys");
+		appendFrom(sb);
+		appendWhere(sb, searchParams);
+		sb.and().append("curriculumElement.key is not null");
+		
+		TypedQuery<String> query = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), String.class);
+		appendParameters(query, searchParams);
+		return query.getResultList();
+	}
+
+	public Long loadFilterDataCollectionCount(AnalysisSearchParameter searchParams) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select count(distinct collection.key)");
+		appendFrom(sb);
+		appendWhere(sb, searchParams);
+		
+		TypedQuery<Long> query = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Long.class);
+		appendParameters(query, searchParams);
+		return query.getResultList().get(0);
+	}
+
+	private void appendFrom(QueryBuilder sb) {
+		sb.append("  from qualitydatacollection collection");
+		sb.append("       inner join evaluationformsurvey survey");
+		sb.append("               on survey.resName = '").append(QualityDataCollectionLight.RESOURCEABLE_TYPE_NAME).append("'");
+		sb.append("              and survey.resId = collection.key");
+		sb.append("       left join qualitycontext context");
+		sb.append("               on context.dataCollection.key = collection.key");
+		sb.append("       left join contexttocurriculum contextToCurriculum");
+		sb.append("               on contextToCurriculum.context.key = context.key");
+		sb.append("       left join curriculum curriculum");
+		sb.append("               on contextToCurriculum.curriculum.key = curriculum.key");
+		sb.append("       left join contexttocurriculumelement contextToCurriculumElement");
+		sb.append("               on contextToCurriculumElement.context.key = context.key");
+		sb.append("       left join curriculumelement curriculumElement");
+		sb.append("               on contextToCurriculumElement.curriculumElement.key = curriculumElement.key");
+		sb.append("       left join contexttoorganisation contextToOrganisation");
+		sb.append("               on contextToOrganisation.context.key = context.key");
+		sb.append("       left join organisation organisation");
+		sb.append("               on contextToOrganisation.organisation.key = organisation.key");
+	}
+	
+	private void appendWhere(QueryBuilder sb, AnalysisSearchParameter searchParams) {
+		sb.and().append("collection.status = '").append(QualityDataCollectionStatus.FINISHED).append("'");
+		if (searchParams.getFormEntryRef() != null) {
+			sb.and().append("survey.formEntry.key = :formEntryKey");
+		}
+		if (searchParams.getDateRangeFrom() != null) {
+			sb.and().append("collection.deadline >= :dateRangeFrom");
+		}
+		if (searchParams.getDateRangeTo() != null) {
+			sb.and().append("collection.deadline <= :dateRangeTo");
+		}
+		if (searchParams.getOrganisationRefs() != null) {
+			sb.and();
+			for (int i = 0; i < searchParams.getOrganisationRefs().size(); i++) {
+				if (i == 0) {
+					sb.append("(");
+				} else {
+					sb.append(" or ");
+				}
+				sb.append("organisation.materializedPathKeys like :orgPath").append(i);
+				if (i == searchParams.getOrganisationRefs().size() - 1) {
+					sb.append(")");
+				}
+			}
+		}
+		if (searchParams.getCurriculumRefs() != null) {
+			sb.and().append("curriculum.key in :curriculumKeys");
+		}
+		if (searchParams.getCurriculumElementRefs() != null) {
+			sb.and();
+			for (int i = 0; i < searchParams.getCurriculumElementRefs().size(); i++) {
+				if (i == 0) {
+					sb.append("(");
+				} else {
+					sb.append(" or ");
+				}
+				sb.append("curriculumElement.materializedPathKeys like :elePath").append(i);
+				if (i == searchParams.getCurriculumElementRefs().size() - 1) {
+					sb.append(")");
+				}
+			}
+		}
+	}
+
+	private void appendParameters(Query query, AnalysisSearchParameter searchParams) {
+		if (searchParams.getFormEntryRef() != null) {
+			query.setParameter("formEntryKey", searchParams.getFormEntryRef().getKey());
+		}
+		if (searchParams.getDateRangeFrom() != null) {
+			query.setParameter("dateRangeFrom", searchParams.getDateRangeFrom());
+		}
+		if (searchParams.getDateRangeTo() != null) {
+			query.setParameter("dateRangeTo", searchParams.getDateRangeTo());
+		}
+		if (searchParams.getOrganisationRefs() != null) {
+			for (int i = 0; i < searchParams.getOrganisationRefs().size(); i++) {
+				String parameter = new StringBuilder(12).append("orgPath").append(i).toString();
+				Long key = searchParams.getOrganisationRefs().get(i).getKey();
+				String value = new StringBuilder(32).append("%/").append(key).append("/%").toString();
+				query.setParameter(parameter, value);
+			}
+		}
+		if (searchParams.getCurriculumRefs() != null) {
+			List<Long> curriculumKeys = searchParams.getCurriculumRefs().stream().map(CurriculumRef::getKey).collect(toList());
+			query.setParameter("curriculumKeys", curriculumKeys);
+		}
+		if (searchParams.getCurriculumElementRefs() != null) {
+			for (int i = 0; i < searchParams.getCurriculumElementRefs().size(); i++) {
+				String parameter = new StringBuilder(12).append("elePath").append(i).toString();
+				Long key = searchParams.getCurriculumElementRefs().get(i).getKey();
+				String value = new StringBuilder(32).append("%/").append(key).append("/%").toString();
+				query.setParameter(parameter, value);
+			}
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java b/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java
index d773b18f5be..90de51fa063 100644
--- a/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java
+++ b/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java
@@ -19,8 +19,14 @@
  */
 package org.olat.modules.quality.analysis.manager;
 
+import java.util.ArrayList;
 import java.util.List;
 
+import org.olat.core.id.Organisation;
+import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumService;
+import org.olat.modules.quality.analysis.AnalysisSearchParameter;
 import org.olat.modules.quality.analysis.EvaluationFormView;
 import org.olat.modules.quality.analysis.EvaluationFormViewSearchParams;
 import org.olat.modules.quality.analysis.QualityAnalysisService;
@@ -36,11 +42,52 @@ import org.springframework.stereotype.Service;
 @Service
 public class QualityAnalysisServiceImpl implements QualityAnalysisService {
 
+	@Autowired
+	private AnalysisFilterDAO filterDao;
 	@Autowired
 	private EvaluationFormDAO evaluationFromDao;
+	@Autowired
+	private CurriculumService curriculumService;
 
 	@Override
 	public List<EvaluationFormView> loadEvaluationForms(EvaluationFormViewSearchParams searchParams) {
 		return evaluationFromDao.load(searchParams);
 	}
+
+	@Override
+	public List<Organisation> loadFilterOrganisations(AnalysisSearchParameter searchParams) {
+		return filterDao.loadOrganisations(searchParams);
+	}
+
+	@Override
+	public List<Curriculum> loadFilterCurriculums(AnalysisSearchParameter searchParams) {
+		return filterDao.loadCurriculums(searchParams);
+	}
+
+	@Override
+	public List<CurriculumElement> loadFilterCurriculumElements(AnalysisSearchParameter searchParams) {
+		if (searchParams == null || searchParams.getCurriculumRefs() == null) {
+			return new ArrayList<>(0);
+		}
+
+		List<CurriculumElement> elementsOfCurriculums = curriculumService
+				.getCurriculumElementsByCurriculums(searchParams.getCurriculumRefs());
+		List<String> pathes = filterDao.loadCurriculumElementPathes(searchParams);
+		elementsOfCurriculums.removeIf(e -> isUnusedLeaf(e, pathes));
+		return elementsOfCurriculums;
+	}
+
+	private boolean isUnusedLeaf(CurriculumElement e, List<String> pathsOfContexts) {
+		for (String path : pathsOfContexts) {
+			if (path.contains(e.getMaterializedPathKeys())) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	@Override
+	public Long loadFilterDataCollectionCount(AnalysisSearchParameter searchParams) {
+		return filterDao.loadFilterDataCollectionCount(searchParams);
+	}
 }
diff --git a/src/main/java/org/olat/modules/quality/analysis/model/EvaluationFormViewImpl.java b/src/main/java/org/olat/modules/quality/analysis/model/EvaluationFormViewImpl.java
index da93c443833..6903abd7624 100644
--- a/src/main/java/org/olat/modules/quality/analysis/model/EvaluationFormViewImpl.java
+++ b/src/main/java/org/olat/modules/quality/analysis/model/EvaluationFormViewImpl.java
@@ -50,6 +50,16 @@ public class EvaluationFormViewImpl implements EvaluationFormView {
 		this.numberParticipationsDone = numberParticipationsDone;
 	}
 
+	@Override
+	public String getResourceableTypeName() {
+		return EvaluationFormView.RESOURCEABLE_TYPE;
+	}
+
+	@Override
+	public Long getResourceableId() {
+		return formEntryKey;
+	}
+
 	@Override
 	public Long getFormEntryKey() {
 		return formEntryKey;
diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisController.java b/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisController.java
new file mode 100644
index 00000000000..331af87d5d1
--- /dev/null
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisController.java
@@ -0,0 +1,102 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.quality.analysis.ui;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
+import org.olat.core.gui.components.velocity.VelocityContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.controller.BasicController;
+import org.olat.core.gui.control.controller.BlankController;
+import org.olat.modules.quality.QualitySecurityCallback;
+import org.olat.modules.quality.analysis.AnalysisSearchParameter;
+import org.olat.modules.quality.analysis.EvaluationFormView;
+
+/**
+ * 
+ * Initial date: 04.09.2018<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class AnalysisController extends BasicController {
+	
+	enum Presentation {REPORT, HEAT_MAP};
+
+	private VelocityContainer mainVC;
+	private Controller filterCtrl;
+	private Controller presentationCtrl;
+	private final QualitySecurityCallback secCallback;
+	private final TooledStackedPanel stackPanel;
+	
+	private final EvaluationFormView formView;
+	
+	protected AnalysisController(UserRequest ureq, WindowControl wControl, QualitySecurityCallback secCallback,
+			TooledStackedPanel stackPanel, EvaluationFormView formView) {
+		super(ureq, wControl);
+		this.secCallback = secCallback;
+		this.stackPanel = stackPanel;
+		this.formView = formView;
+		mainVC = createVelocityContainer("analysis");
+		putInitialPanel(mainVC);
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		searchParams.setFormEntryRef(() -> formView.getFormEntryKey());
+		filterCtrl= new FilterController(ureq, wControl, searchParams);
+		listenTo(filterCtrl);
+		mainVC.put("filter", filterCtrl.getInitialComponent());
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		//TODO uh if click on breadcrumb shows blankcontroller
+	}
+
+	@Override
+	protected void doDispose() {
+		removeAsListenerAndDispose(presentationCtrl);
+		removeAsListenerAndDispose(filterCtrl);
+		presentationCtrl = null;
+		filterCtrl = null;
+	}
+
+	public void setPresentation(UserRequest ureq, Presentation presentation) {
+		removeAsListenerAndDispose(presentationCtrl);
+		presentationCtrl = null;
+		
+		switch (presentation) {
+			case REPORT:
+				presentationCtrl = new AnalysisReportController(ureq, getWindowControl(), secCallback, stackPanel);
+				break;
+			case HEAT_MAP:
+				presentationCtrl = new HeatMapController(ureq, getWindowControl(), secCallback, stackPanel);
+				break;
+			default:
+				presentationCtrl = new BlankController(ureq, getWindowControl());
+				break;
+		}
+		listenTo(presentationCtrl);
+		mainVC.put("presentation", presentationCtrl.getInitialComponent());
+		mainVC.setDirty(true);
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisListController.java b/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisListController.java
index 2ca9cefa3c1..ab93facb03a 100644
--- a/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisListController.java
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisListController.java
@@ -27,10 +27,12 @@ import org.olat.basesecurity.OrganisationRoles;
 import org.olat.basesecurity.OrganisationService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
 import org.olat.core.gui.components.form.flexible.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.DefaultFlexiColumnModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableCssDelegate;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnModel;
@@ -38,6 +40,7 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTable
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponentDelegate;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableRendererType;
+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.stack.TooledStackedPanel;
 import org.olat.core.gui.components.velocity.VelocityContainer;
@@ -75,6 +78,7 @@ public class AnalysisListController extends FormBasicController implements Flexi
 	private QualityAnalysisService analysisService;
 	@Autowired
 	private OrganisationService organisationService;
+	private AnalysisSegmentsController analysisCtrl;
 	
 	public AnalysisListController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
 			QualitySecurityCallback secCallback) {
@@ -137,7 +141,7 @@ public class AnalysisListController extends FormBasicController implements Flexi
 	
 	private AnalysisRow forgeRow(EvaluationFormView form) {
 		String openLinkId = "open_" + (++counter);
-		FormLink openLink = uifactory.addFormLink(openLinkId, "analysis.table.open", "analysis.table.open", null, flc, Link.LINK);
+		FormLink openLink = uifactory.addFormLink(openLinkId, CMD_OPEN, "analysis.table.open", null, flc, Link.LINK);
 		openLink.setElementCssClass("o_qual_ana_open_link");
 		openLink.setIconRightCSS("o_icon o_icon_start");
 
@@ -155,6 +159,34 @@ public class AnalysisListController extends FormBasicController implements Flexi
 		}
 		return components;
 	}
+	
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if (source == tableEl && event instanceof SelectionEvent) {
+			SelectionEvent se = (SelectionEvent)event;
+			String cmd = se.getCommand();
+			AnalysisRow row = dataModel.getObject(se.getIndex());
+			if (CMD_OPEN.equals(cmd)) {
+				doOpenAnalysis(ureq, row);
+			}
+		} else if (source instanceof FormLink) {
+			FormLink link = (FormLink)source;
+			if(CMD_OPEN.equals(link.getCmd())) {
+				doOpenAnalysis(ureq, (AnalysisRow)link.getUserObject());
+			}
+		}
+		
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	private void doOpenAnalysis(UserRequest ureq, EvaluationFormView formView) {
+		WindowControl bwControl = addToHistory(ureq, formView, null);
+		analysisCtrl = new AnalysisSegmentsController(ureq, bwControl, secCallback, stackPanel, formView);
+		listenTo(analysisCtrl);
+		String title = formView.getFormTitle();
+		stackPanel.pushController(title, analysisCtrl);
+		analysisCtrl.activate(ureq, null, null);
+	}
 
 	@Override
 	protected void formOK(UserRequest ureq) {
diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisReportController.java b/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisReportController.java
new file mode 100644
index 00000000000..12f0b1f5822
--- /dev/null
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisReportController.java
@@ -0,0 +1,72 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.quality.analysis.ui;
+
+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.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.modules.quality.QualitySecurityCallback;
+
+/**
+ * 
+ * Initial date: 04.09.2018<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class AnalysisReportController extends FormBasicController {
+
+	private final QualitySecurityCallback secCallback;
+	private final TooledStackedPanel stackPanel;
+
+	public AnalysisReportController(UserRequest ureq, WindowControl wControl, QualitySecurityCallback secCallback,
+			TooledStackedPanel stackPanel) {
+		super(ureq, wControl);
+		this.secCallback = secCallback;
+		this.stackPanel = stackPanel;
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		
+		uifactory.addStaticExampleText("re3", "", "Report, report, report,..", formLayout);
+
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		super.event(ureq, source, event);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisRow.java b/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisRow.java
index c6545ae496c..e656fa7b0f1 100644
--- a/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisRow.java
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisRow.java
@@ -30,7 +30,7 @@ import org.olat.modules.quality.analysis.EvaluationFormView;
  * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
  *
  */
-public class AnalysisRow {
+public class AnalysisRow implements EvaluationFormView {
 	
 	private final EvaluationFormView formView;
 	private final FormLink openLink;
@@ -39,31 +39,48 @@ public class AnalysisRow {
 		this.formView = formView;
 		this.openLink = openLink;
 	}
+
+	@Override
+	public String getResourceableTypeName() {
+		return formView.getResourceableTypeName();
+	}
+
+	@Override
+	public Long getResourceableId() {
+		return formView.getResourceableId();
+	}
 	
+	@Override
 	public Long getFormEntryKey() {
 		return formView.getFormEntryKey();
 	}
 	
+	@Override
 	public Date getFormCreatedDate() {
 		return formView.getFormCreatedDate();
 	}
 
+	@Override
 	public String getFormTitle() {
 		return formView.getFormTitle();
 	}
 
+	@Override
 	public Long getNumberDataCollections() {
 		return formView.getNumberDataCollections();
 	}
 
+	@Override
 	public Date getSoonestDataCollectionDate() {
 		return formView.getSoonestDataCollectionDate();
 	}
 
+	@Override
 	public Date getLatestDataCollectionDate() {
 		return formView.getLatestDataCollectionDate();
 	}
 
+	@Override
 	public Long getNumberParticipationsDone() {
 		return formView.getNumberParticipationsDone();
 	}
diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisSegmentsController.java b/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisSegmentsController.java
new file mode 100644
index 00000000000..2f690f646ab
--- /dev/null
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/AnalysisSegmentsController.java
@@ -0,0 +1,138 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.quality.analysis.ui;
+
+import java.util.List;
+
+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.panel.SimpleStackedPanel;
+import org.olat.core.gui.components.panel.StackedPanel;
+import org.olat.core.gui.components.stack.ButtonGroupComponent;
+import org.olat.core.gui.components.stack.PopEvent;
+import org.olat.core.gui.components.stack.TooledController;
+import org.olat.core.gui.components.stack.TooledStackedPanel;
+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.id.context.ContextEntry;
+import org.olat.core.id.context.StateEntry;
+import org.olat.modules.quality.QualitySecurityCallback;
+import org.olat.modules.quality.analysis.EvaluationFormView;
+
+/**
+ * 
+ * Initial date: 04.09.2018<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class AnalysisSegmentsController extends BasicController implements TooledController, Activateable2 {
+
+	private final TooledStackedPanel stackPanel;
+	private final StackedPanel mainPanel;
+	private final ButtonGroupComponent segmentButtonsCmp;
+	private Link reportLink;
+	private Link heatMapLink;
+
+	private AnalysisController analysisCtrl;
+	
+	private final QualitySecurityCallback secCallback;
+	private final EvaluationFormView formView;
+	
+	//TODO uh Wo wird das StackPanel und das SecCallback benätigt
+
+	public AnalysisSegmentsController(UserRequest ureq, WindowControl wControl, QualitySecurityCallback secCallback,
+			TooledStackedPanel stackPanel, EvaluationFormView formView) {
+		super(ureq, wControl);
+		this.secCallback = secCallback;
+		this.stackPanel = stackPanel;
+		stackPanel.addListener(this);
+		this.formView = formView;
+		
+		segmentButtonsCmp = new ButtonGroupComponent("segments");
+		reportLink = LinkFactory.createLink("segments.report.link", getTranslator(), this);
+		segmentButtonsCmp.addButton(reportLink, false);
+		heatMapLink = LinkFactory.createLink("segments.heatmap.link", getTranslator(), this);
+		segmentButtonsCmp.addButton(heatMapLink, false);
+		
+		mainPanel = putInitialPanel(new SimpleStackedPanel("analysisSegments"));
+		mainPanel.setContent(new Panel("empty"));
+	}
+	
+	@Override
+	public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
+		doOpenReport(ureq);
+	}
+
+	@Override
+	public void initTools() {
+		stackPanel.addTool(segmentButtonsCmp, true);
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		if (reportLink == source) {
+			doOpenReport(ureq);
+		} else if(heatMapLink == source) {
+			doOpenHeatMap(ureq);
+		} else if (stackPanel == source && stackPanel.getLastController() == this && event instanceof PopEvent) {
+			PopEvent popEvent = (PopEvent) event;
+			if (popEvent.isClose()) {
+				stackPanel.popController(this);
+			} else {
+				doOpenReport(ureq);
+			}
+		}
+	}
+	
+	private void doOpenAnalysis(UserRequest ureq) {
+		if (analysisCtrl == null) {
+			analysisCtrl = new AnalysisController(ureq, getWindowControl(), secCallback, stackPanel, formView);
+			listenTo(analysisCtrl);
+			stackPanel.pushController("segments.report.breadcrumb", analysisCtrl);
+		}
+	}
+
+	private void doOpenReport(UserRequest ureq) {
+		doOpenAnalysis(ureq);
+		analysisCtrl.setPresentation(ureq, AnalysisController.Presentation.REPORT);
+		stackPanel.changeDisplayname(translate("segments.report.breadcrumb"));
+		segmentButtonsCmp.setSelectedButton(reportLink);
+	}
+
+	private void doOpenHeatMap(UserRequest ureq) {
+		doOpenAnalysis(ureq);
+		analysisCtrl.setPresentation(ureq, AnalysisController.Presentation.HEAT_MAP);
+		stackPanel.changeDisplayname(translate("segments.heatmap.breadcrumb"));
+		segmentButtonsCmp.setSelectedButton(heatMapLink);
+	}
+	
+	@Override
+	protected void doDispose() {
+		if(stackPanel != null) {
+			stackPanel.removeListener(this);
+		}
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/FilterController.java b/src/main/java/org/olat/modules/quality/analysis/ui/FilterController.java
new file mode 100644
index 00000000000..d203e0abbcd
--- /dev/null
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/FilterController.java
@@ -0,0 +1,269 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.quality.analysis.ui;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import org.olat.basesecurity.OrganisationModule;
+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.DateChooser;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.elements.StaticTextElement;
+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.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.id.Organisation;
+import org.olat.core.id.OrganisationRef;
+import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumElementRef;
+import org.olat.modules.curriculum.CurriculumModule;
+import org.olat.modules.curriculum.CurriculumRef;
+import org.olat.modules.curriculum.ui.CurriculumTreeModel;
+import org.olat.modules.quality.analysis.AnalysisSearchParameter;
+import org.olat.modules.quality.analysis.QualityAnalysisService;
+import org.olat.modules.quality.ui.QualityUIFactory;
+import org.olat.modules.quality.ui.QualityUIFactory.KeysValues;
+import org.olat.user.ui.organisation.OrganisationTreeModel;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 04.09.2018<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class FilterController extends FormBasicController {
+
+	private DateChooser dateRangeFromEl;
+	private DateChooser dateRangeToEl;
+	private MultipleSelectionElement organisationEl;
+	private MultipleSelectionElement curriculumEl;
+	private MultipleSelectionElement curriculumElementEl;
+	private StaticTextElement countFilteredEl;
+	
+	private final AnalysisSearchParameter searchParams;
+	
+	@Autowired
+	private QualityAnalysisService analysisService;
+	@Autowired
+	private OrganisationModule organisationModule;
+	@Autowired
+	private CurriculumModule curriculumModule;
+
+	public FilterController(UserRequest ureq, WindowControl wControl, AnalysisSearchParameter searchParams) {
+		super(ureq, wControl);
+		this.searchParams = searchParams;
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		dateRangeFromEl = uifactory.addDateChooser("filter.date.range.from", null, formLayout);
+		dateRangeFromEl.addActionListener(FormEvent.ONCHANGE);
+		
+		dateRangeToEl = uifactory.addDateChooser("filter.date.range.to", null, formLayout);
+		dateRangeToEl.addActionListener(FormEvent.ONCHANGE);
+
+		organisationEl = uifactory.addCheckboxesDropdown("filter.organisations", formLayout);
+		organisationEl.addActionListener(FormEvent.ONCLICK);
+		
+		curriculumEl = uifactory.addCheckboxesDropdown("filter.curriculums", formLayout);
+		curriculumEl.addActionListener(FormEvent.ONCLICK);
+
+		curriculumElementEl = uifactory.addCheckboxesDropdown("filter.curriculum.elements", formLayout);
+		curriculumElementEl.addActionListener(FormEvent.ONCLICK);
+		
+		countFilteredEl = uifactory.addStaticTextElement("filter.count", "", formLayout);
+		
+		setSelectionValues();
+	}
+	
+	private void setSelectionValues() {
+		setOrganisationValues();
+		setCurriculumValues();
+		setCurriculumElementValues();
+		setCountFiltered();
+	}
+	
+	private void setOrganisationValues() {
+		if (!organisationModule.isEnabled()) {
+			organisationEl.setVisible(false);
+			return;
+		}
+		
+		Collection<String> selectedKeys = organisationEl.getSelectedKeys();
+		
+		AnalysisSearchParameter orgSearchParams = new AnalysisSearchParameter();
+		orgSearchParams.setFormEntryRef(searchParams.getFormEntryRef());
+		List<Organisation> organisations = analysisService.loadFilterOrganisations(orgSearchParams);
+		OrganisationTreeModel organisationModel = new OrganisationTreeModel();
+		organisationModel.loadTreeModel(organisations);
+		
+		KeysValues keysValues = QualityUIFactory.getTopicOrganisationKeysValues(organisationModel, null);
+		organisationEl.setKeysAndValues(keysValues.getKeys(), keysValues.getValues());
+		for (String key: selectedKeys) {
+			organisationEl.select(key, true);
+		}
+	}
+
+	private void setCurriculumValues() {
+		if (!curriculumModule.isEnabled()) {
+			curriculumEl.setVisible(false);
+			return;
+		}
+		
+		Collection<String> selectedKeys = curriculumEl.getSelectedKeys();
+		
+		AnalysisSearchParameter curriculumSearchParams = searchParams.clone();
+		curriculumSearchParams.setCurriculumRefs(null);
+		curriculumSearchParams.setCurriculumElementRefs(null);
+		List<Curriculum> curriculums = analysisService.loadFilterCurriculums(curriculumSearchParams);
+		KeysValues keysValues = QualityUIFactory.getCurriculumKeysValues(curriculums, null);
+		curriculumEl.setKeysAndValues(keysValues.getKeys(), keysValues.getValues());
+		for (String key: selectedKeys) {
+			curriculumEl.select(key, true);
+		}
+	}
+
+	private void setCurriculumElementValues() {
+		if (!curriculumModule.isEnabled()) {
+			curriculumElementEl.setVisible(false);
+			return;
+		}
+		
+		Collection<String> selectedKeys = curriculumEl.getSelectedKeys();
+
+		AnalysisSearchParameter curriculumElementSearchParams = searchParams.clone();
+		Collection<String> curriculumKeys = curriculumEl.isAtLeastSelected(1)
+				? curriculumEl.getSelectedKeys()
+				: curriculumEl.getKeys();
+		List<? extends CurriculumRef> curriculumRefs = curriculumKeys.stream()
+				.map(key -> QualityUIFactory.getCurriculumRef(key))
+				.collect(toList());
+		curriculumElementSearchParams.setCurriculumRefs(curriculumRefs);
+		curriculumElementSearchParams.setCurriculumElementRefs(null);
+		List<CurriculumElement> curriculumElements = analysisService.loadFilterCurriculumElements(curriculumElementSearchParams);
+		
+		CurriculumTreeModel curriculumTreeModel = new CurriculumTreeModel();
+		curriculumTreeModel.loadTreeModel(curriculumElements);
+		KeysValues keysValues = QualityUIFactory.getCurriculumElementKeysValues(curriculumTreeModel, null);
+		curriculumElementEl.setKeysAndValues(keysValues.getKeys(), keysValues.getValues());
+		for (String key: selectedKeys) {
+			curriculumElementEl.select(key, true);
+		}
+	}
+	
+	private void setCountFiltered() {
+		Long count = analysisService.loadFilterDataCollectionCount(searchParams);
+		countFilteredEl.setValue(count.toString());
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		 if (source == dateRangeFromEl) {
+			doFiltered();
+		} else if (source == dateRangeToEl) {
+			doFiltered();
+		} else if (source == organisationEl) {
+			doFiltered();
+		} else if (source == curriculumEl) {
+			doFiltered();
+		} else if (source == curriculumElementEl) {
+			doFiltered();
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	private void doFiltered() {
+		getSearchParams();
+		setSelectionValues();
+		//TODO uh filter presentation -> fireEvent
+	}
+
+	private void getSearchParams() {
+		getSearchParamOrganisations();
+		getSearchParamCurriculums();
+		getSearchParamCurriculumElements();
+		getSearchParamDateRangeFrom();
+		getSearchParamDateRangeTo();
+	}
+
+	private void getSearchParamOrganisations() {
+		if (organisationModule.isEnabled() && organisationEl.isAtLeastSelected(1)) {
+			List<OrganisationRef> organisationRefs = organisationEl.getSelectedKeys().stream()
+					.map(key -> QualityUIFactory.getOrganisationRef(key))
+					.collect(toList());
+			searchParams.setOrganisationRefs(organisationRefs);
+		} else {
+			searchParams.setOrganisationRefs(null);
+		}
+	}
+
+	private void getSearchParamCurriculums() {
+		if (curriculumEl.isEnabled() && curriculumEl.isAtLeastSelected(1)) {
+			Collection<CurriculumRef> curriculumRefs = curriculumEl.getSelectedKeys().stream()
+					.map(key -> QualityUIFactory.getCurriculumRef(key))
+					.collect(toList());
+			searchParams.setCurriculumRefs(curriculumRefs);
+		} else {
+			searchParams.setCurriculumRefs(null);
+		}
+	}
+
+	private void getSearchParamCurriculumElements() {
+		if (curriculumEl.isEnabled() && curriculumElementEl.isAtLeastSelected(1)) {
+			List<CurriculumElementRef> curriculumElementRefs = curriculumElementEl.getSelectedKeys().stream()
+					.map(key -> QualityUIFactory.getCurriculumElementRef(key))
+					.collect(toList());
+			searchParams.setCurriculumElementRefs(curriculumElementRefs);
+		} else {
+			searchParams.setCurriculumElementRefs(null);
+		}
+	}
+
+	private void getSearchParamDateRangeFrom() {
+		Date dateRangeFrom = dateRangeFromEl.getDate();
+		searchParams.setDateRangeFrom(dateRangeFrom);
+	}
+
+	private void getSearchParamDateRangeTo() {
+		Date dateRangeTo = dateRangeToEl.getDate();
+		searchParams.setDateRangeTo(dateRangeTo);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapController.java b/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapController.java
new file mode 100644
index 00000000000..3d1da8e73b2
--- /dev/null
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/HeatMapController.java
@@ -0,0 +1,64 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.quality.analysis.ui;
+
+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.stack.TooledStackedPanel;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.modules.quality.QualitySecurityCallback;
+
+/**
+ * 
+ * Initial date: 04.09.2018<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class HeatMapController extends FormBasicController {
+
+	private final QualitySecurityCallback secCallback;
+	private final TooledStackedPanel stackPanel;
+
+	public HeatMapController(UserRequest ureq, WindowControl wControl, QualitySecurityCallback secCallback,
+			TooledStackedPanel stackPanel) {
+		super(ureq, wControl);
+		this.secCallback = secCallback;
+		this.stackPanel = stackPanel;
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		uifactory.addStaticExampleText("hm", "", "HEAT MAP", formLayout);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/_content/analysis.html b/src/main/java/org/olat/modules/quality/analysis/ui/_content/analysis.html
new file mode 100644
index 00000000000..6c8d15d7780
--- /dev/null
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/_content/analysis.html
@@ -0,0 +1,4 @@
+$r.render("filter")
+#if($r.available("presentation"))
+	$r.render("presentation")
+#end
diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_de.properties
index f01be9fc3f3..d3e2f0e2bd8 100644
--- a/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_de.properties
@@ -2,8 +2,18 @@ analysis.table.data.collections.latest=Letzte Datenerhebung
 analysis.table.data.collections.number=Datenerhebungen
 analysis.table.data.collections.soonest=Erste Datenerhebung
 analysis.table.empty=Diese Tabelle enth\u00e4hlt keine Daten.
-analysis.table.form.created=Erstellt
 analysis.table.form.created.on=Erstellt am {0}
+analysis.table.form.created=Erstellt
 analysis.table.form.title=Fragebogen
-analysis.table.participations.number=Teilnahmen
 analysis.table.open=\u00d6ffnen
+analysis.table.participations.number=Teilnahmen
+filter.count=Anzahl Datenerhebungen
+filter.curriculum.elements=Curriculumelement
+filter.curriculums=Curriculum
+filter.date.range.from=Datenerhebungen von
+filter.date.range.to=Datenerhebungen to
+filter.organisations=Organisationen
+segments.heatmap.breadcrumb=Heatmap
+segments.heatmap.link=Heatmap
+segments.report.breadcrumb=Report
+segments.report.link=Report
diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_en.properties
index 6977d515fd4..a4ee3901cae 100644
--- a/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/_i18n/LocalStrings_en.properties
@@ -2,8 +2,18 @@ analysis.table.data.collections.latest=Latest data collection
 analysis.table.data.collections.number=Data collections
 analysis.table.data.collections.soonest=Soonest data collection
 analysis.table.empty=This table contains no data.
-analysis.table.form.created=Created
 analysis.table.form.created.on=Created on {0}
+analysis.table.form.created=Created
 analysis.table.form.title=Questionnaire
-analysis.table.participations.number=Participations
 analysis.table.open=Open
+analysis.table.participations.number=Participations
+filter.count=Number of data collections
+filter.curriculum.elements=Curriculumelement
+filter.curriculums=Curriculum
+filter.date.range.from=Data collections from
+filter.date.range.to=Data collections to
+filter.organisations=Organisations
+segments.heatmap.breadcrumb=Heat map
+segments.heatmap.link=Heat map
+segments.report.breadcrumb=Report
+segments.report.link=Report
diff --git a/src/main/java/org/olat/modules/quality/ui/QualityUIFactory.java b/src/main/java/org/olat/modules/quality/ui/QualityUIFactory.java
index edd8814368a..885ead38edf 100644
--- a/src/main/java/org/olat/modules/quality/ui/QualityUIFactory.java
+++ b/src/main/java/org/olat/modules/quality/ui/QualityUIFactory.java
@@ -153,13 +153,7 @@ public class QualityUIFactory {
 		if (StringHelper.containsNonWhitespace(curriculumKey)) {
 			try {
 				Long key = Long.valueOf(curriculumKey);
-				return new CurriculumRef() {
-					
-					@Override
-					public Long getKey() {
-						return key;
-					}
-				};
+				return () -> key;
 			} catch (Exception e) {
 				//
 			}
@@ -214,13 +208,7 @@ public class QualityUIFactory {
 		if (StringHelper.containsNonWhitespace(curriculumElementKey)) {
 			try {
 				Long key = Long.valueOf(curriculumElementKey);
-				return new CurriculumElementRef() {
-					
-					@Override
-					public Long getKey() {
-						return key;
-					}
-				};
+				return () -> key;
 			} catch (Exception e) {
 				//
 			}
@@ -271,17 +259,11 @@ public class QualityUIFactory {
 		return String.valueOf(organisation.getKey());
 	}
 
-	static OrganisationRef getOrganisationRef(String organisationKey) {
+	public static OrganisationRef getOrganisationRef(String organisationKey) {
 		if (StringHelper.containsNonWhitespace(organisationKey)) {
 			try {
 				Long key = Long.valueOf(organisationKey);
-				return new OrganisationRef() {
-					
-					@Override
-					public Long getKey() {
-						return key;
-					}
-				};
+				return () -> key;
 			} catch (Exception e) {
 				//
 			}
diff --git a/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementDAOTest.java b/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementDAOTest.java
index 50d86545b77..3da0c2de367 100644
--- a/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementDAOTest.java
+++ b/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementDAOTest.java
@@ -19,7 +19,10 @@
  */
 package org.olat.modules.curriculum.manager;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
@@ -34,6 +37,7 @@ import org.olat.modules.curriculum.CurriculumElement;
 import org.olat.modules.curriculum.CurriculumElementMembership;
 import org.olat.modules.curriculum.CurriculumElementStatus;
 import org.olat.modules.curriculum.CurriculumElementType;
+import org.olat.modules.curriculum.CurriculumRef;
 import org.olat.modules.curriculum.CurriculumRoles;
 import org.olat.modules.curriculum.CurriculumService;
 import org.olat.modules.curriculum.model.CurriculumElementImpl;
@@ -172,6 +176,26 @@ public class CurriculumElementDAOTest extends OlatTestCase {
 		Assert.assertEquals(1, relations.size());
 		Assert.assertEquals(element, relations.get(0));
 	}
+	
+	@Test
+	public void loadElementsByCurriculums() {
+		Curriculum curriculum1 = curriculumDao.createAndPersist("", "", null, null);
+		CurriculumElement parentElement = curriculumElementDao.createCurriculumElement("", "", null, null,  null, null, curriculum1);
+		CurriculumElement element1 = curriculumElementDao.createCurriculumElement("", "", null, null, parentElement, null, curriculum1);
+		CurriculumElement element2 = curriculumElementDao.createCurriculumElement("", "", null, null, parentElement, null, curriculum1);
+		Curriculum curriculum2 = curriculumDao.createAndPersist("", "", null, null);
+		CurriculumElement parentElement2 = curriculumElementDao.createCurriculumElement("", "", null, null,  null, null, curriculum2);
+		Curriculum otherCurriculum = curriculumDao.createAndPersist("", "", null, null);
+		CurriculumElement otherElement = curriculumElementDao.createCurriculumElement("", "", null, null,  null, null, otherCurriculum);
+		dbInstance.commitAndCloseSession();
+		
+		Collection<CurriculumRef> curriculumRefs = Arrays.asList(curriculum1, curriculum2);
+		 List<CurriculumElement> elements = curriculumElementDao.loadElementsByCurriculums(curriculumRefs);
+		
+		assertThat(elements)
+				.containsExactlyInAnyOrder(parentElement, element1, element2, parentElement2)
+				.doesNotContain(otherElement);
+	}
 
 	@Test
 	public void createCurriculumElementParentChildren() {
diff --git a/src/test/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAOTest.java b/src/test/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAOTest.java
new file mode 100644
index 00000000000..9d9c52c32e3
--- /dev/null
+++ b/src/test/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAOTest.java
@@ -0,0 +1,427 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.quality.analysis.manager;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.olat.basesecurity.OrganisationService;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.core.id.Organisation;
+import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumService;
+import org.olat.modules.forms.EvaluationFormParticipation;
+import org.olat.modules.quality.QualityContextBuilder;
+import org.olat.modules.quality.QualityDataCollection;
+import org.olat.modules.quality.QualityDataCollectionStatus;
+import org.olat.modules.quality.QualityService;
+import org.olat.modules.quality.analysis.AnalysisSearchParameter;
+import org.olat.modules.quality.manager.QualityTestHelper;
+import org.olat.repository.RepositoryEntry;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * 
+ * Initial date: 05.09.2018<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class AnalysisFilterDAOTest extends OlatTestCase {
+
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private QualityTestHelper qualityTestHelper;
+	@Autowired
+	private QualityService qualityService;
+	@Autowired
+	private OrganisationService organisationService;
+	@Autowired
+	private CurriculumService curriculumService;
+	
+	@Autowired
+	private AnalysisFilterDAO sut;
+
+	@Before
+	public void cleanUp() {
+		qualityTestHelper.deleteAll();
+	}
+	
+	@Test
+	public void shouldLoadDistinctOrganisations() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		Organisation organisation1 = qualityTestHelper.createOrganisation();
+		Organisation organisation2 = qualityTestHelper.createOrganisation();
+		// Participation with two organisations
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations1 = qualityService.addParticipations(dc1, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder1 = qualityService.createContextBuilder(dc1, participations1.get(0));
+		contextBuilder1.addOrganisation(organisation1).addOrganisation(organisation2).build();
+		// Participation with the same organisation
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations2 = qualityService.addParticipations(dc2, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder2 = qualityService.createContextBuilder(dc2, participations2.get(0));
+		contextBuilder2.addOrganisation(organisation2).build();
+		// Participation without organisation
+		QualityDataCollection dcNull = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityService.addParticipations(dcNull, Collections.singletonList(executor));
+		finish(asList(dc1, dc2, dcNull));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		List<Organisation> filtered = sut.loadOrganisations(searchParams);
+		
+		assertThat(filtered)
+				.containsExactlyInAnyOrder(organisation1, organisation2)
+				.doesNotContainNull();
+	}
+	
+	@Test
+	public void shouldLoadDistinctCurriculum() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		Curriculum curriculum1 = qualityTestHelper.createCurriculum();
+		Curriculum curriculum2 = qualityTestHelper.createCurriculum();
+		// Participation with curriculum
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations1 = qualityService.addParticipations(dc1, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder1 = qualityService.createContextBuilder(dc1, participations1.get(0));
+		contextBuilder1.addCurriculum(curriculum1).build();
+		// Participation with another curriculum
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations2 = qualityService.addParticipations(dc2, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder2 = qualityService.createContextBuilder(dc2, participations2.get(0));
+		contextBuilder2.addCurriculum(curriculum2).build();
+		// Second participation with curriculum (to test distinct)
+		QualityDataCollection dcDistinct = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participationsDistinct = qualityService.addParticipations(dcDistinct, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilderDistinct = qualityService.createContextBuilder(dcDistinct, participationsDistinct.get(0));
+		contextBuilderDistinct.addCurriculum(curriculum1).build();
+		// Participation without curriculum (to test no nulls)
+		QualityDataCollection dcNull = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityService.addParticipations(dcNull, Collections.singletonList(executor));
+		finish(asList(dc1, dc2, dcDistinct, dcNull));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		List<Curriculum> filtered = sut.loadCurriculums(searchParams);
+		
+		assertThat(filtered)
+			.containsExactlyInAnyOrder(curriculum1, curriculum2)
+			.doesNotContainNull();
+	}
+
+	@Test
+	public void shouldLoadDistinctCurriculumElementPathes() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		CurriculumElement element1 = qualityTestHelper.createCurriculumElement();
+		CurriculumElement element2 = qualityTestHelper.createCurriculumElement();
+		// Participation with curriculum element
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations1 = qualityService.addParticipations(dc1, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder1 = qualityService.createContextBuilder(dc1, participations1.get(0));
+		contextBuilder1.addCurriculumElement(element1).build();
+		// Participation with another curriculum element
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations2 = qualityService.addParticipations(dc2, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder2 = qualityService.createContextBuilder(dc2, participations2.get(0));
+		contextBuilder2.addCurriculumElement(element2).build();
+		// Second participation with curriculum element (to test distinct)
+		QualityDataCollection dcDistinct = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participationsDistinct = qualityService.addParticipations(dcDistinct, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilderDistinct = qualityService.createContextBuilder(dcDistinct, participationsDistinct.get(0));
+		contextBuilderDistinct.addCurriculumElement(element1).build();
+		// Participation without curriculum (to test no nulls)
+		QualityDataCollection dcNull = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityService.addParticipations(dcNull, Collections.singletonList(executor));
+		finish(asList(dc1, dc2, dcDistinct, dcNull));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		List<String> filtered = sut.loadCurriculumElementPathes(searchParams);
+		
+		assertThat(filtered)
+			.containsExactlyInAnyOrder(element1.getMaterializedPathKeys(), element2.getMaterializedPathKeys())
+			.doesNotContainNull();
+	}
+	
+	@Test
+	public void shouldLoadDataCollectionCount() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor1 = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Identity executor2 = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Identity executor3 = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Organisation dcOrganisation = organisationService.createOrganisation("", "", null, null, null);
+		// Data collection with three participations
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityService.addParticipations(dc1, asList(executor1, executor2, executor3));
+		// Another data collection with three participations
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityService.addParticipations(dc2, asList(executor1, executor2, executor3));
+		// Data collection without participation
+		QualityDataCollection dcWithout = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		finish(asList(dc1, dc2, dcWithout));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		Long count = sut.loadFilterDataCollectionCount(searchParams);
+		
+		assertThat(count).isEqualTo(3);
+	}
+
+	@Test
+	public void shouldFilterByFinishedDataCollections() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		QualityDataCollection dcFinished1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityTestHelper.updateStatus(dcFinished1, QualityDataCollectionStatus.FINISHED);
+		QualityDataCollection dcFinishe2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityTestHelper.updateStatus(dcFinishe2, QualityDataCollectionStatus.FINISHED);
+		QualityDataCollection dcRunning = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityTestHelper.updateStatus(dcRunning, QualityDataCollectionStatus.RUNNING);
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		Long count = sut.loadFilterDataCollectionCount(searchParams);
+		
+		assertThat(count).isEqualTo(2);
+	}
+	
+	@Test
+	public void shouldFilterByFormEntry() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		RepositoryEntry otherFormEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		QualityDataCollection dc3 = qualityService.createDataCollection(asList(dcOrganisation), otherFormEntry);
+		finish(asList(dc1, dc2, dc3));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		searchParams.setFormEntryRef(formEntry);
+		Long count = sut.loadFilterDataCollectionCount(searchParams);
+		
+		assertThat(count).isEqualTo(2);
+	}
+	
+	@Test
+	public void shouldFilterByDateRangeFrom() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		Date now = new Date();
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		dc1.setDeadline(addDays(now, 1));
+		qualityService.updateDataCollection(dc1);
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		dc2.setDeadline(addDays(now, 20));
+		qualityService.updateDataCollection(dc2);
+		QualityDataCollection dcToEarly = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		dcToEarly.setDeadline(addDays(now, -2));
+		qualityService.updateDataCollection(dcToEarly);
+		finish(asList(dc1, dc2, dcToEarly));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		searchParams.setDateRangeFrom(now);
+		Long count = sut.loadFilterDataCollectionCount(searchParams);
+		
+		assertThat(count).isEqualTo(2);
+	}
+	
+	@Test
+	public void shouldFilterByDateRangeTo() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		Date now = new Date();
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		dc1.setDeadline(addDays(now, -1));
+		qualityService.updateDataCollection(dc1);
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		dc2.setDeadline(addDays(now, -20));
+		qualityService.updateDataCollection(dc2);
+		QualityDataCollection dcToEarly = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		dcToEarly.setDeadline(addDays(now, 2));
+		qualityService.updateDataCollection(dcToEarly);
+		finish(asList(dc1, dc2, dcToEarly));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		searchParams.setDateRangeTo(now);
+		Long count = sut.loadFilterDataCollectionCount(searchParams);
+		
+		assertThat(count).isEqualTo(2);
+	}
+	
+	@Test
+	public void shouldFilterByCurriculums() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		Curriculum curriculum1 = qualityTestHelper.createCurriculum();
+		Curriculum curriculum2 = qualityTestHelper.createCurriculum();
+		Curriculum otherCurriculum = qualityTestHelper.createCurriculum();
+		// Participation with curriculum
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations1 = qualityService.addParticipations(dc1, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder1 = qualityService.createContextBuilder(dc1, participations1.get(0));
+		contextBuilder1.addCurriculum(curriculum1).build();
+		// Participation with another curriculum
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations2 = qualityService.addParticipations(dc2, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder2 = qualityService.createContextBuilder(dc2, participations2.get(0));
+		contextBuilder2.addCurriculum(curriculum2).build();
+		// Participation with other curriculum
+		QualityDataCollection dcOther = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participationsOther = qualityService.addParticipations(dcOther, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilderOther = qualityService.createContextBuilder(dcOther, participationsOther.get(0));
+		contextBuilderOther.addCurriculum(otherCurriculum).build();
+		// Participation without curriculum
+		QualityDataCollection dcNull = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityService.addParticipations(dcNull, Collections.singletonList(executor));
+		finish(asList(dc1, dc2, dcOther, dcNull));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		searchParams.setCurriculumRefs(asList(curriculum1, curriculum2));
+		Long count = sut.loadFilterDataCollectionCount(searchParams);
+		
+		assertThat(count).isEqualTo(2);
+	}
+	
+	@Test
+	public void shouldFilterByCurriculumElements() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		Curriculum curriculum = qualityTestHelper.createCurriculum();
+		CurriculumElement element1 = qualityTestHelper.createCurriculumElement();
+		CurriculumElement element2 = qualityTestHelper.createCurriculumElement();
+		CurriculumElement subElement = curriculumService.createCurriculumElement("", "", null, null, element1, null, curriculum);
+		CurriculumElement otherElement = qualityTestHelper.createCurriculumElement();
+		// Participation with curriculum element
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations1 = qualityService.addParticipations(dc1, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder1 = qualityService.createContextBuilder(dc1, participations1.get(0));
+		contextBuilder1.addCurriculumElement(element1).build();
+		// Participation with another curriculum element
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations2 = qualityService.addParticipations(dc2, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder2 = qualityService.createContextBuilder(dc2, participations2.get(0));
+		contextBuilder2.addCurriculumElement(element2).build();
+		// Participation with a child element
+		QualityDataCollection dcChild = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participationsChild = qualityService.addParticipations(dcChild, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilderChild = qualityService.createContextBuilder(dcChild, participationsChild.get(0));
+		contextBuilderChild.addCurriculumElement(subElement).build();
+		// Participation with other curriculum element
+		QualityDataCollection dcOther = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participationsOther = qualityService.addParticipations(dcOther, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilderOther = qualityService.createContextBuilder(dcOther, participationsOther.get(0));
+		contextBuilderOther.addCurriculumElement(otherElement).build();
+		// Participation without curriculum
+		QualityDataCollection dcNull = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityService.addParticipations(dcNull, Collections.singletonList(executor));
+		finish(asList(dc1, dc2, dcChild, dcOther, dcNull));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		searchParams.setCurriculumElementRefs(asList(element1, element2));
+		Long count = sut.loadFilterDataCollectionCount(searchParams);
+		
+		long expected = asList(element1, element2, subElement).size();
+		assertThat(count).isEqualTo(expected);
+	}
+	
+	@Test
+	public void shouldFilterByOrganisations() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		Organisation organisation1 = qualityTestHelper.createOrganisation();
+		Organisation organisation2 = qualityTestHelper.createOrganisation();
+		Organisation subOrganisation = organisationService.createOrganisation("", "", null, organisation1, null);
+		Organisation otherOrganisation = qualityTestHelper.createOrganisation();
+		// Participation with two organisations
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations1 = qualityService.addParticipations(dc1, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder1 = qualityService.createContextBuilder(dc1, participations1.get(0));
+		contextBuilder1.addOrganisation(organisation1).addOrganisation(organisation2).build();
+		// Participation with the same organisation
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations2 = qualityService.addParticipations(dc2, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilder2 = qualityService.createContextBuilder(dc2, participations2.get(0));
+		contextBuilder2.addOrganisation(organisation2).build();
+		// Participation in a child organisation (include them)
+		QualityDataCollection dcChild = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participationscild = qualityService.addParticipations(dcChild, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilderChild = qualityService.createContextBuilder(dcChild, participationscild.get(0));
+		contextBuilderChild.addOrganisation(subOrganisation).build();
+		// Participation with an other organisation
+		QualityDataCollection dcOther = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participationsOther = qualityService.addParticipations(dcOther, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilderOther = qualityService.createContextBuilder(dcOther, participationsOther.get(0));
+		contextBuilderOther.addOrganisation(otherOrganisation).build();
+		// Participation without organisation
+		QualityDataCollection dcNull = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityService.addParticipations(dcNull, Collections.singletonList(executor));
+		finish(asList(dc1, dc2, dcOther, dcChild, dcNull));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		searchParams.setOrganisationRefs(asList(organisation1, organisation2));
+		Long count = sut.loadFilterDataCollectionCount(searchParams);
+		
+		long expected = asList(organisation1, organisation2, subOrganisation).size();
+		assertThat(count).isEqualTo(expected);
+	}
+	
+	private void finish(Collection<QualityDataCollection> dataCollections) {
+		for (QualityDataCollection dataCollection: dataCollections) {
+			qualityTestHelper.updateStatus(dataCollection, QualityDataCollectionStatus.FINISHED);
+		}
+	}
+	
+	private static Date addDays(Date date, int days) {
+		Calendar c = Calendar.getInstance();
+		c.setTime(date);
+		c.add(Calendar.DATE, days);
+		return c.getTime();
+	}
+
+}
diff --git a/src/test/java/org/olat/modules/quality/manager/QualityTestHelper.java b/src/test/java/org/olat/modules/quality/manager/QualityTestHelper.java
index 83eb156fe49..8dd9a8b9d96 100644
--- a/src/test/java/org/olat/modules/quality/manager/QualityTestHelper.java
+++ b/src/test/java/org/olat/modules/quality/manager/QualityTestHelper.java
@@ -215,11 +215,11 @@ public class QualityTestHelper {
 		return organisationService.createOrganisation(UUID.randomUUID().toString(), UUID.randomUUID().toString(), null, null, null);
 	}
 
-	Curriculum createCurriculum() {
+	public Curriculum createCurriculum() {
 		return curriculumService.createCurriculum("i", "d", "d", createOrganisation());
 	}
 
-	CurriculumElement createCurriculumElement() {
+	public CurriculumElement createCurriculumElement() {
 		return curriculumService.createCurriculumElement("i", "d", null, null, null, null, createCurriculum());
 	}
 
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index 53b34352d2a..807ae74f876 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -217,6 +217,7 @@ import org.junit.runners.Suite;
 	org.olat.modules.portfolio.manager.SharedWithMeQueriesTest.class,
 	org.olat.modules.portfolio.manager.PortfolioServiceTest.class,
 	org.olat.modules.portfolio.manager.BinderUserInformationsDAOTest.class,
+	org.olat.modules.quality.analysis.manager.AnalysisFilterDAOTest.class,
 	org.olat.modules.quality.analysis.manager.EvaluationFormDAOTest.class,
 	org.olat.modules.quality.generator.manager.QualityGeneratorDAOTest.class,
 	org.olat.modules.quality.generator.manager.QualityGeneratorConfigDAOTest.class,
-- 
GitLab