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

Allow fields in group to trigger action outside #671

Merged
merged 4 commits into from
Jul 15, 2022

Conversation

lognaturel
Copy link
Member

@lognaturel lognaturel commented Apr 11, 2022

Closes #670

What has been done to verify that this works as intended?

Tests.

Why is this the best possible solution? Were any other approaches considered?

I briefly looked for a way to address groups separately from repeats. However, as far as I know, there’s no way to know whether a step in a reference is repeatable.

Removing the check for a predicate is not necessary to solve the problem but once I understood the code it felt like something low risk I wanted to change so we get good error messages in the case of a target ref that isn’t fully qualified (targeting a repeated field).

I spent a little bit of time trying to get full test coverage for processAction but I didn't want to spend too much time on it. I think FormInstanceParser.verifyActions fails in so many repeat-related cases that we might not be able to get to processAction with the cases that are not tested.

How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?

it should fix the limitation and offer a better error when targeting a repeated field.

The biggest risk would be for an action to now target the wrong reference. I’ve spent a lot of time thinking about how this change could lead to that and haven’t come up with anything.

I did discover that the following test leads to a StackOverflow now whereas it just would have failed the parenting test before. That's what happens when a ref with a predicate is expanded using itself as context (and it's always been that way as far as I can tell). Hopefully the exception means we hear about it if someone tries to do it so I don't think it's so bad.

   @Test
    public void parallelRepeats() throws IOException {
        Scenario scenario = Scenario.init("Parallel repeats", html(
            head(
                title("Parallel repeats"),
                model(
                    mainInstance(t("data id=\"parallel-repeats\"",
                        t("repeat1",
                            t("source")),
                        t("repeat2",
                            t("destination")
                        )
                    ))
                )
            ),
            body(
                repeat("/data/repeat1",
                    input("/data/repeat1/source",
                        setvalue("xforms-value-changed", "/data/repeat2[position()=position(current()/..)]/destination", "/data/repeat1/source"))
                ),
                repeat("/data/repeat2",
                    input("/data/repeat2/destination")
                ))));

        scenario.createNewRepeat("/data/repeat1");
        scenario.createNewRepeat("/data/repeat1");
        scenario.createNewRepeat("/data/repeat1");

        scenario.createNewRepeat("/data/repeat2");
        scenario.createNewRepeat("/data/repeat2");
        scenario.createNewRepeat("/data/repeat2");

        scenario.answer("/data/repeat1[0]/source", "foo");
    }

Do we need any specific form for testing your changes? If so, please attach one.

See tests.

Does this change require updates to documentation? If so, please file an issue here and include the link below.

no.

Quick way to keep predicates with position().
Given the comment, I believe this restriction was put in to guard against repeat insert events triggering actions elsewhere. I wasn't able to come up with a problematic case without the check.
}
}

//TODO: either the target or the value's node might not exist here, catch and throw reasonably
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, this can never happen. In fact, as I wrote elsewhere, I think FormInstanceParser.verifyActions is being too restrictive at parse time.

if (references.size() == 0) {
// If after finding our concrete reference it is a template, this action is outside of the
// scope of the current target, so we can leave.
if (model.getMainInstance().hasTemplatePath(target)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been unable to come up with a form that would result in either an expanded reference set of size 0 or a reference with a template path. It feels safer to leave these cases in for now, though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For 0 refs, I believe FormInstanceParser.verifyActions makes that impossible.

TODO: try a predicate that does lead to a node being found at parse time but not at event time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can get to the references.size() == 0 and then hasTemplatePath code path here if I comment out the contents of verifyActions and run the following:

   @Test
    public void foo() throws IOException {
        Scenario scenario = Scenario.init("Setvalue into repeat", html(
            head(
                title("Setvalue into repeat"),
                model(
                    mainInstance(t("data id=\"setvalue-into-repeat\"",
                        t("dest_index", "1"),
                        t("source"),
                        t("repeat",
                            t("destination")
                        )
                    ))
                )
            ),
            body(
                input("/data/dest_index"),
                input("/data/source",
                    setvalue("xforms-value-changed", "/data/repeat[position()=/data/dest_index]/destination", "/data/source")),
                repeat("/data/repeat",
                    input("/data/repeat/destination")
                ))));

        scenario.createNewRepeat("/data/repeat");
        scenario.createNewRepeat("/data/repeat");
        scenario.createNewRepeat("/data/repeat");

        scenario.answer("/data/source", "foo");
        assertThat(scenario.answerOf("/data/repeat[0]/destination").getDisplayText(), is("foo"));

        scenario.answer("/data/dest_index", "7");
        scenario.answer("/data/source", "bar");
    }

Again, I think verifyActions is doing more than it should be, but it makes sense to me to wait for a user to report.

@@ -113,18 +94,14 @@ public TreeReference processAction(FormDef model, TreeReference contextRef) {
//an unbound template, so see if such a reference could exist. Unfortunately this
//won't be included in the above walk if the template is nested, since only the
//top level template retains its subelement templates
if(model.getMainInstance().hasTemplatePath(target)) {
if (model.getMainInstance().hasTemplatePath(target)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above -- I tried to come up with a form def that would lead to a null resolved reference and couldn't do it but feel it's safer to leave the case in for now.

@lognaturel lognaturel marked this pull request as ready for review July 14, 2022 05:39
@seadowg seadowg self-requested a review July 14, 2022 16:35
It makes sense to expand the reference and guard against a nodeset target. I see no reason to only make this check if the target reference has a predicate. I've added tests with and without predicates.
@lognaturel
Copy link
Member Author

@seadowg and I had a good chat about this. He's going to chew on it and see if he thinks the tests I added are meaningful and whether he can think of cases we should add.

Whether or not he gets feedback to me by then, I'll merge tomorrow and do a beta.

@seadowg
Copy link
Member

seadowg commented Jul 15, 2022

@lognaturel just a heads-up that I won't have a chance to review again today

Copy link
Member

@yanokwa yanokwa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving blindly

@lognaturel lognaturel merged commit aff5456 into getodk:master Jul 15, 2022
@lognaturel lognaturel deleted the setvalue-group branch July 15, 2022 16:25
@lognaturel
Copy link
Member Author

Merging so we can do the beta. @seadowg will take a look before we release!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Field in group can't trigger an action outside that group
3 participants