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 PDF config properties in QuestionnaireConfig #3498

Merged
merged 14 commits into from
Sep 27, 2024
Merged
Prev Previous commit
Next Next commit
Address review
  • Loading branch information
FikriMilano committed Sep 17, 2024
commit bf8f581b57f8ac1c8fdfb3847dc9ee6d011df8e2
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,18 @@ import org.smartregister.fhircore.engine.util.extension.interpolate
@Serializable
@Parcelize
data class PdfConfig(
val pdfTitle: String? = null,
val pdfTitleSuffix: String? = null,
val pdfStructureReference: String? = null,
val title: String? = null,
val titleSuffix: String? = null,
val structureReference: String? = null,
val subjectReference: String? = null,
val questionnaireReferences: List<String> = emptyList(),
) : java.io.Serializable, Parcelable {

fun interpolate(computedValuesMap: Map<String, Any>) =
this.copy(
pdfTitle = pdfTitle?.interpolate(computedValuesMap),
pdfStructureReference = pdfStructureReference?.interpolate(computedValuesMap),
title = title?.interpolate(computedValuesMap),
titleSuffix = titleSuffix?.interpolate(computedValuesMap),
structureReference = structureReference?.interpolate(computedValuesMap),
subjectReference = subjectReference?.interpolate(computedValuesMap),
questionnaireReferences = questionnaireReferences.map { it.interpolate(computedValuesMap) },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,30 @@ import org.smartregister.fhircore.engine.util.extension.valueToString
class HtmlPopulator(
questionnaireResponses: List<QuestionnaireResponse>,
) {
// Store answers as key-value pairs with link-id as key
private val answerMap: MutableMap<String, List<QuestionnaireResponseItemAnswerComponent>> =
mutableMapOf()

// Store submitted-date of a questionnaire response with link-id as key
private val submittedDateMap: MutableMap<String, Date> = mutableMapOf()

// Store questionnaire-id related to each questionnaire-response
private val questionnaireIds: MutableList<String> = mutableListOf()
private var answerMap: Map<String, List<QuestionnaireResponseItemAnswerComponent>>
private var submittedDateMap: Map<String, Date>
private var questionnaireIds: List<String>

init {
val answerMap = mutableMapOf<String, List<QuestionnaireResponseItemAnswerComponent>>()
val submittedDateMap = mutableMapOf<String, Date>()
val questionnaireIds = mutableListOf<String>()

questionnaireResponses.forEach { questionnaireResponse ->
val questionnaireId = questionnaireResponse.questionnaire.extractLogicalIdUuid()
val answerMap =
questionnaireResponse.allItems.associateBy(
questionnaireResponse.allItems
.associateBy(
keySelector = { "$questionnaireId/${it.linkId}" },
valueTransform = { it.answer },
)
this.answerMap.putAll(answerMap)
this.submittedDateMap[questionnaireId] = questionnaireResponse.meta.lastUpdated ?: Date()
this.questionnaireIds.add(questionnaireId)
.let { answerMap.putAll(it) }
submittedDateMap[questionnaireId] = questionnaireResponse.meta.lastUpdated ?: Date()
questionnaireIds.add(questionnaireId)
}

this.answerMap = answerMap
this.submittedDateMap = submittedDateMap
this.questionnaireIds = questionnaireIds
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ class PdfLauncherFragment : DialogFragment() {

val pdfConfig = getPdfConfig()

val pdfStructureId = pdfConfig.pdfStructureReference!!.extractLogicalIdUuid()
val pdfTitle =
StringBuilder().append(pdfConfig.pdfTitle ?: getString(R.string.default_html_title))
val pdfTitleSuffix = pdfConfig.pdfTitleSuffix
val structureId = pdfConfig.structureReference!!.extractLogicalIdUuid()
val title =
StringBuilder().append(pdfConfig.title ?: getString(R.string.default_html_title))
val titleSuffix = pdfConfig.titleSuffix
val subjectReference = pdfConfig.subjectReference!!
val questionnaireIds =
pdfConfig.questionnaireReferences.map { it.extractLogicalIdUuid() } ?: emptyList()
Expand All @@ -70,11 +70,11 @@ class PdfLauncherFragment : DialogFragment() {
subjectReference,
)
}
val htmlBinary = pdfLauncherViewModel.retrieveBinary(pdfStructureId)
val htmlBinary = pdfLauncherViewModel.retrieveBinary(structureId)

if (pdfTitleSuffix != null) pdfTitle.append(" - $pdfTitleSuffix")
if (titleSuffix != null) title.append(" - $titleSuffix")

generatePdf(questionnaireResponses, htmlBinary, pdfTitle.toString())
generatePdf(questionnaireResponses, htmlBinary, title.toString())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ class PdfLauncherFragmentTest : RobolectricTest() {

val pdfConfig =
PdfConfig(
pdfTitle = "title",
pdfTitleSuffix = "suffix",
pdfStructureReference = "Binary/id",
title = "title",
titleSuffix = "suffix",
structureReference = "Binary/id",
subjectReference = "Patient/id",
questionnaireReferences = listOf("QuestionnaireResponse/id"),
)
Expand Down Expand Up @@ -98,9 +98,9 @@ class PdfLauncherFragmentTest : RobolectricTest() {

val pdfConfig =
PdfConfig(
pdfTitle = "title",
pdfTitleSuffix = "suffix",
pdfStructureReference = "Binary/id",
title = "title",
titleSuffix = "suffix",
structureReference = "Binary/id",
subjectReference = "Patient/id",
questionnaireReferences = listOf("QuestionnaireResponse/id"),
)
Expand Down Expand Up @@ -135,9 +135,9 @@ class PdfLauncherFragmentTest : RobolectricTest() {

val pdfConfig =
PdfConfig(
pdfTitle = "title",
pdfTitleSuffix = "suffix",
pdfStructureReference = "Binary/id",
title = "title",
titleSuffix = "suffix",
structureReference = "Binary/id",
subjectReference = "Patient/id",
questionnaireReferences = listOf("QuestionnaireResponse/id"),
)
Expand Down Expand Up @@ -172,9 +172,9 @@ class PdfLauncherFragmentTest : RobolectricTest() {

val pdfConfig =
PdfConfig(
pdfTitle = "title",
pdfTitleSuffix = "suffix",
pdfStructureReference = "Binary/id",
title = "title",
titleSuffix = "suffix",
structureReference = "Binary/id",
subjectReference = "Patient/id",
questionnaireReferences = listOf("QuestionnaireResponse/id"),
)
Expand Down
42 changes: 42 additions & 0 deletions docs/engineering/app/configuring/config-types/pdf.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: PDF
---

# PDF configuration

Launch a PDF preview from a defined HTML structure and Questionnaire Responses that contains data to be populated into the PDF. App user then can save that previewed PDF into their device as a file.

:::info
The [PDF Generation](https://docs.opensrp.io/engineering/app/configuring/pdf-generation) feature is responsible in populating data from Questionnaire Response to HTML.
:::

## Sample JSON

```json
{
"trigger": "ON_CLICK",
"workflow": "LAUNCH_PDF_GENERATION",
"pdfConfig": {
"title": "Depression Report",
"titleSuffix": "James",
"structureReference": "Binary/b1234",
"subjectReference": "Patient/p1234",
"questionnaireReferences": [
"Questionnaire/q1",
"Questionnaire/q2",
"Questionnaire/q3",
"Questionnaire/q4"
]
}
}
```

## Config properties

|Property | Description | Required | Default |
|--|--|:--:|:--:|
`title` | The saved PDF file title | No | `null` |
`titleSuffix` | The saved PDF file title suffix; usually contains the subject name | No | `null` |
`structureReference` | The Binary resource that contains HTML structure of the PDF | Yes | `null` |
`subjectReference` | The subject/patient/child who the answers from the Questionnaire Response apply to | Yes | `null` |
`questionnaireReferences` | The Questionnaire resource that is related to a Questionnaire Response which contains data to be populated into the PDF | Yes | `[]` |
36 changes: 24 additions & 12 deletions docs/engineering/app/configuring/pdf-generation.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# PDF Generation

## Overview
The PDF generation feature using the `HtmlPopulator` class simplifies the process of dynamically populating HTML templates with data from a QuestionnaireResponse, making it easy to generate customized content based on user responses.
The PDF generation feature is using the `HtmlPopulator` class that simplifies the process of dynamically populating HTML templates with data from a QuestionnaireResponse, making it easy to generate customized content based on user responses.

The `HtmlPopulator` class is utilized by replacing custom tags with data from a QuestionnaireResponse. It supports tags such as `@is-not-empty`, `@answer-as-list`, `@answer`, `@submitted-date`, and `@contains`.
The `HtmlPopulator` class is utilized by replacing custom tags with data from a QuestionnaireResponse. Currently supported tags are: `@is-not-empty`, `@answer-as-list`, `@answer`, `@submitted-date`, `@contains`, and `@is-questionnaire-submitted`.

The `HtmlPopulator` class allows multiple QuestionnaireResponses to be populated into the HTML. Please use `questionnaireId/linkId` format to describe which linkId from which Questionnaire you want to retrieve the answer from.

## Usage
Below are examples of how each custom tag can be used in an HTML template and the expected output.
Expand All @@ -12,7 +14,7 @@ Below are examples of how each custom tag can be used in an HTML template and th

#### Template HTML:
``` html
<p>@is-not-empty('linkId')This content will be included if the answer exists.@is-not-empty('linkId')</p>
<p>@is-not-empty('questionnaireId/linkId')This content will be included if the answer exists.@is-not-empty('questionnaireId/linkId')</p>
```

#### Explanation:
Expand All @@ -23,7 +25,7 @@ The `@is-not-empty` tag checks if there is an answer for the specified `linkId`.
#### Template HTML:
``` html
<ul>
@answer-as-list('linkId')
@answer-as-list('questionnaireId/linkId')
</ul>
```

Expand All @@ -34,7 +36,7 @@ The `@answer-as-list` tag will be replaced with a list of answers for the specif

#### Template HTML:
``` html
<p>The answer is: @answer('linkId')</p>
<p>The answer is: @answer('questionnaireId/linkId')</p>
```

#### Explanation:
Expand All @@ -44,35 +46,45 @@ The `@answer tag` will be replaced with the answer for the specified `linkId`. I

#### Template HTML:
``` html
<p>Submitted on: @submitted-date('MM/dd/yyyy')</p>
<p>Submitted on: @submitted-date('questionnaireId','MM/dd/yyyy')</p>
```

#### Explanation:
The `@submitted-date` tag will be replaced with the formatted submission date. If no format is provided, a default date format will be used.
The `@submitted-date` tag will be replaced with the formatted submission date from the specified `questionnaireId`. If no format is provided, a default date format will be used.

### @contains

#### Template HTML:
``` html
<p>@contains('linkId', 'indicator')This content will be included if the indicator is found.@contains('linkId', 'indicator')</p>
<p>@contains('questionnaireId/linkId', 'indicator')This content will be included if the indicator is found.@contains('questionnaireId/linkId', 'indicator')</p>
```

#### Explanation:
The `@contains` tag checks if the specified `linkId` contains the given `indicator`. If the indicator is found, the content within the tags will be included in the final HTML. If the indicator is not found, the content will be removed.

### @is-questionnaire-submitted

#### Template HTML:
``` html
<p>@is-questionnaire-submitted('questionnaireId')This content will only show if the Questionnaire Response of the described Questionnaire exists.@is-questionnaire-submitted('questionnaireId')</p>
```

#### Explanation:
The `@is-questionnaire-submitted` tag checks if the specified `questionnaireId` has been submitted i.e. the matching Questionnaire Response is passed to the HtmlPopulator class. If the Questionnaire Response is found, the content within the tags will be included in the final HTML. If the indicator is not found, the content will be removed.

## Example

### Input HTML Template
``` html
<html>
<body>
<p>@is-not-empty('name')Name: @answer('name')@is-not-empty('name')</p>
<p>@is-not-empty('Q123/name')Name: @answer('Q123/name')@is-not-empty('Q123/name')</p>
<p>Hobbies:</p>
<ul>
@answer-as-list('hobbies')
@answer-as-list('Q123/hobbies')
</ul>
<p>Submitted on: @submitted-date('yyyy-MM-dd')</p>
<p>@contains('age', '30')This person is 30 years old.@contains('age', '30')</p>
<p>Submitted on: @submitted-date('Q123','yyyy-MM-dd')</p>
<p>@contains('Q123/age', '30')This person is 30 years old.@contains('Q123/age', '30')</p>
</body>
</html>
```
Expand Down