-
Notifications
You must be signed in to change notification settings - Fork 294
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
Conversation
…ionnaire response created by traversing through the questionnaire
… items 2. Input questionnaire Response has less items
…re items than the questionnaire
@jingtang10 please take a look at this. These two questions are dealt as follows:
|
…hub.com/google/android-fhir into kj/validation-fix-questionnaireResponse
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
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 high-level comments:
- Should the validation code live in
QuestionnaireResponseValidator.kt
instead? - 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.
- 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.
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
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. |
… removed the need to use questionnaire Response for validation.
…tionnaireItem.type
@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) |
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.
Nit: Prefer ==
in Kotlin
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.
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() |
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.
Nit: Would be a bit more performant to use
.flatMapIndexed { index, questionnaireItem ->
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.
done
questionnaireResponseInputItemList: | ||
List<QuestionnaireResponse.QuestionnaireResponseItemComponent>, | ||
) { | ||
val questionnaireResponseInputItemListIterator = questionnaireResponseInputItemList.iterator() |
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.
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) -> ... }
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.
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.
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 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)) { |
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.
Nit: I'd also do this via require
, and use ==
.
require(questionnaireItem.linkId == questionnaireResponseInputItem.linkId) { "Mismatching..." }
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.
done
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply { | ||
linkId = "a-link-id" | ||
answer = | ||
mutableListOf( |
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.
Does this need to be mutable?
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.
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( |
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.
Can the function signature be changed to take a questionnaire and a questionnaire response. This funciton itself can probably be a private helper function?
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.
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.
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.
done, I have made this function a private helper and changed the names of the validate functions.
questionnaireResponseInputItem.answer.forEachIndexed { | ||
index, |
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.
Nit: Looks like the index is not needed, could this become forEach
instead?
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.
Will take care of it in my next PR. Thanks!
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.
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. |
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
./gradlew spotlessApply
and./gradlew spotlessCheck
to check my code follows the style guide of this project./gradlew check
and./gradlew connectedCheck
to test my changes locally