Skip to content

Commit

Permalink
Remove Engine dependency from SDC. (#1742)
Browse files Browse the repository at this point in the history
* Added interface to resolve x-fhir-query for library

* Review changes

* Removed unnecessary imports
  • Loading branch information
aditya-07 committed Dec 22, 2022
1 parent f41979d commit 4e18ef2
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 23 deletions.
4 changes: 0 additions & 4 deletions datacapture/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ dependencies {

coreLibraryDesugaring(Dependencies.desugarJdkLibs)

debugImplementation(project(":engine"))

implementation(Dependencies.androidFhirCommon)
implementation(Dependencies.Androidx.appCompat)
implementation(Dependencies.Androidx.constraintLayout)
Expand All @@ -92,8 +90,6 @@ dependencies {
implementation(Dependencies.lifecycleExtensions)
implementation(Dependencies.timber)

releaseImplementation(Dependencies.androidFhirEngine)

testImplementation(Dependencies.AndroidxTest.core)
testImplementation(Dependencies.AndroidxTest.fragmentTesting)
testImplementation(Dependencies.Kotlin.kotlinTestJunit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import android.app.Application
import com.google.android.fhir.datacapture.DataCaptureConfig.Provider
import org.hl7.fhir.r4.context.SimpleWorkerContext
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.StructureMap
import org.hl7.fhir.utilities.npm.NpmPackage

Expand All @@ -45,7 +46,13 @@ data class DataCaptureConfig(
* should try to include the smallest [NpmPackage] possible that contains only the resources
* needed by [StructureMap]s used by the client app.
*/
var npmPackage: NpmPackage? = null
var npmPackage: NpmPackage? = null,

/**
* A [XFhirQueryResolver] may be set by the client to resolve x-fhir-query for the library. See
* https://build.fhir.org/ig/HL7/sdc/expressions.html#fhirquery for more details.
*/
var xFhirQueryResolver: XFhirQueryResolver? = null,
) {

internal val simpleWorkerContext: SimpleWorkerContext by lazy {
Expand Down Expand Up @@ -75,3 +82,14 @@ data class DataCaptureConfig(
interface ExternalAnswerValueSetResolver {
suspend fun resolve(uri: String): List<Coding>
}

/**
* Resolves resources based on the provided xFhir query. This allows the library to resolve
* x-fhir-query answer expressions.
*
* NOTE: The result of the resolution may be cached to improve performance. In other words, the
* resolver may be called only once after which the Resources may be used multiple times in the UI.
*/
fun interface XFhirQueryResolver {
suspend fun resolve(xFhirQuery: String): List<Resource>
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import androidx.core.text.HtmlCompat
import com.google.android.fhir.datacapture.common.datatype.asStringValue
import com.google.android.fhir.datacapture.utilities.evaluateToDisplay
import com.google.android.fhir.getLocalizedText
import com.google.android.fhir.logicalId
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.BooleanType
import org.hl7.fhir.r4.model.CodeType
Expand Down Expand Up @@ -545,3 +544,8 @@ fun List<Questionnaire.QuestionnaireItemComponent>.flattened():
*/
fun Questionnaire.QuestionnaireItemComponent.getNestedQuestionnaireResponseItems() =
item.map { it.createQuestionnaireResponseItem() }

val Resource.logicalId: String
get() {
return this.idElement?.idPart.orEmpty()
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import androidx.lifecycle.viewModelScope
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import ca.uhn.fhir.parser.IParser
import com.google.android.fhir.FhirEngineProvider
import com.google.android.fhir.datacapture.enablement.EnablementEvaluator
import com.google.android.fhir.datacapture.fhirpath.ExpressionEvaluator.detectExpressionCyclicDependency
import com.google.android.fhir.datacapture.fhirpath.ExpressionEvaluator.evaluateCalculatedExpressions
Expand All @@ -38,7 +37,6 @@ import com.google.android.fhir.datacapture.validation.QuestionnaireResponseValid
import com.google.android.fhir.datacapture.validation.Valid
import com.google.android.fhir.datacapture.validation.ValidationResult
import com.google.android.fhir.datacapture.views.QuestionnaireItemViewItem
import com.google.android.fhir.search.search
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -57,7 +55,9 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
AndroidViewModel(application) {

private val parser: IParser by lazy { FhirContext.forCached(FhirVersionEnum.R4).newJsonParser() }
private val fhirEngine by lazy { FhirEngineProvider.getInstance(application) }
private val xFhirQueryResolver: XFhirQueryResolver? by lazy {
DataCapture.getConfiguration(application).xFhirQueryResolver
}

/** The current questionnaire as questions are being answered. */
internal val questionnaire: Questionnaire
Expand Down Expand Up @@ -448,13 +448,18 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
expression: Expression,
): List<Questionnaire.QuestionnaireItemAnswerOptionComponent> {
val data =
if (expression.isXFhirQuery) fhirEngine.search(expression.expression)
else if (expression.isFhirPath)
if (expression.isXFhirQuery) {
checkNotNull(xFhirQueryResolver) {
"XFhirQueryResolver cannot be null. Please provide the XFhirQueryResolver via DataCaptureConfig."
}
xFhirQueryResolver!!.resolve(expression.expression)
} else if (expression.isFhirPath) {
fhirPathEngine.evaluate(questionnaireResponse, expression.expression)
else
} else {
throw UnsupportedOperationException(
"${expression.language} not supported for answer-expression yet"
)
}

return item.extractAnswerOptions(data)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import com.google.android.fhir.datacapture.testing.DataCaptureTestApplication
import com.google.android.fhir.datacapture.validation.Invalid
import com.google.android.fhir.datacapture.validation.NotValidated
import com.google.android.fhir.datacapture.views.QuestionnaireItemViewItem
import com.google.android.fhir.logicalId
import com.google.android.fhir.testing.FhirEngineProviderTestRule
import com.google.common.truth.Truth.assertThat
import java.util.Calendar
Expand All @@ -62,7 +61,7 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.StringType
import org.hl7.fhir.r4.model.ValueSet
import org.hl7.fhir.r4.utils.ToolingExtensions
import org.junit.Assert
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
Expand Down Expand Up @@ -2894,13 +2893,13 @@ class QuestionnaireViewModelTest {
fun `resolveAnswerExpression() should return questionnaire item answer options for answer expression and choice column`() =
runBlocking {
val practitioner =
Practitioner()
.apply {
id = UUID.randomUUID().toString()
active = true
addName(HumanName().apply { this.family = "John" })
}
.also { fhirEngine.create(it) }
Practitioner().apply {
id = UUID.randomUUID().toString()
active = true
addName(HumanName().apply { this.family = "John" })
}
ApplicationProvider.getApplicationContext<DataCaptureTestApplication>()
.dataCaptureConfiguration = DataCaptureConfig(xFhirQueryResolver = { listOf(practitioner) })

val questionnaire =
Questionnaire().apply {
Expand Down Expand Up @@ -2939,6 +2938,47 @@ class QuestionnaireViewModelTest {
.isEqualTo("Practitioner/${practitioner.logicalId}")
}

@Test
fun `resolveAnswerExpression() should throw exception when XFhirQueryResolver is not provided`() {
val questionnaire =
Questionnaire().apply {
addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "a"
text = "answer expression question text"
type = Questionnaire.QuestionnaireItemType.REFERENCE
extension =
listOf(
Extension(
"http:https://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-answerExpression",
Expression().apply {
this.expression = "Practitioner?active=true"
this.language = Expression.ExpressionLanguage.APPLICATION_XFHIRQUERY.toCode()
}
),
Extension(
"http:https://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-choiceColumn"
)
.apply {
this.addExtension(Extension("path", StringType("id")))
this.addExtension(Extension("label", StringType("name")))
this.addExtension(Extension("forDisplay", BooleanType(true)))
}
)
}
)
}
state.set(EXTRA_QUESTIONNAIRE_JSON_STRING, printer.encodeResourceToString(questionnaire))
val viewModel = QuestionnaireViewModel(context, state)
val exception =
assertThrows(null, IllegalStateException::class.java) {
runBlocking { viewModel.resolveAnswerExpression(questionnaire.itemFirstRep) }
}
assertThat(exception.message)
.isEqualTo(
"XFhirQueryResolver cannot be null. Please provide the XFhirQueryResolver via DataCaptureConfig."
)
}
// Test cases for submit button

@Test
Expand Down Expand Up @@ -3602,7 +3642,7 @@ class QuestionnaireViewModelTest {
}

val exception =
Assert.assertThrows(null, IllegalStateException::class.java) {
assertThrows(null, IllegalStateException::class.java) {
createQuestionnaireViewModel(questionnaire)
}
assertThat(exception.message)
Expand Down Expand Up @@ -3667,7 +3707,7 @@ class QuestionnaireViewModelTest {
}

val exception =
Assert.assertThrows(null, IllegalStateException::class.java) {
assertThrows(null, IllegalStateException::class.java) {
createQuestionnaireViewModel(questionnaire)
}
assertThat(exception.message)
Expand Down

0 comments on commit 4e18ef2

Please sign in to comment.