Skip to content

Commit d94ae4a

Browse files
committed
Merge remote-tracking branch 'd-tree-org/test-fix-duplicated-extraction' into update-sdk-for-update-app
2 parents b752877 + 1bf012d commit d94ae4a

File tree

5 files changed

+168
-127
lines changed

5 files changed

+168
-127
lines changed

android/engine/src/main/AndroidManifest.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
<activity
3131
android:name=".ui.questionnaire.QuestionnaireActivity"
3232
android:exported="false"
33-
android:theme="@style/AppTheme" />
33+
android:theme="@style/AppTheme"
34+
android:launchMode="singleTop"/>
3435
<activity
3536
android:name=".HiltActivityForTest"
3637
android:exported="false"

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@
1616

1717
package org.smartregister.fhircore.engine.ui.questionnaire
1818

19+
import java.lang.Exception
20+
import org.hl7.fhir.r4.model.QuestionnaireResponse
1921
import org.hl7.fhir.r4.model.Resource
2022

2123
sealed class ExtractionProgress {
22-
class Success(val extras: List<Resource>? = null) : ExtractionProgress()
24+
class Success(
25+
val questionnaireResponse: QuestionnaireResponse,
26+
val extras: List<Resource>? = null,
27+
) : ExtractionProgress()
2328

24-
object Failed : ExtractionProgress()
29+
class Failed(val questionnaireResponse: QuestionnaireResponse, val exception: Exception) :
30+
ExtractionProgress()
2531
}

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

+37-43
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ open class QuestionnaireActivity : BaseMultiLanguageActivity(), View.OnClickList
145145
}
146146
},
147147
)
148+
149+
questionnaireViewModel.extractionProgress.observe(this) { result ->
150+
if (result is ExtractionProgress.Success) {
151+
onPostSave(true, result.questionnaireResponse, result.extras)
152+
} else {
153+
onPostSave(false, (result as ExtractionProgress.Failed).questionnaireResponse)
154+
}
155+
}
148156
}
149157

150158
fun updateViews() {
@@ -190,7 +198,7 @@ open class QuestionnaireActivity : BaseMultiLanguageActivity(), View.OnClickList
190198
QuestionnaireItemViewHolderFactoryMatchersProviderFactoryImpl.DEFAULT_PROVIDER,
191199
)
192200
.setIsReadOnly(questionnaireType.isReadOnly())
193-
questionnaireResponse?.let {
201+
questionnaireResponse.let {
194202
it.distinctifyLinkId()
195203
// Timber.e(it.encodeResourceToString())
196204
questionnaireFragmentBuilder.setQuestionnaireResponse(it.encodeResourceToString())
@@ -329,23 +337,22 @@ open class QuestionnaireActivity : BaseMultiLanguageActivity(), View.OnClickList
329337
}
330338

331339
open fun showFormSubmissionConfirmAlert() {
332-
if (questionnaire.experimental) {
333-
showConfirmAlert(
334-
context = this,
335-
message = R.string.questionnaire_alert_test_only_message,
336-
title = R.string.questionnaire_alert_test_only_title,
337-
confirmButtonListener = { handleQuestionnaireSubmit() },
338-
confirmButtonText = R.string.questionnaire_alert_test_only_button_title,
339-
)
340-
} else {
341-
showConfirmAlert(
342-
context = this,
343-
message = R.string.questionnaire_alert_submit_message,
344-
title = R.string.questionnaire_alert_submit_title,
345-
confirmButtonListener = { handleQuestionnaireSubmit() },
346-
confirmButtonText = R.string.questionnaire_alert_submit_button_title,
347-
)
348-
}
340+
showConfirmAlert(
341+
context = this,
342+
message =
343+
if (questionnaire.experimental) {
344+
R.string.questionnaire_alert_test_only_message
345+
} else R.string.questionnaire_alert_submit_message,
346+
title =
347+
if (questionnaire.experimental) {
348+
R.string.questionnaire_alert_test_only_title
349+
} else R.string.questionnaire_alert_submit_title,
350+
confirmButtonListener = { handleQuestionnaireSubmit() },
351+
confirmButtonText =
352+
if (questionnaire.experimental) {
353+
R.string.questionnaire_alert_test_only_button_title
354+
} else R.string.questionnaire_alert_submit_button_title,
355+
)
349356
}
350357

351358
suspend fun getQuestionnaireResponse(): QuestionnaireResponse {
@@ -361,33 +368,20 @@ open class QuestionnaireActivity : BaseMultiLanguageActivity(), View.OnClickList
361368
}
362369

363370
open fun handleQuestionnaireSubmit() {
364-
lifecycleScope.launch {
365-
val questionnaireResponse = getQuestionnaireResponse()
366-
val isQuestionnaireResponseValid: Boolean
367-
withContext(dispatcherProvider.unconfined()) {
368-
isQuestionnaireResponseValid = validQuestionnaireResponse(questionnaireResponse)
369-
}
370-
371-
if (!isQuestionnaireResponseValid) {
372-
saveProcessingAlertDialog.dismiss()
373-
374-
AlertDialogue.showErrorAlert(
375-
this@QuestionnaireActivity,
376-
R.string.questionnaire_alert_invalid_message,
377-
R.string.questionnaire_alert_invalid_title,
378-
)
379-
return@launch
380-
}
381-
handleQuestionnaireResponse(questionnaireResponse)
382-
383-
questionnaireViewModel.extractionProgress.observe(this@QuestionnaireActivity) { result ->
384-
if (result is ExtractionProgress.Success) {
385-
onPostSave(true, questionnaireResponse, result.extras)
386-
} else {
387-
onPostSave(false, questionnaireResponse)
371+
saveProcessingAlertDialog = showProgressAlert(this, R.string.form_progress_message)
372+
val doHandleQuestionnaireResponse = suspend {
373+
getQuestionnaireResponse()
374+
.takeIf { validQuestionnaireResponse(it) }
375+
?.let { handleQuestionnaireResponse(it) }
376+
?: saveProcessingAlertDialog.dismiss().also {
377+
AlertDialogue.showErrorAlert(
378+
this,
379+
R.string.questionnaire_alert_invalid_message,
380+
R.string.questionnaire_alert_invalid_title,
381+
)
388382
}
389-
}
390383
}
384+
lifecycleScope.launch { doHandleQuestionnaireResponse() }
391385
}
392386

393387
fun onPostSave(

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

+119-79
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ import java.util.Calendar
3939
import java.util.Date
4040
import java.util.UUID
4141
import javax.inject.Inject
42-
import kotlinx.coroutines.Dispatchers
42+
import kotlin.time.Duration.Companion.milliseconds
43+
import kotlinx.coroutines.FlowPreview
44+
import kotlinx.coroutines.flow.MutableStateFlow
45+
import kotlinx.coroutines.flow.debounce
46+
import kotlinx.coroutines.flow.filter
4347
import kotlinx.coroutines.launch
4448
import kotlinx.coroutines.withContext
4549
import org.hl7.fhir.r4.context.IWorkerContext
@@ -93,6 +97,7 @@ import org.smartregister.fhircore.engine.util.helper.TransformSupportServices
9397
import timber.log.Timber
9498

9599
@HiltViewModel
100+
@OptIn(FlowPreview::class)
96101
open class QuestionnaireViewModel
97102
@Inject
98103
constructor(
@@ -126,6 +131,17 @@ constructor(
126131
?.extractLogicalIdUuid()
127132
}
128133

134+
private val extractAndSaveRequestStateFlow: MutableStateFlow<suspend () -> Unit> =
135+
MutableStateFlow {}
136+
137+
init {
138+
viewModelScope.launch(dispatcherProvider.io()) {
139+
extractAndSaveRequestStateFlow.debounce(800.milliseconds).collect {
140+
it.invoke() // invoke request
141+
}
142+
}
143+
}
144+
129145
suspend fun loadQuestionnaire(id: String, type: QuestionnaireType): Questionnaire? =
130146
defaultRepository.loadResource<Questionnaire>(id)?.apply {
131147
if (type.isReadOnly() || type.isEditMode()) {
@@ -254,100 +270,124 @@ constructor(
254270
questionnaireType: QuestionnaireType = QuestionnaireType.DEFAULT,
255271
questionnaire: Questionnaire,
256272
) {
257-
viewModelScope.launch(dispatcherProvider.io()) {
258-
tracer.startTrace(QUESTIONNAIRE_TRACE)
259-
// important to set response subject so that structure map can handle subject for all entities
260-
handleQuestionnaireResponseSubject(resourceId, questionnaire, questionnaireResponse)
261-
Timber.e(jsonParser.encodeResourceToString(questionnaireResponse))
262-
val extras = mutableListOf<Resource>()
263-
if (questionnaire.isExtractionCandidate()) {
264-
val bundle = performExtraction(context, questionnaire, questionnaireResponse)
265-
questionnaireResponse.contained = mutableListOf()
266-
bundle.entry.forEach { bundleEntry ->
267-
// add organization to entities representing individuals in registration questionnaire
268-
if (bundleEntry.resource.resourceType.isIn(ResourceType.Patient, ResourceType.Group)) {
269-
if (questionnaireConfig.setOrganizationDetails) {
270-
appendOrganizationInfo(bundleEntry.resource)
271-
}
272-
// if it is new registration set response subject
273-
if (resourceId == null) {
274-
questionnaireResponse.subject = bundleEntry.resource.asReference()
275-
}
276-
}
277-
if (questionnaireConfig.setPractitionerDetails) {
278-
appendPractitionerInfo(bundleEntry.resource)
279-
}
273+
val request = suspend {
274+
try {
275+
val extras =
276+
doExtractAndSaveResources(
277+
context,
278+
resourceId,
279+
groupResourceId,
280+
questionnaireResponse,
281+
questionnaireType,
282+
questionnaire,
283+
)
284+
extractionProgress.postValue(ExtractionProgress.Success(questionnaireResponse, extras))
285+
} catch (e: Exception) {
286+
extractionProgress.postValue(ExtractionProgress.Failed(questionnaireResponse, e))
287+
}
288+
}
289+
290+
extractAndSaveRequestStateFlow.value = request
291+
}
280292

281-
if (
282-
questionnaireType != QuestionnaireType.EDIT &&
283-
bundleEntry.resource.resourceType.isIn(
284-
ResourceType.Patient,
285-
ResourceType.RelatedPerson,
286-
)
287-
) {
288-
groupResourceId?.let {
289-
appendPatientsAndRelatedPersonsToGroups(
290-
resource = bundleEntry.resource,
291-
groupResourceId = it,
292-
)
293-
}
293+
private suspend fun doExtractAndSaveResources(
294+
context: Context,
295+
resourceId: String?,
296+
groupResourceId: String? = null,
297+
questionnaireResponse: QuestionnaireResponse,
298+
questionnaireType: QuestionnaireType = QuestionnaireType.DEFAULT,
299+
questionnaire: Questionnaire,
300+
): List<Resource> {
301+
tracer.startTrace(QUESTIONNAIRE_TRACE)
302+
// important to set response subject so that structure map can handle subject for all entities
303+
handleQuestionnaireResponseSubject(resourceId, questionnaire, questionnaireResponse)
304+
Timber.e(jsonParser.encodeResourceToString(questionnaireResponse))
305+
val extras = mutableListOf<Resource>()
306+
if (questionnaire.isExtractionCandidate()) {
307+
val bundle = performExtraction(context, questionnaire, questionnaireResponse)
308+
questionnaireResponse.contained = mutableListOf()
309+
bundle.entry.forEach { bundleEntry ->
310+
// add organization to entities representing individuals in registration questionnaire
311+
if (bundleEntry.resource.resourceType.isIn(ResourceType.Patient, ResourceType.Group)) {
312+
if (questionnaireConfig.setOrganizationDetails) {
313+
appendOrganizationInfo(bundleEntry.resource)
294314
}
315+
// if it is new registration set response subject
316+
if (resourceId == null) {
317+
questionnaireResponse.subject = bundleEntry.resource.asReference()
318+
}
319+
}
320+
if (questionnaireConfig.setPractitionerDetails) {
321+
appendPractitionerInfo(bundleEntry.resource)
322+
}
295323

296-
// response MUST have subject by far otherwise flow has issues
297-
if (!questionnaire.experimental) questionnaireResponse.assertSubject()
298-
299-
// TODO https://github.com/opensrp/fhircore/issues/900
300-
// for edit mode replace client and resource subject ids.
301-
// Ideally ResourceMapper should allow this internally via structure-map
302-
if (questionnaireType.isEditMode()) {
303-
if (bundleEntry.resource.resourceType.isIn(ResourceType.Patient, ResourceType.Group)) {
304-
bundleEntry.resource.id = questionnaireResponse.subject.extractId()
305-
} else {
306-
bundleEntry.resource.setPropertySafely("subject", questionnaireResponse.subject)
307-
bundleEntry.resource.setPropertySafely("patient", questionnaireResponse.subject)
308-
}
324+
if (
325+
questionnaireType != QuestionnaireType.EDIT &&
326+
bundleEntry.resource.resourceType.isIn(
327+
ResourceType.Patient,
328+
ResourceType.RelatedPerson,
329+
)
330+
) {
331+
groupResourceId?.let {
332+
appendPatientsAndRelatedPersonsToGroups(
333+
resource = bundleEntry.resource,
334+
groupResourceId = it,
335+
)
309336
}
310-
questionnaireResponse.contained.add(bundleEntry.resource)
337+
}
311338

312-
if (bundleEntry.resource is Encounter) extras.add(bundleEntry.resource)
339+
// response MUST have subject by far otherwise flow has issues
340+
if (!questionnaire.experimental) questionnaireResponse.assertSubject()
313341

314-
if (
315-
(bundleEntry.resource is CarePlan || bundleEntry.resource is Patient) &&
316-
bundleEntry.resource.meta.tag.isNotEmpty()
317-
) {
318-
carePlanAndPatientMetaExtraction(bundleEntry.resource)
342+
// TODO https://github.com/opensrp/fhircore/issues/900
343+
// for edit mode replace client and resource subject ids.
344+
// Ideally ResourceMapper should allow this internally via structure-map
345+
if (questionnaireType.isEditMode()) {
346+
if (bundleEntry.resource.resourceType.isIn(ResourceType.Patient, ResourceType.Group)) {
347+
bundleEntry.resource.id = questionnaireResponse.subject.extractId()
348+
} else {
349+
bundleEntry.resource.setPropertySafely("subject", questionnaireResponse.subject)
350+
bundleEntry.resource.setPropertySafely("patient", questionnaireResponse.subject)
319351
}
320352
}
353+
questionnaireResponse.contained.add(bundleEntry.resource)
321354

322-
if (questionnaire.experimental) {
323-
Timber.w(
324-
"${questionnaire.name}(${questionnaire.logicalId}) is experimental and not save any data",
325-
)
326-
} else {
327-
saveBundleResources(bundle)
328-
}
355+
if (bundleEntry.resource is Encounter) extras.add(bundleEntry.resource)
329356

330-
if (questionnaireType.isEditMode() && editQuestionnaireResponse != null) {
331-
questionnaireResponse.retainMetadata(editQuestionnaireResponse!!)
357+
if (
358+
(bundleEntry.resource is CarePlan || bundleEntry.resource is Patient) &&
359+
bundleEntry.resource.meta.tag.isNotEmpty()
360+
) {
361+
carePlanAndPatientMetaExtraction(bundleEntry.resource)
332362
}
363+
}
333364

334-
saveQuestionnaireResponse(questionnaire, questionnaireResponse)
335-
questionnaireResponseLiveData.postValue(questionnaireResponse)
336-
// TODO https://github.com/opensrp/fhircore/issues/900
337-
// reassess following i.e. deleting/updating older resources because one resource
338-
// might have generated other flow in subsequent followups
339-
if (questionnaireType.isEditMode() && editQuestionnaireResponse != null) {
340-
editQuestionnaireResponse!!.deleteRelatedResources(defaultRepository)
341-
}
342-
extractCarePlan(questionnaireResponse, bundle)
365+
if (questionnaire.experimental) {
366+
Timber.w(
367+
"${questionnaire.name}(${questionnaire.logicalId}) is experimental and not save any data",
368+
)
343369
} else {
344-
saveQuestionnaireResponse(questionnaire, questionnaireResponse)
370+
saveBundleResources(bundle)
371+
}
372+
373+
if (questionnaireType.isEditMode() && editQuestionnaireResponse != null) {
374+
questionnaireResponse.retainMetadata(editQuestionnaireResponse!!)
345375
}
346-
tracer.stopTrace(QUESTIONNAIRE_TRACE)
347-
viewModelScope.launch(Dispatchers.Main) {
348-
extractionProgress.postValue(ExtractionProgress.Success(extras))
376+
377+
saveQuestionnaireResponse(questionnaire, questionnaireResponse)
378+
questionnaireResponseLiveData.postValue(questionnaireResponse)
379+
// TODO https://github.com/opensrp/fhircore/issues/900
380+
// reassess following i.e. deleting/updating older resources because one resource
381+
// might have generated other flow in subsequent followups
382+
if (questionnaireType.isEditMode() && editQuestionnaireResponse != null) {
383+
editQuestionnaireResponse!!.deleteRelatedResources(defaultRepository)
349384
}
385+
extractCarePlan(questionnaireResponse, bundle)
386+
} else {
387+
saveQuestionnaireResponse(questionnaire, questionnaireResponse)
350388
}
389+
tracer.stopTrace(QUESTIONNAIRE_TRACE)
390+
return extras
351391
}
352392

353393
suspend fun carePlanAndPatientMetaExtraction(source: Resource) {

android/quest/src/main/java/org/smartregister/fhircore/quest/util/mappers/AppointmentFilterMappers.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,15 @@ fun transformAppointmentUiReasonToCode(uiReason: Reason): CodeableConcept? =
8686
Coding(
8787
SystemConstants.REASON_CODE_SYSTEM,
8888
"welcome-service-follow-up",
89-
"Index Case Testing"
89+
"Index Case Testing",
9090
),
9191
)
9292
Reason.TB_HISTORY_REGIMEN ->
9393
CodeableConcept(
9494
Coding(
9595
SystemConstants.REASON_CODE_SYSTEM,
9696
"tb_history_and_regimen",
97-
"Welcome Service Follow Up"
97+
"Welcome Service Follow Up",
9898
),
9999
)
100100
else -> null

0 commit comments

Comments
 (0)