getodk/javarosa

DAG does not compute relevance for all repeats

lognaturel opened this issue · 3 comments

Software versions

JavaRosa v3.0.1

Problem description

The form posted as part of getodk/collect#4090 still prompts for adding egg_photos and light_source repeats even when their relevance expressions should evaluate to false.

Steps to reproduce the problem

Run the form from getodk/collect#4090, set a GPS coordinate, swipe forward.

Expected behavior

There should be no prompt to add repeats.

Other information

Troubleshooting notes. May or may not be related to #601.

I see that on initialization, all the other repeats' relevance are computed in evaluateTriggerable. The triggerables for egg_photos and light_source's relevances are not evaluated then.

Looks like it has to do with a repeat and a group sharing the same relevance condition. Minimal repro form.

The issue has to do with how relevance expressions (and all expressions) are reduced to a single Triggerable with a contextRef that represents the highest common parent between the expression's targets. For example, if /data/q0 and /data/repeat both have the same relevance expression, that will be represented by a single Triggerable with a contextRef of /data/.

This really helps explain how folks ended up with the strange-looking code replaced in #581. I'm going to try again with a clear head tomorrow morning to get a solution that doesn't involve saving and recomputing the relevance expressions for each repeat. If anyone has good ideas in the mean time, please try them out!

Failing test:

   @Test
    public void whenRepeatAndTopLevelNodeHaveSameRelevanceExpression_andExpressionEvaluatesToFalse_repeatPromptIsSkipped() throws Exception {
        Scenario scenario = Scenario.init("Repeat relevance same as other", html(
            head(
                title("Repeat relevance same as other"),
                model(
                    mainInstance(t("data id=\"repeat_relevance_same_as_other\"",
                        t("selectYesNo", "no"),
                        t("repeat1",
                            t("q1")),
                        t("q0")
                    )),
                    bind("/data/q0").relevant("/data/selectYesNo = 'yes'"),
                    bind("/data/repeat1").relevant("/data/selectYesNo = 'yes'")
                ),
                body(
                    select1("/data/selectYesNo",
                        item("yes", "Yes"),
                        item("no", "No")),
                    input("/data/q0"),
                    repeat("/data/repeat1",
                        input("/data/repeat1/q1")
                    )
                ))));

        scenario.jumpToBeginningOfForm();
        scenario.next();
        int event = scenario.next();

        assertThat(event, is(FormEntryController.EVENT_END_OF_FORM));
    }

I've tried adding the following in TriggerableDag.evaluateTriggerable:

        // For targets that are repeats, update the template because it's used to determine whether there's a prompt or not
        // We can't do this to the contextRef because it is the highest common parent of the targets (see intersectContextWith)
        for (TreeReference target : toTrigger.getTargets()) {
            TreeElement template = mainInstance.getTemplate(target);
            if (template != null) {
                TreeReference templateRef = template.getRef();
                qualifiedTargets.add(templateRef);
            }
        }

It doesn't work with the test above but it does work if the order of the binds is swapped. This points to some real weirdness in originalContextRef and how it's used. @ggalmazor and I did a ton of simplifying and cleanup in this area of the code but TriggerableDag.evaluateTriggerable and Triggerable.apply are still mostly as-is and very confusing.

This makes me concerned that there may be other issues when the same expression is repeated multiple times. I want to add more tests for cases where intersectContextWith will be called, particularly that involve getTriggerablesAffectingAllInstances.

I can get my test to pass by adding the includeTemplates flag for the expandReference call in Triggerable.apply. But the ChildVaccination smoketest fails.

I'm going to revert #581. I think we've learned a lot here but there's not a lot to gain by the simplification and we're taking on too much risk.