From ac8dcf685782f5d1b5ea873e0a33d3c81c8be5de Mon Sep 17 00:00:00 2001 From: Rohit Date: Sat, 7 Jun 2025 14:23:32 +0530 Subject: [PATCH 01/16] feat: increment and add list results --- maxun-core/src/interpret.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/maxun-core/src/interpret.ts b/maxun-core/src/interpret.ts index 19b97707..b51652b1 100644 --- a/maxun-core/src/interpret.ts +++ b/maxun-core/src/interpret.ts @@ -47,6 +47,7 @@ interface InterpreterOptions { activeId: (id: number) => void, debugMessage: (msg: string) => void, setActionType: (type: string) => void, + incrementScrapeListIndex: () => void, }> } @@ -475,6 +476,11 @@ export default class Interpreter extends EventEmitter { } await this.ensureScriptsLoaded(page); + + if (this.options.debugChannel?.incrementScrapeListIndex) { + this.options.debugChannel.incrementScrapeListIndex(); + } + if (!config.pagination) { const scrapeResults: Record[] = await page.evaluate((cfg) => window.scrapeList(cfg), config); await this.options.serializableCallback(scrapeResults); @@ -624,6 +630,8 @@ export default class Interpreter extends EventEmitter { }); allResults = allResults.concat(newResults); debugLog("Results collected:", allResults.length); + + await this.options.serializableCallback(allResults); }; const checkLimit = () => { From 682da3879a53edd2ed4b200af0b250a9b64819fb Mon Sep 17 00:00:00 2001 From: Rohit Date: Sat, 7 Jun 2025 14:50:28 +0530 Subject: [PATCH 02/16] feat: push list data at index position --- server/src/workflow-management/classes/Interpreter.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/src/workflow-management/classes/Interpreter.ts b/server/src/workflow-management/classes/Interpreter.ts index ca853489..f249f26e 100644 --- a/server/src/workflow-management/classes/Interpreter.ts +++ b/server/src/workflow-management/classes/Interpreter.ts @@ -107,6 +107,11 @@ export class WorkflowInterpreter { */ public binaryData: { mimetype: string, data: string }[] = []; + /** + * Track current scrapeList index + */ + private currentScrapeListIndex: number = 0; + /** * An array of id's of the pairs from the workflow that are about to be paused. * As "breakpoints". @@ -288,6 +293,7 @@ export class WorkflowInterpreter { scrapeList: [], }; this.binaryData = []; + this.currentScrapeListIndex = 0; } /** @@ -322,6 +328,9 @@ export class WorkflowInterpreter { }, setActionType: (type: string) => { this.currentActionType = type; + }, + incrementScrapeListIndex: () => { + this.currentScrapeListIndex++; } }, serializableCallback: (data: any) => { @@ -334,7 +343,7 @@ export class WorkflowInterpreter { this.serializableDataByType.scrapeSchema.push([data]); } } else if (this.currentActionType === 'scrapeList') { - this.serializableDataByType.scrapeList.push(data); + this.serializableDataByType.scrapeList[this.currentScrapeListIndex] = data; } this.socket.emit('serializableCallback', data); From e98222f4dda5e7503dc0a0bfd5e97f2e8b7d7f18 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 10:26:51 +0530 Subject: [PATCH 03/16] feat: rm reset remote browser --- server/src/pgboss-worker.ts | 44 ------------------------------------- 1 file changed, 44 deletions(-) diff --git a/server/src/pgboss-worker.ts b/server/src/pgboss-worker.ts index 1a32f79b..c8e8229f 100644 --- a/server/src/pgboss-worker.ts +++ b/server/src/pgboss-worker.ts @@ -82,41 +82,6 @@ function AddGeneratedFlags(workflow: WorkflowFile) { return copy; }; -/** - * Function to reset browser state without creating a new browser - */ -async function resetBrowserState(browser: RemoteBrowser): Promise { - try { - const currentPage = browser.getCurrentPage(); - if (!currentPage) { - logger.log('error', 'No current page available to reset browser state'); - return false; - } - - // Navigate to blank page to reset state - await currentPage.goto('about:blank', { waitUntil: 'networkidle', timeout: 10000 }); - - // Clear browser storage - await currentPage.evaluate(() => { - try { - localStorage.clear(); - sessionStorage.clear(); - } catch (e) { - // Ignore errors in cleanup - } - }); - - // Clear cookies - const context = currentPage.context(); - await context.clearCookies(); - - return true; - } catch (error) { - logger.log('error', `Failed to reset browser state`); - return false; - } -} - /** * Modified checkAndProcessQueuedRun function - only changes browser reset logic */ @@ -137,13 +102,6 @@ async function checkAndProcessQueuedRun(userId: string, browserId: string): Prom return false; } - // Reset the browser state before next run - const browser = browserPool.getRemoteBrowser(browserId); - if (browser) { - logger.log('info', `Resetting browser state for browser ${browserId} before next run`); - await resetBrowserState(browser); - } - // Update the queued run to running status await queuedRun.update({ status: 'running', @@ -251,8 +209,6 @@ async function processRunExecution(job: Job) { } try { - // Reset the browser state before executing this run - await resetBrowserState(browser); const isRunAborted = async (): Promise => { const currentRun = await Run.findOne({ where: { runId: data.runId } }); From bc52b9bb4514be0d672175f19f46ad46e37494bf Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 10:29:04 +0530 Subject: [PATCH 04/16] feat: rm logic to process queued runs --- server/src/pgboss-worker.ts | 99 ------------------------------------- 1 file changed, 99 deletions(-) diff --git a/server/src/pgboss-worker.ts b/server/src/pgboss-worker.ts index c8e8229f..73bf4be4 100644 --- a/server/src/pgboss-worker.ts +++ b/server/src/pgboss-worker.ts @@ -82,52 +82,6 @@ function AddGeneratedFlags(workflow: WorkflowFile) { return copy; }; -/** - * Modified checkAndProcessQueuedRun function - only changes browser reset logic - */ -async function checkAndProcessQueuedRun(userId: string, browserId: string): Promise { - try { - // Find the oldest queued run for this specific browser - const queuedRun = await Run.findOne({ - where: { - browserId: browserId, - runByUserId: userId, - status: 'queued' - }, - order: [['startedAt', 'ASC']] - }); - - if (!queuedRun) { - logger.log('info', `No queued runs found for browser ${browserId}`); - return false; - } - - // Update the queued run to running status - await queuedRun.update({ - status: 'running', - log: 'Run started - using browser from previous run' - }); - - // Use user-specific queue - const userQueueName = `execute-run-user-${userId}`; - - // Schedule the run execution - await pgBoss.createQueue(userQueueName); - const executeJobId = await pgBoss.send(userQueueName, { - userId: userId, - runId: queuedRun.runId, - browserId: browserId - }); - - logger.log('info', `Scheduled queued run ${queuedRun.runId} to use browser ${browserId}, job ID: ${executeJobId}`); - return true; - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.log('error', `Error checking for queued runs: ${errorMessage}`); - return false; - } -} - /** * Modified processRunExecution function - only add browser reset */ @@ -189,9 +143,6 @@ async function processRunExecution(job: Job) { } } - // Check for queued runs even if this one failed - await checkAndProcessQueuedRun(data.userId, data.browserId); - return { success: false }; } @@ -202,9 +153,6 @@ async function processRunExecution(job: Job) { if (!browser || !currentPage) { logger.log('error', `Browser or page not available for run ${data.runId}`); - // Even if this run failed, check for queued runs - await checkAndProcessQueuedRun(data.userId, data.browserId); - return { success: false }; } @@ -227,13 +175,6 @@ async function processRunExecution(job: Job) { if (await isRunAborted()) { logger.log('info', `Run ${data.runId} was aborted during execution, not updating status`); - const queuedRunProcessed = await checkAndProcessQueuedRun(data.userId, plainRun.browserId); - - if (!queuedRunProcessed) { - await destroyRemoteBrowser(plainRun.browserId, data.userId); - logger.log('info', `No queued runs found for browser ${plainRun.browserId}, browser destroyed`); - } - return { success: true }; } @@ -371,15 +312,6 @@ async function processRunExecution(job: Job) { finishedAt: new Date().toLocaleString() }); - // Check for and process queued runs before destroying the browser - const queuedRunProcessed = await checkAndProcessQueuedRun(data.userId, plainRun.browserId); - - // Only destroy the browser if no queued run was found - if (!queuedRunProcessed) { - await destroyRemoteBrowser(plainRun.browserId, data.userId); - logger.log('info', `No queued runs found for browser ${plainRun.browserId}, browser destroyed`); - } - return { success: true }; } catch (executionError: any) { logger.log('error', `Run execution failed for run ${data.runId}: ${executionError.message}`); @@ -433,19 +365,6 @@ async function processRunExecution(job: Job) { logger.log('info', `Run ${data.runId} was aborted, not updating status to failed`); } - // Check for queued runs before destroying the browser - const queuedRunProcessed = await checkAndProcessQueuedRun(data.userId, plainRun.browserId); - - // Only destroy the browser if no queued run was found - if (!queuedRunProcessed) { - try { - await destroyRemoteBrowser(plainRun.browserId, data.userId); - logger.log('info', `No queued runs found for browser ${plainRun.browserId}, browser destroyed`); - } catch (cleanupError: any) { - logger.log('warn', `Failed to clean up browser for failed run ${data.runId}: ${cleanupError.message}`); - } - } - return { success: false }; } @@ -563,25 +482,7 @@ async function abortRun(runId: string, userId: string): Promise { } catch (socketError) { logger.log('warn', `Failed to emit run-aborted event: ${socketError}`); } - - let queuedRunProcessed = false; - try { - queuedRunProcessed = await checkAndProcessQueuedRun(userId, plainRun.browserId); - } catch (queueError) { - logger.log('warn', `Error checking queued runs: ${queueError}`); - } - if (!queuedRunProcessed) { - try { - await new Promise(resolve => setTimeout(resolve, 500)); - - await destroyRemoteBrowser(plainRun.browserId, userId); - logger.log('info', `Browser ${plainRun.browserId} destroyed successfully after abort`); - } catch (cleanupError) { - logger.log('warn', `Failed to clean up browser for aborted run ${runId}: ${cleanupError}`); - } - } - return true; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); From d02545f6d9a9ba0e90352e727d02545c59b87e82 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 10:33:49 +0530 Subject: [PATCH 05/16] feat: add partial data processing helper func --- server/src/pgboss-worker.ts | 107 +++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/server/src/pgboss-worker.ts b/server/src/pgboss-worker.ts index 73bf4be4..d89fa04c 100644 --- a/server/src/pgboss-worker.ts +++ b/server/src/pgboss-worker.ts @@ -82,6 +82,108 @@ function AddGeneratedFlags(workflow: WorkflowFile) { return copy; }; +/** + * Helper function to extract and process scraped data from browser interpreter + */ +async function extractAndProcessScrapedData( + browser: RemoteBrowser, + run: any +): Promise<{ + categorizedOutput: any; + uploadedBinaryOutput: any; + totalDataPointsExtracted: number; + totalSchemaItemsExtracted: number; + totalListItemsExtracted: number; + extractedScreenshotsCount: number; +}> { + let categorizedOutput: { + scrapeSchema: Record; + scrapeList: Record; + } = { + scrapeSchema: {}, + scrapeList: {} + }; + + if ((browser?.interpreter?.serializableDataByType?.scrapeSchema ?? []).length > 0) { + browser?.interpreter?.serializableDataByType?.scrapeSchema?.forEach((schemaItem: any, index: any) => { + categorizedOutput.scrapeSchema[`schema-${index}`] = schemaItem; + }); + } + + if ((browser?.interpreter?.serializableDataByType?.scrapeList ?? []).length > 0) { + browser?.interpreter?.serializableDataByType?.scrapeList?.forEach((listItem: any, index: any) => { + categorizedOutput.scrapeList[`list-${index}`] = listItem; + }); + } + + const binaryOutput = browser?.interpreter?.binaryData?.reduce( + (reducedObject: Record, item: any, index: number): Record => { + return { + [`item-${index}`]: item, + ...reducedObject, + }; + }, + {} + ) || {}; + + let totalDataPointsExtracted = 0; + let totalSchemaItemsExtracted = 0; + let totalListItemsExtracted = 0; + let extractedScreenshotsCount = 0; + + if (categorizedOutput.scrapeSchema) { + Object.values(categorizedOutput.scrapeSchema).forEach((schemaResult: any) => { + if (Array.isArray(schemaResult)) { + schemaResult.forEach(obj => { + if (obj && typeof obj === 'object') { + totalDataPointsExtracted += Object.keys(obj).length; + } + }); + totalSchemaItemsExtracted += schemaResult.length; + } else if (schemaResult && typeof schemaResult === 'object') { + totalDataPointsExtracted += Object.keys(schemaResult).length; + totalSchemaItemsExtracted += 1; + } + }); + } + + if (categorizedOutput.scrapeList) { + Object.values(categorizedOutput.scrapeList).forEach((listResult: any) => { + if (Array.isArray(listResult)) { + listResult.forEach(obj => { + if (obj && typeof obj === 'object') { + totalDataPointsExtracted += Object.keys(obj).length; + } + }); + totalListItemsExtracted += listResult.length; + } + }); + } + + if (binaryOutput) { + extractedScreenshotsCount = Object.keys(binaryOutput).length; + totalDataPointsExtracted += extractedScreenshotsCount; + } + + const binaryOutputService = new BinaryOutputService('maxun-run-screenshots'); + const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput( + run, + binaryOutput + ); + + return { + categorizedOutput: { + scrapeSchema: categorizedOutput.scrapeSchema || {}, + scrapeList: categorizedOutput.scrapeList || {} + }, + uploadedBinaryOutput, + totalDataPointsExtracted, + totalSchemaItemsExtracted, + totalListItemsExtracted, + extractedScreenshotsCount + }; +} + /** * Modified processRunExecution function - only add browser reset */ @@ -156,8 +258,7 @@ async function processRunExecution(job: Job) { return { success: false }; } - try { - + try { const isRunAborted = async (): Promise => { const currentRun = await Run.findOne({ where: { runId: data.runId } }); return currentRun ? (currentRun.status === 'aborted' || currentRun.status === 'aborting') : false; @@ -482,7 +583,7 @@ async function abortRun(runId: string, userId: string): Promise { } catch (socketError) { logger.log('warn', `Failed to emit run-aborted event: ${socketError}`); } - + return true; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); From b0403aeb40ac3f4b885cde28233588889c17872b Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 10:34:57 +0530 Subject: [PATCH 06/16] feat: add trigger integration helper func --- server/src/pgboss-worker.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/server/src/pgboss-worker.ts b/server/src/pgboss-worker.ts index d89fa04c..aec74159 100644 --- a/server/src/pgboss-worker.ts +++ b/server/src/pgboss-worker.ts @@ -184,6 +184,30 @@ async function extractAndProcessScrapedData( }; } +// Helper function to handle integration updates +async function triggerIntegrationUpdates(runId: string, robotMetaId: string): Promise { + try { + googleSheetUpdateTasks[runId] = { + robotId: robotMetaId, + runId: runId, + status: 'pending', + retries: 5, + }; + + airtableUpdateTasks[runId] = { + robotId: robotMetaId, + runId: runId, + status: 'pending', + retries: 5, + }; + + processAirtableUpdates(); + processGoogleSheetUpdates(); + } catch (err: any) { + logger.log('error', `Failed to update integrations for run: ${runId}: ${err.message}`); + } +} + /** * Modified processRunExecution function - only add browser reset */ From 67e6e0c3c18db071c25461f1c5a1bb6d7c0c9fc5 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 11:03:39 +0530 Subject: [PATCH 07/16] feat: revamp process run execution --- server/src/pgboss-worker.ts | 383 ++++++++++++++++++++++++------------ 1 file changed, 254 insertions(+), 129 deletions(-) diff --git a/server/src/pgboss-worker.ts b/server/src/pgboss-worker.ts index aec74159..15907b19 100644 --- a/server/src/pgboss-worker.ts +++ b/server/src/pgboss-worker.ts @@ -212,10 +212,12 @@ async function triggerIntegrationUpdates(runId: string, robotMetaId: string): Pr * Modified processRunExecution function - only add browser reset */ async function processRunExecution(job: Job) { - try { - const data = job.data; - logger.log('info', `Processing run execution job for runId: ${data.runId}, browserId: ${data.browserId}`); - + const BROWSER_INIT_TIMEOUT = 30000; + + const data = job.data; + logger.log('info', `Processing run execution job for runId: ${data.runId}, browserId: ${data.browserId}`); + + try { // Find the run const run = await Run.findOne({ where: { runId: data.runId } }); if (!run) { @@ -229,64 +231,56 @@ async function processRunExecution(job: Job) { } const plainRun = run.toJSON(); - - // Find the recording - const recording = await Robot.findOne({ where: { 'recording_meta.id': plainRun.robotMetaId }, raw: true }); - if (!recording) { - logger.log('error', `Recording for run ${data.runId} not found`); - - const currentRun = await Run.findOne({ where: { runId: data.runId } }); - if (currentRun && (currentRun.status !== 'aborted' && currentRun.status !== 'aborting')) { - await run.update({ - status: 'failed', - finishedAt: new Date().toLocaleString(), - log: 'Failed: Recording not found', - }); - - // Trigger webhooks for run failure - const failedWebhookPayload = { - robot_id: plainRun.robotMetaId, - run_id: data.runId, - robot_name: 'Unknown Robot', - status: 'failed', - started_at: plainRun.startedAt, - finished_at: new Date().toLocaleString(), - error: { - message: "Failed: Recording not found", - type: 'RecordingNotFoundError' - }, - metadata: { - browser_id: plainRun.browserId, - user_id: data.userId, - } - }; - - try { - await sendWebhook(plainRun.robotMetaId, 'run_failed', failedWebhookPayload); - logger.log('info', `Failure webhooks sent successfully for run ${data.runId}`); - } catch (webhookError: any) { - logger.log('error', `Failed to send failure webhooks for run ${data.runId}: ${webhookError.message}`); - } - } - - return { success: false }; - } - - // Get the browser and execute the run - const browser = browserPool.getRemoteBrowser(plainRun.browserId); - let currentPage = browser?.getCurrentPage(); + const browserId = data.browserId || plainRun.browserId; - if (!browser || !currentPage) { - logger.log('error', `Browser or page not available for run ${data.runId}`); - - return { success: false }; + if (!browserId) { + throw new Error(`No browser ID available for run ${data.runId}`); } + logger.log('info', `Looking for browser ${browserId} for run ${data.runId}`); + + let browser = browserPool.getRemoteBrowser(browserId); + const browserWaitStart = Date.now(); + + while (!browser && (Date.now() - browserWaitStart) < BROWSER_INIT_TIMEOUT) { + logger.log('debug', `Browser ${browserId} not ready yet, waiting...`); + await new Promise(resolve => setTimeout(resolve, 1000)); + browser = browserPool.getRemoteBrowser(browserId); + } + + if (!browser) { + throw new Error(`Browser ${browserId} not found in pool after timeout`); + } + + logger.log('info', `Browser ${browserId} found and ready for execution`); + try { + // Find the recording + const recording = await Robot.findOne({ where: { 'recording_meta.id': plainRun.robotMetaId }, raw: true }); + + if (!recording) { + throw new Error(`Recording for run ${data.runId} not found`); + } + const isRunAborted = async (): Promise => { const currentRun = await Run.findOne({ where: { runId: data.runId } }); return currentRun ? (currentRun.status === 'aborted' || currentRun.status === 'aborting') : false; }; + + let currentPage = browser.getCurrentPage(); + + const pageWaitStart = Date.now(); + while (!currentPage && (Date.now() - pageWaitStart) < 30000) { + logger.log('debug', `Page not ready for browser ${browserId}, waiting...`); + await new Promise(resolve => setTimeout(resolve, 1000)); + currentPage = browser.getCurrentPage(); + } + + if (!currentPage) { + throw new Error(`No current page available for browser ${browserId} after timeout`); + } + + logger.log('info', `Starting workflow execution for run ${data.runId}`); // Execute the workflow const workflow = AddGeneratedFlags(recording.recording); @@ -299,23 +293,27 @@ async function processRunExecution(job: Job) { if (await isRunAborted()) { logger.log('info', `Run ${data.runId} was aborted during execution, not updating status`); + + await destroyRemoteBrowser(plainRun.browserId, data.userId); return { success: true }; } + + logger.log('info', `Workflow execution completed for run ${data.runId}`); const binaryOutputService = new BinaryOutputService('maxun-run-screenshots'); const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput); - if (await isRunAborted()) { - logger.log('info', `Run ${data.runId} was aborted while processing results, not updating status`); - return { success: true }; - } - const categorizedOutput = { scrapeSchema: interpretationInfo.scrapeSchemaOutput || {}, scrapeList: interpretationInfo.scrapeListOutput || {} }; + if (await isRunAborted()) { + logger.log('info', `Run ${data.runId} was aborted while processing results, not updating status`); + return { success: true }; + } + await run.update({ ...run, status: 'success', @@ -330,6 +328,7 @@ async function processRunExecution(job: Job) { }); // Track extraction metrics + let totalDataPointsExtracted = 0; let totalSchemaItemsExtracted = 0; let totalListItemsExtracted = 0; let extractedScreenshotsCount = 0; @@ -337,23 +336,35 @@ async function processRunExecution(job: Job) { if (categorizedOutput.scrapeSchema) { Object.values(categorizedOutput.scrapeSchema).forEach((schemaResult: any) => { if (Array.isArray(schemaResult)) { + schemaResult.forEach(obj => { + if (obj && typeof obj === 'object') { + totalDataPointsExtracted += Object.keys(obj).length; + } + }); totalSchemaItemsExtracted += schemaResult.length; } else if (schemaResult && typeof schemaResult === 'object') { + totalDataPointsExtracted += Object.keys(schemaResult).length; totalSchemaItemsExtracted += 1; } }); } - + if (categorizedOutput.scrapeList) { Object.values(categorizedOutput.scrapeList).forEach((listResult: any) => { if (Array.isArray(listResult)) { + listResult.forEach(obj => { + if (obj && typeof obj === 'object') { + totalDataPointsExtracted += Object.keys(obj).length; + } + }); totalListItemsExtracted += listResult.length; } }); } - + if (uploadedBinaryOutput) { extractedScreenshotsCount = Object.keys(uploadedBinaryOutput).length; + totalDataPointsExtracted += extractedScreenshotsCount; } const totalRowsExtracted = totalSchemaItemsExtracted + totalListItemsExtracted; @@ -362,6 +373,7 @@ async function processRunExecution(job: Job) { console.log(`Extracted List Items Count: ${totalListItemsExtracted}`); console.log(`Extracted Screenshots Count: ${extractedScreenshotsCount}`); console.log(`Total Rows Extracted: ${totalRowsExtracted}`); + console.log(`Total Data Points Extracted: ${totalDataPointsExtracted}`); // Capture metrics capture( @@ -392,7 +404,8 @@ async function processRunExecution(job: Job) { total_rows: totalRowsExtracted, captured_texts_count: totalSchemaItemsExtracted, captured_lists_count: totalListItemsExtracted, - screenshots_count: extractedScreenshotsCount + screenshots_count: extractedScreenshotsCount, + total_data_points_extracted: totalDataPointsExtracted, }, metadata: { browser_id: plainRun.browserId, @@ -408,94 +421,206 @@ async function processRunExecution(job: Job) { } // Schedule updates for Google Sheets and Airtable - try { - googleSheetUpdateTasks[plainRun.runId] = { - robotId: plainRun.robotMetaId, - runId: plainRun.runId, - status: 'pending', - retries: 5, - }; + await triggerIntegrationUpdates(plainRun.runId, plainRun.robotMetaId); - airtableUpdateTasks[plainRun.runId] = { - robotId: plainRun.robotMetaId, - runId: plainRun.runId, - status: 'pending', - retries: 5, - }; - - processAirtableUpdates(); - processGoogleSheetUpdates(); - } catch (err: any) { - logger.log('error', `Failed to update Google Sheet for run: ${plainRun.runId}: ${err.message}`); - } - - serverIo.of(plainRun.browserId).emit('run-completed', { + const completionData = { runId: data.runId, robotMetaId: plainRun.robotMetaId, robotName: recording.recording_meta.name, status: 'success', finishedAt: new Date().toLocaleString() - }); + }; + + serverIo.of(browserId).emit('run-completed', completionData); + serverIo.of('/queued-run').to(`user-${data.userId}`).emit('run-completed', completionData); + + await destroyRemoteBrowser(browserId, data.userId); + logger.log('info', `Browser ${browserId} destroyed after successful run ${data.runId}`); return { success: true }; } catch (executionError: any) { logger.log('error', `Run execution failed for run ${data.runId}: ${executionError.message}`); - const currentRun = await Run.findOne({ where: { runId: data.runId } }); - if (currentRun && (currentRun.status !== 'aborted' && currentRun.status !== 'aborting')) { - await run.update({ + let partialDataExtracted = false; + let partialData: any = null; + let partialUpdateData: any = { + status: 'failed', + finishedAt: new Date().toLocaleString(), + log: `Failed: ${executionError.message}`, + }; + + try { + if (browser && browser.interpreter) { + const hasSchemaData = (browser.interpreter.serializableDataByType?.scrapeSchema ?? []).length > 0; + const hasListData = (browser.interpreter.serializableDataByType?.scrapeList ?? []).length > 0; + const hasBinaryData = (browser.interpreter.binaryData ?? []).length > 0; + + if (hasSchemaData || hasListData || hasBinaryData) { + logger.log('info', `Extracting partial data from failed run ${data.runId}`); + + partialData = await extractAndProcessScrapedData(browser, run); + + partialUpdateData.serializableOutput = { + scrapeSchema: Object.values(partialData.categorizedOutput.scrapeSchema), + scrapeList: Object.values(partialData.categorizedOutput.scrapeList), + }; + partialUpdateData.binaryOutput = partialData.uploadedBinaryOutput; + + partialDataExtracted = true; + logger.log('info', `Partial data extracted for failed run ${data.runId}: ${partialData.totalDataPointsExtracted} data points`); + + await triggerIntegrationUpdates(plainRun.runId, plainRun.robotMetaId); + } + } + } catch (partialDataError: any) { + logger.log('warn', `Failed to extract partial data for run ${data.runId}: ${partialDataError.message}`); + } + + await run.update(partialUpdateData); + + try { + const recording = await Robot.findOne({ where: { 'recording_meta.id': run.robotMetaId }, raw: true }); + + const failureData = { + runId: data.runId, + robotMetaId: plainRun.robotMetaId, + robotName: recording ? recording.recording_meta.name : 'Unknown Robot', status: 'failed', finishedAt: new Date().toLocaleString(), - log: `Failed: ${executionError.message}`, - }); - - // Capture failure metrics - capture( - 'maxun-oss-run-created-manual', - { - runId: data.runId, - user_id: data.userId, - created_at: new Date().toISOString(), - status: 'failed', - error_message: executionError.message, - } - ); - - // Trigger webhooks for run failure - const failedWebhookPayload = { - robot_id: plainRun.robotMetaId, - run_id: data.runId, - robot_name: recording.recording_meta.name, - status: 'failed', - started_at: plainRun.startedAt, - finished_at: new Date().toLocaleString(), - error: { - message: executionError.message, - stack: executionError.stack, - type: executionError.name || 'ExecutionError' - }, - metadata: { - browser_id: plainRun.browserId, - user_id: data.userId, - } + hasPartialData: partialDataExtracted }; - try { - await sendWebhook(plainRun.robotMetaId, 'run_failed', failedWebhookPayload); - logger.log('info', `Failure webhooks sent successfully for run ${data.runId}`); - } catch (webhookError: any) { - logger.log('error', `Failed to send failure webhooks for run ${data.runId}: ${webhookError.message}`); - } - } else { - logger.log('info', `Run ${data.runId} was aborted, not updating status to failed`); + serverIo.of(browserId).emit('run-completed', failureData); + serverIo.of('/queued-run').to(`user-${data.userId}`).emit('run-completed', failureData); + } catch (emitError: any) { + logger.log('warn', `Failed to emit failure event: ${emitError.message}`); } - - return { success: false }; + + const recording = await Robot.findOne({ where: { 'recording_meta.id': run.robotMetaId }, raw: true }); + + const failedWebhookPayload = { + robot_id: plainRun.robotMetaId, + run_id: data.runId, + robot_name: recording ? recording.recording_meta.name : 'Unknown Robot', + status: 'failed', + started_at: plainRun.startedAt, + finished_at: new Date().toLocaleString(), + error: { + message: executionError.message, + stack: executionError.stack, + type: 'ExecutionError', + }, + partial_data_extracted: partialDataExtracted, + extracted_data: partialDataExtracted ? { + captured_texts: Object.values(partialUpdateData.serializableOutput?.scrapeSchema || []).flat() || [], + captured_lists: partialUpdateData.serializableOutput?.scrapeList || {}, + total_data_points_extracted: partialData?.totalDataPointsExtracted || 0, + captured_texts_count: partialData?.totalSchemaItemsExtracted || 0, + captured_lists_count: partialData?.totalListItemsExtracted || 0, + screenshots_count: partialData?.extractedScreenshotsCount || 0 + } : null, + metadata: { + browser_id: plainRun.browserId, + user_id: data.userId, + } + }; + + try { + await sendWebhook(plainRun.robotMetaId, 'run_failed', failedWebhookPayload); + logger.log('info', `Failure webhooks sent successfully for run ${data.runId}`); + } catch (webhookError: any) { + logger.log('error', `Failed to send failure webhooks for run ${data.runId}: ${webhookError.message}`); + } + + try { + const failureSocketData = { + runId: data.runId, + robotMetaId: run.robotMetaId, + robotName: recording ? recording.recording_meta.name : 'Unknown Robot', + status: 'failed', + finishedAt: new Date().toLocaleString() + }; + + serverIo.of(run.browserId).emit('run-completed', failureSocketData); + serverIo.of('/queued-run').to(`user-${data.userId}`).emit('run-completed', failureSocketData); + } catch (socketError: any) { + logger.log('warn', `Failed to emit failure event in main catch: ${socketError.message}`); + } + + capture('maxun-oss-run-created-manual', { + runId: data.runId, + user_id: data.userId, + created_at: new Date().toISOString(), + status: 'failed', + error_message: executionError.message, + partial_data_extracted: partialDataExtracted, + totalRowsExtracted: partialData?.totalSchemaItemsExtracted + partialData?.totalListItemsExtracted + partialData?.extractedScreenshotsCount || 0, + }); + + await destroyRemoteBrowser(browserId, data.userId); + logger.log('info', `Browser ${browserId} destroyed after failed run`); + + return { success: false, partialDataExtracted }; } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); logger.log('error', `Failed to process run execution job: ${errorMessage}`); + + try { + const run = await Run.findOne({ where: { runId: data.runId }}); + + if (run) { + await run.update({ + status: 'failed', + finishedAt: new Date().toLocaleString(), + log: `Failed: ${errorMessage}`, + }); + + const recording = await Robot.findOne({ where: { 'recording_meta.id': run.robotMetaId }, raw: true }); + + const failedWebhookPayload = { + robot_id: run.robotMetaId, + run_id: data.runId, + robot_name: recording ? recording.recording_meta.name : 'Unknown Robot', + status: 'failed', + started_at: run.startedAt, + finished_at: new Date().toLocaleString(), + error: { + message: errorMessage, + }, + metadata: { + browser_id: run.browserId, + user_id: data.userId, + } + }; + + try { + await sendWebhook(run.robotMetaId, 'run_failed', failedWebhookPayload); + logger.log('info', `Failure webhooks sent successfully for run ${data.runId}`); + } catch (webhookError: any) { + logger.log('error', `Failed to send failure webhooks for run ${data.runId}: ${webhookError.message}`); + } + + try { + const failureSocketData = { + runId: data.runId, + robotMetaId: run.robotMetaId, + robotName: recording ? recording.recording_meta.name : 'Unknown Robot', + status: 'failed', + finishedAt: new Date().toLocaleString() + }; + + serverIo.of(run.browserId).emit('run-completed', failureSocketData); + serverIo.of('/queued-run').to(`user-${data.userId}`).emit('run-completed', failureSocketData); + } catch (socketError: any) { + logger.log('warn', `Failed to emit failure event in main catch: ${socketError.message}`); + } + } + } catch (updateError: any) { + logger.log('error', `Failed to update run status: ${updateError.message}`); + } + return { success: false }; } } From 01f4ea3e21a5519e7fe3ec381f8bacf02d4bd55d Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 11:08:20 +0530 Subject: [PATCH 08/16] feat: revamp abort run execution --- server/src/pgboss-worker.ts | 79 ++++++++++--------------------------- 1 file changed, 20 insertions(+), 59 deletions(-) diff --git a/server/src/pgboss-worker.ts b/server/src/pgboss-worker.ts index 15907b19..7b9ae5f1 100644 --- a/server/src/pgboss-worker.ts +++ b/server/src/pgboss-worker.ts @@ -627,12 +627,7 @@ async function processRunExecution(job: Job) { async function abortRun(runId: string, userId: string): Promise { try { - const run = await Run.findOne({ - where: { - runId: runId, - runByUserId: userId - } - }); + const run = await Run.findOne({ where: { runId: runId } }); if (!run) { logger.log('warn', `Run ${runId} not found or does not belong to user ${userId}`); @@ -683,32 +678,9 @@ async function abortRun(runId: string, userId: string): Promise { } let currentLog = 'Run aborted by user'; - let categorizedOutput = { - scrapeSchema: {}, - scrapeList: {}, - }; - let binaryOutput: Record = {}; - - try { - if (browser.interpreter) { - if (browser.interpreter.debugMessages) { - currentLog = browser.interpreter.debugMessages.join('\n') || currentLog; - } - - if (browser.interpreter.serializableDataByType) { - categorizedOutput = { - scrapeSchema: collectDataByType(browser.interpreter.serializableDataByType.scrapeSchema || []), - scrapeList: collectDataByType(browser.interpreter.serializableDataByType.scrapeList || []), - }; - } - - if (browser.interpreter.binaryData) { - binaryOutput = collectBinaryData(browser.interpreter.binaryData); - } - } - } catch (interpreterError) { - logger.log('warn', `Error collecting data from interpreter: ${interpreterError}`); - } + const extractedData = await extractAndProcessScrapedData(browser, run); + + console.log(`Total Data Points Extracted in aborted run: ${extractedData.totalDataPointsExtracted}`); await run.update({ status: 'aborted', @@ -716,12 +688,16 @@ async function abortRun(runId: string, userId: string): Promise { browserId: plainRun.browserId, log: currentLog, serializableOutput: { - scrapeSchema: Object.values(categorizedOutput.scrapeSchema), - scrapeList: Object.values(categorizedOutput.scrapeList), + scrapeSchema: Object.values(extractedData.categorizedOutput.scrapeSchema), + scrapeList: Object.values(extractedData.categorizedOutput.scrapeList), }, - binaryOutput, + binaryOutput: extractedData.uploadedBinaryOutput, }); + if (extractedData.totalDataPointsExtracted > 0) { + await triggerIntegrationUpdates(runId, plainRun.robotMetaId); + } + try { serverIo.of(plainRun.browserId).emit('run-aborted', { runId, @@ -733,6 +709,15 @@ async function abortRun(runId: string, userId: string): Promise { logger.log('warn', `Failed to emit run-aborted event: ${socketError}`); } + try { + await new Promise(resolve => setTimeout(resolve, 500)); + + await destroyRemoteBrowser(plainRun.browserId, userId); + logger.log('info', `Browser ${plainRun.browserId} destroyed successfully after abort`); + } catch (cleanupError) { + logger.log('warn', `Failed to clean up browser for aborted run ${runId}: ${cleanupError}`); + } + return true; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); @@ -741,30 +726,6 @@ async function abortRun(runId: string, userId: string): Promise { } } -/** - * Helper function to collect data from arrays into indexed objects - * @param dataArray Array of data to be transformed into an object with indexed keys - * @returns Object with indexed keys - */ -function collectDataByType(dataArray: any[]): Record { - return dataArray.reduce((result: Record, item, index) => { - result[`item-${index}`] = item; - return result; - }, {}); -} - -/** - * Helper function to collect binary data (like screenshots) - * @param binaryDataArray Array of binary data objects to be transformed - * @returns Object with indexed keys - */ -function collectBinaryData(binaryDataArray: { mimetype: string, data: string, type?: string }[]): Record { - return binaryDataArray.reduce((result: Record, item, index) => { - result[`item-${index}`] = item; - return result; - }, {}); -} - async function registerRunExecutionWorker() { try { const registeredUserQueues = new Map(); From 02c603c518afc01e3b2c5349be4e93d16b54e2da Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 11:11:13 +0530 Subject: [PATCH 09/16] fix: uuid import --- server/src/schedule-worker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/schedule-worker.ts b/server/src/schedule-worker.ts index 91c3c224..c75770e4 100644 --- a/server/src/schedule-worker.ts +++ b/server/src/schedule-worker.ts @@ -6,6 +6,7 @@ import logger from './logger'; import Robot from './models/Robot'; import { handleRunRecording } from './workflow-management/scheduler'; import { computeNextRun } from './utils/schedule'; +import { v4 as uuid } from "uuid"; if (!process.env.DB_USER || !process.env.DB_PASSWORD || !process.env.DB_HOST || !process.env.DB_PORT || !process.env.DB_NAME) { throw new Error('One or more required environment variables are missing.'); @@ -32,7 +33,7 @@ interface ScheduledWorkflowData { */ export async function scheduleWorkflow(id: string, userId: string, cronExpression: string, timezone: string): Promise { try { - const runId = require('uuidv4').uuid(); + const runId = uuid(); const queueName = `scheduled-workflow-${id}`; From 83280fbcf37854a54e114434fb049eb21a64e619 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 11:16:31 +0530 Subject: [PATCH 10/16] feat: revamp abort run handler --- src/pages/MainPage.tsx | 53 +++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 8d1a623c..42c6eb1a 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -43,22 +43,59 @@ export const MainPage = ({ handleEditRecording, initialContent }: MainPageProps) runId: '', robotMetaId: '' }); + const [queuedRuns, setQueuedRuns] = React.useState>(new Set()); let aborted = false; const { notify, setRerenderRuns, setRecordingId } = useGlobalInfoStore(); const navigate = useNavigate(); - const abortRunHandler = (runId: string) => { + const abortRunHandler = (runId: string, robotName: string, browserId: string) => { + notify('info', t('main_page.notifications.abort_initiated', { name: robotName })); + aborted = true; + notifyAboutAbort(runId).then(async (response) => { - if (response) { - notify('success', t('main_page.notifications.abort_success', { name: runningRecordingName })); - await stopRecording(ids.browserId); - } else { - notify('error', t('main_page.notifications.abort_failed', { name: runningRecordingName })); + if (!response.success) { + notify('error', t('main_page.notifications.abort_failed', { name: robotName })); + setRerenderRuns(true); + return; } - }) + + if (response.isQueued) { + setRerenderRuns(true); + + notify('success', t('main_page.notifications.abort_success', { name: robotName })); + + setQueuedRuns(prev => { + const newSet = new Set(prev); + newSet.delete(runId); + return newSet; + }); + + return; + } + + const abortSocket = io(`${apiUrl}/${browserId}`, { + transports: ["websocket"], + rejectUnauthorized: false + }); + + abortSocket.on('run-aborted', (abortData) => { + if (abortData.runId === runId) { + notify('success', t('main_page.notifications.abort_completed', { name: abortData.robotName || robotName })); + setRerenderRuns(true); + abortSocket.disconnect(); + } + }); + + abortSocket.on('connect_error', (error) => { + console.log('Abort socket connection error:', error); + notify('error', t('main_page.notifications.abort_failed', { name: robotName })); + setRerenderRuns(true); + abortSocket.disconnect(); + }); + }); } const setRecordingInfo = (id: string, name: string) => { @@ -156,7 +193,7 @@ export const MainPage = ({ handleEditRecording, initialContent }: MainPageProps) case 'runs': return abortRunHandler(ids.runId)} + abortRunHandler={abortRunHandler} runId={ids.runId} runningRecordingName={runningRecordingName} />; From 9b9bc6dae7cbb7917952eb5656f120f057b54880 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 11:17:27 +0530 Subject: [PATCH 11/16] feat: pass params to abort run handler --- src/components/run/ColapsibleRow.tsx | 4 ++-- src/components/run/Runs.tsx | 2 +- src/components/run/RunsTable.tsx | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/run/ColapsibleRow.tsx b/src/components/run/ColapsibleRow.tsx index eadf0823..d69acb8f 100644 --- a/src/components/run/ColapsibleRow.tsx +++ b/src/components/run/ColapsibleRow.tsx @@ -33,7 +33,7 @@ interface CollapsibleRowProps { handleDelete: () => void; isOpen: boolean; currentLog: string; - abortRunHandler: () => void; + abortRunHandler: (runId: string, robotName: string, browserId: string) => void; runningRecordingName: string; urlRunId: string | null; } @@ -60,7 +60,7 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRun } const handleAbort = () => { - abortRunHandler(); + abortRunHandler(row.runId, row.name, row.browserId); } useEffect(() => { diff --git a/src/components/run/Runs.tsx b/src/components/run/Runs.tsx index 736378f6..4287d006 100644 --- a/src/components/run/Runs.tsx +++ b/src/components/run/Runs.tsx @@ -4,7 +4,7 @@ import { RunsTable } from "./RunsTable"; interface RunsProps { currentInterpretationLog: string; - abortRunHandler: () => void; + abortRunHandler: (runId: string, robotName: string, browserId: string) => void; runId: string; runningRecordingName: string; } diff --git a/src/components/run/RunsTable.tsx b/src/components/run/RunsTable.tsx index 0864c493..2628bdda 100644 --- a/src/components/run/RunsTable.tsx +++ b/src/components/run/RunsTable.tsx @@ -53,6 +53,7 @@ export interface Data { finishedAt: string; runByUserId?: string; runByScheduleId?: string; + browserId: string; runByAPI?: boolean; log: string; runId: string; @@ -65,7 +66,7 @@ export interface Data { interface RunsTableProps { currentInterpretationLog: string; - abortRunHandler: () => void; + abortRunHandler: (runId: string, robotName: string, browserId: string) => void; runId: string; runningRecordingName: string; } From 2eb7c4d8516ad19138d0dbc7aeec98f0306454ee Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 11:17:59 +0530 Subject: [PATCH 12/16] feat: modify notify abort route --- src/api/storage.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/api/storage.ts b/src/api/storage.ts index 295f340c..9e47f533 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -184,20 +184,24 @@ export const interpretStoredRecording = async (id: string): Promise => } } -export const notifyAboutAbort = async (id: string): Promise => { +export const notifyAboutAbort = async (id: string): Promise<{ success: boolean; isQueued?: boolean }> => { try { - const response = await axios.post(`${apiUrl}/storage/runs/abort/${id}`); + const response = await axios.post(`${apiUrl}/robot/runs/abort/${id}`, { withCredentials: true }); if (response.status === 200) { - return response.data; + return { + success: response.data.success, + isQueued: response.data.isQueued + }; } else { throw new Error(`Couldn't abort a running recording with id ${id}`); } } catch (error: any) { console.log(error); - return false; + return { success: false }; } } + export const scheduleStoredRecording = async (id: string, settings: ScheduleSettings): Promise => { try { const response = await axios.put( From 8e7ffd40e1bc3592b057da1e92cbde7bb97a09f7 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 11:19:35 +0530 Subject: [PATCH 13/16] chore: modify route name --- src/api/storage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/storage.ts b/src/api/storage.ts index 9e47f533..65b6caa4 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -186,7 +186,7 @@ export const interpretStoredRecording = async (id: string): Promise => export const notifyAboutAbort = async (id: string): Promise<{ success: boolean; isQueued?: boolean }> => { try { - const response = await axios.post(`${apiUrl}/robot/runs/abort/${id}`, { withCredentials: true }); + const response = await axios.post(`${apiUrl}/storage/runs/abort/${id}`, { withCredentials: true }); if (response.status === 200) { return { success: response.data.success, From c0370e56659a919ef851931544c7f2feee4d1431 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 11:26:44 +0530 Subject: [PATCH 14/16] feat: revamp abort run route --- server/src/routes/storage.ts | 54 ++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index b4e8cdfd..b8e91c76 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -891,31 +891,57 @@ router.post('/runs/abort/:id', requireSignIn, async (req: AuthenticatedRequest, try { if (!req.user) { return res.status(401).send({ error: 'Unauthorized' }); } - const run = await Run.findOne({ where: { - runId: req.params.id, - runByUserId: req.user.id, - } }); + const run = await Run.findOne({ where: { runId: req.params.id } }); if (!run) { - return res.status(404).send(false); + return res.status(404).send({ error: 'Run not found' }); + } + + if (!['running', 'queued'].includes(run.status)) { + return res.status(400).send({ + error: `Cannot abort run with status: ${run.status}` + }); + } + + const isQueued = run.status === 'queued'; + + await run.update({ + status: 'aborting' + }); + + if (isQueued) { + await run.update({ + status: 'aborted', + finishedAt: new Date().toLocaleString(), + log: 'Run aborted while queued' + }); + + return res.send({ + success: true, + message: 'Queued run aborted', + isQueued: true + }); } const userQueueName = `abort-run-user-${req.user.id}`; await pgBoss.createQueue(userQueueName); - await pgBoss.send(userQueueName, { + const jobId = await pgBoss.send(userQueueName, { userId: req.user.id, runId: req.params.id }); - - await run.update({ - status: 'aborting' - }); - - return res.send(true); + + logger.log('info', `Abort signal sent for run ${req.params.id}, job ID: ${jobId}`); + + return res.send({ + success: true, + message: 'Abort signal sent', + jobId, + isQueued: false + }); } catch (e) { const { message } = e as Error; - logger.log('info', `Error while aborting run with id: ${req.params.id} - ${message}`); - return res.send(false); + logger.log('error', `Error aborting run ${req.params.id}: ${message}`); + return res.status(500).send({ error: 'Failed to abort run' }); } }); From 7048c8a0928953806597549a0254c8aa29a88aa0 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 11:38:34 +0530 Subject: [PATCH 15/16] chore: alter translation message abort --- src/pages/MainPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 42c6eb1a..bc3be214 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -83,7 +83,7 @@ export const MainPage = ({ handleEditRecording, initialContent }: MainPageProps) abortSocket.on('run-aborted', (abortData) => { if (abortData.runId === runId) { - notify('success', t('main_page.notifications.abort_completed', { name: abortData.robotName || robotName })); + notify('success', t('main_page.notifications.abort_success', { name: abortData.robotName || robotName })); setRerenderRuns(true); abortSocket.disconnect(); } From 4b69f379ebb55b293ed9f45957e05e75bbaec0f0 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 12 Jun 2025 11:41:23 +0530 Subject: [PATCH 16/16] feat: add translations abort --- public/locales/de.json | 3 ++- public/locales/en.json | 3 ++- public/locales/es.json | 3 ++- public/locales/ja.json | 3 ++- public/locales/zh.json | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/public/locales/de.json b/public/locales/de.json index 4e26dc35..486c5b65 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -473,7 +473,8 @@ "schedule_success": "Roboter {{name}} erfolgreich geplant", "schedule_failed": "Planen des Roboters {{name}} fehlgeschlagen", "abort_success": "Interpretation des Roboters {{name}} erfolgreich abgebrochen", - "abort_failed": "Abbrechen der Interpretation des Roboters {{name}} fehlgeschlagen" + "abort_failed": "Abbrechen der Interpretation des Roboters {{name}} fehlgeschlagen", + "abort_initiated": "Interpretation des Roboters {{name}} wird abgebrochen" }, "menu": { "recordings": "Roboter", diff --git a/public/locales/en.json b/public/locales/en.json index e4f15c52..5c12f67c 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -486,7 +486,8 @@ "schedule_success": "Robot {{name}} scheduled successfully", "schedule_failed": "Failed to schedule robot {{name}}", "abort_success": "Interpretation of robot {{name}} aborted successfully", - "abort_failed": "Failed to abort the interpretation of robot {{name}}" + "abort_failed": "Failed to abort the interpretation of robot {{name}}", + "abort_initiated": "Aborting the interpretation of robot {{name}}" }, "menu": { "recordings": "Robots", diff --git a/public/locales/es.json b/public/locales/es.json index 35b1fba1..e3c81408 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -474,7 +474,8 @@ "schedule_success": "Robot {{name}} programado exitosamente", "schedule_failed": "Error al programar el robot {{name}}", "abort_success": "Interpretación del robot {{name}} abortada exitosamente", - "abort_failed": "Error al abortar la interpretación del robot {{name}}" + "abort_failed": "Error al abortar la interpretación del robot {{name}}", + "abort_initiated": "Cancelando la interpretación del robot {{name}}" }, "menu": { "recordings": "Robots", diff --git a/public/locales/ja.json b/public/locales/ja.json index b9e1174a..d252dac0 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -474,7 +474,8 @@ "schedule_success": "ロボット{{name}}のスケジュールが正常に設定されました", "schedule_failed": "ロボット{{name}}のスケジュール設定に失敗しました", "abort_success": "ロボット{{name}}の解釈を中止しました", - "abort_failed": "ロボット{{name}}の解釈中止に失敗しました" + "abort_failed": "ロボット{{name}}の解釈中止に失敗しました", + "abort_initiated": "ロボット {{name}} の解釈を中止しています" }, "menu": { "recordings": "ロボット", diff --git a/public/locales/zh.json b/public/locales/zh.json index 6ac76ed9..75a0553e 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -474,7 +474,8 @@ "schedule_success": "机器人{{name}}调度成功", "schedule_failed": "机器人{{name}}调度失败", "abort_success": "成功中止机器人{{name}}的解释", - "abort_failed": "中止机器人{{name}}的解释失败" + "abort_failed": "中止机器人{{name}}的解释失败", + "abort_initiated": "正在中止机器人 {{name}} 的解释" }, "menu": { "recordings": "机器人",