Skip to content

Commit

Permalink
Refactor to just send FhirValidator error messages to logcat
Browse files Browse the repository at this point in the history
  • Loading branch information
LZRS committed Jul 27, 2024
1 parent 73d4d6e commit 5e59ef5
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ class FhirValidatorModule {

@Provides
@Singleton
fun provideFhirValidator(): FhirValidator {
val fhirContext = FhirContext.forR4()

fun provideFhirValidator(fhirContext: FhirContext): FhirValidator {
val validationSupportChain =
ValidationSupportChain(
DefaultProfileValidationSupport(fhirContext),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,54 @@ import kotlin.coroutines.coroutineContext
import kotlinx.coroutines.withContext
import org.hl7.fhir.r4.model.Resource
import org.smartregister.fhircore.engine.BuildConfig
import timber.log.Timber

data class ResourceValidationResult(
val resource: Resource,
val validationResult: ValidationResult,
) {
val errorMessages
get() = buildString {
val messages =
validationResult.messages.filter {
it.severity.ordinal >= ResultSeverityEnum.WARNING.ordinal
}

for (validationMsg in messages) {
appendLine(
"${validationMsg.message} - ${validationMsg.locationString} -- (${validationMsg.severity})",
)
}
}
}

data class FhirValidatorResultsWrapper(val results: List<ResourceValidationResult> = emptyList()) {
val errorMessages = results.map { it.errorMessages }
}

suspend fun FhirValidator.checkResourceValid(
vararg resource: Resource,
isDebug: Boolean = BuildConfig.BUILD_TYPE.contains("debug", ignoreCase = true),
): List<ValidationResult> {
if (!isDebug) return emptyList()
isDebug: Boolean = BuildConfig.DEBUG,
): FhirValidatorResultsWrapper {
if (!isDebug) return FhirValidatorResultsWrapper()

return withContext(coroutineContext) {
resource.map { this@checkResourceValid.validateWithResult(it) }
FhirValidatorResultsWrapper(
results =
resource.map {
val result = this@checkResourceValid.validateWithResult(it)
ResourceValidationResult(it, result)
},
)
}
}

val ValidationResult.errorMessages
get() = buildString {
for (validationMsg in
messages.filter { it.severity.ordinal >= ResultSeverityEnum.WARNING.ordinal }) {
appendLine(
"${validationMsg.message} - ${validationMsg.locationString} -- (${validationMsg.severity})",
)
fun FhirValidatorResultsWrapper.logErrorMessages() {
results.forEach {

Check warning on line 69 in android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/FhirValidatorExtension.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/FhirValidatorExtension.kt#L69

Added line #L69 was not covered by tests
if (it.errorMessages.isNotBlank()) {
Timber.tag("$TAG (${it.resource.referenceValue()})").e(it.errorMessages)

Check warning on line 71 in android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/FhirValidatorExtension.kt

View check run for this annotation

Codecov / codecov/patch

android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/FhirValidatorExtension.kt#L71

Added line #L71 was not covered by tests
}
}
}

private const val TAG = "FhirValidator"
Original file line number Diff line number Diff line change
Expand Up @@ -49,28 +49,27 @@ class FhirValidatorExtensionTest : RobolectricTest() {
fun testCheckResourceValidRunsNoValidationWhenBuildTypeIsNotDebug() = runTest {
val basicResource = CarePlan()
val fhirValidatorSpy = spyk(validator)
val results = fhirValidatorSpy.checkResourceValid(basicResource, isDebug = false)
Assert.assertTrue(results.isEmpty())
val resultsWrapper = fhirValidatorSpy.checkResourceValid(basicResource, isDebug = false)
Assert.assertTrue(resultsWrapper.results.isEmpty())
verify(exactly = 0) { fhirValidatorSpy.validateWithResult(any<IBaseResource>()) }
verify(exactly = 0) { fhirValidatorSpy.validateWithResult(any<String>()) }
}

@Test
fun testCheckResourceValidValidatesResourceStructureWhenCarePlanResourceInvalid() = runTest {
val basicCarePlan = CarePlan()
val results = validator.checkResourceValid(basicCarePlan)
Assert.assertFalse(results.isEmpty())
val resultsWrapper = validator.checkResourceValid(basicCarePlan)
Assert.assertTrue(
results.any {
it.errorMessages.contains(
resultsWrapper.errorMessages.any {
it.contains(
"CarePlan.status: minimum required = 1, but only found 0",
ignoreCase = true,
)
},
)
Assert.assertTrue(
results.any {
it.errorMessages.contains(
resultsWrapper.errorMessages.any {
it.contains(
"CarePlan.intent: minimum required = 1, but only found 0",
ignoreCase = true,
)
Expand All @@ -86,13 +85,11 @@ class FhirValidatorExtensionTest : RobolectricTest() {
intent = CarePlan.CarePlanIntent.PLAN
subject = Reference("Task/unknown")
}
val results = validator.checkResourceValid(carePlan)
Assert.assertFalse(results.isEmpty())
Assert.assertEquals(1, results.size)
val resultsWrapper = validator.checkResourceValid(carePlan)
Assert.assertEquals(1, resultsWrapper.errorMessages.size)
Assert.assertTrue(
results
resultsWrapper.errorMessages
.first()
.errorMessages
.contains(
"The type 'Task' implied by the reference URL Task/unknown is not a valid Target for this element (must be one of [Group, Patient]) - CarePlan.subject",
ignoreCase = true,
Expand All @@ -108,13 +105,11 @@ class FhirValidatorExtensionTest : RobolectricTest() {
intent = CarePlan.CarePlanIntent.PLAN
subject = Reference("unknown")
}
val results = validator.checkResourceValid(carePlan)
Assert.assertFalse(results.isEmpty())
Assert.assertEquals(1, results.size)
val resultsWrapper = validator.checkResourceValid(carePlan)
Assert.assertEquals(1, resultsWrapper.errorMessages.size)
Assert.assertTrue(
results
resultsWrapper.errorMessages
.first()
.errorMessages
.contains(
"The syntax of the reference 'unknown' looks incorrect, and it should be checked - CarePlan.subject",
ignoreCase = true,
Expand All @@ -131,9 +126,8 @@ class FhirValidatorExtensionTest : RobolectricTest() {
intent = CarePlan.CarePlanIntent.PLAN
subject = Reference(patient)
}
val results = validator.checkResourceValid(carePlan)
Assert.assertFalse(results.isEmpty())
Assert.assertEquals(1, results.size)
Assert.assertTrue(results.first().errorMessages.isBlank())
val resultsWrapper = validator.checkResourceValid(carePlan)
Assert.assertEquals(1, resultsWrapper.errorMessages.size)
Assert.assertTrue(resultsWrapper.errorMessages.first().isBlank())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import dagger.hilt.android.AndroidEntryPoint
import java.io.Serializable
import javax.inject.Inject
import kotlinx.coroutines.launch
import org.hl7.fhir.r4.model.IdType
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Resource
Expand All @@ -53,11 +52,9 @@ import org.smartregister.fhircore.engine.util.DispatcherProvider
import org.smartregister.fhircore.engine.util.extension.encodeResourceToString
import org.smartregister.fhircore.engine.util.extension.parcelable
import org.smartregister.fhircore.engine.util.extension.parcelableArrayList
import org.smartregister.fhircore.engine.util.extension.referenceValue
import org.smartregister.fhircore.engine.util.extension.showToast
import org.smartregister.fhircore.engine.util.location.LocationUtils
import org.smartregister.fhircore.engine.util.location.PermissionUtils
import org.smartregister.fhircore.quest.BuildConfig
import org.smartregister.fhircore.quest.R
import org.smartregister.fhircore.quest.databinding.QuestionnaireActivityBinding
import org.smartregister.fhircore.quest.util.ResourceUtils
Expand Down Expand Up @@ -339,75 +336,26 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() {
questionnaireConfig = questionnaireConfig,
actionParameters = actionParameters,
context = this@QuestionnaireActivity,
) { idTypes, questionnaireResponse, extractedValidationErrors ->
) { idTypes, questionnaireResponse ->
// Dismiss progress indicator dialog, submit result then finish activity
// TODO Ensure this dialog is dismissed even when an exception is encountered
setProgressState(QuestionnaireProgressState.ExtractionInProgress(false))

if (BuildConfig.BUILD_TYPE.contains("debug", ignoreCase = true)) {
val message =
if (extractedValidationErrors.isEmpty()) {
"Questionnaire submitted and saved successfully with no validation errors"
} else {
"""
Questionnaire `${questionnaire?.referenceValue()}` was submitted but had the following validation errors
${
buildString {
extractedValidationErrors.forEach {
appendLine("${it.key}: ")
append(it.value)
appendLine()
}
}
}
"""
.trimIndent()
}

AlertDialogue.showInfoAlert(
this@QuestionnaireActivity,
message,
"Questionnaire submitted",
{
it.dismiss()
finishOnQuestionnaireSubmission(
questionnaireResponse,
idTypes,
questionnaireConfig,
)
},
)
} else {
finishOnQuestionnaireSubmission(
questionnaireResponse,
idTypes,
questionnaireConfig,
)
}
setResult(
Activity.RESULT_OK,
Intent().apply {
putExtra(QUESTIONNAIRE_RESPONSE, questionnaireResponse as Serializable)
putExtra(QUESTIONNAIRE_SUBMISSION_EXTRACTED_RESOURCE_IDS, idTypes as Serializable)
putExtra(QUESTIONNAIRE_CONFIG, questionnaireConfig as Parcelable)
},
)
finish()
}
}
}
}
}
}

private fun finishOnQuestionnaireSubmission(
questionnaireResponse: QuestionnaireResponse,
idTypes: List<IdType>,
questionnaireConfig: QuestionnaireConfig,
) {
setResult(
Activity.RESULT_OK,
Intent().apply {
putExtra(QUESTIONNAIRE_RESPONSE, questionnaireResponse as Serializable)
putExtra(QUESTIONNAIRE_SUBMISSION_EXTRACTED_RESOURCE_IDS, idTypes as Serializable)
putExtra(QUESTIONNAIRE_CONFIG, questionnaireConfig as Parcelable)
},
)
finish()
}

private fun handleBackPress() {
if (questionnaireConfig.isReadOnly()) {
finish()
Expand Down
Loading

0 comments on commit 5e59ef5

Please sign in to comment.