From 8f57716b03003ad55103c04915a86a564f5fd24b Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Tue, 18 Dec 2018 14:51:57 +0100
Subject: [PATCH] OO-3778: Filter quality analysis by curriculum element type

---
 .../analysis/AnalysisSearchParameter.java     |  14 +++
 .../quality/analysis/AvailableAttributes.java |  10 +-
 .../analysis/QualityAnalysisService.java      |   3 +
 .../analysis/manager/AnalysisFilterDAO.java   |  23 ++++
 .../manager/QualityAnalysisServiceImpl.java   |   6 +
 .../quality/analysis/ui/FilterController.java |  52 ++++++++-
 .../ui/_i18n/LocalStrings_de.properties       |   1 +
 .../ui/_i18n/LocalStrings_en.properties       |   1 +
 .../modules/quality/ui/QualityUIFactory.java  |  26 +++++
 .../manager/AnalysisFilterDAOTest.java        | 110 ++++++++++++++++++
 10 files changed, 241 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/olat/modules/quality/analysis/AnalysisSearchParameter.java b/src/main/java/org/olat/modules/quality/analysis/AnalysisSearchParameter.java
index 1bdcac3d573..b01c07f8088 100644
--- a/src/main/java/org/olat/modules/quality/analysis/AnalysisSearchParameter.java
+++ b/src/main/java/org/olat/modules/quality/analysis/AnalysisSearchParameter.java
@@ -27,6 +27,7 @@ import java.util.List;
 import org.olat.basesecurity.IdentityRef;
 import org.olat.core.id.OrganisationRef;
 import org.olat.modules.curriculum.CurriculumElementRef;
+import org.olat.modules.curriculum.CurriculumElementTypeRef;
 import org.olat.modules.curriculum.CurriculumRef;
 import org.olat.modules.quality.QualityContextRole;
 import org.olat.modules.taxonomy.TaxonomyLevelRef;
@@ -53,6 +54,7 @@ public class AnalysisSearchParameter {
 	private List<? extends OrganisationRef> contextOrganisationRefs; // of the executor
 	private Collection<? extends CurriculumRef> contextCurriculumRefs;
 	private List<? extends CurriculumElementRef> contextCurriculumElementRefs;
+	private Collection<? extends CurriculumElementTypeRef> contextCurriculumElementTypeRefs;
 	private List<? extends OrganisationRef> contextCurriculumOrganisationRefs;
 	private List<? extends TaxonomyLevelRef> contextTaxonomyLevelRefs;
 	private Collection<Integer> seriesIndexes;
@@ -164,6 +166,15 @@ public class AnalysisSearchParameter {
 		this.contextCurriculumElementRefs = contextCurriculumElementRefs;
 	}
 
+	public Collection<? extends CurriculumElementTypeRef> getContextCurriculumElementTypeRefs() {
+		return contextCurriculumElementTypeRefs;
+	}
+
+	public void setContextCurriculumElementTypeRefs(
+			Collection<? extends CurriculumElementTypeRef> contextCurriculumElementTypeRefs) {
+		this.contextCurriculumElementTypeRefs = contextCurriculumElementTypeRefs;
+	}
+
 	public List<? extends TaxonomyLevelRef> getContextTaxonomyLevelRefs() {
 		return contextTaxonomyLevelRefs;
 	}
@@ -229,6 +240,9 @@ public class AnalysisSearchParameter {
 		clone.contextCurriculumElementRefs = this.contextCurriculumElementRefs != null
 				? new ArrayList<>(this.contextCurriculumElementRefs)
 				: null;
+		clone.contextCurriculumElementTypeRefs = this.contextCurriculumElementTypeRefs != null
+				? new ArrayList<>(this.contextCurriculumElementTypeRefs)
+				: null;
 		clone.contextTaxonomyLevelRefs = this.contextTaxonomyLevelRefs != null
 				? new ArrayList<>(this.contextTaxonomyLevelRefs)
 				: null;
diff --git a/src/main/java/org/olat/modules/quality/analysis/AvailableAttributes.java b/src/main/java/org/olat/modules/quality/analysis/AvailableAttributes.java
index d8ac2208071..d4ff85b8fa2 100644
--- a/src/main/java/org/olat/modules/quality/analysis/AvailableAttributes.java
+++ b/src/main/java/org/olat/modules/quality/analysis/AvailableAttributes.java
@@ -36,6 +36,7 @@ public class AvailableAttributes {
 	private final boolean contextExecutorOrganisation;
 	private final boolean contextCurriculum;
 	private final boolean contextCurriculumElement;
+	private final boolean contextCurriculumElementType;
 	private final boolean contextCurriculumOrganisation;
 	private final boolean contextTaxonomyLevel;
 	private final boolean seriesIndex;
@@ -44,8 +45,8 @@ public class AvailableAttributes {
 	public AvailableAttributes(boolean topicIdentity, boolean topicRepository, boolean topicOrganisation,
 			boolean topicCurriculum, boolean topicCurriculumElement, Boolean contextLocation,
 			boolean contextExecutorOrganisation, boolean contextCurriculum, boolean contextCurriculumElement,
-			boolean contextCurriculumOrganisation, boolean contextTaxonomyLevel, boolean seriesIndex,
-			boolean dataCollection) {
+			boolean contextCurriculumElementType, boolean contextCurriculumOrganisation, boolean contextTaxonomyLevel,
+			boolean seriesIndex, boolean dataCollection) {
 		this.topicIdentity = topicIdentity;
 		this.topicRepository = topicRepository;
 		this.topicOrganisation = topicOrganisation;
@@ -55,6 +56,7 @@ public class AvailableAttributes {
 		this.contextExecutorOrganisation = contextExecutorOrganisation;
 		this.contextCurriculum = contextCurriculum;
 		this.contextCurriculumElement = contextCurriculumElement;
+		this.contextCurriculumElementType = contextCurriculumElementType;
 		this.contextCurriculumOrganisation = contextCurriculumOrganisation;
 		this.contextTaxonomyLevel = contextTaxonomyLevel;
 		this.seriesIndex = seriesIndex;
@@ -97,6 +99,10 @@ public class AvailableAttributes {
 		return contextCurriculumElement;
 	}
 
+	public boolean isContextCurriculumElementType() {
+		return contextCurriculumElementType;
+	}
+
 	public boolean isContextCurriculumOrganisation() {
 		return contextCurriculumOrganisation;
 	}
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 367e306d48f..d0d0390a3e4 100644
--- a/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java
+++ b/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java
@@ -26,6 +26,7 @@ import org.olat.basesecurity.IdentityShort;
 import org.olat.core.id.Organisation;
 import org.olat.modules.curriculum.Curriculum;
 import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumElementType;
 import org.olat.modules.forms.SessionFilter;
 import org.olat.modules.forms.model.xml.Rubric;
 import org.olat.modules.quality.QualityContextRole;
@@ -77,6 +78,8 @@ public interface QualityAnalysisService {
 
 	public List<CurriculumElement> loadContextCurriculumElements(AnalysisSearchParameter searchParams, boolean withParents);
 	
+	public List<CurriculumElementType> loadContextCurriculumElementTypes(AnalysisSearchParameter searchParams);
+	
 	public List<Organisation> loadContextCurriculumOrganisations(AnalysisSearchParameter searchParams, boolean withParents);
 
 	public List<TaxonomyLevel> loadContextTaxonomyLevels(AnalysisSearchParameter searchParams, boolean withParents);
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
index bd9b2c9ea22..e592da4a345 100644
--- a/src/main/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAO.java
+++ b/src/main/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAO.java
@@ -35,6 +35,8 @@ 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.CurriculumElementType;
+import org.olat.modules.curriculum.CurriculumElementTypeRef;
 import org.olat.modules.curriculum.CurriculumRef;
 import org.olat.modules.quality.QualityContextRole;
 import org.olat.modules.quality.QualityDataCollection;
@@ -74,6 +76,7 @@ public class AnalysisFilterDAO {
 		sb.append("     , count(contextToOrganisation.organisation.key) > 0");
 		sb.append("     , count(contextCurriculum.key) > 0");
 		sb.append("     , count(contextCurriculumElement.key) > 0");
+		sb.append("     , count(contextCurriculumElement.type.key) > 0");
 		sb.append("     , count(contextCurriculumOrganisation.key) > 0");
 		sb.append("     , count(contextToTaxonomyLevel.taxonomyLevel.key) > 0");
 		sb.append("     , CASE WHEN max(survey.seriesIndex) is not null THEN max(survey.seriesIndex) ELSE 0 END >= 2");
@@ -194,6 +197,19 @@ public class AnalysisFilterDAO {
 		return query.getResultList();
 	}
 
+	List<CurriculumElementType> loadContextCurriculumElementsTypes(AnalysisSearchParameter searchParams) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select distinct contextCurriculumElement.type");
+		appendFrom(sb, searchParams);
+		appendWhere(sb, searchParams);
+		sb.and().append("contextCurriculumElement.type is not null");
+		
+		TypedQuery<CurriculumElementType> query = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), CurriculumElementType.class);
+		appendParameters(query, searchParams);
+		return query.getResultList();
+	}
+
 	List<String> loadContextTaxonomyLevelPathes(AnalysisSearchParameter searchParams) {
 		QueryBuilder sb = new QueryBuilder();
 		sb.append("select distinct taxonomyLevel.materializedPathKeys");
@@ -502,6 +518,9 @@ public class AnalysisFilterDAO {
 				}
 			}
 		}
+		if (searchParams.getContextCurriculumElementTypeRefs() != null && !searchParams.getContextCurriculumElementTypeRefs().isEmpty()) {
+			sb.and().append("contextCurriculumElement.type.key in :contextCurriculumElementTypeKeys");
+		}
 		if (searchParams.getContextCurriculumOrganisationRefs() != null && !searchParams.getContextCurriculumOrganisationRefs().isEmpty()) {
 			// load the organisations and all children
 			sb.and();
@@ -605,6 +624,10 @@ public class AnalysisFilterDAO {
 				query.setParameter(parameter, value);
 			}
 		}
+		if (searchParams.getContextCurriculumElementTypeRefs() != null && !searchParams.getContextCurriculumElementTypeRefs().isEmpty()) {
+			List<Long> keys = searchParams.getContextCurriculumElementTypeRefs().stream().map(CurriculumElementTypeRef::getKey).collect(toList());
+			query.setParameter("contextCurriculumElementTypeKeys", keys);
+		}
 		if (searchParams.getContextCurriculumOrganisationRefs() != null && !searchParams.getContextCurriculumOrganisationRefs().isEmpty()) {
 			for (int i = 0; i < searchParams.getContextCurriculumOrganisationRefs().size(); i++) {
 				String parameter = new StringBuilder(40).append("contextCurriculumOrganisationPath").append(i).toString();
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 20e0580a770..cf053e2985e 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
@@ -32,6 +32,7 @@ import org.olat.core.id.Organisation;
 import org.olat.modules.curriculum.Curriculum;
 import org.olat.modules.curriculum.CurriculumElement;
 import org.olat.modules.curriculum.CurriculumElementRef;
+import org.olat.modules.curriculum.CurriculumElementType;
 import org.olat.modules.curriculum.CurriculumRef;
 import org.olat.modules.curriculum.CurriculumService;
 import org.olat.modules.curriculum.model.CurriculumElementRefImpl;
@@ -218,6 +219,11 @@ public class QualityAnalysisServiceImpl implements QualityAnalysisService {
 		}
 		return elementsOfCurriculums;
 	}
+	
+	@Override
+	public List<CurriculumElementType> loadContextCurriculumElementTypes(AnalysisSearchParameter searchParams) {
+		return filterDao.loadContextCurriculumElementsTypes(searchParams);
+	}
 
 	@Override
 	public List<Organisation> loadContextCurriculumOrganisations(AnalysisSearchParameter searchParams, boolean withParents) {
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
index 3ecdbe9814b..4719e3f264b 100644
--- a/src/main/java/org/olat/modules/quality/analysis/ui/FilterController.java
+++ b/src/main/java/org/olat/modules/quality/analysis/ui/FilterController.java
@@ -51,6 +51,8 @@ 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.CurriculumElementType;
+import org.olat.modules.curriculum.CurriculumElementTypeRef;
 import org.olat.modules.curriculum.CurriculumModule;
 import org.olat.modules.curriculum.CurriculumRef;
 import org.olat.modules.curriculum.ui.CurriculumTreeModel;
@@ -92,6 +94,7 @@ public class FilterController extends FormBasicController {
 	private MultipleSelectionElement contextExecutorOrganisationEl;
 	private MultipleSelectionElement contextCurriculumEl;
 	private MultipleSelectionElement contextCurriculumElementEl;
+	private MultipleSelectionElement contextCurriculumElementTypeEl;
 	private MultipleSelectionElement contextCurriculumOrganisationEl;
 	private MultipleSelectionElement contextTaxonomyLevelEl;
 	private MultipleSelectionElement contextLocationEl;
@@ -167,6 +170,9 @@ public class FilterController extends FormBasicController {
 		contextCurriculumElementEl = uifactory.addCheckboxesDropdown("filter.context.curriculum.elements", formLayout);
 		contextCurriculumElementEl.addActionListener(FormEvent.ONCLICK);
 
+		contextCurriculumElementTypeEl = uifactory.addCheckboxesDropdown("filter.context.curriculum.element.types", formLayout);
+		contextCurriculumElementTypeEl.addActionListener(FormEvent.ONCLICK);
+
 		contextCurriculumOrganisationEl = uifactory.addCheckboxesDropdown("filter.context.curriculum.organisations", formLayout);
 		contextCurriculumOrganisationEl.addActionListener(FormEvent.ONCLICK);
 
@@ -199,6 +205,7 @@ public class FilterController extends FormBasicController {
 		setContextExecutorOrganisationValues();
 		setContextCurriculumValues();
 		setContextCurriculumElementValues();
+		setContextCurriculumElementTypeValues();
 		setContextCurriculumOrganisationValues();
 		setContextTaxonomyLevelValues();
 		setContextLocationValues();
@@ -263,7 +270,7 @@ public class FilterController extends FormBasicController {
 
 	private void setTopicCurriculumElementValues() {
 		if (!availableAttributes.isTopicCurriculumElement() || !curriculumModule.isEnabled()) {
-			contextCurriculumElementEl.setVisible(false);
+			topicCurriculumElementEl.setVisible(false);
 			return;
 		}
 
@@ -330,7 +337,6 @@ public class FilterController extends FormBasicController {
 
 		AnalysisSearchParameter curriculumSearchParams = searchParams.clone();
 		curriculumSearchParams.setContextCurriculumRefs(null);
-		curriculumSearchParams.setContextCurriculumElementRefs(null);
 		List<Curriculum> curriculums = analysisService.loadContextCurriculums(curriculumSearchParams);
 		KeysValues keysValues = QualityUIFactory.getCurriculumKeysValues(curriculums, null);
 		contextCurriculumEl.setKeysAndValues(keysValues.getKeys(), keysValues.getValues());
@@ -345,7 +351,7 @@ public class FilterController extends FormBasicController {
 			return;
 		}
 
-		Collection<String> selectedKeys = contextCurriculumEl.getSelectedKeys();
+		Collection<String> selectedKeys = contextCurriculumElementEl.getSelectedKeys();
 
 		AnalysisSearchParameter curriculumElementSearchParams = searchParams.clone();
 		Collection<String> curriculumKeys = contextCurriculumEl.isAtLeastSelected(1)
@@ -367,6 +373,32 @@ public class FilterController extends FormBasicController {
 		}
 	}
 
+	private void setContextCurriculumElementTypeValues() {
+		if (!availableAttributes.isContextCurriculumElementType() || !curriculumModule.isEnabled()) {
+			contextCurriculumElementTypeEl.setVisible(false);
+			return;
+		}
+
+		Collection<String> selectedKeys = contextCurriculumElementTypeEl.getSelectedKeys();
+
+		AnalysisSearchParameter clonedSearchParams = searchParams.clone();
+		Collection<String> curriculumKeys = contextCurriculumEl.isAtLeastSelected(1)
+				? contextCurriculumEl.getSelectedKeys()
+				: contextCurriculumEl.getKeys();
+		List<? extends CurriculumRef> curriculumRefs = curriculumKeys.stream()
+				.map(key -> QualityUIFactory.getCurriculumRef(key)).collect(toList());
+		clonedSearchParams.setContextCurriculumRefs(curriculumRefs);
+		clonedSearchParams.setContextCurriculumElementTypeRefs(null);
+		List<CurriculumElementType> curriculumElementTypes = analysisService
+				.loadContextCurriculumElementTypes(clonedSearchParams);
+
+		KeysValues keysValues = QualityUIFactory.getCurriculumElementTypeKeysValues(curriculumElementTypes);
+		contextCurriculumElementTypeEl.setKeysAndValues(keysValues.getKeys(), keysValues.getValues());
+		for (String key : selectedKeys) {
+			contextCurriculumElementEl.select(key, true);
+		}
+	}
+
 	private void setContextCurriculumOrganisationValues() {
 		if (!availableAttributes.isContextCurriculumOrganisation() || !organisationModule.isEnabled()
 				|| !curriculumModule.isEnabled()) {
@@ -380,6 +412,7 @@ public class FilterController extends FormBasicController {
 		searchParamsClone.setContextCurriculumOrganisationRefs(null);
 		searchParamsClone.setContextCurriculumRefs(null);
 		searchParamsClone.setContextCurriculumElementRefs(null);
+		searchParamsClone.setContextCurriculumElementTypeRefs(null);
 		List<Organisation> organisations = analysisService.loadContextCurriculumOrganisations(searchParamsClone, true);
 		OrganisationTreeModel organisationModel = new OrganisationTreeModel();
 		organisationModel.loadTreeModel(organisations);
@@ -521,6 +554,8 @@ public class FilterController extends FormBasicController {
 			doFiltered(ureq);
 		} else if (source == contextCurriculumElementEl) {
 			doFiltered(ureq);
+		} else if (source == contextCurriculumElementTypeEl) {
+			doFiltered(ureq);
 		} else if (source == contextCurriculumOrganisationEl) {
 			doFiltered(ureq);
 		} else if (source == contextTaxonomyLevelEl) {
@@ -554,6 +589,7 @@ public class FilterController extends FormBasicController {
 		getSearchParamContextExecutorOrganisations();
 		getSearchParamContextCurriculums();
 		getSearchParamContextCurriculumElements();
+		getSearchParamContextCurriculumElementTypes();
 		getSearchParamContextCurriculumOrganisations();
 		getSearchParamContextTaxonomyLevels();
 		getSearchParamContextLocations();
@@ -665,6 +701,16 @@ public class FilterController extends FormBasicController {
 		}
 	}
 	
+	private void getSearchParamContextCurriculumElementTypes() {
+		if (contextCurriculumElementTypeEl.isVisible() && contextCurriculumElementTypeEl.isAtLeastSelected(1)) {
+			List<CurriculumElementTypeRef> curriculumElementTypeRefs = contextCurriculumElementTypeEl.getSelectedKeys().stream()
+					.map(key -> QualityUIFactory.getCurriculumElementTypeRef(key)).collect(toList());
+			searchParams.setContextCurriculumElementTypeRefs(curriculumElementTypeRefs);
+		} else {
+			searchParams.setContextCurriculumElementTypeRefs(null);
+		}
+	}
+	
 	private void getSearchParamContextCurriculumOrganisations() {
 		if (contextCurriculumOrganisationEl.isVisible() && contextCurriculumOrganisationEl.isAtLeastSelected(1)) {
 			List<OrganisationRef> organisationRefs = contextCurriculumOrganisationEl.getSelectedKeys().stream()
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 34ddeefc55a..654262a7c17 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
@@ -8,6 +8,7 @@ analysis.table.form.created=Erstellt
 analysis.table.form.title=Fragebogen
 analysis.table.open=\u00D6ffnen
 analysis.table.participations.number=Teilnahmen
+filter.context.curriculum.element.types=Typ Curriculumelement
 filter.context.curriculum.elements=Curriculumelement
 filter.context.curriculum.organisations=Organisation des Curriculum
 filter.context.curriculums=Curriculum
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 1ac42ca04db..99949e590de 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
@@ -8,6 +8,7 @@ analysis.table.form.created=Created
 analysis.table.form.title=Questionnaire
 analysis.table.open=Open
 analysis.table.participations.number=Participations
+filter.context.curriculum.element.types=Type of curriculum element
 filter.context.curriculum.elements=Curriculum element
 filter.context.curriculum.organisations=Organisation of curriculum
 filter.context.curriculums=Curriculum
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 f0952b9b6b2..728bb09b468 100644
--- a/src/main/java/org/olat/modules/quality/ui/QualityUIFactory.java
+++ b/src/main/java/org/olat/modules/quality/ui/QualityUIFactory.java
@@ -54,9 +54,12 @@ import org.olat.core.util.nodes.INode;
 import org.olat.modules.curriculum.Curriculum;
 import org.olat.modules.curriculum.CurriculumElement;
 import org.olat.modules.curriculum.CurriculumElementRef;
+import org.olat.modules.curriculum.CurriculumElementType;
+import org.olat.modules.curriculum.CurriculumElementTypeRef;
 import org.olat.modules.curriculum.CurriculumModule;
 import org.olat.modules.curriculum.CurriculumRef;
 import org.olat.modules.curriculum.model.CurriculumElementRefImpl;
+import org.olat.modules.curriculum.model.CurriculumElementTypeRefImpl;
 import org.olat.modules.curriculum.model.CurriculumRefImpl;
 import org.olat.modules.curriculum.ui.CurriculumTreeModel;
 import org.olat.modules.quality.QualityDataCollectionTopicType;
@@ -264,6 +267,29 @@ public class QualityUIFactory {
 		return null;
 	}
 	
+	public static KeysValues getCurriculumElementTypeKeysValues(List<CurriculumElementType> types) {
+		String[] keys = new String[types.size()];
+		String[] values = new String[types.size()];
+		for (int i = types.size(); i-->0; ) {
+			CurriculumElementType type = types.get(i);
+			keys[i] = Long.toString(type.getKey());
+			values[i] = type.getDisplayName();
+		}
+		return new KeysValues(keys, values);
+	}
+	
+	public static CurriculumElementTypeRef getCurriculumElementTypeRef(String typeKey) {
+		if (StringHelper.containsNonWhitespace(typeKey)) {
+			try {
+				Long key = Long.valueOf(typeKey);
+				return new CurriculumElementTypeRefImpl(key);
+			} catch (Exception e) {
+				//
+			}
+		}
+		return null;
+	}
+	
 	public static KeysValues getOrganisationFlatKeysValues(List<Organisation> organisations, Organisation current) {
 		List<Organisation> orgs = new ArrayList<>(organisations);
 		if (current != null && !orgs.contains(current)) {
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
index 0f3f2c651d5..debdacc9b93 100644
--- a/src/test/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAOTest.java
+++ b/src/test/java/org/olat/modules/quality/analysis/manager/AnalysisFilterDAOTest.java
@@ -22,6 +22,7 @@ package org.olat.modules.quality.analysis.manager;
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.olat.modules.curriculum.CurriculumCalendars.disabled;
 import static org.olat.modules.quality.analysis.GroupBy.CONTEXT_CURRICULUM;
 import static org.olat.modules.quality.analysis.GroupBy.CONTEXT_ORGANISATION;
 import static org.olat.modules.quality.analysis.GroupBy.CONTEXT_TAXONOMY_LEVEL;
@@ -46,6 +47,7 @@ import org.olat.core.id.Organisation;
 import org.olat.modules.curriculum.Curriculum;
 import org.olat.modules.curriculum.CurriculumCalendars;
 import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumElementType;
 import org.olat.modules.curriculum.CurriculumService;
 import org.olat.modules.forms.EvaluationFormManager;
 import org.olat.modules.forms.EvaluationFormParticipation;
@@ -210,6 +212,7 @@ public class AnalysisFilterDAOTest extends OlatTestCase {
 		assertThat(attributes.isContextExecutorOrganisation()).isFalse();
 		assertThat(attributes.isContextCurriculum()).isFalse();
 		assertThat(attributes.isContextCurriculumElement()).isFalse();
+		assertThat(attributes.isContextCurriculumElementType()).isFalse();
 		assertThat(attributes.isContextCurriculumOrganisation()).isFalse();
 		assertThat(attributes.isContextTaxonomyLevel()).isFalse();
 		assertThat(attributes.isSeriesIndex()).isFalse();
@@ -359,6 +362,27 @@ public class AnalysisFilterDAOTest extends OlatTestCase {
 		assertThat(attributes.isContextCurriculumElement()).isTrue();
 	}
 
+	@Test
+	public void shouldGetAvailableAttributeForContextCurriculumElementType() {
+		QualityDataCollection dataCollection = createFinishedDataCollection();
+		Identity executor = JunitTestHelper.createAndPersistIdentityAsRndUser("");
+		CurriculumElement element = qualityTestHelper.createCurriculumElement();
+		CurriculumElementType type = curriculumService.createCurriculumElementType("t", "s", null, null);
+		element.setType(type);
+		element = curriculumService.updateCurriculumElement(element);
+		List<EvaluationFormParticipation> participations = qualityService.addParticipations(dataCollection,
+				singletonList(executor));
+		qualityService.createContextBuilder(dataCollection, participations.get(0))
+				.addCurriculumElement(element)
+				.build();
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		AvailableAttributes attributes = sut.getAvailableAttributes(searchParams);
+		
+		assertThat(attributes.isContextCurriculumElementType()).isTrue();
+	}
+
 	@Test
 	public void shouldGetAvailableAttributeForContextCurriculumOrganisation() {
 		QualityDataCollection dataCollection = createFinishedDataCollection();
@@ -768,6 +792,45 @@ public class AnalysisFilterDAOTest extends OlatTestCase {
 			.containsExactlyInAnyOrder(element1.getCurriculum().getKey(), element2.getCurriculum().getKey())
 			.doesNotContainNull();
 	}
+
+	@Test
+	public void shouldLoadDistinctContextCurriculumElementTypes() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		CurriculumElementType type1 = curriculumService.createCurriculumElementType("a", "b", null, null);
+		CurriculumElementType type2 = curriculumService.createCurriculumElementType("a", "b", null, null);
+		Curriculum curriculum = qualityTestHelper.createCurriculum();
+		CurriculumElement element1 = curriculumService.createCurriculumElement("", "", null, null, null, type1, disabled, curriculum);
+		CurriculumElement element2 = curriculumService.createCurriculumElement("", "", null, null, null, type2, disabled, curriculum);;
+		// Participation with curriculum element of type1
+		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 curriculum element of type2
+		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 of type1 (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<CurriculumElementType> filtered = sut.loadContextCurriculumElementsTypes(searchParams);
+		
+		assertThat(filtered)
+			.containsExactlyInAnyOrder(type1, type2)
+			.doesNotContainNull();
+	}
 	
 	@Test
 	public void shouldLoadDistinctContextOrganisationPath() {
@@ -1458,6 +1521,53 @@ public class AnalysisFilterDAOTest extends OlatTestCase {
 		assertThat(count).isEqualTo(expected);
 	}
 	
+	@Test
+	public void shouldFilterByContextCurriculumElementTypes() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor = JunitTestHelper.createAndPersistIdentityAsUser("");
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		CurriculumElementType type = curriculumService.createCurriculumElementType("a", "b", null, null);
+		CurriculumElementType typeOther = curriculumService.createCurriculumElementType("y", "z", null, null);
+		Curriculum curriculum = qualityTestHelper.createCurriculum();
+		CurriculumElement element1 = curriculumService.createCurriculumElement("", "", null, null, null, type, disabled, curriculum);
+		CurriculumElement element2 = curriculumService.createCurriculumElement("", "", null, null, null, type, disabled, curriculum);;
+		CurriculumElement elementNull = curriculumService.createCurriculumElement("", "", null, null, null, null, disabled, curriculum);
+		CurriculumElement elementOther = curriculumService.createCurriculumElement("", "", null, null, null, typeOther, disabled, curriculum);
+		// Participation with curriculum element of type
+		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 of type
+		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 other curriculum element  of other type
+		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(elementOther).build();
+		// Participation with curriculum element without type
+		QualityDataCollection dcTypeNull = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participationsTypeNull = qualityService.addParticipations(dcTypeNull, Collections.singletonList(executor));
+		QualityContextBuilder contextBuilderTypeNull = qualityService.createContextBuilder(dcTypeNull, participationsTypeNull.get(0));
+		contextBuilderTypeNull.addCurriculumElement(elementNull).build();
+		// Participation without curriculum
+		QualityDataCollection dcNull = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		qualityService.addParticipations(dcNull, Collections.singletonList(executor));
+		finish(asList(dc1, dc2, dcTypeNull, dcNull, dcOther));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		searchParams.setContextCurriculumElementTypeRefs(asList(type));
+		List<QualityDataCollection> dataCollections = sut.loadDataCollection(searchParams);
+		
+		assertThat(dataCollections)
+				.containsExactlyInAnyOrder(dc1, dc2)
+				.doesNotContain(dcTypeNull, dcNull, dcOther);
+	}
+	
 	@Test
 	public void shouldFilterByContextCurriculumOrganisations() {
 		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
-- 
GitLab