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

Populate initial value from dropdown list for questionnaire items with type reference #2065

Merged
merged 27 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cc22d43
set the initial value using the initial expression extension for refe…
omarismail94 Jan 30, 2023
3870224
Allow developers to add custom search parameter to the engine. (#1778)
aditya-07 Feb 6, 2023
dfe0f8d
Show submit button in review mode. (#1826)
aditya-07 Feb 6, 2023
551209d
Fix for removing old indexes when resource is updated (#1840)
aditya-07 Feb 6, 2023
87d2f5a
Merge remote-tracking branch 'upstream/master' into answerExpression
omarismail94 Feb 6, 2023
a2e4cde
Merge remote-tracking branch 'upstream/master' into answerExpression
omarismail94 Feb 6, 2023
f137a90
Merge remote-tracking branch 'upstream/master' into answerExpression
omarismail94 Feb 16, 2023
4d2c62a
add datacapture config in demo app
omarismail94 Feb 16, 2023
75bc566
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jun 26, 2023
4886ba2
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jun 27, 2023
2a32685
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jun 29, 2023
b6283f8
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jul 4, 2023
2f76ca3
Fix dropdown initial selection for answerOptions of type Reference
LZRS Jul 5, 2023
8700742
Support expression evaluating to ResourceType for items of type Refer…
LZRS Jul 5, 2023
08c0a0a
Merge branch 'master' into answerExpression
LZRS Jul 11, 2023
1639a7d
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jul 18, 2023
bdd7363
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jul 24, 2023
6de72b7
Update autocompleteTextView to check answerId
LZRS Jul 24, 2023
4674086
Add AutoCompleteViewHolderFactory ui tests
LZRS Aug 2, 2023
7f39ec3
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Aug 2, 2023
f38e6f3
Fix failing tests for AutoCompleteViewHolderFactory
LZRS Aug 2, 2023
4fed0f7
Merge branch 'master' into answerExpression
LZRS Aug 10, 2023
f214488
Refactor MoreTypes.kt to remove duplications
LZRS Aug 14, 2023
d4ab2a9
Refactor evaluation of questionnaireItem initial value
LZRS Aug 14, 2023
6b576ec
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Aug 14, 2023
84537e2
Fix AutoCompleteTextViewFactory espresso test failing in api 24
LZRS Aug 16, 2023
9d2d603
Merge branch 'master' into answerExpression
LZRS Aug 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.google.android.fhir.datacapture.test.views
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
Expand All @@ -35,15 +36,19 @@ import com.google.android.fhir.datacapture.test.TestActivity
import com.google.android.fhir.datacapture.test.utilities.showDropDown
import com.google.android.fhir.datacapture.validation.NotValidated
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
import com.google.android.fhir.datacapture.views.factories.DropDownAnswerOption
import com.google.android.fhir.datacapture.views.factories.DropDownViewHolderFactory
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolder
import com.google.android.material.textfield.MaterialAutoCompleteTextView
import com.google.common.truth.Truth.assertThat
import org.hamcrest.Matchers.instanceOf
import org.hamcrest.Matchers.`is`
import org.hl7.fhir.r4.model.Attachment
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Reference
import org.hl7.fhir.r4.model.StringType
import org.junit.Before
import org.junit.Rule
Expand Down Expand Up @@ -264,6 +269,53 @@ class DropDownViewHolderFactoryEspressoTest {
.isEqualTo(0)
}

@Test
fun shouldSetCorrectDropDownValueToAutoCompleteTextViewForDifferentAnswerOptionsWithSimilarDisplayString() {
val questionnaireItem =
Questionnaire.QuestionnaireItemComponent().apply {
addAnswerOption(
Questionnaire.QuestionnaireItemAnswerOptionComponent().apply {
value =
Reference().apply {
id = "ref_1"
display = "Reference"
}
}
)
addAnswerOption(
Questionnaire.QuestionnaireItemAnswerOptionComponent().apply {
value =
Reference().apply {
id = "ref_2"
display = "Reference"
}
}
)
}

var answerHolder: List<QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent>? = null
val questionnaireViewItem =
QuestionnaireViewItem(
questionnaireItem,
responseValueStringOptions(),
validationResult = NotValidated,
answersChangedCallback = { _, _, answers, _ -> answerHolder = answers }
)

runOnUI { viewHolder.bind(questionnaireViewItem) }

onView(withId(R.id.auto_complete)).perform(showDropDown())
onData(`is`(instanceOf(DropDownAnswerOption::class.java)))
.atPosition(2)
.inRoot(isPlatformPopup())
.perform(click())

assertThat(viewHolder.itemView.findViewById<TextView>(R.id.auto_complete).text.toString())
.isEqualTo("Reference")
assertThat((answerHolder!!.single().value as Reference).display).isEqualTo("Reference")
assertThat((answerHolder!!.single().value as Reference).id).isEqualTo("ref_2")
}

/** Method to run code snippet on UI/main thread */
private fun runOnUI(action: () -> Unit) {
activityScenarioRule.scenario.onActivity { action() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ fun Type.displayString(context: Context): String =
else -> context.getString(R.string.not_answered)
}

/**
LZRS marked this conversation as resolved.
Show resolved Hide resolved
* Returns the unique identifier of a [Type]. Used to differentiate between item answer options that
* may have similar display strings
*/
fun Type.identifierString(context: Context): String =
omarismail94 marked this conversation as resolved.
Show resolved Hide resolved
id
?: when {
this is Coding && this.code != null -> this.code
this is Reference && this.reference != null -> this.reference
else -> displayString(context)
}

/** Converts StringType to toUriType. */
internal fun StringType.toUriType(): UriType {
return UriType(value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.google.android.fhir.datacapture.mapping

import com.google.android.fhir.datacapture.DataCapture
import com.google.android.fhir.datacapture.extensions.createQuestionnaireResponseItem
import com.google.android.fhir.datacapture.extensions.logicalId
import com.google.android.fhir.datacapture.extensions.targetStructureMap
import com.google.android.fhir.datacapture.extensions.toCodeType
import com.google.android.fhir.datacapture.extensions.toCoding
Expand All @@ -28,6 +29,7 @@ import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.util.Locale
import org.hl7.fhir.exceptions.FHIRException
import org.hl7.fhir.r4.context.IWorkerContext
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Bundle
Expand All @@ -45,6 +47,7 @@ import org.hl7.fhir.r4.model.IntegerType
import org.hl7.fhir.r4.model.Parameters
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Reference
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.StringType
import org.hl7.fhir.r4.model.StructureDefinition
Expand Down Expand Up @@ -253,9 +256,17 @@ object ResourceMapper {
// Set initial value for the questionnaire item. Questionnaire items should not have both
// initial value and initial expression.
questionnaireItem.initial =
mutableListOf(
Questionnaire.QuestionnaireItemInitialComponent().setValue(it.asExpectedType())
)
when (questionnaireItem.type) {
omarismail94 marked this conversation as resolved.
Show resolved Hide resolved
Questionnaire.QuestionnaireItemType.REFERENCE ->
mutableListOf(
Questionnaire.QuestionnaireItemInitialComponent()
.setValue(it.asExpectedReferenceType())
)
else ->
mutableListOf(
Questionnaire.QuestionnaireItemInitialComponent().setValue(it.asExpectedType())
)
}
}

populateInitialValues(questionnaireItem.item, *resources)
Expand Down Expand Up @@ -766,6 +777,24 @@ private fun Base.asExpectedType(): Type {
}
}

private fun Base.asExpectedReferenceType(): Type {
return when {
this.isResource -> {
this@asExpectedReferenceType as Resource
Reference().apply {
reference =
"${this@asExpectedReferenceType.resourceType}/${this@asExpectedReferenceType.logicalId}"
}
}
this is IdType ->
Reference().apply {
reference =
"${this@asExpectedReferenceType.resourceType}/${this@asExpectedReferenceType.idPart}"
}
else -> throw FHIRException("Expression supplied does not evaluate to IdType.")
}
}

/**
* Returns a newly created [Resource] from the item extraction context extension if one and only one
* such extension exists in the questionnaire item, or null otherwise.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.extensions.displayString
import com.google.android.fhir.datacapture.extensions.getRequiredOrOptionalText
import com.google.android.fhir.datacapture.extensions.getValidationErrorMessage
import com.google.android.fhir.datacapture.extensions.identifierString
import com.google.android.fhir.datacapture.extensions.itemAnswerOptionImage
import com.google.android.fhir.datacapture.extensions.localizedFlyoverSpanned
import com.google.android.fhir.datacapture.validation.ValidationResult
Expand Down Expand Up @@ -67,19 +68,26 @@ internal object DropDownViewHolderFactory :
this.questionnaireViewItem.answerOption
.map {
DropDownAnswerOption(
it.value.identifierString(context),
omarismail94 marked this conversation as resolved.
Show resolved Hide resolved
it.value.displayString(context),
it.itemAnswerOptionImage(context)
)
}
.toMutableList()
answerOptionList.add(0, DropDownAnswerOption(context.getString(R.string.hyphen), null))
answerOptionList.add(
0,
DropDownAnswerOption(
context.getString(R.string.hyphen),
context.getString(R.string.hyphen),
null
)
)
val adapter =
AnswerOptionDropDownArrayAdapter(context, R.layout.drop_down_list_item, answerOptionList)
val selectedAnswer =
questionnaireViewItem.answers.singleOrNull()?.value?.displayString(header.context)
val selectedAnswerIdentifier =
omarismail94 marked this conversation as resolved.
Show resolved Hide resolved
questionnaireViewItem.answers.singleOrNull()?.value?.identifierString(header.context)
answerOptionList
.filter { it.answerOptionString == selectedAnswer }
.singleOrNull()
.firstOrNull { it.answerIdentifierString == selectedAnswerIdentifier }
?.let {
autoCompleteTextView.setText(it.answerOptionString)
autoCompleteTextView.setSelection(it.answerOptionString.length)
Expand All @@ -103,7 +111,9 @@ internal object DropDownViewHolderFactory :
)
val selectedAnswer =
questionnaireViewItem.answerOption
.firstOrNull { it.value.displayString(context) == selectedItem?.answerOptionString }
.firstOrNull {
it.value.identifierString(context) == selectedItem?.answerIdentifierString
}
omarismail94 marked this conversation as resolved.
Show resolved Hide resolved
?.value

if (selectedAnswer == null) {
Expand Down Expand Up @@ -167,6 +177,7 @@ internal class AnswerOptionDropDownArrayAdapter(
}

internal data class DropDownAnswerOption(
val answerIdentifierString: String,
omarismail94 marked this conversation as resolved.
Show resolved Hide resolved
val answerOptionString: String,
val answerOptionImage: Drawable?
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import org.hl7.fhir.r4.model.Observation
import org.hl7.fhir.r4.model.Patient
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Reference
import org.hl7.fhir.r4.model.RelatedPerson
import org.hl7.fhir.r4.model.ResourceFactory
import org.hl7.fhir.r4.model.StringType
Expand Down Expand Up @@ -1522,6 +1523,96 @@ class ResourceMapperTest {
.isEqualTo(patientId)
}

@Test
fun `populate() should correctly populate Reference value in QuestionnaireResponse`() =
runBlocking {
val questionnaire =
Questionnaire()
.addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "patient-id"
type = Questionnaire.QuestionnaireItemType.REFERENCE
extension =
listOf(
Extension(
ITEM_INITIAL_EXPRESSION_URL,
Expression().apply {
language = "text/fhirpath"
expression = "Patient.id"
}
)
)
}
)

val patientId = UUID.randomUUID().toString()
val patient = Patient().apply { id = "Patient/$patientId" }
val questionnaireResponse = ResourceMapper.populate(questionnaire, patient)

assertThat((questionnaireResponse.item[0].answer[0].value as Reference).reference)
.isEqualTo(patient.id)
}

@Test
fun `populate() should throw error when Reference value in QuestionnaireResponse but FhirExpression `() =
runBlocking {
val questionnaire =
Questionnaire()
.addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "patient-id"
type = Questionnaire.QuestionnaireItemType.REFERENCE
extension =
listOf(
Extension(
ITEM_INITIAL_EXPRESSION_URL,
Expression().apply {
language = "text/fhirpath"
expression = "Patient.gender"
}
)
)
}
)

val patientId = UUID.randomUUID().toString()
val patient =
Patient().apply {
id = "Patient/$patientId"
gender = Enumerations.AdministrativeGender.MALE
}

val errorMessage =
assertFailsWith<FHIRException> { ResourceMapper.populate(questionnaire, patient) }
.localizedMessage
assertThat(errorMessage).isEqualTo("Expression supplied does not evaluate to IdType.")
}

@Test
fun `populate() should correctly populate Reference value in QuestionnaireResponse when expression resolves to type Resource`() =
runBlocking {
val questionnaire =
Questionnaire()
.addItem(
Questionnaire.QuestionnaireItemComponent().apply {
type = Questionnaire.QuestionnaireItemType.REFERENCE
addExtension(
Extension(
ITEM_INITIAL_EXPRESSION_URL,
Expression().apply {
language = "text/fhirpath"
expression = "Patient"
}
)
)
}
)
val patient = Patient().apply { id = UUID.randomUUID().toString() }
val questionnaireResponse = ResourceMapper.populate(questionnaire, patient)

assertThat(questionnaireResponse.itemFirstRep.answerFirstRep.valueReference.reference)
.isEqualTo("Patient/${patient.id}")
}
@Test
fun `populate() should correctly populate IdType value with history in QuestionnaireResponse`() =
runBlocking {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,50 @@ class DropDownViewHolderFactoryTest {
.isEqualTo(answerOption.value.displayString(parent.context))
}

@Test
fun shouldAutoCompleteTextViewToDisplayIfAnswerNotNullAndDisplayMatchesMoreThanOneOption() {
val answerOption1 =
Questionnaire.QuestionnaireItemAnswerOptionComponent().apply {
value =
Reference().apply {
reference = "Patient/1234"
display = "John"
}
}

val answerOption2 =
Questionnaire.QuestionnaireItemAnswerOptionComponent().apply {
value =
Reference().apply {
reference = "Patient/6789"
display = "John"
}
}

viewHolder.bind(
QuestionnaireViewItem(
Questionnaire.QuestionnaireItemComponent().apply {
addAnswerOption(answerOption1)
addAnswerOption(answerOption2)
},
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = answerOption2.value
}
)
},
validationResult = NotValidated,
answersChangedCallback = { _, _, _, _ -> }
)
)

assertThat(
viewHolder.itemView.findViewById<AutoCompleteTextView>(R.id.auto_complete).text.toString()
)
.isEqualTo(answerOption2.value.displayString(parent.context))
}

@Test
fun displayValidationResult_error_shouldShowErrorMessage() {
viewHolder.bind(
Expand Down