Skip to content

Commit

Permalink
work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
LZRS committed Nov 27, 2023
1 parent 20d6b14 commit 1178abe
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 55 deletions.
8 changes: 8 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ allprojects {
url = uri("/Users/ndegwamartin/.m2.dev/fhirsdk")
}
}

configurations {
configureEach {
resolutionStrategy {
force "ca.uhn.hapi.fhir:hapi-fhir-validation:6.0.1"
}
}
}
}

subprojects {
Expand Down
9 changes: 3 additions & 6 deletions android/engine/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,6 @@ android {
}
}
}

configurations.all {
resolutionStrategy {
force "ca.uhn.hapi.fhir:org.hl7.fhir.utilities:5.5.7"
}
}
}

dependencies {
Expand Down Expand Up @@ -147,6 +141,9 @@ dependencies {
implementation(group: "com.github.java-json-tools", name: "msg-simple", version: "1.2");
implementation 'org.codehaus.woodstox:woodstox-core-asl:4.4.1'
implementation "ca.uhn.hapi.fhir:hapi-fhir-android:5.4.0"
implementation ("ca.uhn.hapi.fhir:hapi-fhir-validation:6.0.1"){
exclude module: "commons-logging"
}
implementation 'org.opencds.cqf.cql:engine:1.5.4'
implementation 'org.opencds.cqf.cql:engine.fhir:1.5.4'
implementation 'org.opencds.cqf.cql:evaluator:1.4.2'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2021 Ona Systems, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.smartregister.fhircore.engine.di

import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.validation.FhirValidator
import ca.uhn.fhir.validation.ResultSeverityEnum
import ca.uhn.fhir.validation.ValidationResult
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import kotlin.coroutines.coroutineContext
import kotlinx.coroutines.withContext
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator
import org.hl7.fhir.r4.model.Resource

@InstallIn(SingletonComponent::class)
@Module
class FhirValidatorModule {

@Singleton
@Provides
fun provideFhirValidator(): FhirValidator {
val fhirContext = FhirContext.forR4()
val validatorModule = FhirInstanceValidator(fhirContext)
return fhirContext.newValidator().apply { registerValidatorModule(validatorModule) }
}
}

val ValidationResult.errorMessages
get() = buildString {
for (validationMsg in messages.filter { it.severity == ResultSeverityEnum.ERROR }) {
appendLine("${validationMsg.message} - ${validationMsg.locationString}")
}
}

suspend fun FhirValidator.checkResourceValid(resource: Resource): ValidationResult {
return withContext(coroutineContext) { this@checkResourceValid.validateWithResult(resource) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ import org.hl7.fhir.r4.model.Resource

sealed class ExtractionProgress {
class Success(val extras: List<Resource>? = null) : ExtractionProgress()
object Failed : ExtractionProgress()
class Failed(val errorMessages: String? = null) : ExtractionProgress()

Check warning on line 23 in android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/ExtractionProgress.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/ExtractionProgress.kt#L23

Added line #L23 was not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,13 @@ open class QuestionnaireActivity : BaseMultiLanguageActivity(), View.OnClickList
if (result is ExtractionProgress.Success) {
onPostSave(true, questionnaireResponse, result.extras)
} else {
result as ExtractionProgress.Failed
AlertDialogue.showErrorAlert(
this,

Check warning on line 361 in android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireActivity.kt#L359-L361

Added lines #L359 - L361 were not covered by tests
result.errorMessages ?: "",
getString(R.string.questionnaire_alert_extraction_fail)

Check warning on line 363 in android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireActivity.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireActivity.kt#L363

Added line #L363 was not covered by tests
)

onPostSave(false, questionnaireResponse)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ package org.smartregister.fhircore.engine.ui.questionnaire

import android.content.Context
import android.content.Intent
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import ca.uhn.fhir.rest.gclient.TokenClientParam
import ca.uhn.fhir.rest.param.ParamPrefixEnum
import ca.uhn.fhir.validation.FhirValidator
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.datacapture.mapping.ResourceMapper
import com.google.android.fhir.datacapture.mapping.StructureMapExtractionContext
Expand Down Expand Up @@ -71,6 +74,8 @@ import org.smartregister.fhircore.engine.configuration.view.FormConfiguration
import org.smartregister.fhircore.engine.cql.LibraryEvaluator
import org.smartregister.fhircore.engine.data.local.DefaultRepository
import org.smartregister.fhircore.engine.data.remote.model.response.UserInfo
import org.smartregister.fhircore.engine.di.checkResourceValid
import org.smartregister.fhircore.engine.di.errorMessages
import org.smartregister.fhircore.engine.task.FhirCarePlanGenerator
import org.smartregister.fhircore.engine.trace.PerformanceReporter
import org.smartregister.fhircore.engine.util.AssetUtil
Expand Down Expand Up @@ -110,14 +115,20 @@ constructor(
val dispatcherProvider: DispatcherProvider,
val sharedPreferencesHelper: SharedPreferencesHelper,
val libraryEvaluatorProvider: Provider<LibraryEvaluator>,
val fhirValidatorProvider: Provider<FhirValidator>,
var tracer: PerformanceReporter
) : ViewModel() {
@Inject lateinit var fhirCarePlanGenerator: FhirCarePlanGenerator

val extractionProgress = MutableLiveData<ExtractionProgress>()
private val _extractionProgress = MutableLiveData<ExtractionProgress>()
val extractionProgress: LiveData<ExtractionProgress> = _extractionProgress

val questionnaireResponseLiveData = MutableLiveData<QuestionnaireResponse?>(null)

val extractionProgressMessage = MutableLiveData<String>()
@Suppress("PropertyName")
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
val _extractionProgressMessage = MutableLiveData<String>()
val extractionProgressMessage: LiveData<String> = _extractionProgressMessage

var editQuestionnaireResponse: QuestionnaireResponse? = null

Expand Down Expand Up @@ -334,7 +345,23 @@ constructor(
Timber.w(
"${questionnaire.name}(${questionnaire.logicalId}) is experimental and not save any data"
)
} else saveBundleResources(bundle)
} else {
val unsuccessfulValidationResults =
bundle.entry
.map { fhirValidatorProvider.get().checkResourceValid(it.resource) }
.filter { !it.isSuccessful }

if (unsuccessfulValidationResults.isNotEmpty()) {
val mergedErrorMessages = buildString {
unsuccessfulValidationResults.forEach { appendLine(it.errorMessages) }
}
println(mergedErrorMessages)
_extractionProgress.postValue(ExtractionProgress.Failed(mergedErrorMessages))
return@launch

Check warning on line 360 in android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt#L355-L360

Added lines #L355 - L360 were not covered by tests
}

saveBundleResources(bundle)
}

if (questionnaireType.isEditMode() && editQuestionnaireResponse != null) {
questionnaireResponse.retainMetadata(editQuestionnaireResponse!!)
Expand All @@ -357,7 +384,7 @@ constructor(
}
tracer.stopTrace(QUESTIONNAIRE_TRACE)
viewModelScope.launch(Dispatchers.Main) {
extractionProgress.postValue(ExtractionProgress.Success(extras))
_extractionProgress.postValue(ExtractionProgress.Success(extras))
}
}
}
Expand Down Expand Up @@ -398,7 +425,7 @@ constructor(
.runCatching { fhirCarePlanGenerator.generateCarePlan(planId, subject, data) }
.onFailure {
Timber.e(it)
extractionProgressMessage.postValue("Error extracting care plan. ${it.message}")
_extractionProgressMessage.postValue("Error extracting care plan. ${it.message}")

Check warning on line 428 in android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt#L428

Added line #L428 was not covered by tests
}
}
}
Expand All @@ -420,7 +447,7 @@ constructor(
libraryEvaluatorProvider.get().runCqlLibrary(it, patient, data, defaultRepository)
}
.forEach { output ->
if (output.isNotEmpty()) extractionProgressMessage.postValue(output.joinToString("\n"))
if (output.isNotEmpty()) _extractionProgressMessage.postValue(output.joinToString("\n"))
}
}
}
Expand Down Expand Up @@ -466,7 +493,6 @@ constructor(
questionnaire.useContext.filter { it.hasValueCodeableConcept() }.forEach {
it.valueCodeableConcept.coding.forEach { questionnaireResponse.meta.addTag(it) }
}

defaultRepository.addOrUpdate(true, questionnaireResponse)
}

Expand Down Expand Up @@ -615,10 +641,6 @@ constructor(
return tasks.filter { it.status in arrayOf(TaskStatus.READY, TaskStatus.INPROGRESS) }
}

fun saveResource(resource: Resource) {
viewModelScope.launch { defaultRepository.save(resource = resource) }
}

fun extractRelevantObservation(
resource: Bundle,
questionnaireLogicalId: String,
Expand Down
1 change: 1 addition & 0 deletions android/engine/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
<string name="questionnaire_alert_confirm_button_title">Yes</string>
<string name="questionnaire_alert_invalid_message">Given details have validation errors. Resolve errors and submit again</string>
<string name="questionnaire_alert_invalid_title">Validation Failed</string>
<string name="questionnaire_alert_extraction_fail">Extraction did not succeed</string>
<string name="questionnaire_alert_ack_button_title">Ok</string>
<string name="username">Username</string>
<string name="password">Password</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.fragment.app.commitNow
import androidx.test.core.app.ApplicationProvider
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import ca.uhn.fhir.validation.FhirValidator
import com.google.android.fhir.datacapture.QuestionnaireFragment
import com.google.android.fhir.datacapture.validation.QuestionnaireResponseValidator.checkQuestionnaireResponse
import dagger.hilt.android.testing.BindValue
Expand All @@ -45,6 +46,8 @@ import io.mockk.runs
import io.mockk.spyk
import io.mockk.unmockkObject
import io.mockk.verify
import javax.inject.Inject
import javax.inject.Provider
import kotlin.test.assertFailsWith
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
Expand Down Expand Up @@ -110,25 +113,29 @@ class QuestionnaireActivityTest : ActivityRobolectricTest() {

@BindValue val syncBroadcaster = mockk<SyncBroadcaster>()

@BindValue
val questionnaireViewModel: QuestionnaireViewModel =
spyk(
QuestionnaireViewModel(
fhirEngine = mockk(),
defaultRepository = mockk { coEvery { addOrUpdate(true, any()) } just runs },
configurationRegistry = mockk(),
transformSupportServices = mockk(),
dispatcherProvider = dispatcherProvider,
sharedPreferencesHelper = mockk(),
libraryEvaluatorProvider = { mockk<LibraryEvaluator>() },
tracer = FakePerformanceReporter()
)
)
@Inject lateinit var fhirValidatorProvider: Provider<FhirValidator>

@BindValue lateinit var questionnaireViewModel: QuestionnaireViewModel

@Before
fun setUp() {
// TODO Proper set up
hiltRule.inject()
questionnaireViewModel =
spyk(
QuestionnaireViewModel(
fhirEngine = mockk(),
defaultRepository = mockk { coEvery { addOrUpdate(true, any()) } just runs },
configurationRegistry = mockk(),
transformSupportServices = mockk(),
dispatcherProvider = dispatcherProvider,
sharedPreferencesHelper = mockk(),
libraryEvaluatorProvider = { mockk<LibraryEvaluator>() },
fhirValidatorProvider = fhirValidatorProvider,
tracer = FakePerformanceReporter()
)
)

ApplicationProvider.getApplicationContext<Context>().apply { setTheme(R.style.AppTheme) }
intent =
Intent().apply {
Expand Down Expand Up @@ -599,7 +606,7 @@ class QuestionnaireActivityTest : ActivityRobolectricTest() {

@Test
fun testPostSaveSuccessfulWithExtractionMessageShouldShowAlert() = runTest {
questionnaireActivity.questionnaireViewModel.extractionProgressMessage.postValue("ABC")
questionnaireActivity.questionnaireViewModel._extractionProgressMessage.postValue("ABC")
questionnaireActivity.postSaveSuccessful(QuestionnaireResponse())

val dialog = shadowOf(ShadowAlertDialog.getLatestDialog())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.parser.IParser
import ca.uhn.fhir.validation.FhirValidator
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.SearchResult
import com.google.android.fhir.datacapture.mapping.ResourceMapper
Expand All @@ -47,6 +48,7 @@ import io.mockk.verify
import java.util.Calendar
import java.util.Date
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
Expand Down Expand Up @@ -110,6 +112,8 @@ class QuestionnaireViewModelTest : RobolectricTest() {

@Inject lateinit var sharedPreferencesHelper: SharedPreferencesHelper

@Inject lateinit var fhirValidatorProvider: Provider<FhirValidator>

@get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)

@get:Rule(order = 1) var instantTaskExecutorRule = InstantTaskExecutorRule()
Expand Down Expand Up @@ -175,6 +179,7 @@ class QuestionnaireViewModelTest : RobolectricTest() {
dispatcherProvider = defaultRepo.dispatcherProvider,
sharedPreferencesHelper = sharedPreferencesHelper,
libraryEvaluatorProvider = { libraryEvaluator },
fhirValidatorProvider = fhirValidatorProvider,
tracer = FakePerformanceReporter()
)
)
Expand Down Expand Up @@ -630,13 +635,6 @@ class QuestionnaireViewModelTest : RobolectricTest() {
}
}

@Test
fun testSaveResourceShouldVerifyResourceSaveMethodCall() {
coEvery { defaultRepo.save(any()) } returns Unit
questionnaireViewModel.saveResource(mockk())
coVerify(exactly = 1) { defaultRepo.save(any()) }
}

@Test
fun testGetPopulationResourcesShouldReturnListOfResources() = runTest {
coEvery { questionnaireViewModel.loadPatient("2") } returns Patient().apply { id = "2" }
Expand Down Expand Up @@ -1093,14 +1091,6 @@ class QuestionnaireViewModelTest : RobolectricTest() {
coVerify { libraryEvaluator.runCqlLibrary("123", any(), any(), any()) }
}

@Test
fun testSaveResourceShouldCallDefaultRepositorySave() {
val sourcePatient = Patient().apply { id = "test_patient_1_id" }
questionnaireViewModel.saveResource(sourcePatient)

coVerify { defaultRepo.save(sourcePatient) }
}

@Test
fun `getStructureMapProvider() should return valid provider`() {
Assert.assertNull(questionnaireViewModel.structureMapProvider)
Expand Down
Loading

0 comments on commit 1178abe

Please sign in to comment.