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