Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…low-ai into feature/fwf-3749-application-get-api-changes
  • Loading branch information
auslin-aot committed Jan 21, 2025
2 parents 32d8876 + f4feb61 commit f9bb040
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 15 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/trivy-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ jobs:
- { name: "redash", tag: "24.04.0" }
- { name: "forms-flow-data-analysis-api", tag: "latest" }
- { name: "forms-flow-documents-api", tag: "latest" }

steps:
- name: Authenticate with Docker Hub
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_ACCESS_TOKEN }}
run: |
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- name: Install Trivy
run: |
sudo apt-get update
Expand Down
17 changes: 17 additions & 0 deletions forms-flow-api/src/formsflow_api/models/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,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.is_draft.is_(False),
)
)
return count_query.scalar()

@classmethod
def find_applications_by_auth_formids_user( # pylint: disable=too-many-arguments, too-many-positional-arguments
cls,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 (
(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
14 changes: 13 additions & 1 deletion forms-flow-api/src/formsflow_api/services/form_process_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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,
)

Expand Down
60 changes: 60 additions & 0 deletions forms-flow-api/tests/unit/api/test_form_process_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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]
12 changes: 5 additions & 7 deletions forms-flow-web/src/components/CustomComponents/SortableHeader.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from "react-i18next";
import { SortIcon } from "@formsflow/components";
import { StyleServices } from "@formsflow/service";

const SortableHeader = ({ columnKey, title, currentSort, handleSort,className = "" }) => {
const { t } = useTranslation();
Expand All @@ -11,6 +13,7 @@ const SortableHeader = ({ columnKey, title, currentSort, handleSort,className =
handleSort(columnKey);
}
};
const grayColor = StyleServices.getCSSVariable('--ff-gray-400');
return (
<button
className={`button-as-div ${className}`}
Expand All @@ -21,13 +24,8 @@ const SortableHeader = ({ columnKey, title, currentSort, handleSort,className =
aria-label={`${title}-header-btn`}
>
<span className="mt-1">{t(title)}</span>
<span>
<i
data-testid={`${columnKey}-${sortedOrder}-sort-icon`}
className={`fa fa-arrow-${sortedOrder === "asc" ? "up" : "down"} sort-icon fs-16 ms-2`}
data-toggle="tooltip"
title={t(sortedOrder === "asc" ? "Ascending" : "Descending")}
></i>
<span className={sortedOrder === "asc" ? "arrow-up" : "arrow-down"}>
<SortIcon color={grayColor}/>
</span>
</button>
);
Expand Down
15 changes: 9 additions & 6 deletions forms-flow-web/src/components/Form/constants/FormTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function FormTable() {
<LoadingOverlay active={searchFormLoading || isApplicationCountLoading} spinner text={t("Loading...")}>
<div className="min-height-400">
<div className="custom-tables-wrapper">
<table className="table custom-tables table-responsive-sm">
<table className="table custom-tables table-responsive-sm mb-0">
<thead className="table-header">
<tr>
<th className="w-20">
Expand All @@ -117,7 +117,7 @@ function FormTable() {
title="Form Name"
currentSort={currentFormSort}
handleSort={handleSort}
className="ms-4"
className="gap-2"
/>
</th>
<th className="w-30" scope="col">{t("Description")}</th>
Expand All @@ -127,21 +127,24 @@ function FormTable() {
title="Last Edited"
currentSort={currentFormSort}
handleSort={handleSort}
className="gap-2"
/>
</th>
<th className="w-13" scope="col">
<SortableHeader
columnKey="visibility"
title="Visibility"
currentSort={currentFormSort}
handleSort={handleSort} />
handleSort={handleSort}
className="gap-2"/>
</th>
<th className="w-12" scope="col" colSpan="4">
<SortableHeader
columnKey="status"
title="Status"
currentSort={currentFormSort}
handleSort={handleSort} />
handleSort={handleSort}
className="gap-2"/>
</th>
<th className="w-12" colSpan="4" aria-label="Search Forms by form title"></th>
</tr>
Expand All @@ -156,7 +159,7 @@ function FormTable() {
<tr key={index}>
<td className="w-20">
<div className="d-flex">
<span className="ms-4 text-container">{e.title}</span>
<span className="text-container">{e.title}</span>
</div>
</td>
<td className="w-30 cursor-pointer">
Expand All @@ -177,7 +180,7 @@ function FormTable() {
{e.status === "active" ? t("Live") : t("Draft")}
</span>
</td>
<td className="w-12">
<td className="w-12 text-end">
{(createDesigns || viewDesigns) && (
<CustomButton
variant="secondary"
Expand Down
5 changes: 4 additions & 1 deletion forms-flow-web/src/components/Modeler/ProcessTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ const ProcessTable = React.memo(() => {
title="Name"
currentSort={currentState.sortConfig}
handleSort={handleSort}
className="ms-4"
className="gap-2"
/>
</th>
<th className="w-20" scope="col">
Expand All @@ -247,6 +247,7 @@ const ProcessTable = React.memo(() => {
title="ID"
currentSort={currentState.sortConfig}
handleSort={handleSort}
className="gap-2"
/>
</th>
<th className="w-15" scope="col">
Expand All @@ -255,6 +256,7 @@ const ProcessTable = React.memo(() => {
title="Last Edited"
currentSort={currentState.sortConfig}
handleSort={handleSort}
className="gap-2"
/>
</th>
<th className="w-15" scope="col">
Expand All @@ -263,6 +265,7 @@ const ProcessTable = React.memo(() => {
title="Status"
currentSort={currentState.sortConfig}
handleSort={handleSort}
className="gap-2"
/>
</th>
<th className="w-25" colSpan="4" aria-label="edit-button"></th>
Expand Down

0 comments on commit f9bb040

Please sign in to comment.