Skip to content

Commit

Permalink
fix: roundabout with except (#1179)
Browse files Browse the repository at this point in the history
* test: add test questionnaire

* fix: roundabout with occurrence filter and control

* chore: version 3.31.2
  • Loading branch information
nsenave authored Dec 24, 2024
1 parent f3e8f8b commit 65924fe
Show file tree
Hide file tree
Showing 8 changed files with 1,487 additions and 8 deletions.
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
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

0 comments on commit 65924fe

Please sign in to comment.