From 0250b77c780648eb42dfe0459f68493fe8206b9c Mon Sep 17 00:00:00 2001 From: auslin-aot <99173163+auslin-aot@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:00:30 +0530 Subject: [PATCH 1/2] FWF-3748: [Feature] Added submissions cout for form list with create_submissions permission --- .../src/formsflow_api/models/application.py | 17 +++++++++++++++++ .../formsflow_api/models/form_process_mapper.py | 1 + .../resources/form_process_mapper.py | 2 ++ .../schemas/form_process_mapper.py | 3 +++ .../services/form_process_mapper.py | 14 +++++++++++++- 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/forms-flow-api/src/formsflow_api/models/application.py b/forms-flow-api/src/formsflow_api/models/application.py index 15a317aaed..bc2f2332cd 100644 --- a/forms-flow-api/src/formsflow_api/models/application.py +++ b/forms-flow-api/src/formsflow_api/models/application.py @@ -295,6 +295,23 @@ def find_by_form_names( # pylint: disable=too-many-arguments, too-many-position pagination = query.paginate(page=page_no, per_page=limit, error_out=False) return pagination.items, total_count + @classmethod + def find_applications_count_by_parent_form_id_user( + cls, parent_form_id, user_name, tenant + ): + """Fetch application count based on parent_form_id and user who submitted the application.""" + count_query = ( + db.session.query(func.count(Application.id)) + .join(FormProcessMapper, cls.form_process_mapper_id == FormProcessMapper.id) + .filter( + FormProcessMapper.parent_form_id == parent_form_id, + FormProcessMapper.tenant == tenant, + cls.created_by == user_name, + cls.application_status != DRAFT_APPLICATION_STATUS, + ) + ) + return count_query.scalar() + @classmethod def find_applications_by_auth_formids_user( # pylint: disable=too-many-arguments, too-many-positional-arguments cls, diff --git a/forms-flow-api/src/formsflow_api/models/form_process_mapper.py b/forms-flow-api/src/formsflow_api/models/form_process_mapper.py index 6de3bea066..9935682fa4 100644 --- a/forms-flow-api/src/formsflow_api/models/form_process_mapper.py +++ b/forms-flow-api/src/formsflow_api/models/form_process_mapper.py @@ -297,6 +297,7 @@ def find_all_active_by_formid( cls.form_name, cls.modified, cls.description, + cls.parent_form_id, ) limit = total_count if limit is None else limit query = query.paginate(page=page_number, per_page=limit, error_out=False) diff --git a/forms-flow-api/src/formsflow_api/resources/form_process_mapper.py b/forms-flow-api/src/formsflow_api/resources/form_process_mapper.py index af4496cf2b..aff6dd3e31 100644 --- a/forms-flow-api/src/formsflow_api/resources/form_process_mapper.py +++ b/forms-flow-api/src/formsflow_api/resources/form_process_mapper.py @@ -300,6 +300,7 @@ def get(): # pylint: disable=too-many-locals ) sort_by = sort_by.split(",") sort_order = sort_order.split(",") + include_submissions_count = dict_data.get("include_submissions_count", False) if form_type: form_type = form_type.split(",") if search: @@ -319,6 +320,7 @@ def get(): # pylint: disable=too-many-locals is_active=is_active, is_designer=is_designer, active_forms=active_forms, + include_submissions_count=include_submissions_count, ) return ( ( diff --git a/forms-flow-api/src/formsflow_api/schemas/form_process_mapper.py b/forms-flow-api/src/formsflow_api/schemas/form_process_mapper.py index 5830b1cde7..7d0744e749 100644 --- a/forms-flow-api/src/formsflow_api/schemas/form_process_mapper.py +++ b/forms-flow-api/src/formsflow_api/schemas/form_process_mapper.py @@ -58,3 +58,6 @@ class FormProcessMapperListRequestSchema(FormProcessMapperListReqSchema): ignore_designer = fields.Bool( data_key="showForOnlyCreateSubmissionUsers", required=False ) + include_submissions_count = fields.Bool( + data_key="includeSubmissionsCount", required=False + ) diff --git a/forms-flow-api/src/formsflow_api/services/form_process_mapper.py b/forms-flow-api/src/formsflow_api/services/form_process_mapper.py index ac06e6d06a..c53c8b58bc 100644 --- a/forms-flow-api/src/formsflow_api/services/form_process_mapper.py +++ b/forms-flow-api/src/formsflow_api/services/form_process_mapper.py @@ -8,6 +8,7 @@ from flask import current_app from formsflow_api_utils.exceptions import BusinessException from formsflow_api_utils.services.external import FormioService +from formsflow_api_utils.utils import CREATE_SUBMISSIONS from formsflow_api_utils.utils.enums import FormProcessMapperStatus from formsflow_api_utils.utils.user_context import UserContext, user_context @@ -50,6 +51,7 @@ def get_all_forms( # pylint: disable=too-many-positional-arguments is_active, is_designer: bool, active_forms: bool, + include_submissions_count: bool, **kwargs, ): # pylint: disable=too-many-arguments, too-many-locals """Get all forms.""" @@ -90,8 +92,18 @@ def get_all_forms( # pylint: disable=too-many-positional-arguments **designer_filters if is_designer else {}, ) mapper_schema = FormProcessMapperSchema() + mappers_response = mapper_schema.dump(mappers, many=True) + if include_submissions_count and CREATE_SUBMISSIONS in user.roles: + current_app.logger.debug("Fetching submissions count..") + for mapper in mappers_response: + mapper["submissionsCount"] = ( + Application.find_applications_count_by_parent_form_id_user( + mapper["parentFormId"], user.user_name, user.tenant_key + ) + ) + return ( - mapper_schema.dump(mappers, many=True), + mappers_response, get_all_mappers_count, ) From 58771f36b0201ee2de5bea5da7e1e4b3421352c8 Mon Sep 17 00:00:00 2001 From: auslin-aot <99173163+auslin-aot@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:42:56 +0530 Subject: [PATCH 2/2] FWF-3748: [Feature] Testcase added --- .../unit/api/test_form_process_mapper.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/forms-flow-api/tests/unit/api/test_form_process_mapper.py b/forms-flow-api/tests/unit/api/test_form_process_mapper.py index a5910cb4cf..2f357a22d7 100644 --- a/forms-flow-api/tests/unit/api/test_form_process_mapper.py +++ b/forms-flow-api/tests/unit/api/test_form_process_mapper.py @@ -16,6 +16,7 @@ from formsflow_api.services import FormHistoryService from tests.utilities.base_test import ( get_application_create_payload, + get_draft_create_payload, get_form_request_payload, get_formio_form_request_payload, get_token, @@ -778,3 +779,62 @@ def test_unpublish(app, client, session, jwt, mock_redis_client, create_mapper): mock_post.return_value = mock_response response = client.post(f"/form/{mapper_id}/unpublish", headers=headers) assert response.status_code == 200 + + +def test_form_list_submission_count(app, client, session, jwt, create_mapper): + """Tests the form list endpoint with includeSubmissionsCount query param.""" + token = get_token(jwt, role=CREATE_DESIGNS) + headers = { + "Authorization": f"Bearer {token}", + "content-type": "application/json", + } + + form_id = create_mapper["formId"] + auth_payload = { + "resourceId": "1234", + "resourceDetails": {}, + "roles": [], + } + # create authorization for the form. + client.post( + "/authorizations/form", headers=headers, data=json.dumps(auth_payload) + ) + client.post( + "/authorizations/designer", headers=headers, data=json.dumps(auth_payload) + ) + client.post( + "/authorizations/application", headers=headers, data=json.dumps(auth_payload) + ) + token = get_token(jwt, role=CREATE_SUBMISSIONS) + headers = { + "Authorization": f"Bearer {token}", + "content-type": "application/json", + } + # create application. + rv = client.post( + "/application/create", + headers=headers, + json=get_application_create_payload(form_id), + ) + assert rv.status_code == 201 + + # create draft + client.post("/draft", headers=headers, json=get_draft_create_payload(form_id)) + + token = get_token(jwt, role=CREATE_SUBMISSIONS) + headers = {"Authorization": f"Bearer {token}", "content-type": "application/json"} + # submissionsCount exclude draft and return submission count + response = client.get("/form?includeSubmissionsCount=true", headers=headers) + assert response.status_code == 200 + forms = response.json["forms"] + assert len(forms) == 1 + assert forms[0]["submissionsCount"] == 1 + + # Assert form list api with no create_submissions permission. + token = get_token(jwt, role=CREATE_DESIGNS) + headers = {"Authorization": f"Bearer {token}", "content-type": "application/json"} + response = client.get("/form?includeSubmissionsCount=true", headers=headers) + assert response.status_code == 200 + forms = response.json["forms"] + assert len(forms) == 1 + assert "submissionsCount" not in forms[0]