From 1520ebd752c0597b3e44678cf1de7ac2ea48b78f Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Fri, 14 Dec 2018 16:38:13 +0100
Subject: [PATCH] OO-3778: Filter quality analysis by participation role

---
 .../analysis/AnalysisSearchParameter.java     | 13 ++++
 .../analysis/QualityAnalysisService.java      |  4 +-
 .../analysis/manager/AnalysisFilterDAO.java   | 22 ++++++-
 .../manager/QualityAnalysisServiceImpl.java   |  6 ++
 .../quality/analysis/ui/FilterController.java | 48 +++++++++++++++
 .../ui/_i18n/LocalStrings_de.properties       |  7 ++-
 .../ui/_i18n/LocalStrings_en.properties       |  5 ++
 .../manager/AnalysisFilterDAOTest.java        | 59 +++++++++++++++++++
 8 files changed, 161 insertions(+), 3 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 4cc960f3baf..1bdcac3d573 100644
--- a/src/main/java/org/olat/modules/quality/analysis/AnalysisSearchParameter.java
+++ b/src/main/java/org/olat/modules/quality/analysis/AnalysisSearchParameter.java
@@ -28,6 +28,7 @@ import org.olat.basesecurity.IdentityRef;
 import org.olat.core.id.OrganisationRef;
 import org.olat.modules.curriculum.CurriculumElementRef;
 import org.olat.modules.curriculum.CurriculumRef;
+import org.olat.modules.quality.QualityContextRole;
 import org.olat.modules.taxonomy.TaxonomyLevelRef;
 import org.olat.repository.RepositoryEntryRef;
 import org.olat.repository.model.RepositoryEntryRefImpl;
@@ -55,6 +56,7 @@ public class AnalysisSearchParameter {
 	private List<? extends OrganisationRef> contextCurriculumOrganisationRefs;
 	private List<? extends TaxonomyLevelRef> contextTaxonomyLevelRefs;
 	private Collection<Integer> seriesIndexes;
+	private Collection<QualityContextRole> contextRoles;
 	private boolean withUserInfosOnly;
 
 	public RepositoryEntryRef getFormEntryRef() {
@@ -178,6 +180,14 @@ public class AnalysisSearchParameter {
 		return seriesIndexes;
 	}
 
+	public Collection<QualityContextRole> getContextRoles() {
+		return contextRoles;
+	}
+
+	public void setContextRoles(Collection<QualityContextRole> contextRoles) {
+		this.contextRoles = contextRoles;
+	}
+
 	public void setSeriesIndexes(Collection<Integer> seriesIndexes) {
 		this.seriesIndexes = seriesIndexes;
 	}
@@ -225,6 +235,9 @@ public class AnalysisSearchParameter {
 		clone.seriesIndexes = this.seriesIndexes != null
 				? new ArrayList<>(this.seriesIndexes)
 				: null;
+		clone.contextRoles = this.contextRoles != null
+				? new ArrayList<>(this.contextRoles)
+				: null;
 		clone.withUserInfosOnly = this.withUserInfosOnly;
 		return clone;
 	}
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 c09c59f53b7..367e306d48f 100644
--- a/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java
+++ b/src/main/java/org/olat/modules/quality/analysis/QualityAnalysisService.java
@@ -28,6 +28,7 @@ import org.olat.modules.curriculum.Curriculum;
 import org.olat.modules.curriculum.CurriculumElement;
 import org.olat.modules.forms.SessionFilter;
 import org.olat.modules.forms.model.xml.Rubric;
+import org.olat.modules.quality.QualityContextRole;
 import org.olat.modules.quality.QualityDataCollection;
 import org.olat.modules.taxonomy.TaxonomyLevel;
 import org.olat.repository.RepositoryEntry;
@@ -80,6 +81,8 @@ public interface QualityAnalysisService {
 
 	public List<TaxonomyLevel> loadContextTaxonomyLevels(AnalysisSearchParameter searchParams, boolean withParents);
 
+	public List<QualityContextRole> loadContextRoles(AnalysisSearchParameter clonedSearchParams);
+
 	public List<QualityDataCollection> loadDataCollections(AnalysisSearchParameter searchParams);
 	
 	public Integer loadMaxSeriesIndex(AnalysisSearchParameter searchParams);
@@ -90,5 +93,4 @@ public interface QualityAnalysisService {
 			Collection<String> responseIdentifiers, Collection<Rubric> rubrics, MultiGroupBy multiGroupBy);
 
 	public boolean isInsufficient(Rubric rubric, Double avg);
-
 }
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 7469f02d65c..bd9b2c9ea22 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
@@ -36,6 +36,7 @@ import org.olat.modules.curriculum.Curriculum;
 import org.olat.modules.curriculum.CurriculumElement;
 import org.olat.modules.curriculum.CurriculumElementRef;
 import org.olat.modules.curriculum.CurriculumRef;
+import org.olat.modules.quality.QualityContextRole;
 import org.olat.modules.quality.QualityDataCollection;
 import org.olat.modules.quality.QualityDataCollectionLight;
 import org.olat.modules.quality.QualityDataCollectionStatus;
@@ -271,7 +272,20 @@ public class AnalysisFilterDAO {
 		return query.getResultList();
 	}
 	
-	public List<QualityDataCollection> loadDataCollection(AnalysisSearchParameter searchParams) {
+	List<QualityContextRole> loadContextRoles(AnalysisSearchParameter searchParams) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select distinct context.role");
+		appendFrom(sb, searchParams);
+		appendWhere(sb, searchParams);
+		sb.and().append("context.role is not null");
+		
+		TypedQuery<QualityContextRole> query = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), QualityContextRole.class);
+		appendParameters(query, searchParams);
+		return query.getResultList();
+	}
+	
+	List<QualityDataCollection> loadDataCollection(AnalysisSearchParameter searchParams) {
 		QueryBuilder sb = new QueryBuilder();
 		sb.append("select distinct collection");
 		appendFrom(sb, searchParams);
@@ -521,6 +535,9 @@ public class AnalysisFilterDAO {
 		if (searchParams.getSeriesIndexes() != null && !searchParams.getSeriesIndexes().isEmpty()) {
 			sb.and().append("survey.seriesIndex in :seriesIndexes");
 		}
+		if (searchParams.getContextRoles() != null && !searchParams.getContextRoles().isEmpty()) {
+			sb.and().append("context.role in :contextRoles");
+		}
 		if (searchParams.isWithUserInfosOnly()) {
 			sb.and();
 			sb.append("(");
@@ -607,6 +624,9 @@ public class AnalysisFilterDAO {
 		if (searchParams.getSeriesIndexes() != null && !searchParams.getSeriesIndexes().isEmpty()) {
 			query.setParameter("seriesIndexes", searchParams.getSeriesIndexes());
 		}
+		if (searchParams.getContextRoles() != null && !searchParams.getContextRoles().isEmpty()) {
+			query.setParameter("contextRoles", searchParams.getContextRoles());
+		}
 	}
 
 }
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 93b1e6aa200..20e0580a770 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
@@ -40,6 +40,7 @@ import org.olat.modules.forms.EvaluationFormManager;
 import org.olat.modules.forms.RubricRating;
 import org.olat.modules.forms.SessionFilter;
 import org.olat.modules.forms.model.xml.Rubric;
+import org.olat.modules.quality.QualityContextRole;
 import org.olat.modules.quality.QualityDataCollection;
 import org.olat.modules.quality.analysis.AnalysisPresentation;
 import org.olat.modules.quality.analysis.AnalysisPresentationRef;
@@ -241,6 +242,11 @@ public class QualityAnalysisServiceImpl implements QualityAnalysisService {
 		}	
 		return levels;
 	}
+	
+	@Override
+	public List<QualityContextRole> loadContextRoles(AnalysisSearchParameter searchParams) {
+		return filterDao.loadContextRoles(searchParams);
+	}
 
 	@Override
 	public List<QualityDataCollection> loadDataCollections(AnalysisSearchParameter searchParams) {
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 fbe17f31eea..3ecdbe9814b 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
@@ -57,6 +57,7 @@ import org.olat.modules.curriculum.ui.CurriculumTreeModel;
 import org.olat.modules.forms.model.xml.AbstractElement;
 import org.olat.modules.forms.model.xml.Form;
 import org.olat.modules.forms.model.xml.SessionInformations;
+import org.olat.modules.quality.QualityContextRole;
 import org.olat.modules.quality.analysis.AnalysisSearchParameter;
 import org.olat.modules.quality.analysis.AvailableAttributes;
 import org.olat.modules.quality.analysis.QualityAnalysisService;
@@ -95,6 +96,7 @@ public class FilterController extends FormBasicController {
 	private MultipleSelectionElement contextTaxonomyLevelEl;
 	private MultipleSelectionElement contextLocationEl;
 	private MultipleSelectionElement seriesIndexEl;
+	private MultipleSelectionElement contextRoleEl;
 	private MultipleSelectionElement withUserInformationsEl;
 
 	private final AnalysisSearchParameter searchParams;
@@ -176,6 +178,9 @@ public class FilterController extends FormBasicController {
 
 		seriesIndexEl = uifactory.addCheckboxesDropdown("filter.series.index", formLayout);
 		seriesIndexEl.addActionListener(FormEvent.ONCLICK);
+		
+		contextRoleEl = uifactory.addCheckboxesDropdown("filter.context.role", formLayout);
+		contextRoleEl.addActionListener(FormEvent.ONCLICK);
 
 		withUserInformationsEl = uifactory.addCheckboxesVertical("filter.with.user.informations.label", formLayout,
 				WITH_USER_INFOS_KEYS, translateAll(getTranslator(), WITH_USER_INFOS_KEYS), 1);
@@ -198,6 +203,7 @@ public class FilterController extends FormBasicController {
 		setContextTaxonomyLevelValues();
 		setContextLocationValues();
 		setSeriesIndexValues();
+		setContextRoleValues();
 	}
 
 	private void setTopicIdentityValues() {
@@ -465,6 +471,34 @@ public class FilterController extends FormBasicController {
 		}
 	}
 
+	private void setContextRoleValues() {
+		Collection<String> selectedKeys = contextRoleEl.getSelectedKeys();
+
+		AnalysisSearchParameter clonedSearchParams = searchParams.clone();
+		clonedSearchParams.setContextRoles(null);
+		List<QualityContextRole> roles = analysisService.loadContextRoles(clonedSearchParams);
+		KeyValues kv = new KeyValues();
+		for (QualityContextRole role: QualityContextRole.values()) {
+			if (roles.contains(role)) {
+				kv.add(entry(role.name(), translateRole(role)));
+			}
+		}
+		contextRoleEl.setKeysAndValues(kv.keys(), kv.values());
+		for (String key : selectedKeys) {
+			contextRoleEl.select(key, true);
+		}
+	}
+
+	private String translateRole(QualityContextRole role) {
+		switch (role) {
+		case owner: return getTranslator().translate("filter.context.role.owner");
+		case coach: return getTranslator().translate("filter.context.role.coach");
+		case participant: return getTranslator().translate("filter.context.role.participant");
+		case none: return getTranslator().translate("filter.context.role.none");
+		default: return role.toString();
+		}
+	}
+
 	@Override
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
 		if (source == dateRangeFromEl) {
@@ -495,6 +529,8 @@ public class FilterController extends FormBasicController {
 			doFiltered(ureq);
 		} else if (source == seriesIndexEl) {
 			doFiltered(ureq);
+		} else if (source == contextRoleEl) {
+			doFiltered(ureq);
 		} else if (source == withUserInformationsEl) {
 			doFiltered(ureq);
 		}
@@ -522,6 +558,7 @@ public class FilterController extends FormBasicController {
 		getSearchParamContextTaxonomyLevels();
 		getSearchParamContextLocations();
 		getSearchParamSeriesIndex();
+		getSearchParamContextRole();
 		getSearchParamWithUserInfosOnly();
 	}
 
@@ -666,6 +703,17 @@ public class FilterController extends FormBasicController {
 			searchParams.setSeriesIndexes(null);
 		}
 	}
+	
+	private void getSearchParamContextRole() {
+		if (contextRoleEl.isVisible() && contextRoleEl.isAtLeastSelected(1)) {
+			Collection<QualityContextRole> roles = contextRoleEl.getSelectedKeys().stream()
+					.map(QualityContextRole::valueOf)
+					.collect(toList());
+			searchParams.setContextRoles(roles);
+		} else {
+			searchParams.setContextRoles(null);
+		}
+	}
 
 	private void getSearchParamWithUserInfosOnly() {
 		boolean withUserInfosOnly = withUserInformationsEl.isVisible() && withUserInformationsEl.isAtLeastSelected(1);
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 cd55b360616..34ddeefc55a 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
@@ -13,14 +13,19 @@ filter.context.curriculum.organisations=Organisation des Curriculum
 filter.context.curriculums=Curriculum
 filter.context.location=Standort
 filter.context.organisations=Organisation des Teilnehmers
+filter.context.role.coach=$org.olat.modules.quality.ui\:participation.role.coach
+filter.context.role.none=Ohne Rolle
+filter.context.role.owner=$org.olat.modules.quality.ui\:participation.role.owner
+filter.context.role.participant=$org.olat.modules.quality.ui\:participation.role.participant
+filter.context.role=Rolle des Teilnehmers
 filter.context.taxonomy.level=Fachbereich
 filter.count=Anzahl Datenerhebungen
 filter.date.range.from=Datenerhebungen von
 filter.date.range.to=Datenerhebungen to
 filter.hide=Filter
 filter.panel.header=Filter
-filter.series.index=Serie
 filter.series.index.value=Datenerhebung {0}
+filter.series.index=Serie
 filter.show=Filter
 filter.topic.curriculum.elements=Beurteilungsgegenstand Curriculumelement
 filter.topic.curriculums=Beurteilungsgegenstand 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 05a12fe28f3..1ac42ca04db 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
@@ -13,6 +13,11 @@ filter.context.curriculum.organisations=Organisation of curriculum
 filter.context.curriculums=Curriculum
 filter.context.location=Location
 filter.context.organisations=Organisation of participant
+filter.context.role.coach=$org.olat.modules.quality.ui\:participation.role.coach
+filter.context.role.none=Without role
+filter.context.role.owner=$org.olat.modules.quality.ui\:participation.role.owner
+filter.context.role.participant=$org.olat.modules.quality.ui\:participation.role.participant
+filter.context.role=Role of the participant
 filter.context.taxonomy.level=Subject
 filter.count=Number of data collections
 filter.date.range.from=Data collections from
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 4b5be11c0b0..0f3f2c651d5 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
@@ -38,6 +38,7 @@ import java.util.UUID;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.olat.basesecurity.GroupRoles;
 import org.olat.basesecurity.OrganisationService;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.Identity;
@@ -50,6 +51,7 @@ import org.olat.modules.forms.EvaluationFormManager;
 import org.olat.modules.forms.EvaluationFormParticipation;
 import org.olat.modules.forms.EvaluationFormSession;
 import org.olat.modules.quality.QualityContextBuilder;
+import org.olat.modules.quality.QualityContextRole;
 import org.olat.modules.quality.QualityDataCollection;
 import org.olat.modules.quality.QualityDataCollectionStatus;
 import org.olat.modules.quality.QualityService;
@@ -567,6 +569,31 @@ public class AnalysisFilterDAOTest extends OlatTestCase {
 				.containsExactlyInAnyOrder(entry1.getKey(), entry2.getKey());
 	}
 	
+	@Test
+	public void shouldLoadDistinctContextRoles() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		RepositoryEntry course = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor1 = JunitTestHelper.createAndPersistIdentityAsUser("1");
+		Identity executor2 = JunitTestHelper.createAndPersistIdentityAsUser("2");
+		Identity executor3 = JunitTestHelper.createAndPersistIdentityAsUser("3");
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations1 = qualityService.addParticipations(dc1, asList(executor1, executor2, executor3));
+		qualityService.createContextBuilder(dc1, participations1.get(0), course, GroupRoles.coach).build();
+		qualityService.createContextBuilder(dc1, participations1.get(1), course, GroupRoles.coach).build();
+		qualityService.createContextBuilder(dc1, participations1.get(2)).build();
+		finish(asList(dc1));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		List<QualityContextRole> roles = sut.loadContextRoles(searchParams);
+		
+		assertThat(roles)
+				.doesNotContainNull()
+				.containsExactlyInAnyOrder(QualityContextRole.coach, QualityContextRole.none)
+				.doesNotContain(QualityContextRole.participant, QualityContextRole.owner);
+	}
+	
 	@Test
 	public void shouldLoadDistinctContextLocation() {
 		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
@@ -1273,6 +1300,38 @@ public class AnalysisFilterDAOTest extends OlatTestCase {
 		assertThat(count).isEqualTo(expected);
 	}
 	
+	@Test
+	public void shouldFilterByContextRole() {
+		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		RepositoryEntry course = JunitTestHelper.createAndPersistRepositoryEntry();
+		Identity executor = JunitTestHelper.createAndPersistIdentityAsUser("1");
+		Organisation dcOrganisation = qualityTestHelper.createOrganisation();
+		// Participation as coach
+		QualityDataCollection dc1 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations1 = qualityService.addParticipations(dc1, Collections.singletonList(executor));
+		qualityService.createContextBuilder(dc1, participations1.get(0), course, GroupRoles.coach).build();
+		// Participation as coach again
+		QualityDataCollection dc2 = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participations2 = qualityService.addParticipations(dc2, Collections.singletonList(executor));
+		qualityService.createContextBuilder(dc2, participations2.get(0), course, GroupRoles.coach).build();
+		// Participation as owner
+		QualityDataCollection dcOther = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		List<EvaluationFormParticipation> participationsOther = qualityService.addParticipations(dcOther, Collections.singletonList(executor));
+		qualityService.createContextBuilder(dcOther, participationsOther.get(0), course, GroupRoles.owner).build();
+		// Data collection without participation
+		QualityDataCollection dcNull = qualityService.createDataCollection(asList(dcOrganisation), formEntry);
+		finish(asList(dc1, dc2, dcOther, dcNull));
+		dbInstance.commitAndCloseSession();
+		
+		AnalysisSearchParameter searchParams = new AnalysisSearchParameter();
+		searchParams.setContextRoles(asList(QualityContextRole.coach));
+		List<QualityDataCollection> collections = sut.loadDataCollection(searchParams);
+		
+		assertThat(collections)
+				.containsExactlyInAnyOrder(dc1, dc2)
+				.doesNotContain(dcOther);
+	}
+	
 	@Test
 	public void shouldFilterByContextOrganisations() {
 		RepositoryEntry formEntry = JunitTestHelper.createAndPersistRepositoryEntry();
-- 
GitLab