From d2e6fdbbc05e8846d0fc96d22c594e9902fb5147 Mon Sep 17 00:00:00 2001 From: source-rashi Date: Mon, 27 Oct 2025 02:11:32 +0530 Subject: [PATCH] Enabled users to rename or update the titles of all recorded actions --- package.json | 2 +- server/src/routes/storage.ts | 7 +- src/api/storage.ts | 4 +- src/components/robot/pages/RobotEditPage.tsx | 87 +++++++++++++++++++- 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c99c815a..d0e3fb6a 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "typedoc": "^0.23.8", - "typescript": "^4.6.3", + "typescript": "^5.0.0", "uuid": "^8.3.2", "uuidv4": "^6.2.12", "web-vitals": "^2.1.4", diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index bc33f3dc..89872d6a 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -254,7 +254,7 @@ function handleWorkflowActions(workflow: any[], credentials: Credentials) { router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, res) => { try { const { id } = req.params; - const { name, limits, credentials, targetUrl } = req.body; + const { name, limits, credentials, targetUrl, workflow: incomingWorkflow } = req.body; // Validate input if (!name && !limits && !credentials && !targetUrl) { @@ -298,7 +298,10 @@ router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, r await robot.save(); - let workflow = [...robot.recording.workflow]; // Create a copy of the workflow + // Start with existing workflow or allow client to supply a full workflow replacement + let workflow = incomingWorkflow && Array.isArray(incomingWorkflow) + ? JSON.parse(JSON.stringify(incomingWorkflow)) + : [...robot.recording.workflow]; // Create a copy of the workflow if (credentials) { workflow = handleWorkflowActions(workflow, credentials); diff --git a/src/api/storage.ts b/src/api/storage.ts index d3fa3eb8..e584c36f 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -32,7 +32,9 @@ export const updateRecording = async (id: string, data: { name?: string; limits?: Array<{pairIndex: number, actionIndex: number, argIndex: number, limit: number}>; credentials?: Credentials; - targetUrl?: string + targetUrl?: string; + // optional full workflow replacement (useful for action renames) + workflow?: any[]; }): Promise => { try { const response = await axios.put(`${apiUrl}/storage/recordings/${id}`, data); diff --git a/src/components/robot/pages/RobotEditPage.tsx b/src/components/robot/pages/RobotEditPage.tsx index bce3a5ea..3e7bcd78 100644 --- a/src/components/robot/pages/RobotEditPage.tsx +++ b/src/components/robot/pages/RobotEditPage.tsx @@ -417,6 +417,43 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { }); }; + const handleActionNameChange = ( + pairIndex: number, + actionIndex: number, + newName: string + ) => { + setRobot((prev) => { + if (!prev) return prev; + + const updatedWorkflow = [...prev.recording.workflow]; + if ( + updatedWorkflow.length > pairIndex && + updatedWorkflow[pairIndex]?.what && + updatedWorkflow[pairIndex].what.length > actionIndex + ) { + const action = { ...updatedWorkflow[pairIndex].what[actionIndex] }; + // update the standard name field + action.name = newName; + + // also update legacy __name location if present (args[0].__name) + if (action.args && action.args.length > 0 && typeof action.args[0] === 'object' && action.args[0] !== null && '__name' in action.args[0]) { + try { + action.args[0] = { ...action.args[0], __name: newName }; + } catch (e) { + // ignore + } + } + + updatedWorkflow[pairIndex].what[actionIndex] = action; + } + + return { + ...prev, + recording: { ...prev.recording, workflow: updatedWorkflow }, + }; + }); + }; + const handleTargetUrlChange = (newUrl: string) => { setRobot((prev) => { if (!prev) return prev; @@ -497,6 +534,51 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { ); }; + const renderActionNameFields = () => { + if (!robot || !robot.recording || !robot.recording.workflow) return null; + + const editableActions = new Set(['screenshot', 'scrapeList', 'scrapeSchema']); + const inputs: JSX.Element[] = []; + + robot.recording.workflow.forEach((pair, pairIndex) => { + if (!pair.what) return; + + pair.what.forEach((action, actionIndex) => { + // Only show editable name inputs for meaningful action types + if (!editableActions.has(String(action.action))) return; + + // derive current name from possible fields + const currentName = + action.name || + (action.args && action.args[0] && typeof action.args[0] === 'object' && action.args[0].__name) || + ''; + + inputs.push( + handleActionNameChange(pairIndex, actionIndex, e.target.value)} + style={{ marginBottom: '12px' }} + fullWidth + /> + ); + }); + }); + + if (inputs.length === 0) return null; + + return ( + <> + + {t('Action Names')} + + {inputs} + + ); + }; + const renderCredentialFields = ( selectors: string[], headerText: string, @@ -574,7 +656,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { const targetUrl = getTargetUrl(); - const payload = { + const payload: any = { name: robot.recording_meta.name, limits: scrapeListLimits.map((limit) => ({ pairIndex: limit.pairIndex, @@ -584,6 +666,8 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { })), credentials: credentialsForPayload, targetUrl: targetUrl, + // send the (possibly edited) workflow so backend can persist action name changes + workflow: robot.recording.workflow, }; const success = await updateRecording(robot.recording_meta.id, payload); @@ -647,6 +731,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => { /> {renderScrapeListLimitFields()} + {renderActionNameFields()} )}