Skip to content

Commit

Permalink
Delete saved draft feature (#3631)
Browse files Browse the repository at this point in the history
      * Add delete draft workflow

* Add view model

* Add logic for soft deleteing drafts

* Run the search for QuestinnaireResponse in view model scope

* Move searchQuestionnaireResponse function to default repository

* Use interpolated questionnaire config when launching delete draft fragment

* Ensure delete draft db calls complete before dialog is dismissed

* Add flag to indicate a drft has been deleted

* Use aduti event to keep track of deleted drafts

* Rename delete draft questionnaire workflow
move delete draft classes to questionnaire package

* Rename draft dialog fragment and view model

* Add data class to hold alert dialog button properties
Add ability to set alert dialog button color

* Fix failing tests

* Update CHANGELOG.md

* Verify filtering by encounter works when when searching for latest QR

* Run spotless Apply

* Update test name

Co-authored-by: Martin Ndegwa <[email protected]>

* Add documentation for delete draft functionality

* Add qustionnaire draft dialog view tests

* Run questionnaire soft deletion and audit event creation in a transaction block

* Update docs

---------

Co-authored-by: Martin Ndegwa <[email protected]>
  • Loading branch information
Rkareko and ndegwamartin authored Dec 4, 2024
1 parent 64f8fc7 commit bcff526
Show file tree
Hide file tree
Showing 20 changed files with 877 additions and 226 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1. Added a new class (PdfGenerator) for generating PDF documents from HTML content using Android's WebView and PrintManager
2. Introduced a new class (HtmlPopulator) to populate HTML templates with data from a Questionnaire Response
3. Implemented functionality to launch PDF generation using a configuration setup
- Added Save draft MVP functionality
- Added Save draft MVP functionality
- Added Delete saved draft feature

## [1.1.0] - 2024-02-15

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,7 @@ enum class ApplicationWorkflow {

/** A workflow to launch pdf generation */
LAUNCH_PDF_GENERATION,

/** A workflow to launch delete draft questionnaires */
DELETE_DRAFT_QUESTIONNAIRE,
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ import org.hl7.fhir.r4.model.Group
import org.hl7.fhir.r4.model.IdType
import org.hl7.fhir.r4.model.Location
import org.hl7.fhir.r4.model.Patient
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Reference
import org.hl7.fhir.r4.model.RelatedPerson
import org.hl7.fhir.r4.model.Resource
Expand Down Expand Up @@ -1292,6 +1294,48 @@ constructor(
)
.mapTo(ArrayDeque()) { it.resource }

/**
* This function searches and returns the latest [QuestionnaireResponse] for the given
* [resourceId] that was extracted from the [Questionnaire] identified as [questionnaireId].
* Returns null if non is found.
*/
suspend fun searchQuestionnaireResponse(
resourceId: String,
resourceType: ResourceType,
questionnaireId: String,
encounterId: String?,
questionnaireResponseStatus: String? = null,
): QuestionnaireResponse? {
val search =
Search(ResourceType.QuestionnaireResponse).apply {
filter(
QuestionnaireResponse.SUBJECT,
{ value = resourceId.asReference(resourceType).reference },
)
filter(
QuestionnaireResponse.QUESTIONNAIRE,
{ value = questionnaireId.asReference(ResourceType.Questionnaire).reference },
)
if (!encounterId.isNullOrBlank()) {
filter(
QuestionnaireResponse.ENCOUNTER,
{
value =
encounterId.extractLogicalIdUuid().asReference(ResourceType.Encounter).reference
},
)
}
if (!questionnaireResponseStatus.isNullOrBlank()) {
filter(
QuestionnaireResponse.STATUS,
{ value = of(questionnaireResponseStatus) },
)
}
}
val questionnaireResponses: List<QuestionnaireResponse> = search(search)
return questionnaireResponses.maxByOrNull { it.meta.lastUpdated }
}

/**
* A wrapper data class to hold search results. All related resources are flattened into one Map
* including the nested related resources as required by the Rules Engine facts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ enum class AlertIntent {

data class AlertDialogListItem(val key: String, val value: String)

data class AlertDialogButton(
val listener: ((d: DialogInterface) -> Unit)? = null,
@StringRes val text: Int? = null,
val color: Int? = null,
)

object AlertDialogue {
private val ITEMS_LIST_KEY = "alert_dialog_items_list"

Expand All @@ -51,12 +57,9 @@ object AlertDialogue {
alertIntent: AlertIntent,
message: CharSequence,
title: String? = null,
confirmButtonListener: ((d: DialogInterface) -> Unit)? = null,
@StringRes confirmButtonText: Int = R.string.questionnaire_alert_confirm_button_title,
neutralButtonListener: ((d: DialogInterface) -> Unit)? = null,
@StringRes neutralButtonText: Int = R.string.questionnaire_alert_neutral_button_title,
negativeButtonListener: ((d: DialogInterface) -> Unit)? = null,
@StringRes negativeButtonText: Int = R.string.questionnaire_alert_negative_button_title,
confirmButton: AlertDialogButton? = null,
neutralButton: AlertDialogButton? = null,
negativeButton: AlertDialogButton? = null,
cancellable: Boolean = false,
options: Array<AlertDialogListItem>? = null,
): AlertDialog {
Expand All @@ -67,22 +70,48 @@ object AlertDialogue {
setView(view)
title?.let { setTitle(it) }
setCancelable(cancellable)
neutralButtonListener?.let {
setNeutralButton(neutralButtonText) { d, _ -> neutralButtonListener.invoke(d) }
neutralButton?.listener?.let {
setNeutralButton(
neutralButton.text ?: R.string.questionnaire_alert_neutral_button_title,
) { d, _ ->
neutralButton.listener.invoke(d)
}
}
confirmButtonListener?.let {
setPositiveButton(confirmButtonText) { d, _ -> confirmButtonListener.invoke(d) }
confirmButton?.listener?.let {
setPositiveButton(
confirmButton.text ?: R.string.questionnaire_alert_confirm_button_title,
) { d, _ ->
confirmButton.listener.invoke(d)
}
}
negativeButtonListener?.let {
setNegativeButton(negativeButtonText) { d, _ -> negativeButtonListener.invoke(d) }
negativeButton?.listener?.let {
setNegativeButton(
negativeButton.text ?: R.string.questionnaire_alert_negative_button_title,
) { d, _ ->
negativeButton.listener.invoke(d)
}
}
options?.run { setSingleChoiceItems(options.map { it.value }.toTypedArray(), -1, null) }
}
.show()

val neutralButtonColor = neutralButton?.color ?: R.color.grey_text_color
dialog
.getButton(AlertDialog.BUTTON_NEUTRAL)
.setTextColor(ContextCompat.getColor(context, R.color.grey_text_color))
.setTextColor(ContextCompat.getColor(context, neutralButtonColor))

if (confirmButton?.color != null) {
dialog
.getButton(AlertDialog.BUTTON_POSITIVE)
.setTextColor(ContextCompat.getColor(context, confirmButton.color))
}

if (negativeButton?.color != null) {
dialog
.getButton(AlertDialog.BUTTON_NEGATIVE)
.setTextColor(ContextCompat.getColor(context, negativeButton.color))
}

dialog.findViewById<View>(R.id.pr_circular)?.apply {
if (alertIntent == AlertIntent.PROGRESS) {
this.show()
Expand Down Expand Up @@ -115,8 +144,11 @@ object AlertDialogue {
alertIntent = AlertIntent.INFO,
message = message,
title = title,
confirmButtonListener = confirmButtonListener,
confirmButtonText = confirmButtonText,
confirmButton =
AlertDialogButton(
listener = confirmButtonListener,
text = confirmButtonText,
),
)
}

Expand All @@ -126,8 +158,11 @@ object AlertDialogue {
alertIntent = AlertIntent.ERROR,
message = message,
title = title,
confirmButtonListener = { d -> d.dismiss() },
confirmButtonText = R.string.questionnaire_alert_ack_button_title,
confirmButton =
AlertDialogButton(
listener = { d -> d.dismiss() },
text = R.string.questionnaire_alert_ack_button_title,
),
)
}

Expand Down Expand Up @@ -160,25 +195,28 @@ object AlertDialogue {
alertIntent = AlertIntent.CONFIRM,
message = context.getString(message),
title = title?.let { context.getString(it) },
confirmButtonListener = confirmButtonListener,
confirmButtonText = confirmButtonText,
neutralButtonListener = { d -> d.dismiss() },
neutralButtonText = R.string.questionnaire_alert_neutral_button_title,
confirmButton =
AlertDialogButton(
listener = confirmButtonListener,
text = confirmButtonText,
),
neutralButton =
AlertDialogButton(
listener = { d -> d.dismiss() },
text = R.string.questionnaire_alert_neutral_button_title,
),
cancellable = false,
options = options?.toTypedArray(),
)
}

fun showCancelAlert(
fun showThreeButtonAlert(
context: Context,
@StringRes message: Int,
@StringRes title: Int? = null,
confirmButtonListener: ((d: DialogInterface) -> Unit),
@StringRes confirmButtonText: Int,
neutralButtonListener: ((d: DialogInterface) -> Unit),
@StringRes neutralButtonText: Int,
negativeButtonListener: ((d: DialogInterface) -> Unit),
@StringRes negativeButtonText: Int,
confirmButton: AlertDialogButton? = null,
neutralButton: AlertDialogButton? = null,
negativeButton: AlertDialogButton? = null,
cancellable: Boolean = true,
options: List<AlertDialogListItem>? = null,
): AlertDialog {
Expand All @@ -187,12 +225,9 @@ object AlertDialogue {
alertIntent = AlertIntent.CONFIRM,
message = context.getString(message),
title = title?.let { context.getString(it) },
confirmButtonListener = confirmButtonListener,
confirmButtonText = confirmButtonText,
neutralButtonListener = neutralButtonListener,
neutralButtonText = neutralButtonText,
negativeButtonListener = negativeButtonListener,
negativeButtonText = negativeButtonText,
confirmButton = confirmButton,
neutralButton = neutralButton,
negativeButton = negativeButton,
cancellable = cancellable,
options = options?.toTypedArray(),
)
Expand Down
4 changes: 4 additions & 0 deletions android/engine/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
<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_ack_button_title">OK</string>
<string name="questionnaire_alert_open_draft_button_title">Open draft</string>
<string name="questionnaire_alert_delete_draft_button_title">Delete draft</string>
<string name="username">Username</string>
<string name="password">Password</string>
<string name="forgot_password">Forgot Password</string>
Expand Down Expand Up @@ -202,4 +204,6 @@
<string name="apply_filter">APPLY FILTER</string>
<string name="questionnaire_save_draft_title">Save draft changes</string>
<string name="questionnaire_save_draft_message">Do you want to save draft changes?</string>
<string name="open_draft_changes_title">Open draft changes</string>
<string name="open_draft_changes_message">You can reopen a saved draft form to continue or delete it</string>
</resources>
Loading

0 comments on commit bcff526

Please sign in to comment.