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

Refactor questionnaire implementation #2622

Merged
merged 36 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a3bea5f
Implement QuestionnaireFragment and ViewModel
ellykits Jul 13, 2023
80c03dd
Merge branch 'main' into refactor-questionnaire-implementation
ellykits Jul 13, 2023
cea38ac
Implement logic for perfroming extraction
ellykits Jul 17, 2023
1441ad8
Merge branch 'main' into refactor-questionnaire-implementation
ellykits Jul 17, 2023
8dbc3ef
Fix extraction
ellykits Jul 18, 2023
dac2a16
Merge branch 'main' into refactor-questionnaire-implementation
ellykits Jul 18, 2023
bc0896f
Clean up code
ellykits Jul 18, 2023
5d75134
Fix retrieval of subjectType
ellykits Jul 19, 2023
4906a84
Merge branch 'main' into refactor-questionnaire-implementation
ellykits Jul 19, 2023
1283ccd
Register questionnaire fragment listner after rendering
ellykits Jul 20, 2023
da5cf60
Use SupervisorJob on coroutine
ellykits Jul 20, 2023
7603575
Register group member
ellykits Jul 20, 2023
65bcef6
Fix NoSuchElementException
ellykits Jul 21, 2023
1869a19
Merge branch 'main' into refactor-questionnaire-implementation
ellykits Jul 21, 2023
cdf05f9
Configure deletion of resources via Questionnaire submission
ellykits Jul 24, 2023
fe52bf9
Launch refactored questionnaire activity
ellykits Jul 25, 2023
fc8a29e
Merge branch 'main' into refactor-questionnaire-implementation
ellykits Jul 25, 2023
2f6c05c
Disable dark mode on all activities
ellykits Jul 25, 2023
f86c2ba
Implement functionality to populate resources
ellykits Jul 26, 2023
d0a4691
Populate Questionnaire with previous responses
ellykits Jul 26, 2023
f8f89b4
Retrieve existing subject
ellykits Jul 26, 2023
de51afe
Show progress indicators
ellykits Jul 26, 2023
a84d4c0
Refactor QuestionnaireActivity and QuestionnaireViewModel classes
ellykits Jul 26, 2023
785ad36
Configure setting Barcode on Questionnaire
ellykits Jul 26, 2023
db74635
Reinstate code for setting inital expression
ellykits Jul 26, 2023
dcd556f
Delete unused xml layout
ellykits Jul 26, 2023
4b09775
Fix setting subject
ellykits Jul 27, 2023
fb16c55
Fix updating resources
ellykits Jul 28, 2023
9357a07
Merge branch 'main' into refactor-questionnaire-implementation
ellykits Jul 28, 2023
51b68f7
Refactor engine module failing tests
ellykits Jul 28, 2023
1f454cf
Merge branch 'main' into refactor-questionnaire-implementation
ellykits Jul 31, 2023
0b607e5
Refactor QuestionnaireViewModelTest
ellykits Jul 31, 2023
1cf2463
Write test for QuestionnaireViewModel#handleQuestionnaireSubmission
ellykits Aug 1, 2023
5f259cd
Write test for QuestionnaireActivity
ellykits Aug 1, 2023
c17027b
Test QuestionnaireActivity#onBackPress
ellykits Aug 1, 2023
0c2916d
Merge branch 'main' into refactor-questionnaire-implementation
ellykits Aug 2, 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
1 change: 1 addition & 0 deletions android/engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ android {
buildFeatures {
compose = true
viewBinding = true
dataBinding = true
}
composeOptions { kotlinCompilerExtensionVersion = "1.3.0" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,11 @@ data class QuestionnaireConfig(
val id: String,
val title: String? = null,
val saveButtonText: String? = null,
val setPractitionerDetails: Boolean = true,
val setOrganizationDetails: Boolean = true,
val setAppVersion: Boolean = true,
val planDefinitions: List<String>? = null,
var type: QuestionnaireType = QuestionnaireType.DEFAULT,
val resourceIdentifier: String? = null,
val removeResource: Boolean? = null,
val resourceType: ResourceType? = null,
val removeResource: Boolean? = null,
val confirmationDialog: ConfirmationDialog? = null,
val groupResource: GroupResourceConfig? = null,
val taskId: String? = null,
Expand All @@ -53,6 +50,7 @@ data class QuestionnaireConfig(
val configRules: List<RuleConfig>? = null,
val extraParams: List<ActionParameter>? = null,
val onSubmitActions: List<ActionConfig>? = null,
val barcodeLinkId: String = "patient-barcode",
) : java.io.Serializable, Parcelable {

fun interpolate(computedValuesMap: Map<String, Any>) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import timber.log.Timber
* https://github.com/DBCG/CqlEvaluatorSampleApp See also https://www.hl7.org/fhir/
*/
@Singleton
class LibraryEvaluator @Inject constructor() {
class LibraryEvaluator @Inject constructor(val defaultRepository: DefaultRepository) {
private val fhirContext = FhirContext.forR4Cached()
private val parser = fhirContext.newJsonParser()
private var adapterFactory = AdapterFactory()
Expand Down Expand Up @@ -224,37 +224,40 @@ class LibraryEvaluator @Inject constructor() {
libraryId: String,
patient: Patient?,
data: Bundle,
repository: DefaultRepository,
outputLog: Boolean = false,
): List<String> {
initialize()

val library = repository.fhirEngine.get<LibraryResource>(libraryId)
val library = defaultRepository.loadResource<LibraryResource>(libraryId)

val helpers =
library.relatedArtifact
.filter { it.hasResource() && it.resource.startsWith("Library/") }
.mapNotNull {
repository.fhirEngine.get<LibraryResource>(it.resource.replace("Library/", ""))
library
?.relatedArtifact
?.filter { it.hasResource() && it.resource.startsWith("Library/") }
?.mapNotNull {
defaultRepository.loadResource<LibraryResource>(it.resource.substringAfter("/"))
}

loadConfigs(
library,
helpers,
Bundle(),
// TODO check and handle when data bundle has multiple Patient resources
createBundle(
listOfNotNull(
patient,
*data.entry.map { it.resource }.toTypedArray(),
*repository.search(library.dataRequirementFirstRep).toTypedArray(),
),
),
)
if (library != null && helpers != null) {
loadConfigs(
library = library,
helpers = helpers,
valueSet = Bundle(),
// TODO check and handle when data bundle has multiple Patient resources
data =
createBundle(
listOfNotNull(
patient,
*data.entry.map { it.resource }.toTypedArray(),
*defaultRepository.search(library.dataRequirementFirstRep).toTypedArray(),
),
),
)
}

val result =
libEvaluator!!.evaluate(
VersionedIdentifier().withId(library.name).withVersion(library.version),
VersionedIdentifier().withId(library?.name).withVersion(library?.version),
patient?.let { Pair.of("Patient", it.logicalId) },
null,
null,
Expand All @@ -267,7 +270,7 @@ class LibraryEvaluator @Inject constructor() {

if (p.name.equals(OUTPUT_PARAMETER_KEY) && it.isResource) {
data.addEntry().apply { this.resource = p.resource }
repository.create(true, it as Resource)
defaultRepository.create(true, it as Resource)
}

when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ constructor(
else -> listOf()
}

suspend inline fun <reified R : Resource> search(search: Search) = fhirEngine.search<R>(search)

/**
* Saves a resource in the database. It also updates the [Resource.meta.lastUpdated] and generates
* the [Resource.id] if it is missing before saving the resource.
Expand All @@ -165,8 +167,10 @@ constructor(

private fun preProcessResources(addResourceTags: Boolean, vararg resource: Resource) {
resource.onEach { currentResource ->
currentResource.updateLastUpdated()
currentResource.generateMissingId()
currentResource.apply {
updateLastUpdated()
generateMissingId()
}
if (addResourceTags) {
val tags = configService.provideResourceTags(sharedPreferencesHelper)
tags.forEach {
Expand Down Expand Up @@ -225,7 +229,8 @@ constructor(
resource.updateLastUpdated()
try {
fhirEngine.get(resource.resourceType, resource.logicalId).run {
fhirEngine.update(updateFrom(resource))
val updateFrom = updateFrom(resource)
fhirEngine.update(updateFrom)
}
} catch (resourceNotFoundException: ResourceNotFoundException) {
create(addMandatoryTags, resource)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum class QuestionnaireType {

fun isDefault() = this == DEFAULT

fun isEditMode() = this == EDIT
fun isEditable() = this == EDIT

fun isReadOnly() = this == READ_ONLY
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ constructor(@ApplicationContext val appContext: Context, val defaultRepository:
)
}

Timber.i("Found ${tasks.size} upcoming Tasks to be updated")
Timber.i(
"Found ${tasks.size} upcoming Tasks (with statuses REQUESTED, ACCEPTED or RECEIVED) to be updated",
)

tasks.forEach { task ->
val previousStatus = task.status
Expand All @@ -149,8 +151,10 @@ constructor(@ApplicationContext val appContext: Context, val defaultRepository:

if (task.hasPartOf() && !task.preRequisiteConditionSatisfied()) task.status = previousStatus

if (task.status != previousStatus) defaultRepository.update(task)
Timber.d("Task with ID '${task.id}' status updated to ${task.status}")
if (task.status != previousStatus) {
defaultRepository.update(task)
Timber.d("Task with ID '${task.id}' status updated FROM $previousStatus TO ${task.status}")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@

package org.smartregister.fhircore.engine.ui.base

import android.app.Activity
import android.app.AlertDialog
import android.app.DatePickerDialog
import android.content.Context
import android.content.DialogInterface
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import androidx.annotation.StringRes
Expand All @@ -29,6 +30,7 @@ import androidx.core.view.setPadding
import java.util.Calendar
import java.util.Date
import org.smartregister.fhircore.engine.R
import org.smartregister.fhircore.engine.util.extension.getActivity
import org.smartregister.fhircore.engine.util.extension.hide
import org.smartregister.fhircore.engine.util.extension.show

Expand All @@ -44,20 +46,8 @@ data class AlertDialogListItem(val key: String, val value: String)
object AlertDialogue {
private val ITEMS_LIST_KEY = "alert_dialog_items_list"

fun AlertDialog.getSingleChoiceSelectedKey() = getSingleChoiceSelectedItem()?.key

private fun AlertDialog.getSingleChoiceSelectedItem() =
if (this.listView.checkedItemCount != 1) {
null
} else {
getListItems()!![this.listView.checkedItemPosition]
}

private fun AlertDialog.getListItems() =
this.ownerActivity?.intent?.getSerializableExtra(ITEMS_LIST_KEY) as Array<AlertDialogListItem>?

fun showAlert(
context: Activity,
context: Context,
alertIntent: AlertIntent,
message: CharSequence,
title: String? = null,
Expand All @@ -71,7 +61,7 @@ object AlertDialogue {
val dialog =
AlertDialog.Builder(context, R.style.AlertDialogTheme)
.apply {
val view = context.layoutInflater.inflate(R.layout.alert_dialog, null)
val view = LayoutInflater.from(context).inflate(R.layout.alert_dialog, null)
setView(view)
title?.let { setTitle(it) }
setCancelable(cancellable)
Expand All @@ -97,15 +87,17 @@ object AlertDialogue {
dialog.findViewById<TextView>(R.id.tv_alert_message)?.apply { this.text = message }

options?.let {
context.intent.putExtra(ITEMS_LIST_KEY, it)
dialog.setOwnerActivity(context)
context.getActivity()?.run {
intent?.putExtra(ITEMS_LIST_KEY, it)
dialog.setOwnerActivity(this)
}
}

return dialog
}

fun showInfoAlert(
context: Activity,
context: Context,
message: String,
title: String? = null,
confirmButtonListener: ((d: DialogInterface) -> Unit) = { d -> d.dismiss() },
Expand All @@ -121,7 +113,7 @@ object AlertDialogue {
)
}

fun showErrorAlert(context: Activity, message: String, title: String? = null): AlertDialog {
fun showErrorAlert(context: Context, message: String, title: String? = null): AlertDialog {
return showAlert(
context = context,
alertIntent = AlertIntent.ERROR,
Expand All @@ -133,7 +125,7 @@ object AlertDialogue {
}

fun showErrorAlert(
context: Activity,
context: Context,
@StringRes message: Int,
@StringRes title: Int? = null,
): AlertDialog {
Expand All @@ -144,12 +136,12 @@ object AlertDialogue {
)
}

fun showProgressAlert(context: Activity, @StringRes message: Int): AlertDialog {
fun showProgressAlert(context: Context, @StringRes message: Int): AlertDialog {
return showAlert(context, AlertIntent.PROGRESS, context.getString(message))
}

fun showConfirmAlert(
context: Activity,
context: Context,
@StringRes message: Int,
@StringRes title: Int? = null,
confirmButtonListener: ((d: DialogInterface) -> Unit),
Expand All @@ -171,7 +163,7 @@ object AlertDialogue {
}

fun showCancelAlert(
context: Activity,
context: Context,
@StringRes message: Int,
@StringRes title: Int? = null,
confirmButtonListener: ((d: DialogInterface) -> Unit),
Expand All @@ -196,51 +188,48 @@ object AlertDialogue {
}

fun showDatePickerAlert(
context: Activity,
confirmButtonListener: ((d: Date) -> Unit),
context: Context,
confirmButtonListener: (d: Date) -> Unit,
confirmButtonText: String,
max: Date?,
default: Date = Date(),
title: String?,
dangerActionColor: Boolean = true,
): DatePickerDialog {
val dateDialog = DatePickerDialog(context)

dateDialog.apply {
max?.let { this.datePicker.maxDate = it.time }
val id = Resources.getSystem().getIdentifier("date_picker_header_date", "id", "android")
if (id != 0) {
this.datePicker.findViewById<TextView>(id).textSize = 14f
}
dateDialog
.apply {
max?.let { this.datePicker.maxDate = it.time }
val id = Resources.getSystem().getIdentifier("date_picker_header_date", "id", "android")
if (id != 0) {
this.datePicker.findViewById<TextView>(id).textSize = 14f
}

title?.let {
this.setCustomTitle(
TextView(context).apply {
this.text = it
this.setPadding(20)
},
)
}
title?.let {
this.setCustomTitle(
TextView(context).apply {
this.text = it
this.setPadding(20)
},
)
}

this.setButton(DialogInterface.BUTTON_POSITIVE, confirmButtonText) { d, _ ->
val date =
Calendar.getInstance().apply {
(d as DatePickerDialog).datePicker.let { this.set(it.year, it.month, it.dayOfMonth) }
}
confirmButtonListener.invoke(date.time)
this.setButton(DialogInterface.BUTTON_POSITIVE, confirmButtonText) { d, _ ->
val date =
Calendar.getInstance().apply {
(d as DatePickerDialog).datePicker.let { this.set(it.year, it.month, it.dayOfMonth) }
}
confirmButtonListener.invoke(date.time)
}
}
}

dateDialog.create()
.create()

if (dangerActionColor) {
dateDialog
.getButton(DialogInterface.BUTTON_POSITIVE)
.setTextColor(context.resources.getColor(R.color.colorError))
}

dateDialog.show()

return dateDialog
}
}
Loading