Skip to content

Commit

Permalink
[Bugfix] Import Fixes (#2406)
Browse files Browse the repository at this point in the history
* FWF-4051: [Bugfix] Webapi Import fixes

* Fixed front end issues

* FWF-4051: [Bugfix] Webapi Import validation fixes

* Fixed sonar in list page

* Fixed sonar issue by giving some exctra check in list page

* added isArray check for extracted data

* added prop validation

* fixed sonarcloud issues

* FIxed sonar

* Fixed sonar cloud issues

* Disabled unwanted prop validation

* Disabled unwanted prop validation

* FWF-4051: Sonar fix

---------

Co-authored-by: shuhaib-aot <[email protected]>
  • Loading branch information
auslin-aot and shuhaib-aot authored Dec 10, 2024
1 parent a2344e1 commit 95ce621
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 152 deletions.
18 changes: 16 additions & 2 deletions forms-flow-api/src/formsflow_api/models/form_process_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,23 @@ def find_all_active_forms(
@classmethod
def find_forms_by_title(cls, form_title, exclude_id) -> FormProcessMapper:
"""Find all form process mapper that matches the provided form title."""
query = cls.query.filter(FormProcessMapper.form_name == form_title)
latest_mapper = (
db.session.query(
func.max(cls.id).label("latest_id"),
cls.parent_form_id,
)
.group_by(cls.parent_form_id)
.subquery()
)
query = (
db.session.query(cls)
.join(latest_mapper, cls.id == latest_mapper.c.latest_id)
.filter(cls.form_name == form_title, cls.deleted.is_(False))
)

if exclude_id is not None:
query = query.filter(FormProcessMapper.parent_form_id != exclude_id)
query = query.filter(cls.parent_form_id != exclude_id)

query = cls.tenant_authorization(query=query)
return query.all()

Expand Down
48 changes: 22 additions & 26 deletions forms-flow-api/src/formsflow_api/services/form_process_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ def _sanitize_form_json(self, form_json, tenant_key):
"access",
"submissionAccess",
"parentFormId",
"owner",
]
for key in keys_to_remove:
form_json.pop(key, None)
Expand Down Expand Up @@ -803,39 +804,34 @@ def validate_form_name_path_title(request, **kwargs):
FormProcessMapperService.validate_title_name_path(title, path, name)

if current_app.config.get("MULTI_TENANCY_ENABLED"):
# In multitenant environment, validate title exists validation on mapper & path, name in formio.
if title:
FormProcessMapperService.validate_form_title(title)
user: UserContext = kwargs["user"]
tenant_key = user.tenant_key
name = f"{tenant_key}-{name}"
path = f"{tenant_key}-{path}"
# Validate title exists validation on mapper & path, name in formio.
if title:
FormProcessMapperService.validate_form_title(title)
# Validate path, name exits in formio.
if path or name:
query_params = f"name={name}&path={path}&select=title,path,name"
else:
# In non-multitenant environment, validate title, path, name in formio.
query_params = (
f"title={title}&name={name}&path={path}&select=title,path,name"
# Initialize the FormioService and get the access token
formio_service = FormioService()
form_io_token = formio_service.get_formio_access_token()
validation_response = formio_service.get_form_search(
query_params, form_io_token
)

# Initialize the FormioService and get the access token
formio_service = FormioService()
form_io_token = formio_service.get_formio_access_token()
# Call the external validation API
validation_response = formio_service.get_form_search(
query_params, form_io_token
)

# Check if the validation response has any results
if validation_response:
# Check if the form ID matches
if (
form_id
and len(validation_response) == 1
and validation_response[0].get("_id") == form_id
):
return {}
# If there are results but no matching ID, the form name is still considered invalid
raise BusinessException(BusinessErrorCode.FORM_EXISTS)
# Check if the validation response has any results
if validation_response:
# Check if the form ID matches
if (
form_id
and len(validation_response) == 1
and validation_response[0].get("_id") == form_id
):
return {}
# If there are results but no matching ID, the form name is still considered invalid
raise BusinessException(BusinessErrorCode.FORM_EXISTS)
# If no results, the form name is valid
return {}

Expand Down
97 changes: 58 additions & 39 deletions forms-flow-api/src/formsflow_api/services/import_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
from formsflow_api.constants import BusinessErrorCode
from formsflow_api.models import AuthType, FormHistory, Process, ProcessType
from formsflow_api.schemas import (
FormProcessMapperSchema,
ImportEditRequestSchema,
ImportRequestSchema,
ProcessDataSchema,
form_schema,
form_workflow_schema,
)
Expand Down Expand Up @@ -256,19 +258,18 @@ def validate_form(
name = f"{tenant_key}-{name}"
path = f"{tenant_key}-{path}"

if len(title) > 200 or len(name) > 200:
raise BusinessException(BusinessErrorCode.INVALID_FORM_TITLE_LENGTH)

# Build query params based on validation type
if validate_path_only and mapper:
# In case of edit import validate title in mapper table & path in formio.
FormProcessMapperService.validate_form_title(title, mapper.parent_form_id)
query_params = f"path={path}&select=title,path,name,_id"
elif not validate_path_only and current_app.config.get("MULTI_TENANCY_ENABLED"):
# In case of new import in multitenant env, validate title in mapper table & path,name in formio.
else:
# In case of new import validate title in mapper table & path,name in formio.
FormProcessMapperService.validate_form_title(title, exclude_id=None)
query_params = f"path={path}&name={name}&select=title,path,name,_id"
else:
query_params = (
f"title={title}&name={name}&path={path}&select=title,path,name"
)
current_app.logger.info(f"Validating form exists...{query_params}")
response = self.get_form_by_query(query_params)
return response
Expand Down Expand Up @@ -361,6 +362,7 @@ def save_process_data( # pylint: disable=too-many-arguments, too-many-positiona
)
process.save()
current_app.logger.info("Process data saved successfully...")
return process

def version_response(self, form_major, form_minor, workflow_major, workflow_minor):
"""Version response."""
Expand Down Expand Up @@ -432,47 +434,54 @@ def import_new_form_workflow(
)
process_name = updated_process_name if updated_process_name else process_name
current_app.logger.info(f"Process Name: {process_name}")
self.save_process_data(
process = self.save_process_data(
workflow_data, process_name, is_new=True, process_type=process_type
)
return form_id
return mapper, process

def import_form(
self, selected_form_version, form_json, mapper, form_only=False, **kwargs
): # pylint: disable=too-many-locals
): # pylint: disable=too-many-locals, too-many-statements
"""Import form as major or minor version."""
current_app.logger.info("Form import inprogress...")
# Get current form by mapper form_id
current_form = self.get_form_by_formid(mapper.form_id)
new_path = form_json.get("path")
new_title = form_json.get("title")
anonymous = kwargs.get("anonymous", False)
description = kwargs.get("description", None)
title_changed = bool(not form_only and mapper.form_name != new_title)
name = current_form.get("name")
title_changed = bool(
not form_only and mapper.form_name != form_json.get("title")
)
if form_only:
# In case of form only import take title, path from current form
# and anonymous, description from mapper
path = current_form.get("path")
title = current_form.get("title")
anonymous = mapper.is_anonymous
description = mapper.description
else:
# form+workflow import take title, path, anonymous, description from incoming form json
path = form_json.get("path")
title = form_json.get("title")
anonymous = kwargs.get("anonymous", False)
description = kwargs.get("description", None)
anonymous_changed = bool(
anonymous is not None and mapper.is_anonymous != anonymous
)

if selected_form_version == "major":
# Update current form with random value to path, name & title
# Create new form with current form name, title & path from incoming form
# Create mapper entry for new form version, mark previous version inactive & delete
# Capture form history
current_app.logger.info("Form import major version inprogress...")
path = current_form.get("path")
name = current_form.get("name")
title = current_form.get("title")
# Update name & path of current form
current_form["path"] = f"{path}-v-{uuid1().hex}"
current_form["path"] = f"{current_form['path']}-v-{uuid1().hex}"
current_form["name"] = f"{name}-v-{uuid1().hex}"
current_form["title"] = f"{title}-v-{uuid1().hex}"
FormProcessMapperService.form_design_update(current_form, mapper.form_id)
# Create new form with current form name
form_json["parentFormId"] = mapper.parent_form_id
form_json["name"] = name
# Update path of current form with pathname & title from imported form in case of edit import
# But incase of form only no validation done, so use current form path & title itself.
form_json["title"] = title if form_only else new_title
form_json["path"] = path if form_only else new_path
form_json["title"] = title
form_json["path"] = path
form_json["parentFormId"] = mapper.parent_form_id
form_json = self.set_form_and_submission_access(form_json, anonymous)
form_response = self.form_create(form_json)
form_id = form_response.get("_id")
Expand All @@ -490,7 +499,7 @@ def import_form(
"formName": form_response.get("title"),
"formType": mapper.form_type,
"parentFormId": mapper.parent_form_id,
"anonymous": mapper.is_anonymous if form_only else anonymous,
"anonymous": anonymous,
"taskVariables": json.loads(mapper.task_variable),
"processKey": mapper.process_key,
"processName": mapper.process_name,
Expand All @@ -499,10 +508,10 @@ def import_form(
"formTypeChanged": False,
"titleChanged": title_changed,
"anonymousChanged": anonymous_changed,
"description": mapper.description if form_only else description,
"description": description,
"isMigrated": mapper.is_migrated,
}
FormProcessMapperService.mapper_create(mapper_data)
mapper = FormProcessMapperService.mapper_create(mapper_data)
FormProcessMapperService.mark_unpublished(mapper.id)
else:
current_app.logger.info("Form import minor version inprogress...")
Expand All @@ -513,6 +522,10 @@ def import_form(
# Minor version update form components in formio & create form history.
form_components = {}
form_components["components"] = form_json.get("components")
# Incase of form+workflow title/path is updated even in minor version
form_components["title"] = title
form_components["path"] = path
form_components["parentFormId"] = mapper.parent_form_id
form_response = self.form_update(form_components, form_id)
form_response["componentChanged"] = True
form_response["parentFormId"] = mapper.parent_form_id
Expand All @@ -522,24 +535,25 @@ def import_form(
current_app.logger.info("Updating mapper & form logs...")
mapper.description = description
mapper.is_anonymous = anonymous
mapper.form_name = title
mapper.save()
form_logs_data = {
"titleChanged": title_changed,
"formName": new_title,
"formName": title,
"anonymousChanged": anonymous_changed,
"anonymous": anonymous,
"formId": form_id,
"parentFormId": mapper.parent_form_id,
}
FormHistoryService.create_form_logs_without_clone(data=form_logs_data)
return form_id
return mapper

def import_edit_form(self, file_data, selected_form_version, form_json, mapper):
"""Import edit form."""
current_app.logger.info("Form import with form+workflow json inprogress...")
anonymous = file_data.get("forms")[0].get("anonymous") or False
description = file_data.get("forms")[0].get("formDescription", "")
form_id = self.import_form(
mapper = self.import_form(
selected_form_version,
form_json,
mapper,
Expand All @@ -553,7 +567,7 @@ def import_edit_form(self, file_data, selected_form_version, form_json, mapper):
file_data["authorizations"][0][auth]["resourceId"] = mapper.parent_form_id
# Update authorizations for the form
self.create_authorization(file_data["authorizations"][0])
return form_id
return mapper

@user_context
def import_form_workflow(
Expand All @@ -568,7 +582,9 @@ def import_form_workflow(
action = input_data.get("action")
user: UserContext = kwargs["user"]
tenant_key = user.tenant_key
form_id = None
mapper = None
process = None
response = {}

# Check if the action is valid
if action not in ["validate", "import"]:
Expand Down Expand Up @@ -600,7 +616,7 @@ def import_form_workflow(
form_json = self.append_tenant_key_form_name_path(
form_json, tenant_key
)
form_id = self.import_new_form_workflow(
mapper, process = self.import_new_form_workflow(
file_data, form_json, workflow_data, process_type
)
else:
Expand All @@ -614,7 +630,6 @@ def import_form_workflow(
if mapper.status == FormProcessMapperStatus.ACTIVE.value:
# Raise an exception if the user try to update published form
raise BusinessException(BusinessErrorCode.FORM_INVALID_OPERATION)
form_id = mapper.form_id
if valid_file == ".json":
file_data = self.read_json_data(file)
# Validate input json file whether only form or form+workflow
Expand All @@ -637,7 +652,7 @@ def import_form_workflow(
selected_form_version = edit_request.get("form", {}).get(
"selectedVersion"
)
form_id = self.import_form(
mapper = self.import_form(
selected_form_version, form_json, mapper, form_only=True
)
elif self.validate_input_json(file_data, form_workflow_schema):
Expand Down Expand Up @@ -680,7 +695,7 @@ def import_form_workflow(
form_json = self.append_tenant_key_form_name_path(
form_json, tenant_key
)
form_id = self.import_edit_form(
mapper = self.import_edit_form(
file_data, selected_form_version, form_json, mapper
)
if not skip_workflow:
Expand All @@ -689,7 +704,7 @@ def import_form_workflow(
workflow_data, process_type = self.get_process_details(
file_data
)
self.save_process_data(
process = self.save_process_data(
workflow_data,
mapper.process_key,
selected_workflow_version,
Expand All @@ -715,9 +730,13 @@ def import_form_workflow(
"selectedVersion"
)
file_content = file.read().decode("utf-8")
self.save_process_data(
process = self.save_process_data(
file_content,
mapper.process_key,
selected_workflow_version,
)
return {"formId": form_id}
if mapper:
response["mapper"] = FormProcessMapperSchema().dump(mapper)
if process:
response["process"] = ProcessDataSchema().dump(process)
return response
Loading

0 comments on commit 95ce621

Please sign in to comment.