-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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 forms with parse errors to be downloaded, show errors at form open time #6428
Conversation
258bbf7
to
1b75c7b
Compare
1b75c7b
to
2cbdd45
Compare
collect_app/src/main/java/org/odk/collect/android/formmanagement/metadata/FormMetadataParser.kt
Outdated
Show resolved
Hide resolved
collect_app/src/main/java/org/odk/collect/android/utilities/FileUtils.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function to get geometry XPath doesn't seem quite right. It may be helpful to add a test where there's a setvalue action AND a geopoint with a user interface and the action's target is after the point with a UI.
.../src/test/java/org/odk/collect/android/formmanagement/download/ServerFormDownloaderTest.java
Show resolved
Hide resolved
collect_app/src/test/java/org/odk/collect/android/formmanagement/FormMetadataParserTest.java
Show resolved
Hide resolved
collect_app/src/test/java/org/odk/collect/android/formmanagement/FormMetadataParserTest.java
Show resolved
Hide resolved
* so we need to make sure we've got those files in the right place before we parse. | ||
*/ | ||
@Test | ||
public void whenFormHasMediaFiles_downloadsAndSavesFormAndMediaFiles_beforeParsingForm() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not really redundant, right? It's more that there was some accidental interaction between between media files and metadata parsing that isn't there anymore and so doesn't need to be tested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that at one point, the order was important, and media files were considered during form parsing. However, this didn’t work as intended, as the parser ignored the media files (on current master). See my other comment: #6428 (comment)
So all in all we don't need it anymore.
@@ -192,7 +192,7 @@ class EntityFormTest { | |||
} | |||
|
|||
@Test | |||
fun manualEntityFormDownload_withUnsupportedSpecVersion_completesSuccessfully() { | |||
fun manualEntityFormDownload_withUnsupportedSpecVersion_completesSuccessfully_butThrowsAnErrorAfterOpeningIt() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need all three of these? I feel like we could maybe use our knowledge of the shared metadata parsing implementation and only test one of the paths given that these tests are so slow to run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about that and keeping just one test, but since I didn't really favor any option, I decided to add all three. For me, the time required to run these tests has never been a significant issue, as I don’t run them in bulk locally. I always use Firebase and get results in 4-5 minutes. Additionally, we have recently removed many tests from the regression package—see the last part: #6383 so I believe we have fewer tests overall. Generally, I think it’s better to have such tests, even if they take some time to run. However, as I mentioned, I was unsure about keeping or removing them, so I'm fine with keeping just one test if that’s your preference, just let me know.
val mainInstanceRoot = mainInstance.getElement(0) | ||
for (elementId in 0 until mainInstanceRoot.childCount) { | ||
val child = mainInstanceRoot.getElement(elementId) | ||
val ref = "/${mainInstanceRoot.name}/${child.name}" | ||
if (ref == firstTopLevelBodyGeoPoint) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal of this function is to only return the target of a setvalue action if it comes before a user-facing geopoint. I think returning here is right -- it stops the search. However, I think the value returned should be firstTopLevelBodyGeoPoint
so maybe there was a bug in the original implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal of this function is to only return the target of a setvalue action if it comes before a user-facing geopoint.
Hmm setgeopoint
action represents Geolocation at survey start right? It's the action triggered after opening a form so how could anything go before? Isn't it always the first geo element?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently we only exposed the setvalue
action triggered by the new instance event in XSLForm. You can also have setvalue
triggered by other events like value change. Neither is guaranteed to be in any specific order. In the case of a setvalue
triggered by value change, the action would be in the body.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to take a different approach on this one. When we had the processed form definition from JR we could do smarter things but I don’t think they’re practical in this context. Maybe we could do something like
- iterate through the binds, build a set of the refs that have type geopoint
- iterate through the primary instance, return the first ref that’s in the set of geopoints
That makes some assumptions that are not guaranteed by XForms, most notably that refs in the body are in the same order in the model. But in practice they usually are so I think it’s reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, we can do that. However, my question is: if the order isn't guaranteed, why check the primary instance rather than just the binds? I assume the order of the binds is even less predictable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so please take a look at the new implementation.
collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/InvalidFormTest.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of the naming tripped me up so I'd recommend updating it but otherwise this looks like what we agreed on. 👍
collect_app/src/main/java/org/odk/collect/android/formmanagement/metadata/FormMetadataParser.kt
Outdated
Show resolved
Hide resolved
collect_app/src/main/java/org/odk/collect/android/formmanagement/metadata/FormMetadataParser.kt
Outdated
Show resolved
Hide resolved
..._app/src/test/java/org/odk/collect/android/formmanagement/metadata/FormMetadataParserTest.kt
Show resolved
Hide resolved
a7a4bd9
to
df9d70e
Compare
Tested with Success! Verified on a device with Android 10 Verified cases:
|
Tested with Success Verified on device with Android 14 |
Closes #6385
Why is this the best possible solution? Were any other approaches considered?
As discussed in the issue, I have implemented a new form metadata parser to read the metadata without performing a full Javarosa parse. The most difficult and potentially risky part is how we read the geometry XPath. I think we should release another beta with these changes to ensure the parser doesn’t throw errors due to certain types of forms. We have tested many different forms, and everything is working well, so I’m hopeful that everything will be fine.
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?
I have introduced a new form parser that reads form metadata when a form is added to the database. This can occur after a manual download, a match exactly syncing, or syncing forms from disk after moving them to the forms directory manually (using ADB or a file manager).
Testing this new parser with various forms is crucial to ensure it functions correctly, so please try loading as many forms as possible.
Another change introduced by this pull request is that we no longer ignore broken forms after downloading. Previously, they were not displayed on the list. Now, they should be downloaded and shown to users, but an error will appear when attempting to open them. An example of this is using an entity form with an unsupported version.
Do we need any specific form for testing your changes? If so, please attach one.
No.
Does this change require updates to documentation? If so, please file an issue here and include the link below.
No.
Before submitting this PR, please make sure you have:
./gradlew connectedAndroidTest
(or./gradlew testLab
) and confirmed all checks still passDateFormatsTest