From 6e44541af7600b21455a3a13fb47249f84398ab9 Mon Sep 17 00:00:00 2001 From: abilpraju-aot Date: Tue, 5 Nov 2024 21:07:37 -0800 Subject: [PATCH 01/10] initial commit for check --- .../src/components/Form/EditForm/FlowEdit.js | 2 +- .../src/components/Modeler/Create.js | 52 -- .../src/components/Modeler/DmnCreateEdit.js | 460 ------------------ forms-flow-web/src/components/Modeler/Edit.js | 223 --------- .../BpmnEditor/{index.js => BpmEditor.js} | 0 .../DmnEditor/{index.js => DmnEditor.js} | 0 forms-flow-web/src/components/Modeler/Main.js | 137 ------ ...flowCreateEdit.js => ProcessCreateEdit.js} | 336 +++++++++---- .../src/components/Modeler/index.js | 9 +- 9 files changed, 245 insertions(+), 974 deletions(-) delete mode 100644 forms-flow-web/src/components/Modeler/Create.js delete mode 100644 forms-flow-web/src/components/Modeler/DmnCreateEdit.js delete mode 100644 forms-flow-web/src/components/Modeler/Edit.js rename forms-flow-web/src/components/Modeler/Editors/BpmnEditor/{index.js => BpmEditor.js} (100%) rename forms-flow-web/src/components/Modeler/Editors/DmnEditor/{index.js => DmnEditor.js} (100%) delete mode 100644 forms-flow-web/src/components/Modeler/Main.js rename forms-flow-web/src/components/Modeler/{SubflowCreateEdit.js => ProcessCreateEdit.js} (56%) diff --git a/forms-flow-web/src/components/Form/EditForm/FlowEdit.js b/forms-flow-web/src/components/Form/EditForm/FlowEdit.js index 55c35b7f9a..075cea6c25 100644 --- a/forms-flow-web/src/components/Form/EditForm/FlowEdit.js +++ b/forms-flow-web/src/components/Form/EditForm/FlowEdit.js @@ -17,7 +17,7 @@ import { setProcessData, setProcessHistories, } from "../../../actions/processActions.js"; -import BpmnEditor from "../../Modeler/Editors/BpmnEditor/index.js"; +import BpmnEditor from "../../Modeler/Editors/BpmnEditor/BpmEditor"; import { updateProcess, getProcessHistory, diff --git a/forms-flow-web/src/components/Modeler/Create.js b/forms-flow-web/src/components/Modeler/Create.js deleted file mode 100644 index 26a0d34699..0000000000 --- a/forms-flow-web/src/components/Modeler/Create.js +++ /dev/null @@ -1,52 +0,0 @@ -import React, {useEffect} from 'react'; -import { useSelector } from 'react-redux'; -import BpmnEditor from './Editors/BpmnEditor'; -import DmnEditor from './Editors/DmnEditor'; -import { - createNewDecision, - createNewProcess, -} from "./helpers/helper"; -import { useDispatch } from 'react-redux'; -import { - setProcessDiagramXML, -} from "../../actions/processActions"; - const CreateWorkflow = () => { - //select typeOf workflow form useSelector / redux - const isBpmnModel = useSelector((state) => state.process.isBpmnModel); - const diagramXML = useSelector((state) => state.process.processDiagramXML); - const dispatch = useDispatch(); - - useEffect(()=>{ - if(!diagramXML){ - const newProcess = isBpmnModel ? createNewProcess() : createNewDecision(); - dispatch(setProcessDiagramXML(newProcess.defaultWorkflow.xml)); - } - },[isBpmnModel]); - - useEffect(()=>{ - return () => { - dispatch(setProcessDiagramXML('')); - }; - },[]); - - - return ( -
- - {isBpmnModel ? ( - - ) : ( - - )} -
- ); -}; - -export default CreateWorkflow; \ No newline at end of file diff --git a/forms-flow-web/src/components/Modeler/DmnCreateEdit.js b/forms-flow-web/src/components/Modeler/DmnCreateEdit.js deleted file mode 100644 index b1c103d3f4..0000000000 --- a/forms-flow-web/src/components/Modeler/DmnCreateEdit.js +++ /dev/null @@ -1,460 +0,0 @@ -import React, { useEffect, useState, useRef, useMemo } from "react"; -import "./Modeler.scss"; -import { useSelector, useDispatch } from "react-redux"; -import { useParams } from "react-router-dom"; -import { push } from "connected-react-router"; -import { - updateProcess, - publish, - unPublish, - getProcessDetails, - createProcess, -} from "../../apiManager/services/processServices"; -import Loading from "../../containers/Loading"; -import { MULTITENANCY_ENABLED } from "../../constants/constants"; -import { useTranslation } from "react-i18next"; -import { - createNewProcess, - extractDataFromDiagram, -} from "../../components/Modeler/helpers/helper"; -import { - CustomButton, - HistoryIcon, - BackToPrevIcon, - ConfirmModal, -} from "@formsflow/components"; -import { Card } from "react-bootstrap"; -import ActionModal from "../Modals/ActionModal"; -import ExportDiagram from "../Modals/ExportDiagrams"; -import { toast } from "react-toastify"; -import { - createXMLFromModeler, - compareDmnXML, - //validateDecisionNames, - validateDecisionNames, -} from "../../helper/processHelper"; -import DmnEditor from "./Editors/DmnEditor/index.js"; -import { - setProcessData, - setProcessDiagramXML, -} from "../../actions/processActions"; - -const EXPORT = "EXPORT"; -const CategoryType = { FORM: "FORM", WORKFLOW: "WORKFLOW" }; - -const DecitionEditor = () => { - const { processKey, step } = useParams(); - const isCreate = step === "create"; - const dispatch = useDispatch(); - const dmnRef = useRef(); - const { t } = useTranslation(); - - const tenantKey = useSelector((state) => state.tenants?.tenantId); - const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/"; - const processData = useSelector((state) => state.process?.processData); - const [selectedAction, setSelectedAction] = useState(null); - const [newActionModal, setNewActionModal] = useState(false); - const [exportError, setExportError] = useState(null); - const defaultDmnXmlData = useSelector( - (state) => state.process.defaultDmnXmlData - ); - const [savingFlow, setSavingFlow] = useState(false); - const [historyModalShow, setHistoryModalShow] = useState(false); - const [isPublished, setIsPublished] = useState( - processData?.status === "Published" - ); - const [showConfirmModal, setShowConfirmModal] = useState(false); - const [modalType, setModalType] = useState(""); - const [isPublishLoading, setIsPublishLoading] = useState(false); - // handle history modal - const handleHistoryModal = () => setHistoryModalShow(!historyModalShow); - const [isProcessDetailsLoading, setIsProcessDetailsLoading] = useState(false); - //const [lintErrors, setLintErrors] = useState([]); - //const [isXmlChanged, setIsXmlChanged] = useState(false); - - useEffect(() => { - setIsPublished(processData.status === "Published"); - }, [processData]); - - const publishText = isPublished ? "Unpublish" : "Publish"; - - // get process name to dispaly - const processName = useMemo(() => { - if (!processData.processData) return; - return extractDataFromDiagram(processData?.processData, true).name; - }, [processData]); - - //fetch process details using processkey - useEffect(async () => { - if (processKey) { - try { - setIsProcessDetailsLoading(true); - const { data } = await getProcessDetails(processKey); - setIsPublished(!isPublished); - dispatch(setProcessData(data)); - } catch (error) { - console.error(error); - } finally { - setIsProcessDetailsLoading(false); - } - } - }, [processKey]); - - //reset the data - useEffect(() => { - if (isCreate) { - dispatch(setProcessData({})); - } - return () => { - //if we click duplicate the the data will exist on the redux, so need to reset - isCreate && - dispatch(setProcessDiagramXML(createNewProcess().defaultWorkflow.xml)); - }; - }, [isCreate]); - - const handleToggleConfirmModal = () => setShowConfirmModal(!showConfirmModal); - const openConfirmModal = (type) => { - setModalType(type); - handleToggleConfirmModal(); - }; - - const saveFlow = async (isPublishing = false) => { - try { - const dmnModeler = dmnRef.current?.getDmnModeler(); - const xml = await createXMLFromModeler(dmnModeler); - if (!validateDecisionNames(xml, t)) { - return; - } - if (!isCreate) { - // If XML is the same as existing process data, no need to update - const isEqual = await compareDmnXML(processData?.processData, xml); - if (isEqual) { - !isPublishing && toast.success(t("Dmn is already up to date")); - return; - } - } - - setSavingFlow(true); - // Check if `processId` exists; if so, update the process, otherwise create a new DMN - const response = isCreate - ? await createProcess({ type: "DMN", data: xml }) - : await updateProcess({ type: "DMN", id: processData.id, data: xml }); - - dispatch(setProcessData(response.data)); - !isPublishing && - toast.success( - t(`DMN ${isCreate ? "created" : "updated"} successfully`) - ); - if (isCreate && !isPublishing) { - dispatch( - push(`${redirectUrl}decision-table/edit/${response.data.processKey}`) - ); - } - return response.data; - } catch (error) { - toast.error(t("Failed to save process")); - } finally { - setSavingFlow(false); - } - }; - - // Function to handle publish/unpublish with XML validation - const confirmPublishOrUnPublish = async () => { - try { - const dmnModeler = dmnRef.current?.getDmnModeler(); - const xml = await createXMLFromModeler(dmnModeler); - - // Validate the XML before publishing - - if (!isPublished && !validateDecisionNames(xml, t)) { - return; - } - const actionFunction = isPublished ? unPublish : publish; - const response = !isPublished ? await saveFlow(!isPublished) : null; - closeModal(); // Close confirmation modal - //incase of create if no response no need to call api - if (isCreate && !response) return; - - setIsPublishLoading(true); - - // Perform the publish/unpublish action - await actionFunction({ - id: response?.id || processData.id, - data: xml, - type: "DMN", - }); - - if (isPublished) { - // Fetch updated process details after unpublish - const updatedProcessDetails = await getProcessDetails( - processData.processKey - ); - dispatch(setProcessData(updatedProcessDetails.data)); - setIsPublished(false); // Set to unpublished - } - toast.success( - t(`${isPublished ? "Unpublished" : "Published"} successfully`) - ); - // Handle unpublish success by immediately fetching updated process details - if (!isPublished) { - dispatch(push(`${redirectUrl}decision-table`)); // Redirect on publish - } - setIsPublished(!isPublished); - } catch (error) { - toast.error( - t(`Failed to ${isPublished ? "unpublish" : "publish"} the DMN`) - ); - console.error("Error in publish/unpublish:", error.message); - } finally { - setIsPublishLoading(false); - } - }; - - const closeModal = () => { - setModalType(""); - handleToggleConfirmModal(); - }; - - const handleExport = async () => { - try { - const data = isCreate ? defaultDmnXmlData : processData?.processData; - if (await validateDecisionNames(data)) { - const element = document.createElement("a"); - const file = new Blob([data], { - type: "text/dmn", - }); - element.href = URL.createObjectURL(file); - const processName = - extractDataFromDiagram(data, true).name.replaceAll(" / ", "-") + - ".dmn"; - element.download = processName.replaceAll(" ", ""); - document.body.appendChild(element); - element.click(); - setExportError(null); - } else { - setExportError("Process validation failed."); - } - } catch (error) { - setExportError(error.message || "Export failed due to an error."); - } - }; - - const cancel = () => dispatch(push(`${redirectUrl}decision-table`)); - - const editorActions = () => setNewActionModal(true); - - const handleDuplicateProcess = () => { - handleToggleConfirmModal(); - dispatch(setProcessDiagramXML(processData.processData)); - dispatch(push(`${redirectUrl}decision-table/create`)); - }; - - const handleDiscardConfirm = () => { - if (dmnRef.current) { - dmnRef.current?.handleImport( - isCreate ? defaultDmnXmlData : processData.processData - ); - } - handleToggleConfirmModal(); - }; - - if (isProcessDetailsLoading) return ; - - const getModalContent = () => { - switch (modalType) { - case "publish": - return { - title: "Confirm Publish", - message: - "Publishing will lock the DMN. To save changes on further edits, you will need to unpublish the DMN first.", - primaryBtnAction: confirmPublishOrUnPublish, - secondayBtnAction: closeModal, - primaryBtnText: "Publish This DMN", - secondaryBtnText: "Cancel", - }; - case "unpublish": - return { - title: "Confirm Unpublish", - message: - "This DMN is currently live. To save changes to DMN edits, you need to unpublish it first.By unpublishing this DMN, you will make it unavailable for new submission to those who currently have access to it. You can republish the DMN after making your edits.", - primaryBtnAction: confirmPublishOrUnPublish, - secondayBtnAction: closeModal, - primaryBtnText: "Unpublish and Edit This DMN", - secondaryBtnText: "Cancel, Keep This DMN published", - }; - case "discard": - return { - title: "Are you Sure you want to Discard DMN Changes", - message: - "Are you sure you want to discard all the changes to the DMN?", - messageSecondary: "This action cannot be undone.", - primaryBtnAction: handleDiscardConfirm, - secondayBtnAction: closeModal, - primaryBtnText: "Discard Changes", - secondaryBtnText: "Cancel", - }; - case "duplicate": - return { - title: "Create Duplicate", - message: "Are you Sure want to Duplicate current DMN", - primaryBtnAction: handleDuplicateProcess, - secondayBtnAction: closeModal, - primaryBtnText: "Yes, Duplicate This DMN", - secondaryBtnText: "No, Do Not Duplicate This DMN", - }; - default: - return {}; - } - }; - const modalContent = getModalContent(); - - return ( -
- - - -
-
- -
- {isCreate ? t("Unsaved DMN") : processName} -
- {!isCreate && ( - -
- {isPublished ? t("Live") : t("Draft")} -
- )} -
-
- - { - isPublished - ? openConfirmModal("unpublish") - : openConfirmModal("publish"); - }} - dataTestid="handle-publish-testid" - disabled={isPublishLoading} - ariaLabel={`${t(publishText)} ${t("Button")}`} - /> -
-
-
-
- - -
- -
-
-
{t("Flow")}
- {!isCreate && ( - } - onClick={handleHistoryModal} - label={t("History")} - dataTestid="DMN-history-button-testid" - ariaLabel={t("DMN History Button")} - /> - )} -
-
- - { - openConfirmModal("discard"); - }} - label={t("Discard Changes")} - dataTestid="discard-DMN-changes-testid" - ariaLabel={t("Discard DMN Changes")} - /> -
-
-
-
- - {isProcessDetailsLoading ? ( - <>loading... - ) : ( - - )} - -
- setNewActionModal(false)} - CategoryType={CategoryType.WORKFLOW} - onAction={(action) => { - if (action === "DUPLICATE") { - openConfirmModal("duplicate"); - } - setSelectedAction(action); - }} - isCreate={isCreate} - /> - setSelectedAction(null)} - onExport={handleExport} - fileName={processName || "filename"} - modalTitle={t("Export DMN")} - successMessage={t("Export Successful")} - errorMessage={exportError} - /> -
- ); -}; - -export default DecitionEditor; diff --git a/forms-flow-web/src/components/Modeler/Edit.js b/forms-flow-web/src/components/Modeler/Edit.js deleted file mode 100644 index ca5403b0a7..0000000000 --- a/forms-flow-web/src/components/Modeler/Edit.js +++ /dev/null @@ -1,223 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import { useParams } from "react-router-dom"; -import { push } from "connected-react-router"; -import { fetchDiagram } from "../../apiManager/services/processServices"; -import Loading from "../../containers/Loading"; -import { - setIsPublicDiagram, - setProcessDiagramXML, -} from "../../actions/processActions"; -import { MULTITENANCY_ENABLED } from "../../constants/constants"; -import { useTranslation } from "react-i18next"; -import { extractDataFromDiagram } from "../../components/Modeler/helpers/helper"; -import { CustomButton, HistoryIcon, BackToPrevIcon } from "@formsflow/components"; -import { Card } from "react-bootstrap"; -import ActionModal from "../Modals/ActionModal"; -import ExportDiagram from "../Modals/ExportDiagrams"; -import { ERROR_LINTING_CLASSNAME } from "../Modeler/constants/bpmnModelerConstants"; -import { toast } from "react-toastify"; -import { validateProcessNames } from "../../helper/processHelper"; -import BpmnEditor from "./Editors/BpmnEditor"; -import DmnEditor from "./Editors/DmnEditor"; - -const EXPORT = "EXPORT"; -const CategoryType = { FORM: "FORM", WORKFLOW: "WORKFLOW" }; - -const EditWorkflow = () => { - const tenantKey = useSelector((state) => state.tenants?.tenantId); - const { processId } = useParams(); - const dispatch = useDispatch(); - const diagramXML = useSelector((state) => state.process.processDiagramXML); - const isPublicDiagram = useSelector((state) => state.process.isPublicDiagram); - const processStatus = useSelector((state) => state.process?.processData?.status); - const processType = useSelector((state) => state.process?.processData?.processType); - const [diagramLoading, setDiagramLoading] = useState(false); - const [selectedAction, setSelectedAction] = useState(null); - const [newActionModal, setNewActionModal] = useState(false); - const [lintErrors, setLintErrors] = useState([]); - const [deploymentName, setDeploymentName] = useState(""); - const [exportError, setExportError] = useState(null); - - const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/"; - const { t } = useTranslation(); - - useEffect(() => { - if (diagramXML) { - const extractedName = extractDataFromDiagram(diagramXML).name.replaceAll(" / ", "-"); - setDeploymentName(extractedName); - } - }, [diagramXML]); - - useEffect(() => { - setDiagramLoading(true); - if (MULTITENANCY_ENABLED && isPublicDiagram === null) { - dispatch(push(`${redirectUrl}subflow`)); - } else { - const updatedTenantKey = MULTITENANCY_ENABLED && !isPublicDiagram ? null : tenantKey; - dispatch(fetchDiagram(processId, updatedTenantKey, () => setDiagramLoading(false))); - } - }, [processId]); - - useEffect(() => { - return () => { - dispatch(setProcessDiagramXML("")); - dispatch(setIsPublicDiagram(null)); - }; - }, [dispatch]); - - const validateBpmnLintErrors = () => { - let hasErrors = false; - lintErrors.forEach((err) => { - err.forEach((x) => { - if (x.category === "error") { - hasErrors = true; - toast.error(t(x.message)); - } - }); - }); - return !hasErrors; - }; - - const validateProcess = (xml) => { - if (document.getElementsByClassName(ERROR_LINTING_CLASSNAME).length > 0) { - return validateBpmnLintErrors(); - } - if (!validateProcessNames(xml)) { - toast.error(t("Process name(s) must not be empty")); - return false; - } - return true; - }; - - const handleExport = async () => { - try { - if (await validateProcess(diagramXML)) { - const element = document.createElement("a"); - const file = new Blob([diagramXML], { type: "text/bpmn" }); - element.href = URL.createObjectURL(file); - let deploymentName = extractDataFromDiagram(diagramXML).name.replaceAll(" / ", "-") + ".bpmn"; - element.download = deploymentName.replaceAll(" ", ""); - document.body.appendChild(element); - element.click(); - setExportError(null); - } else { - setExportError("Process validation failed."); - } - } catch (error) { - setExportError(error.message || "Export failed due to an error."); - } - }; - - const cancel = () => dispatch(push(`${redirectUrl}subflow`)); - const editorActions = () => setNewActionModal(true); - - if (diagramLoading) return ; - - return ( -
- - -
-
- -
{deploymentName}
- -
- {t(processStatus === 'Live' ? "Live" : "Draft")} -
-
-
- - -
-
-
-
- - -
- -
-
-
{t("Flow")}
- } - label={t("History")} - dataTestid="bpmn-history-button-testid" - ariaLabel={t("BPMN History Button")} - /> -
-
- - -
-
-
-
- - {diagramXML && (processType === "BPMN" ? ( - - ) : ( - - ))} - -
- - setNewActionModal(false)} - CategoryType={CategoryType.WORKFLOW} - onAction={setSelectedAction} - /> - setSelectedAction(null)} - onExport={handleExport} - fileName={deploymentName} - modalTitle={t("Export BPMN")} - successMessage={t("Export Successful")} - errorMessage={exportError} - /> -
- ); -}; - -export default EditWorkflow; diff --git a/forms-flow-web/src/components/Modeler/Editors/BpmnEditor/index.js b/forms-flow-web/src/components/Modeler/Editors/BpmnEditor/BpmEditor.js similarity index 100% rename from forms-flow-web/src/components/Modeler/Editors/BpmnEditor/index.js rename to forms-flow-web/src/components/Modeler/Editors/BpmnEditor/BpmEditor.js diff --git a/forms-flow-web/src/components/Modeler/Editors/DmnEditor/index.js b/forms-flow-web/src/components/Modeler/Editors/DmnEditor/DmnEditor.js similarity index 100% rename from forms-flow-web/src/components/Modeler/Editors/DmnEditor/index.js rename to forms-flow-web/src/components/Modeler/Editors/DmnEditor/DmnEditor.js diff --git a/forms-flow-web/src/components/Modeler/Main.js b/forms-flow-web/src/components/Modeler/Main.js deleted file mode 100644 index 4e9cf19150..0000000000 --- a/forms-flow-web/src/components/Modeler/Main.js +++ /dev/null @@ -1,137 +0,0 @@ -import React, { useRef } from "react"; -import "./Modeler.scss"; -import BpmnTable from "./constants/bpmnTable"; -import DmnTable from "./constants/dmnTable"; -import { useTranslation } from "react-i18next"; -import { useDispatch, useSelector } from "react-redux"; -import { push } from "connected-react-router"; -import { extractDataFromDiagram } from "./helpers/helper"; -import { - setWorkflowAssociation, - setProcessDiagramXML, - setBpmnModel, -} from "../../actions/processActions"; -import { MULTITENANCY_ENABLED } from "../../constants/constants"; -import Head from "../../containers/Head"; -export default React.memo(() => { - const dispatch = useDispatch(); - const { t } = useTranslation(); - const uploadFormNode = useRef(); - const isBpmnModel = useSelector((state) => state.process?.isBpmnModel); - const tenantKey = useSelector((state) => state.tenants?.tenantId); - - const handleChnageTab = (newValue) => { - dispatch(setBpmnModel(newValue === "BPMN" ? true : false)); - }; - const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/"; - - const handleFile = (e, fileName) => { - const content = e.target.result; - let processId = ""; - let name = ""; - - if (fileName.substr(fileName.length - 5) == ".bpmn") { - dispatch(setBpmnModel(true)); - name = extractDataFromDiagram(content).name; - processId = extractDataFromDiagram(content).processId; - } else { - dispatch(setBpmnModel(false)); - name = extractDataFromDiagram(content, true).name; - processId = extractDataFromDiagram(content, true).processId; - } - const newWorkflow = { - label: name, - value: processId, - fileName: fileName, - xml: content, - }; - dispatch(setWorkflowAssociation(newWorkflow)); - dispatch(setProcessDiagramXML(newWorkflow.xml)); - dispatch(push(`${redirectUrl}subflow/create`)); - }; - - const handleChangeFile = (file) => { - // setFileName(file.name); - let fileData = new FileReader(); - try { - fileData.onloadend = (e) => { - handleFile(e, file.name); - }; - fileData.readAsText(file); - } catch (err) { - handleError(err, "File Import Error: "); - } - }; - const handleError = (err, message = "") => { - console.log(message, err); - document.getElementById("inputWorkflow").value = null; - dispatch(setWorkflowAssociation(null)); - }; - const handleCreateNew = () => { - dispatch(push(`${redirectUrl}subflow/create`)); - }; - - const uploadClick = (e) => { - e.preventDefault(); - uploadFormNode.current?.click(); - return false; - }; - - const headOptions = [ - { - name: "BPMN", - icon: "fa-solid fa-gears me-2", - onClick: () => { - handleChnageTab("BPMN"); - }, - }, - { - name: "DMN", - icon: "fa-solid fa-gears me-2", - onClick: () => { - handleChnageTab("DMN"); - }, - }, - ]; - - return ( -
-
- - -