Skip to content

Commit

Permalink
✨ FWF-3257: Added custom template PDF downloads for formadapter. Upda…
Browse files Browse the repository at this point in the history
…ted ReadMe with bundle PDF example (#2067)

* FWF-3257: [Feature] Updated get_form method

* FWF-3257: [Feature] Added custom template PDF download for form adapter

* FWF-3257: [Feature] Added example for generate bundle PDF with generic custom theme.

* FWF-3257: [Feature] Pytest fix

* Add UserDetails to chrome driver local storage
  • Loading branch information
auslin-aot authored May 30, 2024
1 parent c6fa278 commit f179aa7
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 39 deletions.
Binary file added .images/export_pdf_bundle_template.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,13 @@ def get_user_resource_ids(self):
current_app.logger.info("Fetching user resource ids...")
return self._invoke_service(url, headers={}, method='GET')

def get_form(self, data, formio_token):
def get_form(self, query_params, formio_token):
"""Get request to formio API to get form details."""
return self.get_form_by_id(data["form_id"], formio_token)
headers = {"Content-Type": "application/json", "x-jwt-token": formio_token}
url = f"{self.base_url}/form"
if query_params:
url = f"{url}?{query_params}"
return self._invoke_service(url, headers, method='GET')

def get_form_by_id(self, form_id: str, formio_token):
"""Get request to formio API to get form details."""
Expand Down
21 changes: 21 additions & 0 deletions forms-flow-api-utils/src/formsflow_api_utils/utils/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from seleniumwire import webdriver
from .user_context import _get_token_info


def send_devtools(driver, cmd, params=None):
Expand Down Expand Up @@ -64,6 +65,26 @@ def interceptor(request):

driver.get(path)

# Set user details to local storage
token_info = _get_token_info()
user_details = {
"sub": token_info.get("sub", None),
"email_verified": token_info.get("email_verified", False),
"role": token_info.get("roles", None) or token_info.get(
"role", None
),
"name": token_info.get("name", None),
"groups": token_info.get("groups", None),
"preferred_username": token_info.get("preferred_username", None),
"given_name": token_info.get("given_name", None),
"family_name": token_info.get("family_name", None),
"email": token_info.get("email", None),
"tenantKey": token_info.get("tenantKey", None),
}

user_details_json = json.dumps(user_details)
driver.execute_script(f"window.localStorage.setItem('UserDetails', '{user_details_json}')")

try:
if "wait" in args:
delay = 30 # seconds
Expand Down
55 changes: 55 additions & 0 deletions forms-flow-documents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,61 @@ The example template will produce a PDF in a tabular form
[Preview](https://github.com/sreehari-aot/forms-flow-ai/blob/pdf-template/.images/export_pdf_template_1.pdf)


Example template for bundle

In case of a bundle, the form object contains a list of forms along with the submission data.
```
{% extends "template.html" %}
{% block links %}
<style type="text/css">
.container{
margin-top: 10px;
}
.head{
text-align: center;
margin-bottom: 10px;
}
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #dddddd;
text-align: left;
padding: 15px;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
{% for form_dict in form %}
{% for form_key, form_value in form_dict.items() %}
<div class="head">
<h1>{{ form_value['form']['title'] }}</h1>
</div>
<table>
{% for item in form_value['data'] %}
<TR>
<TD>{{form_value['data'][item]['label']}}</TD>
{% if is_signature(form_value['data'][item]['value']) %}
<TD><img src="{{form_value['data'][item]['value']}}" /></TD>
{% else %}
<TD>{{form_value['data'][item]['value']}}</TD>
{% endif %}
</TR>
{% endfor %}
</table>
{% endfor %}
{% endfor %}
</div>
{% endblock %}
```
The example template will generate a PDF with a table for each form.

[Preview](https://github.com/auslin-aot/forms-flow-ai/blob/feature/FWF-3257-export-pdf-bundle/.images/export_pdf_bundle_template.pdf)

TODO: Provide details for `form` object

TODO: Add usecases
Expand Down
2 changes: 1 addition & 1 deletion forms-flow-documents/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ ecdsa==0.18.0
flask-jwt-oidc==0.3.0
flask-marshmallow==1.2.1
flask-restx==1.3.0
formsflow_api_utils @ git+https://github.com/AOT-Technologies/forms-flow-ai.git@develop#subdirectory=forms-flow-api-utils
formsflow_api_utils @ git+https://github.com/auslin-aot/forms-flow-ai.git@feature/FWF-3257-export-pdf-bundle#subdirectory=forms-flow-api-utils
gunicorn==21.2.0
h11==0.14.0
h2==4.1.0
Expand Down
2 changes: 1 addition & 1 deletion forms-flow-documents/requirements/prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ PyJWT
selenium
selenium-wire
nested-lookup
git+https://github.com/AOT-Technologies/forms-flow-ai.git@develop#egg=formsflow_api_utils&subdirectory=forms-flow-api-utils
git+https://github.com/auslin-aot/forms-flow-ai.git@feature/FWF-3257-export-pdf-bundle#egg=formsflow_api_utils&subdirectory=forms-flow-api-utils
2 changes: 1 addition & 1 deletion forms-flow-documents/src/formsflow_documents/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def create_app(
when=os.getenv("API_LOG_ROTATION_WHEN", "d"),
interval=int(os.getenv("API_LOG_ROTATION_INTERVAL", "1")),
backupCount=int(os.getenv("API_LOG_BACKUP_COUNT", "7")),
configure_log_file=app.config["CONFIGURE_LOGS"]
configure_log_file=app.config["CONFIGURE_LOGS"],
)
app.logger.propagate = False
logging.log.propagate = False
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""API endpoints for managing form resource."""

import string
from http import HTTPStatus

Expand Down
53 changes: 36 additions & 17 deletions forms-flow-documents/src/formsflow_documents/services/pdf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Helper module for PDF export."""

import json
import os
import urllib.parse
Expand Down Expand Up @@ -70,24 +71,42 @@ def __get_submission_data(self) -> Any:

def __get_form_data(self) -> Any:
"""Returns the form data from formio."""
return self.formio.get_form(
{"form_id": self.form_id}, self.__get_formio_access_token()
return self.formio.get_form_by_id(
self.form_id, self.__get_formio_access_token()
)

def __get_chrome_driver_path(self) -> str:
"""Returns the configured chrome driver path."""
return self.__chrome_driver_path

def __get_headers(self, token):
"""Returns the headers."""
return {"Authorization": token, "content-type": "application/json"}

def __fetch_custom_submission_data(self, token: str) -> Any:
"""Returns the submission data from form adapter."""
sub_url = self.__get_custom_submission_url()
submission_url = (
f"{sub_url}/form/" + self.form_id + "/submission/" + self.submission_id
)
current_app.logger.debug(f"Fetching custom submission data..{submission_url}")
headers = self.__get_headers(token)
response = requests.get(submission_url, headers=headers, timeout=HTTP_TIMEOUT)
current_app.logger.debug(
f"Custom submission response code: {response.status_code}"
)
data = {}
if response.status_code == 200:
data = response.json()
return data

def __get_form_and_submission_urls(self, token: str) -> Tuple[str, str, str]:
"""Returns the appropriate form and submission url based on the config."""
"""Returns the appropriate form url and submission data based on the config."""
form_io_url = self.__get_formio_url()
current_app.logger.debug("Fetching form and submission data..")
if self.__is_form_adapter():
sub_url = self.__get_custom_submission_url()
form_url = form_io_url + "/form/" + self.form_id
submission_url = (
sub_url + "/form/" + self.form_id + "/submission/" + self.submission_id
)
auth_token = token
submission_data = self.__fetch_custom_submission_data(token)
else:
form_url = (
form_io_url
Expand All @@ -96,25 +115,21 @@ def __get_form_and_submission_urls(self, token: str) -> Tuple[str, str, str]:
+ "/submission/"
+ self.submission_id
)
submission_url = None
auth_token = None
return (form_url, submission_url, auth_token)
submission_data = None
return (form_url, submission_data)

def __get_template_params(self, token: str) -> dict:
"""Returns the jinja template parameters for pdf export with formio renderer."""
form_io_url = self.__get_formio_url()
(form_url, submission_url, auth_token) = self.__get_form_and_submission_urls(
token
)
(form_url, submission_data) = self.__get_form_and_submission_urls(token)
return {
"form": {
"base_url": form_io_url,
"project_url": form_io_url,
"form_url": form_url,
"token": self.__get_formio_access_token(),
"submission_url": submission_url,
"form_adapter": self.__is_form_adapter(),
"auth_token": auth_token,
"submission_data": submission_data,
}
}

Expand Down Expand Up @@ -237,7 +252,11 @@ def get_render_data(
if template_variable_name:
return self.__read_json(template_variable_name)

submission_data = self.__get_submission_data()
submission_data = (
self.__fetch_custom_submission_data(token)
if self.__is_form_adapter()
else self.__get_submission_data()
)
form_data = self.__get_form_data()

return self.__get_formatted_data(form_data, submission_data)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
const form_options = { readOnly: true, renderMode: "flat" };
// Function to get submission data if formadapter is enabled
async function fetchSubmission() {
const submission = await fetch(form_info.submission_url);
// const submission = await fetch(form_info.submission_url, {
// headers: {
// "Content-Type": "application/json",
// Authorization: form_info.auth_token,
// },
// });
const result = await submission.json();
return result;
}

// Help web driver to idetify the form rendered completely.
function formReady() {
Expand All @@ -24,12 +12,10 @@ function renderFormWithSubmission() {
form_info.form_url,
form_options
).then((form) => {
fetchSubmission().then((submission) => {
form.submission = submission;
form.submission = form_info.submission_data;
form.ready.then(() => {
formReady();
});
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Constants file needed for the static values.
"""

from enum import Enum
from http import HTTPStatus

Expand Down
1 change: 1 addition & 0 deletions forms-flow-documents/src/formsflow_documents/utils/util.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Utility module for Document generation."""

import base64
import urllib.parse

Expand Down
3 changes: 3 additions & 0 deletions forms-flow-documents/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Common setup and fixtures for the pytest suite used by this service."""
import time

import pytest
from unittest.mock import patch
from formsflow_api_utils.utils import jwt as _jwt
Expand Down Expand Up @@ -67,6 +69,7 @@ def auto(docker_services, app):

docker_services.start("forms")
docker_services.start("proxy")
time.sleep(15)


@pytest.fixture(scope="session")
Expand Down
3 changes: 1 addition & 2 deletions forms-flow-documents/tests/unit/services/test_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ def test_get_render_data_without_template_and_template_variable(self, app, mock_
assert "project_url" in render_data["form"]
assert "form_url" in render_data["form"]
assert "token" in render_data["form"]
assert "submission_url" in render_data["form"]
assert "submission_data" in render_data["form"]
assert "form_adapter" in render_data["form"]
assert "auth_token" in render_data["form"]

def test_get_render_data_with_template_and_template_variable(self, app):
"""Test get_render_data method for the request with template and template variables."""
Expand Down

0 comments on commit f179aa7

Please sign in to comment.