Skip to content

Commit

Permalink
feat: [ANDROAPP-6101] modify logic for saving completed events (#3645)
Browse files Browse the repository at this point in the history
* fix: [ANDROAPP-6101] modify logic for saving completed events and add unit tests for new logic

* fix: [ANDROAPP-6101] modify complete save action to ignore validation strategy

* fix: [ANDROAPP-6101] fix test

* fix: [ANDROAPP-6101] allow to save with warnings, update description and secondary button text for warning

* fix: [ANDROAPP-6101] fix flaky test
  • Loading branch information
xavimolloy authored Jun 6, 2024
1 parent 46f31c3 commit 0cd01c9
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class EventRegistrationRobot : BaseRobot() {
}

private fun clickOnNextQR() {
onView(withId(R.id.next)).perform(click())
waitForView(withId(R.id.next)).perform(click())
}

fun clickOnAllQR(listQR: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,29 +113,38 @@ class EventCapturePresenterImpl(
warningFields: List<FieldWithIssue>,
eventMode: EventMode?,
) {
val eventStatus = eventStatus
if (eventStatus != EventStatus.ACTIVE) {
setUpActionByStatus(eventStatus)
} else {
val canSkipErrorFix = canSkipErrorFix(
hasErrorFields = errorFields.isNotEmpty(),
hasEmptyMandatoryFields = emptyMandatoryFields.isNotEmpty(),
hasEmptyEventCreationMandatoryFields = with(emptyMandatoryFields) {
containsValue(EventRepository.EVENT_DETAILS_SECTION_UID) ||
containsValue(EventRepository.EVENT_CATEGORY_COMBO_SECTION_UID)
},
eventMode = eventMode,
validationStrategy = eventCaptureRepository.validationStrategy(),
)
val eventCompletionDialog = configureEventCompletionDialog.invoke(
errorFields,
emptyMandatoryFields,
warningFields,
canComplete,
onCompleteMessage,
canSkipErrorFix,
)
view.showCompleteActions(eventCompletionDialog)
when (eventStatus) {
EventStatus.ACTIVE, EventStatus.COMPLETED -> {
var canSkipErrorFix = canSkipErrorFix(
hasErrorFields = errorFields.isNotEmpty(),
hasEmptyMandatoryFields = emptyMandatoryFields.isNotEmpty(),
hasEmptyEventCreationMandatoryFields = with(emptyMandatoryFields) {
containsValue(EventRepository.EVENT_DETAILS_SECTION_UID) ||
containsValue(EventRepository.EVENT_CATEGORY_COMBO_SECTION_UID)
},
eventMode = eventMode,
validationStrategy = eventCaptureRepository.validationStrategy(),
)
if (eventStatus == EventStatus.COMPLETED) canSkipErrorFix = false
val eventCompletionDialog = configureEventCompletionDialog.invoke(
errorFields,
emptyMandatoryFields,
warningFields,
canComplete,
onCompleteMessage,
canSkipErrorFix,
eventStatus,
)

if (eventStatus == EventStatus.COMPLETED && eventCompletionDialog.fieldsWithIssues.isEmpty()) {
finishCompletedEvent()
} else {
view.showCompleteActions(eventCompletionDialog)
}
}
else -> {
setUpActionByStatus(eventStatus)
}
}
view.showNavigationBar()
}
Expand All @@ -158,13 +167,6 @@ class EventCapturePresenterImpl(

private fun setUpActionByStatus(eventStatus: EventStatus) {
when (eventStatus) {
EventStatus.COMPLETED ->
if (!hasExpired && !eventCaptureRepository.isEnrollmentCancelled) {
view.saveAndFinish()
} else {
view.finishDataEntry()
}

EventStatus.OVERDUE -> view.attemptToSkip()
EventStatus.SKIPPED -> view.attemptToReschedule()
else -> {
Expand All @@ -173,6 +175,14 @@ class EventCapturePresenterImpl(
}
}

private fun finishCompletedEvent() {
if (!hasExpired && !eventCaptureRepository.isEnrollmentCancelled) {
view.saveAndFinish()
} else {
view.finishDataEntry()
}
}

override fun isEnrollmentOpen(): Boolean {
return eventCaptureRepository.isEnrollmentOpen
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.model.EventCom
import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.model.EventCompletionDialog
import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.provider.EventCaptureResourcesProvider
import org.dhis2.utils.customviews.FormBottomDialog
import org.hisp.dhis.android.core.event.EventStatus

class ConfigureEventCompletionDialog(
val provider: EventCaptureResourcesProvider,
Expand All @@ -27,25 +28,26 @@ class ConfigureEventCompletionDialog(
canComplete: Boolean,
onCompleteMessage: String?,
canSkipErrorFix: Boolean,
eventState: EventStatus,
): EventCompletionDialog {
val dialogType = getDialogType(
errorFields,
mandatoryFields,
warningFields,
!canComplete && onCompleteMessage != null,
)
val mainButton = getMainButton(dialogType)
val secondaryButton = if (canSkipErrorFix) {
val mainButton = getMainButton(dialogType, eventState)
val secondaryButton = if (canSkipErrorFix || eventState == EventStatus.COMPLETED) {
EventCompletionButtons(
SecondaryButton(provider.provideNotNow()),
SecondaryButton(if (eventState == EventStatus.COMPLETED) provider.provideSaveAnyway() else provider.provideNotNow()),
FormBottomDialog.ActionType.FINISH,
)
} else {
null
}
val bottomSheetDialogUiModel = BottomSheetDialogUiModel(
title = getTitle(dialogType),
message = getSubtitle(dialogType),
message = getSubtitle(dialogType, eventState),
iconResource = getIcon(dialogType),
mainButton = mainButton.buttonStyle,
secondaryButton = secondaryButton?.buttonStyle,
Expand All @@ -69,10 +71,10 @@ class ConfigureEventCompletionDialog(
else -> provider.provideSavedText()
}

private fun getSubtitle(type: DialogType) = when (type) {
private fun getSubtitle(type: DialogType, eventState: EventStatus) = when (type) {
ERROR -> provider.provideErrorInfo()
MANDATORY -> provider.provideMandatoryInfo()
WARNING -> provider.provideWarningInfo()
WARNING -> if (eventState == EventStatus.COMPLETED) provider.provideWarningInfoCompletedEvent() else provider.provideWarningInfo()
SUCCESSFUL -> provider.provideCompleteInfo()
COMPLETE_ERROR -> provider.provideOnCompleteErrorInfo()
}
Expand All @@ -84,7 +86,7 @@ class ConfigureEventCompletionDialog(
SUCCESSFUL -> provider.provideSavedIcon()
}

private fun getMainButton(type: DialogType) = when (type) {
private fun getMainButton(type: DialogType, eventState: EventStatus) = when (type) {
ERROR,
MANDATORY,
COMPLETE_ERROR,
Expand All @@ -93,7 +95,17 @@ class ConfigureEventCompletionDialog(
FormBottomDialog.ActionType.CHECK_FIELDS,
)

WARNING,
WARNING -> if (eventState == EventStatus.COMPLETED) {
EventCompletionButtons(
MainButton(provider.provideReview()),
FormBottomDialog.ActionType.CHECK_FIELDS,
)
} else {
EventCompletionButtons(
CompleteButton,
FormBottomDialog.ActionType.COMPLETE,
)
}
SUCCESSFUL,
-> EventCompletionButtons(
CompleteButton,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ class EventCaptureResourcesProvider(

fun provideWarningInfo() = resourceManager.getString(R.string.missing_warning_fields_events)

fun provideWarningInfoCompletedEvent() = resourceManager.getString(R.string.missing_warning_fields_completed_events)

fun provideReview() = R.string.review

fun provideNotNow() = R.string.not_now

fun provideSaveAnyway() = R.string.save_anyway

fun provideCompleteInfo() = resourceManager.getString(R.string.event_can_be_completed)

fun provideOnCompleteErrorInfo() = resourceManager.getString(R.string.event_error_on_complete)
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,7 @@
<string name="action_ok">Ok</string>
<string name="init_search">Start a search to find any %s</string>
<string name="search_or_create">You can search or create a new %s</string>
<string name="missing_warning_fields_completed_events">You have some warning messages.</string>
<string name="missing_warning_fields_events">You have some warning messages.\nDo you want to mark this form as complete?</string>
<string name="missing_error_fields_events">Some fields have errors and they are not saved. \nDo you want to review the form?</string>
<string name="event_can_be_completed">Do you want to mark this form as complete?</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.reactivex.Observable
import io.reactivex.Single
import org.dhis2.commons.prefs.PreferenceProvider
import org.dhis2.data.schedulers.TrampolineSchedulerProvider
import org.dhis2.ui.dialogs.bottomsheet.FieldWithIssue
import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.domain.ConfigureEventCompletionDialog
import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.model.EventCompletionDialog
import org.hisp.dhis.android.core.common.ValidationStrategy
Expand Down Expand Up @@ -252,7 +253,16 @@ class EventCapturePresenterTest {
whenever(eventRepository.eventIntegrityCheck()) doReturn Flowable.just(false)
whenever(eventRepository.eventStatus()) doReturn Flowable.just(EventStatus.COMPLETED)
whenever(eventRepository.isEventEditable("eventUid")) doReturn true

whenever(
eventRepository.validationStrategy(),
) doReturn ValidationStrategy.ON_UPDATE_AND_INSERT
val eventCompletionDialog: EventCompletionDialog = mock()
whenever(
configureEventCompletionDialog.invoke(
emptyList(), emptyMap(), emptyList(), true, null, false,
EventStatus.COMPLETED,
),
) doReturn eventCompletionDialog
whenever(eventRepository.isCompletedEventExpired(any())) doReturn Observable.just(true)
whenever(eventRepository.isEventEditable(any())) doReturn true

Expand All @@ -269,7 +279,13 @@ class EventCapturePresenterTest {
whenever(eventRepository.isEventEditable("eventUid")) doReturn true

presenter.init()

whenever(
eventRepository.validationStrategy(),
) doReturn ValidationStrategy.ON_UPDATE_AND_INSERT
val eventCompletionDialog: EventCompletionDialog = mock()
whenever(
configureEventCompletionDialog.invoke(emptyList(), emptyMap(), emptyList(), true, null, false, EventStatus.COMPLETED),
) doReturn eventCompletionDialog
whenever(eventRepository.isCompletedEventExpired(any())) doReturn Observable.just(false)
whenever(eventRepository.isEventEditable(any())) doReturn true
whenever(eventRepository.isEnrollmentCancelled) doReturn true
Expand Down Expand Up @@ -325,7 +341,7 @@ class EventCapturePresenterTest {
) doReturn ValidationStrategy.ON_UPDATE_AND_INSERT
val eventCompletionDialog: EventCompletionDialog = mock()
whenever(
configureEventCompletionDialog.invoke(any(), any(), any(), any(), any(), any()),
configureEventCompletionDialog.invoke(any(), any(), any(), any(), any(), any(), any()),
) doReturn eventCompletionDialog
whenever(
eventRepository.isEnrollmentOpen,
Expand All @@ -334,7 +350,37 @@ class EventCapturePresenterTest {
presenter.attemptFinish(
canComplete = true,
onCompleteMessage = "Complete",
errorFields = emptyList(),
errorFields = listOf(mock()),
emptyMandatoryFields = emptyMap(),
warningFields = emptyList(),
)

verify(view).showCompleteActions(any())
verify(view).showNavigationBar()
}

@Test
fun `Should show completion dialog and not navigate back when event is completed and there are error fields`() {
initializeMocks()
whenever(eventRepository.eventIntegrityCheck()) doReturn Flowable.just(false)
whenever(eventRepository.eventStatus()) doReturn Flowable.just(EventStatus.COMPLETED)
whenever(eventRepository.isEventEditable("eventUid")) doReturn true

whenever(
eventRepository.validationStrategy(),
) doReturn ValidationStrategy.ON_UPDATE_AND_INSERT
val eventCompletionDialog = EventCompletionDialog(mock(), mock(), null, listOf(FieldWithIssue("uid", "fieldName", mock(), "message")))
whenever(
configureEventCompletionDialog.invoke(any(), any(), any(), any(), any(), any(), any()),
) doReturn eventCompletionDialog
whenever(
eventRepository.isEnrollmentOpen,
) doReturn true

presenter.attemptFinish(
canComplete = true,
onCompleteMessage = "Complete",
errorFields = listOf(FieldWithIssue("uid", "fieldName", mock(), "message")),
emptyMandatoryFields = emptyMap(),
warningFields = emptyList(),
)
Expand All @@ -343,6 +389,35 @@ class EventCapturePresenterTest {
verify(view).showNavigationBar()
}

@Test
fun `Should save and finish if event is is completed and has no errors`() {
initializeMocks()
whenever(eventRepository.eventIntegrityCheck()) doReturn Flowable.just(false)
whenever(eventRepository.eventStatus()) doReturn Flowable.just(EventStatus.COMPLETED)
whenever(eventRepository.isEventEditable("eventUid")) doReturn true

whenever(
eventRepository.validationStrategy(),
) doReturn ValidationStrategy.ON_UPDATE_AND_INSERT
val eventCompletionDialog: EventCompletionDialog = mock()
whenever(
configureEventCompletionDialog.invoke(any(), any(), any(), any(), any(), any(), any()),
) doReturn eventCompletionDialog
whenever(
eventRepository.isEnrollmentOpen,
) doReturn true

presenter.attemptFinish(
canComplete = true,
onCompleteMessage = "Complete",
errorFields = emptyList(),
emptyMandatoryFields = emptyMap(),
warningFields = emptyList(),
)

verify(view).saveAndFinish()
}

@Test
fun `Should init note counter`() {
whenever(eventRepository.noteCount) doReturnConsecutively listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.dhis2.usescases.eventsWithoutRegistration.eventCapture.domain
import org.dhis2.ui.dialogs.bottomsheet.FieldWithIssue
import org.dhis2.ui.dialogs.bottomsheet.IssueType
import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.provider.EventCaptureResourcesProvider
import org.hisp.dhis.android.core.event.EventStatus
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
Expand Down Expand Up @@ -56,6 +57,7 @@ class ConfigureEventCompletionDialogTest {
canComplete = true,
onCompleteMessage = null,
canSkipErrorFix = true,
EventStatus.ACTIVE,
)

// Then Dialog should has Error info
Expand All @@ -79,6 +81,7 @@ class ConfigureEventCompletionDialogTest {
canComplete = true,
onCompleteMessage = null,
canSkipErrorFix = true,
EventStatus.ACTIVE,
)

// Then Dialog should has Error info
Expand All @@ -101,6 +104,7 @@ class ConfigureEventCompletionDialogTest {
canComplete = true,
onCompleteMessage = null,
canSkipErrorFix = true,
EventStatus.ACTIVE,
)

// Then Dialog should has Error info
Expand All @@ -121,6 +125,7 @@ class ConfigureEventCompletionDialogTest {
canComplete = true,
onCompleteMessage = null,
canSkipErrorFix = true,
EventStatus.ACTIVE,
)

// Then Dialog should has Error info
Expand All @@ -141,6 +146,7 @@ class ConfigureEventCompletionDialogTest {
canComplete = true,
onCompleteMessage = WARNING_MESSAGE,
canSkipErrorFix = true,
EventStatus.ACTIVE,
)

// Then Dialog should has Error info
Expand All @@ -161,6 +167,7 @@ class ConfigureEventCompletionDialogTest {
canComplete = false,
onCompleteMessage = ERROR_INFO,
canSkipErrorFix = true,
EventStatus.ACTIVE,
)

// Then Dialog should has Error info
Expand Down
1 change: 1 addition & 0 deletions form/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="saved">Saved!</string>
<string name="review_message">Some fields need your attention.\nDo you want to review the form?</string>
<string name="not_now">Not now</string>
<string name="save_anyway">Save anyway</string>
<string name="keep_editing">Keep editing</string>
<string name="discard_go_back">If you exit now all the information in the form will be discarded.</string>
<string name="field_errors_not_saved_discard">Some fields have errors and they are not saved. \nIf you exit now the changes will be discarded.</string>
Expand Down

0 comments on commit 0cd01c9

Please sign in to comment.