diff --git a/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java b/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java index de6875c2ade487535b5f9d59b0f87190436ec6a4..989b9a498d271c071e041c46bb3a34f01ac650d0 100644 --- a/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java +++ b/backend/src/main/java/fr/inra/urgi/faidare/web/study/StudyController.java @@ -4,9 +4,11 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.google.common.collect.Lists; import fr.inra.urgi.faidare.api.NotFoundException; @@ -31,6 +33,7 @@ import org.apache.logging.log4j.util.Strings; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -71,7 +74,7 @@ public class StudyController { } @GetMapping("/{studyId}") - public ModelAndView get(@PathVariable("studyId") String studyId) { + public ModelAndView get(@PathVariable("studyId") String studyId, Locale locale) { StudyDetailVO study = studyRepository.getById(studyId); if (study == null) { @@ -83,7 +86,7 @@ public class StudyController { ); List<GermplasmVO> germplasms = getGermplasms(study); - List<ObservationVariableVO> variables = getVariables(study); + List<ObservationVariableVO> variables = getVariables(study, locale); List<TrialVO> trials = getTrials(study); LocationVO location = getLocation(study); @@ -139,14 +142,78 @@ public class StudyController { } } - private List<ObservationVariableVO> getVariables(StudyDetailVO study) { + private List<ObservationVariableVO> getVariables(StudyDetailVO study, Locale locale) { Set<String> variableIds = studyRepository.getVariableIds(study.getStudyDbId()); - return cropOntologyRepository.getVariableByIds(variableIds) - .stream() + List<ObservationVariableVO> variables = cropOntologyRepository.getVariableByIds(variableIds); + return filterVariablesForLocale(variables, locale) .sorted(Comparator.comparing(ObservationVariableVO::getObservationVariableDbId)) .collect(Collectors.toList()); } + /** + * Filter the variables by language. The principles are the following. First, the languages of the variables + * are normalized (to transform FRA into fr for example). + * Then, several cases are possible. + * + * If there is no variable with the requested language, then we find the reference language. + * The reference language is en if there is at least one variable with that language. + * The reference is the first non null language found if there is no variable with the en language. + * Then, we keep all the variables with the reference language (if any), and all the variables without language. + * + * If there is at least one variable with the requested language, then we keep all the variables + * with the requested language, and all the variables without language. + */ + private Stream<ObservationVariableVO> filterVariablesForLocale(List<ObservationVariableVO> variables, Locale locale) { + if (variables.isEmpty()) { + return variables.stream(); + } + + String requestedLanguage = locale.getLanguage(); + String referenceLanguage = findReferenceLanguage(requestedLanguage, variables); + + return variables.stream() + .filter(variable -> + referenceLanguage == null + || !StringUtils.hasText(variable.getLanguage()) + || normalizeLanguage(variable.getLanguage()).equals(referenceLanguage)); + } + + private String findReferenceLanguage(String requestedLanguage, List<ObservationVariableVO> variables) { + Set<String> normalizedVariableLanguages = + variables.stream() + .map(ObservationVariableVO::getLanguage) + .filter(StringUtils::hasText) + .map(this::normalizeLanguage) + .collect(Collectors.toSet()); + + String referenceLanguage = null; + if (normalizedVariableLanguages.contains(requestedLanguage)) { + referenceLanguage = requestedLanguage; + } else if (normalizedVariableLanguages.contains("en")) { + referenceLanguage = "en"; + } else if (!normalizedVariableLanguages.isEmpty()) { + referenceLanguage = normalizedVariableLanguages.iterator().next(); + } + return referenceLanguage; + } + + private String normalizeLanguage(String language) { + // this is a hack trying to accomodate for languages not bein standard in the data + String languageInLowerCase = language.toLowerCase(); + if (languageInLowerCase.length() == 3) { + switch (languageInLowerCase) { + case "fra": + return "fr"; + case "esp": + case "spa": + return "es"; + case "eng": + return "en"; + } + } + return languageInLowerCase; + } + private List<TrialVO> getTrials(StudyDetailVO study) { if (study.getTrialDbIds() == null || study.getTrialDbIds().isEmpty()) { return Collections.emptyList(); diff --git a/backend/src/test/java/fr/inra/urgi/faidare/web/study/StudyControllerTest.java b/backend/src/test/java/fr/inra/urgi/faidare/web/study/StudyControllerTest.java index a05ee9583fea4514c2b0e9b54265c371cd83812d..784f3cbcd28ac88caaceef3e6ff76b956b68d093 100644 --- a/backend/src/test/java/fr/inra/urgi/faidare/web/study/StudyControllerTest.java +++ b/backend/src/test/java/fr/inra/urgi/faidare/web/study/StudyControllerTest.java @@ -2,6 +2,7 @@ package fr.inra.urgi.faidare.web.study; import static fr.inra.urgi.faidare.web.Fixtures.createSite; import static fr.inra.urgi.faidare.web.Fixtures.htmlContent; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; @@ -12,6 +13,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Set; import fr.inra.urgi.faidare.config.FaidareProperties; @@ -21,6 +23,7 @@ import fr.inra.urgi.faidare.domain.data.TrialVO; import fr.inra.urgi.faidare.domain.data.germplasm.GermplasmVO; import fr.inra.urgi.faidare.domain.data.study.StudyDetailVO; import fr.inra.urgi.faidare.domain.data.study.StudySitemapVO; +import fr.inra.urgi.faidare.domain.data.variable.ObservationVariableVO; import fr.inra.urgi.faidare.domain.datadiscovery.data.DataSource; import fr.inra.urgi.faidare.domain.response.PaginatedList; import fr.inra.urgi.faidare.domain.xref.XRefDocumentSearchCriteria; @@ -37,6 +40,7 @@ import fr.inra.urgi.faidare.web.site.SiteController; import fr.inra.urgi.faidare.web.thymeleaf.CoordinatesDialect; import fr.inra.urgi.faidare.web.thymeleaf.FaidareDialect; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -45,6 +49,7 @@ import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; +import org.springframework.web.servlet.ModelAndView; /** * MVC tests for {@link StudyController} @@ -76,6 +81,9 @@ public class StudyControllerTest { @MockBean private LocationRepository mockLocationRepository; + @Autowired + private StudyController studyController; + private StudyDetailVO study; private GermplasmVO germplasm; private List<XRefDocumentVO> crossReferences; @@ -148,6 +156,78 @@ public class StudyControllerTest { .andExpect(status().isNotFound()); } + @Nested + class Variables { + @Test + void shouldFilterVariablesByLanguageWhenRequestedLanguageIsFound() throws Exception { + ObservationVariableVO variableWithEnglishLanguage = Fixtures.createVariable(); + variableWithEnglishLanguage.setLanguage("EN"); + + ObservationVariableVO variableWithFrenchLanguage = Fixtures.createVariable(); + variableWithFrenchLanguage.setLanguage("FRA"); + + ObservationVariableVO variableWithNoLanguage = Fixtures.createVariable(); + variableWithNoLanguage.setLanguage(null); + + when(mockCropOntologyRepository.getVariableByIds(any())).thenReturn( + Arrays.asList(variableWithEnglishLanguage, variableWithFrenchLanguage, variableWithNoLanguage) + ); + + ModelAndView modelAndView = mockMvc.perform(get("/studies/{id}", study.getStudyDbId()) + .locale(Locale.FRENCH)) + .andReturn() + .getModelAndView(); + StudyModel model = (StudyModel) modelAndView.getModel().get("model"); + assertThat(model.getVariables()).containsOnly(variableWithFrenchLanguage, variableWithNoLanguage); + } + + @Test + void shouldFilterVariablesByLanguageWhenRequestedLanguageIsNotFound() throws Exception { + ObservationVariableVO variableWithEnglishLanguage = Fixtures.createVariable(); + variableWithEnglishLanguage.setLanguage("EN"); + + ObservationVariableVO variableWithFrenchLanguage = Fixtures.createVariable(); + variableWithFrenchLanguage.setLanguage("FRA"); + + ObservationVariableVO variableWithNoLanguage = Fixtures.createVariable(); + variableWithNoLanguage.setLanguage(null); + + when(mockCropOntologyRepository.getVariableByIds(any())).thenReturn( + Arrays.asList(variableWithEnglishLanguage, variableWithFrenchLanguage, variableWithNoLanguage) + ); + + ModelAndView modelAndView = mockMvc.perform(get("/studies/{id}", study.getStudyDbId()) + .locale(Locale.CHINA)) + .andReturn() + .getModelAndView(); + StudyModel model = (StudyModel) modelAndView.getModel().get("model"); + assertThat(model.getVariables()).containsOnly(variableWithEnglishLanguage, variableWithNoLanguage); + } + + @Test + void shouldFilterVariablesByLanguageWhenRequestedLanguageIsNotFoundAndEnglishAbsent() throws Exception { + ObservationVariableVO variableWithSpanishLanguage = Fixtures.createVariable(); + variableWithSpanishLanguage.setLanguage("es"); + + ObservationVariableVO variableWithFrenchLanguage = Fixtures.createVariable(); + variableWithFrenchLanguage.setLanguage("FRA"); + + ObservationVariableVO variableWithNoLanguage = Fixtures.createVariable(); + variableWithNoLanguage.setLanguage(null); + + when(mockCropOntologyRepository.getVariableByIds(any())).thenReturn( + Arrays.asList(variableWithSpanishLanguage, variableWithFrenchLanguage, variableWithNoLanguage) + ); + + ModelAndView modelAndView = mockMvc.perform(get("/studies/{id}", study.getStudyDbId()) + .locale(Locale.CHINA)) + .andReturn() + .getModelAndView(); + StudyModel model = (StudyModel) modelAndView.getModel().get("model"); + assertThat(model.getVariables()).hasSize(2).contains(variableWithNoLanguage); + } + } + private void testSitemap(int index, String expectedContent) throws Exception { MvcResult mvcResult = mockMvc.perform(get("/faidare/studies/sitemap-" + index + ".txt") .contextPath("/faidare"))