Skip to content

Commit

Permalink
Fix disable edit fields not working (#3247)
Browse files Browse the repository at this point in the history
* Remove unanswered linkId from questionnaireResponse

so that default initial values for the linkId get picked from Questionnaire

* Apply patch changes from #3218
  • Loading branch information
LZRS authored May 14, 2024
1 parent d8c0cce commit 79b564e
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,31 @@ fun List<Questionnaire.QuestionnaireItemComponent>.prepareQuestionsForReadingOrE
}
}

/**
* Set all questions that are not of type [Questionnaire.QuestionnaireItemType.GROUP] to readOnly if
* [readOnlyLinkIds] item are there while editing the form. This also generates the correct FHIRPath
* population expression for each question when mapped to the corresponding [QuestionnaireResponse]
*/
fun List<Questionnaire.QuestionnaireItemComponent>.prepareQuestionsForEditing(
path: String = "QuestionnaireResponse.item",
readOnlyLinkIds: List<String>? = emptyList(),
) {
forEach { item ->
if (item.type != Questionnaire.QuestionnaireItemType.GROUP) {
item.readOnly = readOnlyLinkIds?.contains(item.linkId) == true
item.item.prepareQuestionsForEditing(
"$path.where(linkId = '${item.linkId}').answer.item",
readOnlyLinkIds,
)
} else {
item.item.prepareQuestionsForEditing(
"$path.where(linkId = '${item.linkId}').item",
readOnlyLinkIds,
)
}
}
}

/** Delete resources in [QuestionnaireResponse.contained] from the database */
suspend fun QuestionnaireResponse.deleteRelatedResources(defaultRepository: DefaultRepository) {
contained.forEach { defaultRepository.delete(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ abstract class RobolectricTest {
val latch = CountDownLatch(1)
val observer: Observer<T> =
object : Observer<T> {
override fun onChanged(o: T) {
data[0] = o
override fun onChanged(value: T) {
data[0] = value
latch.countDown()
liveData.removeObserver(this)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -895,4 +895,17 @@ class ResourceExtensionTest : RobolectricTest() {
patient.appendOrganizationInfo(listOf("Organization/12345"))
Assert.assertEquals("Organization/12345", patient.managingOrganization.reference)
}

@Test
fun `prepareQuestionsForEditing should set readOnly correctly when readOnlyLinkIds passed`() {
val questionnaire = Questionnaire()
questionnaire.item.add(Questionnaire.QuestionnaireItemComponent().apply { linkId = "1" })
questionnaire.item.add(Questionnaire.QuestionnaireItemComponent().apply { linkId = "2" })
questionnaire.item.add(Questionnaire.QuestionnaireItemComponent().apply { linkId = "3" })
questionnaire.item.prepareQuestionsForEditing("", readOnlyLinkIds = listOf("1", "3"))

Assert.assertTrue(questionnaire.item[0].readOnly)
Assert.assertFalse(questionnaire.item[1].readOnly)
Assert.assertTrue(questionnaire.item[2].readOnly)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import org.hl7.fhir.r4.model.ListResource.ListEntryComponent
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.QuestionnaireResponse.QuestionnaireResponseItemComponent
import org.hl7.fhir.r4.model.RelatedPerson
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType
Expand Down Expand Up @@ -90,6 +91,7 @@ import org.smartregister.fhircore.engine.util.extension.find
import org.smartregister.fhircore.engine.util.extension.generateMissingId
import org.smartregister.fhircore.engine.util.extension.isIn
import org.smartregister.fhircore.engine.util.extension.prePopulateInitialValues
import org.smartregister.fhircore.engine.util.extension.prepareQuestionsForEditing
import org.smartregister.fhircore.engine.util.extension.prepareQuestionsForReadingOrEditing
import org.smartregister.fhircore.engine.util.extension.showToast
import org.smartregister.fhircore.engine.util.extension.updateLastUpdated
Expand Down Expand Up @@ -167,6 +169,10 @@ constructor(
)
}

if (questionnaireConfig.isEditable()) {
item.prepareQuestionsForEditing(readOnlyLinkIds = questionnaireConfig.readOnlyLinkIds)
}

// Pre-populate questionnaire items with configured values
allActionParameters
?.filter { (it.paramType == ActionParameterType.PREPOPULATE && it.value.isNotEmpty()) }
Expand Down Expand Up @@ -974,7 +980,7 @@ constructor(
)
?.let {
QuestionnaireResponse().apply {
item = it.item
item = it.item.removeUnAnsweredItems()
// Clearing the text prompts the SDK to re-process the content, which includes HTML
clearText()
}
Expand All @@ -986,6 +992,13 @@ constructor(
return Pair(questionnaireResponse, launchContextResources)
}

private fun List<QuestionnaireResponseItemComponent>.removeUnAnsweredItems():
List<QuestionnaireResponseItemComponent> {
return this.filter { it.hasAnswer() || it.item.isNotEmpty() }
.onEach { it.item = it.item.removeUnAnsweredItems() }
.filter { it.hasAnswer() || it.item.isNotEmpty() }
}

/**
* Return [Resource]s to be used in the launch context of the questionnaire. Launch context allows
* information to be passed into questionnaire based on the context in which the questionnaire is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import android.app.Application
import androidx.test.core.app.ApplicationProvider
import ca.uhn.fhir.parser.IParser
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.SearchResult
import com.google.android.fhir.datacapture.mapping.ResourceMapper
import com.google.android.fhir.db.ResourceNotFoundException
import com.google.android.fhir.get
import com.google.android.fhir.logicalId
import com.google.android.fhir.search.Search
import com.google.android.fhir.workflow.FhirOperator
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
Expand Down Expand Up @@ -83,6 +85,7 @@ import org.smartregister.fhircore.engine.configuration.app.ConfigService
import org.smartregister.fhircore.engine.data.local.DefaultRepository
import org.smartregister.fhircore.engine.domain.model.ActionParameter
import org.smartregister.fhircore.engine.domain.model.ActionParameterType
import org.smartregister.fhircore.engine.domain.model.QuestionnaireType
import org.smartregister.fhircore.engine.domain.model.RuleConfig
import org.smartregister.fhircore.engine.rulesengine.ConfigRulesExecutor
import org.smartregister.fhircore.engine.rulesengine.ResourceDataRulesExecutor
Expand Down Expand Up @@ -1264,37 +1267,136 @@ class QuestionnaireViewModelTest : RobolectricTest() {
}

@Test
fun testThatPopulateQuestionnaireSetInitialDefaultValueForQuestionnaire() = runTest {
val questionnaireWithDefaultDate =
fun testThatPopulateQuestionnaireSetInitialDefaultValueForQuestionnaireInitialExpression() =
runTest {
val questionnaireWithDefaultDate =
Questionnaire().apply {
id = questionnaireConfig.id
addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "defaultedDate"
type = Questionnaire.QuestionnaireItemType.DATE
addExtension(
Extension(
"http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
Expression().apply {
language = "text/fhirpath"
expression = "today()"
},
),
)
},
)
}
coEvery { fhirEngine.get(ResourceType.Questionnaire, questionnaireConfig.id) } returns
questionnaireWithDefaultDate

questionnaireViewModel.populateQuestionnaire(
questionnaireWithDefaultDate,
questionnaireConfig,
emptyList(),
)
val initialValueDate =
questionnaireWithDefaultDate.item
.first { it.linkId == "defaultedDate" }
.initial
.first()
.value as DateType
Assert.assertTrue(initialValueDate.isToday)
}

@Test
fun testThatPopulateQuestionnaireReturnsQuestionnaireResponseWithUnAnsweredRemoved() = runTest {
val questionnaireConfig1 =
questionnaireConfig.copy(
resourceType = ResourceType.Patient,
resourceIdentifier = patient.logicalId,
type = QuestionnaireType.EDIT.name,
)

val questionnaireWithInitialValue =
Questionnaire().apply {
id = questionnaireConfig.id
id = questionnaireConfig1.id
addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "defaultedDate"
type = Questionnaire.QuestionnaireItemType.DATE
addExtension(
Extension(
"http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
Expression().apply {
language = "text/fhirpath"
expression = "today()"
},
),
linkId = "group-1"
type = Questionnaire.QuestionnaireItemType.GROUP
addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "linkid-1"
type = Questionnaire.QuestionnaireItemType.STRING
addInitial(Questionnaire.QuestionnaireItemInitialComponent(StringType("---")))
},
)

addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "linkid-1.1"
type = Questionnaire.QuestionnaireItemType.STRING
addInitial(Questionnaire.QuestionnaireItemInitialComponent(StringType("---")))
},
)
},
)

addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "linkid-2"
type = Questionnaire.QuestionnaireItemType.STRING
},
)
}
coEvery { fhirEngine.get(ResourceType.Questionnaire, questionnaireConfig.id) } returns
questionnaireWithDefaultDate
val questionnaireResponse =
QuestionnaireResponse().apply {
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "group-1"
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "linkid-1"
},
)
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "linkid-1.1"
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = StringType("World")
},
)
},
)
},
)

questionnaireViewModel.populateQuestionnaire(
questionnaireWithDefaultDate,
questionnaireConfig,
emptyList(),
)
val initialValueDate =
questionnaireWithDefaultDate.item.first { it.linkId == "defaultedDate" }.initial.first().value
as DateType
Assert.assertTrue(initialValueDate.isToday)
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "linkid-2"
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = StringType("45678")
},
)
},
)
}
coEvery {
fhirEngine.get(questionnaireConfig1.resourceType!!, questionnaireConfig1.resourceIdentifier!!)
} returns patient

coEvery { fhirEngine.search<QuestionnaireResponse>(any<Search>()) } returns
listOf(
SearchResult(questionnaireResponse, included = null, revIncluded = null),
)

Assert.assertNotNull(questionnaireResponse.find("linkid-1"))
val result =
questionnaireViewModel.populateQuestionnaire(
questionnaireWithInitialValue,
questionnaireConfig1,
emptyList(),
)
Assert.assertNotNull(result.first)
Assert.assertTrue(result.first!!.find("linkid-1") == null)
}
}

0 comments on commit 79b564e

Please sign in to comment.