From d85f724247fef8c2e9ce1fc865632298d191fc93 Mon Sep 17 00:00:00 2001 From: Vamsi_0 Date: Mon, 19 Jan 2026 17:49:48 +0530 Subject: [PATCH 1/2] refactor: enhance user workflow routes and API integration - Introduced workflowUpdateSchema for validating workflow updates. - Updated userRoutes to handle workflow updates, including error handling and user authentication checks. - Refactored API calls in the frontend to support PUT requests for updating workflows. - Improved workflow loading logic in WorkflowCanvas to manage nodes and edges effectively. - Added a save button in WorkflowCanvas for persisting workflow changes. --- .../src/routes/userRoutes/userRoutes.ts | 93 +++-- apps/web/app/lib/api.ts | 17 +- apps/web/app/workflows/[id]/page.tsx | 342 +++++++++++------- packages/common/src/index.ts | 5 + packages/db/prisma/schema.prisma | 1 + 5 files changed, 291 insertions(+), 167 deletions(-) diff --git a/apps/http-backend/src/routes/userRoutes/userRoutes.ts b/apps/http-backend/src/routes/userRoutes/userRoutes.ts index 84c073c..2843403 100644 --- a/apps/http-backend/src/routes/userRoutes/userRoutes.ts +++ b/apps/http-backend/src/routes/userRoutes/userRoutes.ts @@ -12,6 +12,7 @@ import { NodeSchema, NodeUpdateSchema, TriggerUpdateSchema, + workflowUpdateSchema, } from "@repo/common/zod"; import { GoogleSheetsNodeExecutor } from "@repo/nodes"; const router: Router = Router(); @@ -290,31 +291,23 @@ router.post( "/create/workflow", userMiddleware, async (req: AuthRequest, res) => { - try { - if (!req.user) { - return res.status(statusCodes.BAD_REQUEST).json({ - message: "User is not logged in ", - }); - } - const Data = req.body; - const ParsedData = WorkflowSchema.safeParse(Data); - const UserID = req.user.id; - // const UserID = "343c9a0a-9c3f-40d0-81de-9a5969e03f92"; - // Ensure that the required fields are present in the parsed data and create the workflow properly. - if (!ParsedData.success) { - return res.status(statusCodes.BAD_REQUEST).json({ - message: "Incorrect Workflow Inputs", - }); - } + + if (!req.user) { + return res.status(statusCodes.BAD_REQUEST).json({ + message: "User is not logged in ", + }); + } + const Data = req.body; + const ParsedData = WorkflowSchema.safeParse(Data); + const UserID = req.user.id; + // const UserID = "343c9a0a-9c3f-40d0-81de-9a5969e03f92"; + // Ensure that the required fields are present in the parsed data and create the workflow properly. + if (!ParsedData.success) { + return res.status(statusCodes.BAD_REQUEST).json({ + message: "Incorrect Workflow Inputs", + }); + } try { const createWorkflow = await prismaClient.workflow.create({ - // Example JSON body to test this route: - /* - { - "Name":"workflow-1", - "UserId": "", - "Config":[{}] - } - */ data: { user: { connect: { id: UserID }, @@ -328,12 +321,15 @@ router.post( message: "Workflow Created Successfully", Data: createWorkflow, }); - } catch (e) { + } catch (e: any) { console.log("Internal server error from creating aworkflow", e); return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({ message: "Internal Server Error from CCreating Workflow", + error: e + }); } + } ); @@ -444,10 +440,51 @@ router.get( } ); -// router.put("/workflow/update" , userMiddleware , (req : AuthRequest , res : Response) => { +router.put("/workflow/update", userMiddleware, async (req: AuthRequest, res: Response) => { -// }) -// ---------------------------------------- INSERTING DATA INTO NODES/ TRIGGER TABLE----------------------------- + const data = req.body; + const parsedData = workflowUpdateSchema.safeParse(data); + + if (!parsedData.success) { + return res.status(statusCodes.CONFLICT).json({ + message: "Incorrect Input", + error: parsedData.error + }) + } + const workflowId = parsedData.data.workflowId; + if (!req.user) { + return res.status(statusCodes.UNAUTHORIZED).json({ + message: "User Not Authenticated" + }) + } + const userId = req.user.id + try { + const workflowValid = await prismaClient.workflow.findFirst({ + where: { id: workflowId, userId: userId } + }) + if (!workflowValid) { + return res.status(statusCodes.BAD_REQUEST).json({ + message: "Didn't find the workflow" + }) + } + const update = await prismaClient.workflow.update({ + where: { id: workflowId, userId: userId }, + data: { + Nodes: parsedData.data.nodes, + Edges: parsedData.data.edges + } + }) + return res.status(statusCodes.ACCEPTED).json({ + message: "Workflow Updated Succesfuly", + Data: update + }) + } catch (error: any) { + return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({ + message: "Internal Server Error from Updating workflow", + error: error + }) + } +}) router.post( "/create/trigger", diff --git a/apps/web/app/lib/api.ts b/apps/web/app/lib/api.ts index 97a6e7d..93f13b4 100644 --- a/apps/web/app/lib/api.ts +++ b/apps/web/app/lib/api.ts @@ -16,11 +16,20 @@ export const api = { headers: { "Content-Type": "application/json" }, } ), - get: async (id: string) => - await axios.get(`${BACKEND_URL}/user/workflow/${id}`, { + get: async (id: string) => { + return await axios.get(`${BACKEND_URL}/user/workflow/${id}`, { withCredentials: true, headers: { "Content-Type": "application/json" }, - }), + }); + }, + put: async (data: any) => { + return await axios.put(`${BACKEND_URL}/user/workflow/update`, + data, + { + withCredentials: true, + headers: { "Content-Type": "application/json" }, + }) + } }, triggers: { getAvailable: async () => @@ -59,7 +68,7 @@ export const api = { }, Credentials: { getCredentials: async (type: string) => { - const res = await axios.get(`${BACKEND_URL}/user/getCredentials/${type}`, { + const res = await axios.get(`${BACKEND_URL}/user/getCredentials/${type}`, { withCredentials: true, headers: { "Content-Type": "application/json" }, }); diff --git a/apps/web/app/workflows/[id]/page.tsx b/apps/web/app/workflows/[id]/page.tsx index e0e5e12..9568b71 100644 --- a/apps/web/app/workflows/[id]/page.tsx +++ b/apps/web/app/workflows/[id]/page.tsx @@ -50,6 +50,169 @@ export default function WorkflowCanvas() { const nodeTypes = { customNode: BaseNode, }; + useEffect(() => { + const loadWorkflows = async () => { + const workflows = await api.workflows.get(workflowId); + console.log("This is the Data from workflow form DB",workflows.data.Data) + const dbNodes = workflows.data.Data.nodes; + + const dbEdges = workflows.data.Data.Edges; + console.log("These are the DbNodes ",dbNodes) + console.log("These are the DB Edges ",dbEdges) + const transformedNodes = dbNodes.map((node: any) => ({ + id: node.id, + type: "customNode", // ⚠️ This was missing! + position: node.position || { x: 0, y: 0 }, + data: { + label: node.data?.label || node.name || "Unknown", + icon: node.data?.icon || "⚙️", + nodeType: node.data?.nodeType || (node.TriggerId ? "trigger" : "action"), + isPlaceholder: node.data?.isPlaceholder || false, + isConfigured: node.data?.isConfigured || false, + config: node.data?.config || node.Config || {}, + + // ⚠️ CRITICAL: Re-attach callbacks + onConfigure: () => handleNodeConfigure({ + id: node.id, + name: node.data?.label || node.Name, + type: node.data?.nodeType || (node.TriggerId ? "trigger" : "action"), + actionType: node.AvailableNodeId || node.AvailableTriggerID + }) + } + })); + const actionPlaceholder = { + id: `action-placeholder-${Date.now()}`, + type: "customNode", + position: { x: 550, y: 200 }, + data: { + label: "Add Action", + icon: "➕", + isPlaceholder: true, + nodeType: "action", + config: {}, + onConfigure: () => setActionOpen(true), + }, + }; + + console.log("This Data after Tranforming",transformedNodes) + setNodes([...transformedNodes, actionPlaceholder]); + setEdges(dbEdges); + } + + loadWorkflows(); + }, [workflowId]) + + + + + // useEffect(() => { + // const loadWorkflows = async () => { + // const workflows = await api.workflows.get(workflowId); + // const dbNodes = workflows.data.Data.nodes; + // const dbEdges = workflows.data.Data.edges; + + // // Transform database nodes to React Flow format + // const transformedNodes = dbNodes.map((node: any) => ({ + // id: node.id, + // type: "customNode", + // position: node.position || node.Config?.position || { x: 0, y: 0 }, + // data: { + // label: node.Name || node.data?.label || "Unknown", + // icon: node.data?.icon || "⚙️", + // nodeType: node.TriggerId ? "trigger" : "action", + // isPlaceholder: false, // Loaded nodes are never placeholders + // isConfigured: node.data?.isConfigured || false, + // config: node.Config || node.data?.config || {}, + // onConfigure: () => handleNodeConfigure({ + // id: node.id, + // name: node.Name || node.data?.label, + // type: node.TriggerId ? "trigger" : "action", + // }) + // } + // })); + + // // Filter out any existing placeholders from DB (safety) + // const cleanNodes = transformedNodes.filter((n: any) => !n.data.isPlaceholder); + + // // Find action nodes for logic + // const realActionNodes = cleanNodes.filter((n: any) => + // n.data.nodeType === "action" + // ); + + // // Find trigger node + // const triggerNode = cleanNodes.find((n: any) => n.data.nodeType === "trigger"); + // let actionPlaceholder: any; + + // if (realActionNodes.length === 0) { + // // No real actions: place after trigger + // actionPlaceholder = { + // id: "action-placeholder-end", + // type: "customNode", + // position: triggerNode + // ? { x: triggerNode.position.x + 300, y: triggerNode.position.y + 150 } + // : { x: 550, y: 200 }, + // data: { + // label: "Add Action", + // icon: "➕", + // isPlaceholder: true, + // nodeType: "action", + // onConfigure: () => setActionOpen(true), + // } + // }; + // } else { + // // Place after last real action + // const lastAction = realActionNodes[realActionNodes.length - 1]; + // actionPlaceholder = { + // id: "action-placeholder-end", + // type: "customNode", + // position: { + // x: lastAction.position.x + 200, + // y: lastAction.position.y + 150 + // }, + // data: { + // label: "Add Action", + // icon: "➕", + // isPlaceholder: true, + // nodeType: "action", + // onConfigure: () => setActionOpen(true), + // } + // }; + // } + + // // Connect action-placeholder node as a child of the last action node (via edges) + // let updatedEdges = [...dbEdges]; + // if (realActionNodes.length > 0) { + // // Connect last action to action-placeholder + // const lastAction = realActionNodes[realActionNodes.length - 1]; + // updatedEdges = [ + // ...dbEdges, + // { + // id: `edge-${lastAction.id}-action-placeholder-end`, + // source: lastAction.id, + // target: "action-placeholder-end", + // type: "default" + // } + // ]; + // } else if (triggerNode) { + // // No actions yet, connect trigger to action-placeholder + // updatedEdges = [ + // ...dbEdges, + // { + // id: `edge-${triggerNode.id}-action-placeholder-end`, + // source: triggerNode.id, + // target: "action-placeholder-end", + // type: "default" + // } + // ]; + // } + + // setNodes([...cleanNodes, actionPlaceholder]); + // setEdges(updatedEdges); + // }; + + // if (workflowId) loadWorkflows(); + // }, [workflowId]); + const handleNodeConfigure = (node: any) => { setSelectedNode(node); @@ -70,7 +233,7 @@ export default function WorkflowCanvas() { TriggerId: change.id, Config: { ...(typeof changedNode.data.config === "object" && - changedNode.data.config !== null + changedNode.data.config !== null ? changedNode.data.config : {}), position: change.position, @@ -89,128 +252,6 @@ export default function WorkflowCanvas() { setError(error); } }; - // const handleActionSelection = async (action: any) => { - // try { - // // 1. Find trigger node - // const triggerNode = nodes.find( - // (n) => n.data.nodeType === "trigger" && !n.data.isPlaceholder - // ); - - // if (!triggerNode) { - // throw new Error("No trigger found"); - // } - - // const triggerId = triggerNode.id; - - // // 2. Find existing action nodes - // const existingActionNodes = nodes.filter( - // (n) => n.data.nodeType === "action" && !n.data.isPlaceholder - // ); - - // // 3. Calculate position for new action node - // let newPosition; - // if (existingActionNodes.length === 0) { - // // First action: place below trigger - // newPosition = { - // x: triggerNode.position.x, - // y: triggerNode.position.y + 150 // 150px below trigger - // }; - // } else { - // // Subsequent actions: place below last action - // const lastAction = existingActionNodes[existingActionNodes.length - 1]; - // newPosition = { - // x: lastAction!.position.x, - // y: lastAction!.position.y + 150 // 150px below last action - // }; - // } - - // const sourceNodeId = existingActionNodes.length > 0 - // ? existingActionNodes[existingActionNodes.length - 1]!.id - // : triggerId; - - // // 4. Call API - // const result = await api.nodes.create({ - // Name: action.name, - // AvailableNodeId: action.id, - // Config: { - // Position : newPosition - // }, - // WorkflowId: workflowId, - // Position: 1, // ✅ Dynamic position! - - // }); - - // const actionId = result.data.data.id; - - // // 5. Create action node with calculated position - // const newActionNode = { - // id: actionId, - // type: "customNode", - // position: newPosition, // ✅ Use calculated position - // data: { - // label: action.name, - // icon: action.icon, - // isPlaceholder: false, - // nodeType: "action", // ✅ Fixed from earlier - // config: {}, - // onConfigure: () => console.log("Configure", actionId), - // }, - // }; - - // // 6. Create placeholder below new action - // const placeholderPosition = { - // x: newPosition.x, - // y: newPosition.y + 150 // 150px below new action - // }; - - // const newPlaceholder = { - // id: `action-placeholder-${Date.now()}`, - // type: "customNode", - // position: placeholderPosition, - // data: { - // label: "Add Action", - // icon: "➕", - // isPlaceholder: true, - // nodeType: "action", - // config: {}, - // onConfigure: () => setActionOpen(true), - // }, - // }; - - // // Rest of your code stays the same... - // setNodes((prevNodes) => { - // const filtered = prevNodes.filter( - // (n) => !(n.data.isPlaceholder && n.data.nodeType === "action") - // ); - // return [...filtered, newActionNode, newPlaceholder]; - // }); - - // setEdges((prevEdges) => { - // const filtered = prevEdges.filter((e) => { - // const targetNode = nodes.find(n => n.id === e.target); - // return !(targetNode?.data.isPlaceholder && targetNode?.data.nodeType === "action"); - // }); - - // return [ - // ...filtered, - // { - // id: `e-${sourceNodeId}-${actionId}`, - // source: sourceNodeId, - // target: actionId, - // }, - // { - // id: `e-${actionId}-${newPlaceholder.id}`, - // source: actionId, - // target: newPlaceholder.id, - // }, - // ]; - // }); - - // setActionOpen(false); - // } catch (error: any) { - // setError(error); - // } - // }; const handleActionSelection = async (action: any) => { try { @@ -402,6 +443,28 @@ export default function WorkflowCanvas() { setError(error); } }; + + + const handleSave = async () => { + const payload = { + workflowId: workflowId, + nodes: nodes, + edges: edges + }; + console.log("THe Nodes are ", payload.nodes) + console.log("The payload in handleSave is ", payload); + try { + const response = await api.workflows.put({ + workflowId: workflowId, + nodes : nodes, + edges: edges + }); + // Optionally, you can show a message or update UI on success + console.log("Workflow updated successfully:", response.data); + } catch (error: any) { + setError(error); + } + } return (
+ + {/* ADD THIS MODAL */} @@ -438,15 +509,15 @@ export default function WorkflowCanvas() { const isTrigger = triggerNode?.id === nodeId; console.log("Is this a trigger or not", isTrigger); if (isTrigger) { - const data = await api.triggers.update({ TriggerId: nodeId, Config: config }); - console.log("The Data saved to DataBase is", data); - // The 'data' is undefined because api.triggers.update does not return anything (it's missing a 'return' statement). - // To debug, log after the call and also the actual variable: - console.log("api.triggers.update response:", data); - - const res = JSON.stringify(data); - console.log("THe response is res from saving the data is " , res) - console.log("Updating the trigger"); + const data = await api.triggers.update({ TriggerId: nodeId, Config: config }); + console.log("The Data saved to DataBase is", data); + // The 'data' is undefined because api.triggers.update does not return anything (it's missing a 'return' statement). + // To debug, log after the call and also the actual variable: + console.log("api.triggers.update response:", data); + + const res = JSON.stringify(data); + console.log("THe response is res from saving the data is ", res) + console.log("Updating the trigger"); } else { await api.nodes.update({ NodeId: nodeId, Config: config }); } @@ -455,9 +526,9 @@ export default function WorkflowCanvas() { prevNodes.map((node) => node.id === nodeId ? { - ...node, - data: { ...node.data, config, isConfigured: true }, - } + ...node, + data: { ...node.data, config, isConfigured: true }, + } : node ) ); @@ -477,3 +548,4 @@ export default function WorkflowCanvas() {
); } + \ No newline at end of file diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index b3d9b27..18231bd 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -50,6 +50,11 @@ export const WorkflowSchema = z.object({ description: z.string().optional(), }); +export const workflowUpdateSchema = z.object({ + nodes : z.any().optional(), + edges : z.any().optional(), + workflowId : z.string() +}) export enum statusCodes { OK = 200, CREATED = 201, diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index d0d6bd3..6f73daa 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -88,6 +88,7 @@ model Workflow { status WorkflowStatus? isEmpty Boolean? @default(true) nodes Node[] + Edges Json? Trigger Trigger? user User @relation(fields: [userId], references: [id]) executions WorkflowExecution[] From 7480c34601787941d1f1ec206f9d1fecb0f7b3fc Mon Sep 17 00:00:00 2001 From: Vamsi_0 Date: Mon, 19 Jan 2026 18:08:00 +0530 Subject: [PATCH 2/2] refactor: streamline user routes and enhance workflow loading logic - Removed commented-out code for the getCredentials route in userRoutes for clarity. - Improved error handling in userRoutes to provide more informative messages. - Updated WorkflowCanvas to handle trigger nodes and action placeholders more effectively. - Enhanced workflow loading logic to safeguard against missing data and ensure proper node and edge management. --- .../src/routes/userRoutes/userRoutes.ts | 63 +--- apps/web/app/workflows/[id]/page.tsx | 322 +++++++++++++----- 2 files changed, 252 insertions(+), 133 deletions(-) diff --git a/apps/http-backend/src/routes/userRoutes/userRoutes.ts b/apps/http-backend/src/routes/userRoutes/userRoutes.ts index 2843403..490bf4d 100644 --- a/apps/http-backend/src/routes/userRoutes/userRoutes.ts +++ b/apps/http-backend/src/routes/userRoutes/userRoutes.ts @@ -134,56 +134,6 @@ router.get( } ); -// //------------------------------ GET CREDENTIALS ----------------------------- - -// router.get('/getCredentials/:type', -// userMiddleware, -// async (req: AuthRequest, res) =>{ -// try{ -// console.log("user from getcredentials: ",req.user) -// if(!req.user){ -// return res.status(statusCodes.BAD_REQUEST).json({ -// message: "User is not Loggedin" -// }) -// } -// const userId = req.user.sub; -// const type = req.params.type -// console.log(userId," -userid") -// if(!type || !userId){ -// return res.status(statusCodes.BAD_REQUEST).json({ -// message: "Incorrect type Input", -// }); -// } -// const executor = new GoogleSheetsNodeExecutor() -// const response = await executor.getAllCredentials(userId,type) -// // console.log( typeof(response)); -// // console.log("response: ",response) -// const authUrl = typeof response === 'string' ? response : null -// // console.log(authUrl); - -// const credentials = response instanceof Object ? response : null -// // console.log(credentials) -// if(authUrl){ -// return res.status(statusCodes.OK).json({ -// message: "Credentials not found create credentials using this auth url", -// Data: authUrl, -// }); -// } -// else return res.status(statusCodes.OK).json({ -// message: "Credentials Fetched succesfully", -// Data: credentials, -// }); -// } -// catch(e){ -// console.log("Error Fetching the credentials ", e instanceof Error ? e.message : "Unkown reason"); -// return res -// .status(statusCodes.INTERNAL_SERVER_ERROR) -// .json({ message: "Internal server from fetching the credentials" }); -// } -// } -// ); - -//------------------------------ GET CREDENTIALS ----------------------------- router.get( "/getCredentials/:type", @@ -325,12 +275,12 @@ router.post( console.log("Internal server error from creating aworkflow", e); return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({ message: "Internal Server Error from CCreating Workflow", - error: e - + error: e instanceof Error ? e.message : "Unknown error" }); } + } - } + ); // ------------------------------------ FETCHING WORKFLOWS ----------------------------------- @@ -446,7 +396,7 @@ router.put("/workflow/update", userMiddleware, async (req: AuthRequest, res: Res const parsedData = workflowUpdateSchema.safeParse(data); if (!parsedData.success) { - return res.status(statusCodes.CONFLICT).json({ + return res.status(statusCodes.BAD_REQUEST).json({ message: "Incorrect Input", error: parsedData.error }) @@ -470,7 +420,7 @@ router.put("/workflow/update", userMiddleware, async (req: AuthRequest, res: Res const update = await prismaClient.workflow.update({ where: { id: workflowId, userId: userId }, data: { - Nodes: parsedData.data.nodes, + // Nodes: parsedData.data.nodes, Edges: parsedData.data.edges } }) @@ -479,9 +429,10 @@ router.put("/workflow/update", userMiddleware, async (req: AuthRequest, res: Res Data: update }) } catch (error: any) { + console.log("Error updating workflow:", error); return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({ message: "Internal Server Error from Updating workflow", - error: error + error: error instanceof Error ? error.message : "Unknown error" }) } }) diff --git a/apps/web/app/workflows/[id]/page.tsx b/apps/web/app/workflows/[id]/page.tsx index 9568b71..9156602 100644 --- a/apps/web/app/workflows/[id]/page.tsx +++ b/apps/web/app/workflows/[id]/page.tsx @@ -52,95 +52,257 @@ export default function WorkflowCanvas() { }; useEffect(() => { const loadWorkflows = async () => { - const workflows = await api.workflows.get(workflowId); - console.log("This is the Data from workflow form DB",workflows.data.Data) - const dbNodes = workflows.data.Data.nodes; - - const dbEdges = workflows.data.Data.Edges; - console.log("These are the DbNodes ",dbNodes) - console.log("These are the DB Edges ",dbEdges) - const transformedNodes = dbNodes.map((node: any) => ({ - id: node.id, - type: "customNode", // ⚠️ This was missing! - position: node.position || { x: 0, y: 0 }, - data: { - label: node.data?.label || node.name || "Unknown", - icon: node.data?.icon || "⚙️", - nodeType: node.data?.nodeType || (node.TriggerId ? "trigger" : "action"), - isPlaceholder: node.data?.isPlaceholder || false, - isConfigured: node.data?.isConfigured || false, - config: node.data?.config || node.Config || {}, - - // ⚠️ CRITICAL: Re-attach callbacks - onConfigure: () => handleNodeConfigure({ - id: node.id, - name: node.data?.label || node.Name, - type: node.data?.nodeType || (node.TriggerId ? "trigger" : "action"), - actionType: node.AvailableNodeId || node.AvailableTriggerID - }) + try { + const workflows = await api.workflows.get(workflowId); + console.log("This is the New Triggering trigger Data is",workflows.data.Data.Trigger.AvailableTriggerID); + + // 1. SAFEGUARD: Default to empty arrays to prevent crashes + const dbNodes = workflows.data.Data.nodes || []; + const dbEdges = workflows.data.Data.Edges || []; + const Trigger = workflows.data.Data.Trigger; + console.log("This is the trigger Data is",workflows.data.Data); + + console.log("The Data of Trigger is",Trigger) + // 2. CREATE TRIGGER NODE + // We assume the trigger is always the starting point + const triggerNode = { + id: Trigger.id, + type: "customNode", + // Default to top center if no position exists + position: Trigger.position || { x: 250, y: 50 }, + data: { + label: Trigger.name || Trigger.data?.label || "Trigger", + icon: Trigger.data?.icon || "⚡", // Lightning icon for trigger + nodeType: "trigger", + isConfigured: true, + onConfigure: () => handleNodeConfigure({ + id: Trigger.id, + // ... pass your trigger config props here + }) + }, + }; + if (!Trigger) { + + console.error("No trigger found in workflow data"); + return; + } + + // 3. TRANSFORM ACTION NODES + const transformedNodes = dbNodes.map((node: any) => ({ + id: node.id, + type: "customNode", + position: node.position || { x: 0, y: 0 }, + data: { + label: node.data?.label || node.name || "Unknown", + icon: node.data?.icon || "⚙️", + nodeType: "action", + // ... rest of your data mapping + onConfigure: () => handleNodeConfigure({ + id: node.id, + name: node.data?.label || node.Name, + type: "action", + actionType: node.AvailableNodeId + }) + } + })); + + // 4. CALCULATE PLACEHOLDER POSITION + // Find the "last" node to attach the placeholder to. + // If we have actions, use the last action. If not, use the trigger. + const lastNode = transformedNodes.length > 0 + ? transformedNodes[transformedNodes.length - 1] + : triggerNode; + + // Position placeholder 150 pixels below the last node + const placeholderPosition = { + x: lastNode.position.x, + y: lastNode.position.y + 150 + }; + + const actionPlaceholder = { + id: `action-placeholder-${Date.now()}`, + type: "customNode", + position: placeholderPosition, + data: { + label: "Add Action", + icon: "➕", + isPlaceholder: true, + nodeType: "action", + onConfigure: () => setActionOpen(true), + }, + }; + + // 5. COMBINE NODES + const finalNodes = [triggerNode, ...transformedNodes, actionPlaceholder]; + + // 6. MANAGE EDGES + // If we have DB edges, use them. + // If not, we should auto-connect Trigger -> First Node -> Placeholder + let finalEdges = [...dbEdges]; + + // Auto-connect Placeholder to the last node (visual guide) + const placeholderEdge = { + id: `e-${lastNode.id}-${actionPlaceholder.id}`, + source: lastNode.id, + target: actionPlaceholder.id, + type: 'default', // or your custom edge type + animated: true, // Make it dashed/animated to indicate it's temporary + }; + + // Ensure Trigger is connected to first action if DB edges are empty & actions exist + if (dbEdges.length === 0 && transformedNodes.length > 0) { + const triggerEdge = { + id: `e-${triggerNode.id}-${transformedNodes[0].id}`, + source: triggerNode.id, + target: transformedNodes[0].id, + type: 'default' + }; + finalEdges.push(triggerEdge); } - })); - const actionPlaceholder = { - id: `action-placeholder-${Date.now()}`, - type: "customNode", - position: { x: 550, y: 200 }, - data: { - label: "Add Action", - icon: "➕", - isPlaceholder: true, - nodeType: "action", - config: {}, - onConfigure: () => setActionOpen(true), - }, - }; - - console.log("This Data after Tranforming",transformedNodes) - setNodes([...transformedNodes, actionPlaceholder]); - setEdges(dbEdges); - } - + + finalEdges.push(placeholderEdge); + + setNodes(finalNodes); + setEdges(finalEdges); + + } catch (error) { + console.error("Failed to load workflow:", error); + } + }; + loadWorkflows(); - }, [workflowId]) - - + }, [workflowId]); // useEffect(() => { // const loadWorkflows = async () => { // const workflows = await api.workflows.get(workflowId); + // console.log("This is the Data from workflow form DB",workflows.data.Data) // const dbNodes = workflows.data.Data.nodes; - // const dbEdges = workflows.data.Data.edges; - - // // Transform database nodes to React Flow format + // const Trigger = workflows.data.Data.trigger; + // console.log("The Trigger is",Trigger) + // const dbEdges = workflows.data.Data.Edges || []; + // console.log("These are the DbNodes ",dbNodes) + // console.log("These are the DB Edges ",dbEdges) // const transformedNodes = dbNodes.map((node: any) => ({ // id: node.id, - // type: "customNode", - // position: node.position || node.Config?.position || { x: 0, y: 0 }, + // type: "customNode", // ⚠️ This was missing! + // position: node.position || { x: 0, y: 0 }, // data: { - // label: node.Name || node.data?.label || "Unknown", + // label: node.data?.label || node.name || "Unknown", // icon: node.data?.icon || "⚙️", - // nodeType: node.TriggerId ? "trigger" : "action", - // isPlaceholder: false, // Loaded nodes are never placeholders + // nodeType: node.data?.nodeType || (node.TriggerId ? "trigger" : "action"), + // isPlaceholder: node.data?.isPlaceholder || false, // isConfigured: node.data?.isConfigured || false, - // config: node.Config || node.data?.config || {}, + // config: node.data?.config || node.Config || {}, + + // // ⚠️ CRITICAL: Re-attach callbacks // onConfigure: () => handleNodeConfigure({ // id: node.id, - // name: node.Name || node.data?.label, - // type: node.TriggerId ? "trigger" : "action", + // name: node.data?.label || node.Name, + // type: node.data?.nodeType || (node.TriggerId ? "trigger" : "action"), + // actionType: node.AvailableNodeId || node.AvailableTriggerID // }) // } // })); + // const actionPlaceholder = { + // id: `action-placeholder-${Date.now()}`, + // type: "customNode", + // position: { x: 550, y: 200 }, + // data: { + // label: "Add Action", + // icon: "➕", + // isPlaceholder: true, + // nodeType: "action", + // config: {}, + // onConfigure: () => setActionOpen(true), + // }, + // }; + + // console.log("This Data after Tranforming",transformedNodes) + // setNodes([...transformedNodes, actionPlaceholder]); + // setEdges(dbEdges); + // } + + // loadWorkflows(); + // }, [workflowId]) + + + + // useEffect(() => { + // const loadWorkflows = async () => { + // const workflows = await api.workflows.get(workflowId); + // const dbNodes = workflows.data.Data.nodes; + // // Fix: Normalize dbEdges to always be an array + // let dbEdges = workflows.data.Data.edges; + // if (!Array.isArray(dbEdges)) { + // // Try alternative key (if backend provides Edges sometimes) + // dbEdges = workflows.data.Data.Edges; + // } + // if (!Array.isArray(dbEdges)) { + // dbEdges = []; + // } + + // // Get the trigger (from the "Trigger" key in backend response) + // const trigger = workflows.data.Data.Trigger; - // // Filter out any existing placeholders from DB (safety) - // const cleanNodes = transformedNodes.filter((n: any) => !n.data.isPlaceholder); + // // Transform trigger to a node if it exists and not already a node + // let triggerNode = null; + // if (trigger) { + // triggerNode = { + // id: trigger.id || "trigger-node", // fallback in case trigger has no id + // type: "customNode", + // position: trigger.config?.position || { x: 250, y: 50 }, + // data: { + // label: trigger.name || trigger.Name || "Trigger", + // icon: "⚡", + // nodeType: "trigger", + // isPlaceholder: false, + // isConfigured: true, + // config: trigger.config || trigger.Config || {}, + // onConfigure: () => handleNodeConfigure({ + // id: trigger.id, + // name: trigger.name || trigger.Name, + // type: "trigger", + // actionType: trigger.AvailableTriggerID // triggers have this, not actions + // }), + // } + // }; + // } + + // // Transform database nodes to React Flow format, excluding any possible trigger node + // const transformedNodes = dbNodes + // .filter((node: any) => !node.TriggerId) // trigger node will come separately + // .map((node: any) => ({ + // id: node.id, + // type: "customNode", + // position: node.position || node.Config?.position || { x: 0, y: 0 }, + // data: { + // label: node.Name || node.data?.label || "Unknown", + // icon: node.data?.icon || "⚙️", + // nodeType: node.TriggerId ? "trigger" : "action", + // isPlaceholder: false, // Loaded nodes are never placeholders + // isConfigured: node.data?.isConfigured || false, + // config: node.Config || node.data?.config || {}, + // onConfigure: () => handleNodeConfigure({ + // id: node.id, + // name: node.Name || node.data?.label, + // type: node.TriggerId ? "trigger" : "action", + // }) + // } + // })); + + // // Combine trigger node (if present) and transformed nodes + // let allNodes = []; + // if (triggerNode) { + // allNodes.push(triggerNode); + // } + // allNodes = [...allNodes, ...transformedNodes]; // // Find action nodes for logic - // const realActionNodes = cleanNodes.filter((n: any) => - // n.data.nodeType === "action" - // ); + // const realActionNodes = allNodes.filter((n: any) => n.data.nodeType === "action"); - // // Find trigger node - // const triggerNode = cleanNodes.find((n: any) => n.data.nodeType === "trigger"); + // // Find (now explicit) trigger node ref + // const triggerNodeRef = allNodes.find((n: any) => n.data.nodeType === "trigger"); // let actionPlaceholder: any; // if (realActionNodes.length === 0) { @@ -148,8 +310,8 @@ export default function WorkflowCanvas() { // actionPlaceholder = { // id: "action-placeholder-end", // type: "customNode", - // position: triggerNode - // ? { x: triggerNode.position.x + 300, y: triggerNode.position.y + 150 } + // position: triggerNodeRef + // ? { x: triggerNodeRef.position.x + 300, y: triggerNodeRef.position.y + 150 } // : { x: 550, y: 200 }, // data: { // label: "Add Action", @@ -179,13 +341,13 @@ export default function WorkflowCanvas() { // }; // } - // // Connect action-placeholder node as a child of the last action node (via edges) - // let updatedEdges = [...dbEdges]; + // // Build up-to-date edge array + // let updatedEdges = Array.isArray(dbEdges) ? [...dbEdges] : []; // if (realActionNodes.length > 0) { // // Connect last action to action-placeholder // const lastAction = realActionNodes[realActionNodes.length - 1]; // updatedEdges = [ - // ...dbEdges, + // ...updatedEdges, // { // id: `edge-${lastAction.id}-action-placeholder-end`, // source: lastAction.id, @@ -193,20 +355,23 @@ export default function WorkflowCanvas() { // type: "default" // } // ]; - // } else if (triggerNode) { + // } else if (triggerNodeRef) { // // No actions yet, connect trigger to action-placeholder // updatedEdges = [ - // ...dbEdges, + // ...updatedEdges, // { - // id: `edge-${triggerNode.id}-action-placeholder-end`, - // source: triggerNode.id, + // id: `edge-${triggerNodeRef.id}-action-placeholder-end`, + // source: triggerNodeRef.id, // target: "action-placeholder-end", // type: "default" // } // ]; // } - // setNodes([...cleanNodes, actionPlaceholder]); + // // Add placeholder node + // allNodes = [...allNodes, actionPlaceholder]; + + // setNodes(allNodes); // setEdges(updatedEdges); // }; @@ -214,6 +379,7 @@ export default function WorkflowCanvas() { // }, [workflowId]); + const handleNodeConfigure = (node: any) => { setSelectedNode(node); setConfigOpen(true); @@ -291,6 +457,7 @@ export default function WorkflowCanvas() { // onConfigure: () => console.log("Configure", actionId), }, + }; // 3. Create NEW placeholder @@ -548,4 +715,5 @@ export default function WorkflowCanvas() { ); } + \ No newline at end of file