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

Change validateQuestionnaireResponseItems to allow questionnaire response with missing items #926

Merged
merged 38 commits into from
Dec 15, 2021

Conversation

joiskash
Copy link
Collaborator

@joiskash joiskash commented Nov 17, 2021

IMPORTANT: All PRs must be linked to an issue (except for extremely trivial and straightforward changes).

Fixes #912 #836

Description
Re-wrote validateQuestionnaireResponseItems to take the input questionnaire response and validate it against the questionnaire response that is created by using the questionnaire.

Alternative(s) considered
Yes the alternative was to use the input questionnaire response as the main questionnaire response and add items if and when enable was activated. This seemed to be a more complicated solution

Type
Bug fix

Screenshots (if applicable)

Checklist

  • I have read and acknowledged the Code of conduct
  • I have read How to Contribute
  • I have read the Developer's guide
  • I have signed the Google Individual CLA, or I am covered by my company's Corporate CLA
  • I have discussed my proposed solution with code owners in the linked issue(s) and we have agreed upon the general approach
  • I have run ./gradlew spotlessApply and ./gradlew spotlessCheck to check my code follows the style guide of this project
  • I have run ./gradlew check and ./gradlew connectedCheck to test my changes locally
  • I have built and run the reference app(s) to verify my change fixes the issue and/or does not break the reference app(s)

…ionnaire response created by traversing through the questionnaire
… items 2. Input questionnaire Response has less items
@joiskash
Copy link
Collaborator Author

@jingtang10 please take a look at this. These two questions are dealt as follows:

  1. How do we deal with a questionnaire response that has more items than the questionnaire? Currently throwing an error No matching questionnaire item for questionnaire response item
  2. How do we deal with a questionnaire response that has less items than the questionnaire? Not throwing any error because questionnaire response may not have disabled items.

@joiskash joiskash changed the title Change validateQuestionnaireResponseItems to allow questionnaire repsponse with missing items Change validateQuestionnaireResponseItems to allow questionnaire response with missing items Nov 18, 2021
@joiskash joiskash self-assigned this Nov 18, 2021
@joiskash joiskash marked this pull request as draft November 18, 2021 09:07
@codecov
Copy link

codecov bot commented Nov 18, 2021

Codecov Report

Merging #926 (2a6bf28) into master (9b53185) will decrease coverage by 0.00%.
The diff coverage is 94.82%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #926      +/-   ##
============================================
- Coverage     88.27%   88.27%   -0.01%     
- Complexity      496      500       +4     
============================================
  Files           113      113              
  Lines          9365     9390      +25     
  Branches        649      654       +5     
============================================
+ Hits           8267     8289      +22     
- Misses          750      751       +1     
- Partials        348      350       +2     
Impacted Files Coverage Δ
...android/fhir/datacapture/QuestionnaireViewModel.kt 81.46% <90.00%> (-1.96%) ⬇️
...pture/validation/QuestionnaireResponseValidator.kt 95.23% <95.83%> (+1.12%) ⬆️
...va/com/google/android/fhir/db/impl/DatabaseImpl.kt 88.88% <0.00%> (-1.24%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9b53185...2a6bf28. Read the comment docs.

@joiskash joiskash marked this pull request as ready for review November 18, 2021 12:44
Copy link
Collaborator

@jingtang10 jingtang10 left a comment

Choose a reason for hiding this comment

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

Some high-level comments:

  1. Should the validation code live inQuestionnaireResponseValidator.kt instead?
  2. I think there are two things being done here in the validation code in the view model, one is to validate that the questionnaire response matches the questionnaire, and two is create the matching questionnaire response item for each questionnaire item. I think it probably would be more useful to separate these two processes.
  3. Given that we are now allowing for missing questionnaire response items, should we allow for that? This would mean that when we're generating the view items in the recycler view, we might have no response item in the tuple.

@joiskash
Copy link
Collaborator Author

Should the validation code live in QuestionnaireResponseValidator.kt instead?
That class is responsible for validating answers that are typed in via UI. I dont think we should mix them up. Maybe we change the name of this function to checkQuestionnaireResponseAgainstQuestionnaire

This function is independent of creating the questionnaire response item . What I have done is to use the questionnaire response item that is created with the questionnaire and only fill up answers from the input questionnaire response after verifying that

  1. linkIDs match and order of questions is maintained
  2. type matches for the primitive types
  3. only questionnaire items which allow repeats allow multiple answers
  4. There are no extra questionnaire response items

Since we are not using the input questionnaire response as the main questionnaire response to build view items, we will not be generating view items without response in the tuple. This is to make sure that irrespective of the items present in the input questionnaire response, the SDK will allow the user to answer the questionnaire completely.

PS: input questionnaire response is the one that is passed to the fragment via the bundle. The main questionnaire response is what we create by going through the questionnaire hierarchy.

@jingtang10
Copy link
Collaborator

@kevinmost can you please also take a look at this? thanks!

// If there is an enabled questionnaire response available then we use that. Or else we
// just use an empty questionnaireResponse Item
if (responseIndex < questionnaireResponseItemList.size &&
questionnaireItem.linkId.equals(questionnaireResponseItem.linkId)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Prefer == in Kotlin

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@@ -248,14 +249,22 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
pagination: QuestionnairePagination?,
): QuestionnaireState {
// TODO(kmost): validate pages before switching between next/prev pages
var responseIndex = 0
val items: List<QuestionnaireItemViewItem> =
questionnaireItemList
.asSequence()
.withIndex()
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Would be a bit more performant to use

.flatMapIndexed { index, questionnaireItem ->

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

questionnaireResponseInputItemList:
List<QuestionnaireResponse.QuestionnaireResponseItemComponent>,
) {
val questionnaireResponseInputItemListIterator = questionnaireResponseInputItemList.iterator()
Copy link
Contributor

Choose a reason for hiding this comment

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

If these two need to have the same length (which it looks like they do, as we throw an error if they don't on line 137), I would recommend using zip and doing something like this:

require(questionnaireItemList.size == questionnaireResponseInputItemList.size) {
  "Unequal item amounts: ${questionnaireItemList.size} items and ${questionnaireResponseInputItemList.size} responses"
}
questionnaireItemList.zip(questionnaireResponseInputItemList)
  .forEach { (questionnaireItem, questionnaireResponseInputItem) -> ... }

Copy link
Collaborator Author

@joiskash joiskash Dec 9, 2021

Choose a reason for hiding this comment

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

They don't have to be of the same length. The questionnaire response may not have some items . This can happen if the item was disabled / not answered. That's why i removed the zip from the previous implementation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The error we throw on line 137 is to make sure the questionnaire response does not have more items than the questionnaire. But the questionnaire response can have less number of items. Thats why I am iterating over questionnaireResponseList and using the index to fetch the corresponding questionnaire item. You will find more details regarding this in #836 . Please let me know if it does not make sense.

val questionnaireResponseInputItem = questionnaireResponseInputItemListIterator.next()
if (questionnaireItemListIterator.hasNext()) {
val questionnaireItem = questionnaireItemListIterator.next()
if (!questionnaireItem.linkId.equals(questionnaireResponseInputItem.linkId)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I'd also do this via require, and use ==.

require(questionnaireItem.linkId == questionnaireResponseInputItem.linkId) { "Mismatching..." }

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "a-link-id"
answer =
mutableListOf(
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to be mutable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nope , i made changed it to listOf :)

* items and check if the linkId of the matching pairs of questionnaire item and questionnaire
* response item are equal.
*/
fun validateQuestionnaireResponseItems(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can the function signature be changed to take a questionnaire and a questionnaire response. This funciton itself can probably be a private helper function?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Additionally, how does this function play with the validate function above? I kinda feel at some point we should provide a unified validation API.

Before that though, we should probably change the naming so that it's clear this is a structural validation, and the above function is a validation of answers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done, I have made this function a private helper and changed the names of the validate functions.

Comment on lines +111 to +112
questionnaireResponseInputItem.answer.forEachIndexed {
index,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Looks like the index is not needed, could this become forEach instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Will take care of it in my next PR. Thanks!

Copy link
Collaborator

@jingtang10 jingtang10 left a comment

Choose a reason for hiding this comment

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

Thanks @joiskash!

I think with this change, we should move a lot of the test cases from view model to the validation package. But that can be done in a follow-up.

@jingtang10 jingtang10 enabled auto-merge (squash) December 15, 2021 15:22
@joiskash
Copy link
Collaborator Author

Thanks @joiskash!

I think with this change, we should move a lot of the test cases from view model to the validation package. But that can be done in a follow-up.

Yes absolutely! That shouldn't be too hard to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:bug Something isn't working
Projects
Archived in project
3 participants