Enabled users to rename or update the titles of all recorded actions

This commit is contained in:
source-rashi
2025-10-27 02:11:32 +05:30
parent 5844eaae21
commit d2e6fdbbc0
4 changed files with 95 additions and 5 deletions

View File

@@ -81,7 +81,7 @@
"swagger-jsdoc": "^6.2.8", "swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1", "swagger-ui-express": "^5.0.1",
"typedoc": "^0.23.8", "typedoc": "^0.23.8",
"typescript": "^4.6.3", "typescript": "^5.0.0",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"uuidv4": "^6.2.12", "uuidv4": "^6.2.12",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",

View File

@@ -254,7 +254,7 @@ function handleWorkflowActions(workflow: any[], credentials: Credentials) {
router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, res) => { router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const { name, limits, credentials, targetUrl } = req.body; const { name, limits, credentials, targetUrl, workflow: incomingWorkflow } = req.body;
// Validate input // Validate input
if (!name && !limits && !credentials && !targetUrl) { if (!name && !limits && !credentials && !targetUrl) {
@@ -298,7 +298,10 @@ router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, r
await robot.save(); 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) { if (credentials) {
workflow = handleWorkflowActions(workflow, credentials); workflow = handleWorkflowActions(workflow, credentials);

View File

@@ -32,7 +32,9 @@ export const updateRecording = async (id: string, data: {
name?: string; name?: string;
limits?: Array<{pairIndex: number, actionIndex: number, argIndex: number, limit: number}>; limits?: Array<{pairIndex: number, actionIndex: number, argIndex: number, limit: number}>;
credentials?: Credentials; credentials?: Credentials;
targetUrl?: string targetUrl?: string;
// optional full workflow replacement (useful for action renames)
workflow?: any[];
}): Promise<boolean> => { }): Promise<boolean> => {
try { try {
const response = await axios.put(`${apiUrl}/storage/recordings/${id}`, data); const response = await axios.put(`${apiUrl}/storage/recordings/${id}`, data);

View File

@@ -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) => { const handleTargetUrlChange = (newUrl: string) => {
setRobot((prev) => { setRobot((prev) => {
if (!prev) return 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(
<TextField
key={`action-name-${pairIndex}-${actionIndex}`}
label={`${t('Action')} ${pairIndex}:${actionIndex} - ${String(action.action)}`}
type="text"
value={currentName}
onChange={(e) => handleActionNameChange(pairIndex, actionIndex, e.target.value)}
style={{ marginBottom: '12px' }}
fullWidth
/>
);
});
});
if (inputs.length === 0) return null;
return (
<>
<Typography variant="body1" style={{ marginBottom: '10px' }}>
{t('Action Names')}
</Typography>
{inputs}
</>
);
};
const renderCredentialFields = ( const renderCredentialFields = (
selectors: string[], selectors: string[],
headerText: string, headerText: string,
@@ -574,7 +656,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => {
const targetUrl = getTargetUrl(); const targetUrl = getTargetUrl();
const payload = { const payload: any = {
name: robot.recording_meta.name, name: robot.recording_meta.name,
limits: scrapeListLimits.map((limit) => ({ limits: scrapeListLimits.map((limit) => ({
pairIndex: limit.pairIndex, pairIndex: limit.pairIndex,
@@ -584,6 +666,8 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => {
})), })),
credentials: credentialsForPayload, credentials: credentialsForPayload,
targetUrl: targetUrl, 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); const success = await updateRecording(robot.recording_meta.id, payload);
@@ -647,6 +731,7 @@ export const RobotEditPage = ({ handleStart }: RobotSettingsProps) => {
/> />
{renderScrapeListLimitFields()} {renderScrapeListLimitFields()}
{renderActionNameFields()}
</> </>
)} )}
</Box> </Box>