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

FWF-3971:[Feature] - Form and flow update in import #2392

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions forms-flow-web/src/components/Form/EditForm/FlowEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ const FlowEdit = forwardRef(({ isPublished = false, CategoryType, setWorkflowIsC

useImperativeHandle(ref, () => ({
saveFlow,
handleImport: (xml)=>{bpmnRef.current?.handleImport(xml);}
}));
const handlePreviewAndVariables = () => {
setShowTaskVarModal(true);
Expand Down
125 changes: 66 additions & 59 deletions forms-flow-web/src/components/Form/EditForm/FormEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ import {
getProcessDetails,
unPublishForm,
} from "../../../apiManager/services/processServices";
import {
setProcessData,
} from "../../../actions/processActions.js";
import _ from "lodash";
import SettingsModal from "../../Modals/SettingsModal";
import FlowEdit from "./FlowEdit.js";
Expand All @@ -65,6 +62,8 @@ import userRoles from "../../../constants/permissions.js";
import { generateUniqueId, isFormComponentsChanged } from "../../../helper/helper.js";
import { useMutation } from "react-query";
import NavigateBlocker from "../../CustomComponents/NavigateBlocker";
import { setProcessData } from "../../../actions/processActions.js";

// constant values
const DUPLICATE = "DUPLICATE";
const IMPORT = "IMPORT";
Expand Down Expand Up @@ -203,86 +202,91 @@ const EditComponent = () => {
action: actionType,
mapperId: processListData.id,
};

if (actionType === "import") {
setImportLoader(true);
setFormSubmitted(true);

if (selectedLayoutVersion || selectedFlowVersion) {
data.form = prepareVersionData(selectedLayoutVersion);
data.workflow = prepareVersionData(selectedFlowVersion);
//the workflow shoul send only the value of skip.
data.workflow = {
skip: typeof selectedFlowVersion !== 'string',
};
}
}

return data;
};

// Helper function to prepare version data
const prepareVersionData = (version) => ({
skip: version === true,
...(typeof version === 'string' && { selectedVersion: version }),
skip: typeof version !== 'string', // skip is false if version is a string, true otherwise
...(typeof version === 'string' && { selectedVersion: version }), // Include selectedVersion only if version is a string
});

// Helper function to handle the API response
const handleImportResponse = async (res, fileContent, action) => {
setImportLoader(false);
setFormSubmitted(false);
const formExtracted = await extractForm(fileContent);
const { data: responseData } = res;
if (responseData) {
setFileItems({
workflow: extractVersionInfo(responseData.workflow),
form: extractVersionInfo(responseData.form),
});
if (!responseData) return;
// Set file items based on response data
setFileItems({
workflow: extractVersionInfo(responseData.workflow),
form: extractVersionInfo(responseData.form),
});

// Handle actions based on extracted form and action type
if (formExtracted) {
if (action === "validate") {
if (formExtracted) {
setFormTitle(formExtracted.formTitle);
}
} else if (responseData?.formId) {
if (formExtracted) {
navigateToEditForm(formExtracted);
}
setFormTitle(formExtracted.forms[0]?.formTitle || "");
} else if (responseData.formId) {
updateLayout(formExtracted);
}
}
};

// Helper function to extract version information
const extractVersionInfo = (versionData) => ({
majorVersion: versionData?.majorVersion || null,
minorVersion: versionData?.minorVersion || null,
});

// Helper function to handle validation
const extractVersionInfo = (versionData) => (
{
majorVersion: versionData?.majorVersion,
minorVersion: versionData?.minorVersion
});

const extractForm = async (fileContent) => {
try {
const formExtracted = await FileService.extractFileDetails(fileContent);
if (formExtracted) {
return formExtracted;
} else {
setImportError("No valid form found.");
}
return formExtracted;
} catch (error) {
setImportError(error);
return null;
}
};

// Helper function to navigate to the edit form
const navigateToEditForm = (formExtracted) => {



const updateLayout = (formExtracted) => {
const { forms, xml } = formExtracted || {};
const extractedFormComponents = forms[0]?.components || forms[0]?.content?.components;
dispatchFormAction({
type: "components",
value: _cloneDeep(formExtracted?.content?.components),
value: _cloneDeep(extractedFormComponents),
});
if (xml) {
flowRef.current?.handleImport(xml);
}
handleCloseSelectedAction();
};

// Helper function to handle errors
const handleImportError = (err) => {
setImportLoader(false);
setFormSubmitted(false);
setImportError(err?.response?.data?.message || "An error occurred");
};

/* ------------------------- form history variables ------------------------- */
const [isNewVersionLoading, setIsNewVersionLoading] = useState(false);
const [restoreFormDataLoading, setRestoreFormDataLoading] = useState(false);
Expand Down Expand Up @@ -432,12 +436,12 @@ const EditComponent = () => {
}
}, [processListData.processKey]);

const validateFormNameOnBlur = ({title, ...rest}) => {
const validateFormNameOnBlur = ({ title, ...rest }) => {
if (!title || title.trim() === "") {
setNameError("This field is required");
return;
}
validateFormTitle({title, ...rest});
validateFormTitle({ title, ...rest });
};


Expand Down Expand Up @@ -497,27 +501,29 @@ const EditComponent = () => {
access: accessDetails.formAccess,
};

try{
try {
await dispatch(saveFormProcessMapperPut({ mapper, authorizations }));
const updateFormResponse = await formUpdate(form._id, formData);
dispatchFormAction({
type: "formChange",
value: { ...updateFormResponse.data, components: form.components },
});
dispatch(setFormSuccessData("form", updateFormResponse.data));
}catch(error){
} catch (error) {
console.error(error);
}finally{
} finally {
setIsSettingsSaving(false);
handleToggleSettingsModal();
}
};

const saveFormData = async ({showToast = true}) => {
const saveFormData = async ({ showToast = true }) => {
try {
const isFormChanged = isFormComponentsChanged({restoredFormData,
restoredFormId, formData, form});
if(!isFormChanged && !promptNewVersion) {
const isFormChanged = isFormComponentsChanged({
restoredFormData,
restoredFormId, formData, form
});
if (!isFormChanged && !promptNewVersion) {
showToast && toast.success(t("Form updated successfully"));
return;
}
Expand All @@ -534,10 +540,10 @@ const EditComponent = () => {
newFormData.parentFormId = previousData.parentFormId;
newFormData.title = processListData.formName;

const {data} = await formUpdate(newFormData._id, newFormData);
const { data } = await formUpdate(newFormData._id, newFormData);
dispatch(setFormSuccessData("form", data));
setPromptNewVersion(false);
setFormChangeState(prev=>({...prev,changed:false}));
setFormChangeState(prev => ({ ...prev, changed: false }));
} catch (err) {
const error = err.response?.data || err.message;
dispatch(setFormFailureErrorData("form", error));
Expand Down Expand Up @@ -579,9 +585,9 @@ const EditComponent = () => {
fetchRestoredFormData(cloneId);
};

const handlePreview = () => {
const newTabUrl = `${redirectUrl}formflow/${form._id}/view-edit`;
window.open(newTabUrl, "_blank");
const handlePreview = () => {
const newTabUrl = `${redirectUrl}formflow/${form._id}/view-edit`;
window.open(newTabUrl, "_blank");
};

const discardChanges = () => {
Expand Down Expand Up @@ -1047,12 +1053,13 @@ const EditComponent = () => {
<div className="form-builder">
{!createDesigns ? (
<div className="px-4 pt-4 form-preview">
<Form
<Form
form={form}
options={{
disableAlerts: true,
noAlerts: true,
language: lang, i18n: RESOURCE_BUNDLES_DATA }}
disableAlerts: true,
noAlerts: true,
language: lang, i18n: RESOURCE_BUNDLES_DATA
}}
/>
</div>
) : (
Expand Down
108 changes: 61 additions & 47 deletions forms-flow-web/src/services/FileService.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,58 +35,72 @@ const uploadFile = (evt, callback) => {

const extractFileDetails = (fileContent) => {
return new Promise((resolve, reject) => {
const fileObj = fileContent; // The file object passed directly
if (fileObj) {
const reader = new FileReader(); // Initialize FileReader to read the file
reader.readAsText(fileObj); // Read the file as text

reader.onload = (e) => {
try {
const fileExtension = fileObj.name.split('.').pop().toLowerCase();

if (fileExtension === 'json') {
// Handle JSON file parsing
const fileContents = JSON.parse(e.target.result);

// Check if 'forms' exist and is an array in fileContents
if (fileContents && fileContents.forms && Array.isArray(fileContents.forms)) {
const formToUpload = fileContents.forms[0]; // Extract the first form
resolve(formToUpload); // Resolve with the extracted form details
} else {
console.error("No 'forms' array found in the file.");
reject("No valid form found."); // Reject with an error message if 'forms' is missing
}
} else if (['bpmn', 'dmn'].includes(fileExtension)) {
// Handle XML file parsing
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(e.target.result, "application/xml");

if (xmlDoc?.getElementsByTagName("parsererror").length > 0) {
reject("Invalid XML file."); // Reject if XML parsing fails
} else {
// Return the entire XML document as a string
const xmlString = new XMLSerializer().serializeToString(xmlDoc);
resolve(xmlString); // Resolve with the XML string
}
} else {
reject("Unsupported file type."); // Reject if the file type is unsupported
}
} catch (error) {
reject("Error processing file."); // Reject if there's a general error during processing
}
};

reader.onerror = () => {
console.error("Error reading the file.");
reject("Error reading the file."); // Reject in case of a file reading error
};
} else {
if (!fileContent) {
console.error("No file selected.");
reject("No file selected."); // Reject if no file is provided
return reject("No file selected.");
}

const reader = new FileReader();
reader.readAsText(fileContent);

reader.onload = (e) => {
try {
const fileExtension = getFileExtension(fileContent.name);

if (fileExtension === 'json') {
handleJSONFile(e.target.result, resolve, reject);
} else if (['bpmn', 'dmn'].includes(fileExtension)) {
handleXMLFile(e.target.result, resolve, reject);
} else {
reject("Unsupported file type.");
}
} catch (error) {
reject("Error processing file.");
}
};

reader.onerror = () => {
console.error("Error reading the file.");
reject("Error reading the file.");
};
});
};

// Helper to get the file extension
const getFileExtension = (fileName) => fileName.split('.').pop().toLowerCase();

// Helper to handle JSON files
const handleJSONFile = (fileContent, resolve, reject) => {
try {
const fileContents = JSON.parse(fileContent);
const forms = Array.isArray(fileContents.forms) ? fileContents.forms : [];
const workflows = Array.isArray(fileContents.workflows) ? fileContents.workflows : [];
const xml = workflows.length > 0 ? workflows.map(wf => wf.content).join('') : null;

if (forms.length === 0 && !xml) {
return reject("No valid form or XML found.");
}

resolve({ forms, xml });
} catch {
reject("Invalid JSON file.");
}
};

// Helper to handle XML files
const handleXMLFile = (fileContent, resolve, reject) => {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(fileContent, "application/xml");

if (xmlDoc?.getElementsByTagName("parsererror")?.length > 0) {
return reject("Invalid XML file.");
}

const xmlString = new XMLSerializer().serializeToString(xmlDoc);
resolve({ xml: xmlString, forms: [] });
};



const FileService = {
uploadFile,
Expand Down
Loading