Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V3 next: 3.31.2 #1180

Merged
merged 4 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import org.sonarqube.gradle.SonarTask

plugins {
id("org.springframework.boot") version "3.4.0" apply false
id("io.spring.dependency-management") version "1.1.6" apply false
id("org.springframework.boot") version "3.4.1" apply false
id("io.spring.dependency-management") version "1.1.7" apply false
id("application")
id("jacoco-report-aggregation")
id("org.sonarqube") version "5.1.0.4882"
Expand All @@ -16,7 +16,7 @@ java {

allprojects {
group = "fr.insee.eno"
version = "3.31.1"
version = "3.31.2"
}

subprojects {
Expand All @@ -34,7 +34,7 @@ subprojects {

sonar {
properties {
// The Jacoco coverage report is aggreated in the eno-ws module
// The Jacoco coverage report is aggregated in the eno-ws module
val codeCoveragePath = "$projectDir/eno-ws/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml"
println("Aggregated code coverage report location:$codeCoveragePath")
property("sonar.coverage.jacoco.xmlReportPaths", codeCoveragePath)
Expand Down
2 changes: 1 addition & 1 deletion eno-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ tasks.named<Jar>("jar") {
}

val ddiJavaLibVersion = "1.1.0"
val modelMapperVersion = "3.2.1"
val modelMapperVersion = "3.2.2"

dependencies {
// DDI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private static void insertRowLevelControls(RoundaboutSequence roundaboutSequence
throw new MappingException("Cannot find loop '" + roundaboutSequence.getLoopReference() + "' " +
"referenced in roundabout sequence '" + roundaboutSequence.getId() + "'");
// Insert all the controls that are referenced in the loop in the roundabout sequence object
DDIMarkRowControls.controlReferencesStream(roundaboutLoop.get())
DDIMarkRowControls.getLoopControlReferences(roundaboutLoop.get(), enoQuestionnaire)
.forEach(itemReference -> roundaboutSequence.getControls().add(controlMap.get(itemReference.getId())));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package fr.insee.eno.core.processing.in.steps.ddi;

import fr.insee.eno.core.exceptions.technical.MappingException;
import fr.insee.eno.core.model.EnoQuestionnaire;
import fr.insee.eno.core.model.navigation.Control;
import fr.insee.eno.core.model.navigation.Filter;
import fr.insee.eno.core.model.navigation.Loop;
import fr.insee.eno.core.model.sequence.ItemReference;
import fr.insee.eno.core.processing.ProcessingStep;

import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -22,19 +25,61 @@ public class DDIMarkRowControls implements ProcessingStep<EnoQuestionnaire> {
public void apply(EnoQuestionnaire enoQuestionnaire) {
// index controls by id
Map<String, Control> controls = mapQuestionnaireControls(enoQuestionnaire);
// iterate on loop control references, to set the context of corresponding controls as "row"
enoQuestionnaire.getLoops().forEach(loop -> controlReferencesStream(loop)
.forEach(itemReference -> controls.get(itemReference.getId()).setContext(Control.Context.ROW))
);
// iterate on each loop controls and mark the as "row" controls
enoQuestionnaire.getLoops().forEach(loop ->
markControlsAsRow(getLoopControlReferences(loop, enoQuestionnaire), controls));
}

/**
* Method to index questionnaire's controls to get them easily when needed.
* @param enoQuestionnaire Eno questionnaire.
* @return A map of controls indexed by id.
*/
static Map<String, Control> mapQuestionnaireControls(EnoQuestionnaire enoQuestionnaire) {
return enoQuestionnaire.getControls().stream().collect(Collectors.toMap(Control::getId, control -> control));
}

static Stream<ItemReference> controlReferencesStream(Loop loop) {
/**
* Direct search method for filters, indexing seems not useful.
* @param enoQuestionnaire Eno questionnaire.
* @param filterId Identifier of a filter.
* @return The corresponding filter object.
* @throws MappingException if filter object is not found.
*/
private static Filter getFilterById(EnoQuestionnaire enoQuestionnaire, String filterId) {
return enoQuestionnaire.getFilters().stream()
.filter(filter -> filterId.equals(filter.getId()))
.findAny()
.orElseThrow(() -> new MappingException("Didn't find filter object with id " + filterId));
}

private static void markControlsAsRow(Stream<ItemReference> controlReferences, Map<String, Control> controls) {
controlReferences.forEach(itemReference -> controls.get(itemReference.getId()).setContext(Control.Context.ROW));
}

static Stream<ItemReference> getLoopControlReferences(Loop loop, EnoQuestionnaire enoQuestionnaire) {
// if the loop contains an occurrence filter, return the control references of that filter
Optional<ItemReference> filterReference = loop.getLoopItems().stream()
.filter(itemReference -> ItemReference.ItemType.FILTER.equals(itemReference.getType()))
.findAny();
if (filterReference.isPresent()) {
Filter filter = getFilterById(enoQuestionnaire, filterReference.get().getId());
return controlReferencesStream(filter);
}
// otherwise return the loop control references
return controlReferencesStream(loop);
}

// Note: we could add an interface over loop and filter for the items/scope properties

private static Stream<ItemReference> controlReferencesStream(Loop loop) {
return loop.getLoopItems().stream()
.filter(itemReference -> ItemReference.ItemType.CONTROL.equals(itemReference.getType()));
}

private static Stream<ItemReference> controlReferencesStream(Filter filter) {
return filter.getFilterItems().stream()
.filter(itemReference -> ItemReference.ItemType.CONTROL.equals(itemReference.getType()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,21 @@ void questionnaireWithRoundabout() throws DDIParsingException {
assertEquals(2, roundaboutSequence.getControls().size());
}

@Test
void questionnaireWithRoundaboutWithOccurrenceFilter() throws DDIParsingException {
//
EnoQuestionnaire enoQuestionnaire = new EnoQuestionnaire();
DDIMapper ddiMapper = new DDIMapper();
ddiMapper.mapDDI(
DDIDeserializer.deserialize(this.getClass().getClassLoader().getResourceAsStream(
"integration/ddi/ddi-roundabout-except.xml")),
enoQuestionnaire);

//
new DDIInsertControls().apply(enoQuestionnaire);

//
RoundaboutSequence roundaboutSequence = enoQuestionnaire.getRoundaboutSequences().getFirst();
assertEquals(1, roundaboutSequence.getControls().size());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,26 @@ void roundaboutLevelControls() throws DDIParsingException {
));
}

/** When a roundabout has an occurrence filter, it has an intermediate reference to a filter object that references
* the controls that belong to the loop. */
@Test
void roundaboutLevelControlWithFilter() throws DDIParsingException {
// Given
EnoQuestionnaire enoQuestionnaire = new EnoQuestionnaire();
DDIInstanceDocument ddiInstance = DDIDeserializer.deserialize(
this.getClass().getClassLoader().getResourceAsStream(
"integration/ddi/ddi-roundabout-except.xml"));
//
DDIMapper ddiMapper = new DDIMapper();
ddiMapper.mapDDI(ddiInstance, enoQuestionnaire);

// When
new DDIMarkRowControls().apply(enoQuestionnaire);

// Then
// The questionnaire should have two controls (that are created for a roundabout)
assertEquals(1, enoQuestionnaire.getControls().size());
assertEquals(Control.Context.ROW, enoQuestionnaire.getControls().getFirst().getContext());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,50 @@ void occurrenceControlTest() {
}
}

@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class RoundaboutWithExcept {
private Roundabout roundabout;

@BeforeAll
void ddiToLunatic() throws DDIParsingException {
//
EnoParameters parameters = EnoParameters.of(
EnoParameters.Context.HOUSEHOLD, EnoParameters.ModeParameter.CAWI, Format.LUNATIC);
//
Questionnaire lunaticQuestionnaire = DDIToLunatic.transform(
this.getClass().getClassLoader().getResourceAsStream(
"integration/ddi/ddi-roundabout-except.xml"),
parameters);
// the questionnaire should have 1 roundabout component
List<Roundabout> roundabouts = lunaticQuestionnaire.getComponents().stream()
.filter(Roundabout.class::isInstance).map(Roundabout.class::cast)
.toList();
assertEquals(1, roundabouts.size());
roundabout = roundabouts.getFirst();
}

/** The "except" field in Pogues corresponds to the "disabled" expression in Lunatic. */
@Test
void disabledCondition() {
assertEquals("not(not(Q1 = \"foo\"))", roundabout.getItem().getDisabled().getValue());
assertEquals(LabelTypeEnum.VTL, roundabout.getItem().getDisabled().getType());
}

/** The DDI modeling describes both disabled condition and occurrence-level controls as control objects,
* hence this test. */
@Test
void occurrenceControl_shouldBePresent() {
Optional<ControlType> rowControl = roundabout.getControls().stream()
.filter(control -> ControlContextType.ROW.equals(control.getType())).findAny();
assertTrue(rowControl.isPresent());
assertEquals(ControlCriticalityEnum.INFO, rowControl.get().getCriticality());
assertEquals("not(true)", rowControl.get().getControl().getValue());
assertEquals(LabelTypeEnum.VTL, rowControl.get().getControl().getType());
assertEquals("\"This control should always be displayed.\"",
rowControl.get().getErrorMessage().getValue());
assertEquals(LabelTypeEnum.VTL_MD, rowControl.get().getErrorMessage().getType());
}
}

}
Loading
Loading