Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: prevent page reload on run trigger to open remote browser #389

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from

Conversation

amhsirak
Copy link
Member

@amhsirak amhsirak commented Jan 24, 2025

Summary by CodeRabbit

  • New Features

    • Added support for tracking robot run states in browser pool management.
    • Enhanced workflow interpretation with improved event emissions.
    • Implemented additional checks for active robot runs during browser recording sessions.
    • Introduced job queue management for run workflows.
    • Added local storage management for run information.
    • Differentiated between scheduled and unscheduled runs in job processing.
    • Integrated browser automation and external service interactions for workflow management.
  • Bug Fixes

    • Improved handling of recording interpretation and run completion notifications.
  • Chores

    • Refined socket connection and event handling mechanisms.

Copy link

coderabbitai bot commented Jan 24, 2025

Walkthrough

The pull request introduces enhancements to browser management and workflow interpretation across multiple files. The changes focus on adding a new isRobotRun property to track robot run states in the BrowserPool, implementing a middleware to prevent multiple simultaneous robot runs, and improving event handling for run completions. The modifications aim to provide better control and monitoring of remote browser instances during automated workflows.

Changes

File Change Summary
server/src/browser-management/classes/BrowserPool.ts - Added isRobotRun optional property to BrowserPoolInfo
- Updated addRemoteBrowser method with new parameter
- Added hasActiveRobotRun() and clearRobotRunState() methods
server/src/browser-management/controller.ts - Modified addRemoteBrowser call to include isRobotRun parameter
server/src/routes/record.ts - Added middleware to check for active robot runs
- Prevent browser initialization if an active run exists
server/src/workflow-management/classes/Interpreter.ts - Added 'run-completed' event emission after interpretation
src/pages/MainPage.tsx - Removed conditional notifications in readyForRunHandler
- Added useEffect for handling stored run information
- Enhanced socket event handling
server/src/routes/storage.ts - Added job to workflowQueue for run processing after run creation
- Updated response structure to return browserId and runId
server/src/worker.ts - Introduced new import alias for handleRunRecording
- Modified job data structure to include isScheduled property
server/src/workflow-management/record.ts - Added handleRunRecording function for managing browser automation and workflow execution

Possibly related PRs

  • fix: prevent page reload on run trigger to open remote browser #386: The changes in the main PR are directly related to the modifications in the addRemoteBrowser method and the introduction of the isRobotRun parameter, which are also present in the retrieved PR.
  • feat: ability to update credentials for robots with authentication #363: The changes in the main PR, which involve adding an isRobotRun property and modifying the addRemoteBrowser method, are related to the updates in the retrieved PR that include the isLogin property in the Robot model, as both involve modifications to the handling of boolean properties that affect the state of browser instances.
  • chore: code cleanup #289: The changes in the main PR, specifically the updates to the addRemoteBrowser method in the BrowserPool class, are directly related to the modifications made in the createRemoteBrowserForRun function in the retrieved PR, as both involve the addition of a new parameter to the same method.

Suggested reviewers

  • amhsirak

Poem

🐇 In the realm of code where browsers play,
A new flag is set to guide the way.
Robot runs now in check,
With careful steps, we’ll not wreck.
Hopping through workflows, bright and spry,
Together we’ll reach for the sky! 🌟

Tip

🌐 Web search-backed reviews and chat
  • We have enabled web search-based reviews and chat for all users. This feature allows CodeRabbit to access the latest documentation and information on the web.
  • You can disable this feature by setting web_search: false in the knowledge_base settings.
  • Please share any feedback in the Discord discussion.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e077306 and 8bb2ed3.

📒 Files selected for processing (1)
  • src/pages/MainPage.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/MainPage.tsx

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/pages/MainPage.tsx (2)

93-98: Consider adding metadata when storing run info in local storage.
Right now, only the browserId, runId, and recordingName are stored, which is fine for basic use. However, storing timestamps or versioning could help identify stale or concurrent runs.


109-118: Review additional run statuses beyond 'success'.
Currently, the code only notifies on 'success' or defaults to 'error'. You might consider special handling for other statuses (e.g., 'aborted', 'partial', 'cancelled') if they exist in the backend logic.

server/src/workflow-management/classes/Interpreter.ts (1)

335-336: Add error handling for interpretation failures.
Currently, 'run-completed' is hardcoded to "success" only. In cases of interpretation errors or exceptions, consider emitting a different status (e.g., "error") so the frontend can properly display a failure notification.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3520ddc and 3fe2033.

📒 Files selected for processing (5)
  • server/src/browser-management/classes/BrowserPool.ts (4 hunks)
  • server/src/browser-management/controller.ts (2 hunks)
  • server/src/routes/record.ts (3 hunks)
  • server/src/workflow-management/classes/Interpreter.ts (2 hunks)
  • src/pages/MainPage.tsx (5 hunks)
🔇 Additional comments (8)
server/src/browser-management/classes/BrowserPool.ts (5)

18-19: Clarify the optional isRobotRun behavior.

Adding isRobotRun?: boolean; provides an easy flag for identifying robot-run browsers. However, ensure that any logic that relies on this property's truthiness also accounts for when it's undefined.


51-57: Validate the new isRobotRun parameter in addRemoteBrowser.

Passing isRobotRun as a distinct boolean argument helps control the browser’s run state more explicitly. Just make sure callers consistently provide the correct value based on their flow.


63-65: Confirm performance sufficiency of hasActiveRobotRun.

Using Object.values(this.pool).some(...) to check for an active run is fine for smaller pools. If usage scales, you might consider an alternative approach to avoid scanning the entire pool each time.


67-73: Ensure no concurrency conflicts in clearRobotRunState.

Resetting isRobotRun to false is a simple way to clear the run state. Confirm that there’s no parallel code path that might set it back to true after this method is called.


84-85: Good cleanup of the run state before deletion.

Calling clearRobotRunState prior to removing the browser ensures consistent and predictable state in the pool. Nice approach!

server/src/routes/record.ts (1)

19-19: Ensure browserPool is a valid singleton.

Importing browserPool allows you to share the same pool references across modules. Double-check that it’s indeed a singleton or properly scoped to avoid initialization issues.

server/src/browser-management/controller.ts (1)

62-62: Explicitly marking the browser run as a robot run.

Changing browserPool.addRemoteBrowser(id, browserSession, true, true); clarifies that these instances are indeed active and designated for robot runs. Make sure all relevant teardown/notification hooks also respect this setting.

src/pages/MainPage.tsx (1)

71-78: Clarify or consolidate success logic within readyForRunHandler.
By commenting out the success/failure notifications and only stopping the browser on failure, the success path now resides solely in the 'run-completed' event. This separation could introduce confusion or duplication. Consider either removing the remaining failure handling here, or reintroducing success-handling to keep logic consistent.

Use this script to check if any other places also rely on interpretation return values besides 'run-completed':

Comment on lines +37 to +45
router.use('/', requireSignIn, (req: AuthenticatedRequest, res: Response, next) => {
if (browserPool.hasActiveRobotRun()) {
logger.log('debug', 'Preventing browser initialization - robot run in progress');
return res.status(403).json({
error: 'Cannot initialize recording browser while a robot run is in progress'
});
}
next();
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Race condition risk confirmed in browser pool management

The current implementation of BrowserPool lacks proper synchronization mechanisms. Multiple concurrent requests could potentially pass the hasActiveRobotRun check before any of them sets the robot run state, as the operations are not atomic. While Node.js's event loop provides some protection, it's not guaranteed to be safe in high-concurrency scenarios.

Suggested improvements:

  • Implement proper locking mechanism or semaphore for robot run state changes
  • Consider using atomic operations for state transitions
  • Add request queuing to serialize the robot run initialization
🔗 Analysis chain

Preventing parallel sessions on robot runs.

Blocking new browser sessions when hasActiveRobotRun is true is a solid approach. For high-traffic scenarios, consider if multiple requests might pass this check simultaneously (race condition) before the run is registered. If needed, use a lock or queue to serialize these events and avoid edge cases.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Gather usage references to see how `hasActiveRobotRun` is being checked.
rg -A 4 "hasActiveRobotRun"

Length of output: 878


Script:

#!/bin/bash
# Find where robot runs are initiated and how isRobotRun is set
ast-grep --pattern 'isRobotRun = true'
rg -B 2 -A 4 "isRobotRun.*=.*true"

# Look for any synchronization mechanisms
rg -i "lock|mutex|semaphore|atomic"

Length of output: 3944


Script:

#!/bin/bash
# Get the complete BrowserPool implementation
cat server/src/browser-management/classes/BrowserPool.ts

# Find robot run related code
rg -B 2 -A 4 "robotRun|robot.*run"

Length of output: 14625

Comment on lines 145 to 186
useEffect(() => {
const storedRunInfo = localStorage.getItem('runInfo');
console.log('storedRunInfo', storedRunInfo);

if (storedRunInfo) {
// Parse the stored info
const { browserId, runId, recordingName } = JSON.parse(storedRunInfo);

// Reconnect to the specific browser's namespace
setIds({ browserId, runId });
const socket = io(`${apiUrl}/${browserId}`, {
transports: ["websocket"],
rejectUnauthorized: false
});

// Update component state with stored info
setRunningRecordingName(recordingName);
setSockets(sockets => [...sockets, socket]);

// Set up event listeners
socket.on('ready-for-run', () => readyForRunHandler(browserId, runId));
socket.on('debugMessage', debugMessageHandler);
socket.on('run-completed', (status) => {
if (status === 'success') {
notify('success', t('main_page.notifications.interpretation_success', { name: recordingName }));
} else {
notify('error', t('main_page.notifications.interpretation_failed', { name: recordingName }));
}
setRunningRecordingName('');
setCurrentInterpretationLog('');
setRerenderRuns(true);
localStorage.removeItem('runInfo'); // Clean up stored info
});

// Cleanup function
return () => {
socket.off('ready-for-run', () => readyForRunHandler(browserId, runId));
socket.off('debugMessage', debugMessageHandler);
socket.off('run-completed');
};
}
}, []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure cleanup functions use the same listener references.
Inside the useEffect, the cleanup relies on arrow callbacks that differ from those in socket.on(...). Reacting to new runs or re-renders may result in unremoved listeners. As with line 130, store references to your event handlers, then pass them consistently to on and off.

@@ -108,6 +127,7 @@ export const MainPage = ({ handleEditRecording, initialContent }: MainPageProps)
return (socket: Socket, browserId: string, runId: string) => {
socket.off('ready-for-run', () => readyForRunHandler(browserId, runId));
socket.off('debugMessage', debugMessageHandler);
socket.off('run-completed');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use identical function references to remove socket listeners.
Calling socket.off('run-completed', () => ...) with a new inline arrow function won’t remove the original listener. This can lead to memory leaks or multiple event fires. Store the listener in a variable and pass the same reference to both on and off.

- socket.on('run-completed', (status) => { ... });
- socket.off('run-completed', () => { ... });
+ const handleRunCompleted = (status) => { ... };
+ socket.on('run-completed', handleRunCompleted);
+ socket.off('run-completed', handleRunCompleted);

Committable suggestion skipped: line range outside the PR's diff.

@amhsirak amhsirak added the Status: 🚫🚫 DO NOT MERGE 🚫🚫 Don't merge this PR. It is not perfect. label Jan 24, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
server/src/routes/storage.ts (1)

480-480: Remove commented-out code if unneeded.
Leaving this line in the codebase may cause confusion about whether it should be enabled. If you no longer need to call createRemoteBrowserForRun, consider removing it:

-    // const id = createRemoteBrowserForRun(req.user.id);
server/src/worker.ts (1)

40-59: Use optional chaining for safer schedule checks.
You can simplify this condition by applying optional chaining on robot.schedule. For instance:

-    if (robot.schedule && robot.schedule.cronExpression && robot.schedule.timezone) {
+    if (robot.schedule?.cronExpression && robot.schedule?.timezone) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 48-48: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fe2033 and 058d512.

📒 Files selected for processing (3)
  • server/src/routes/storage.ts (3 hunks)
  • server/src/worker.ts (3 hunks)
  • src/pages/MainPage.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/MainPage.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
server/src/worker.ts

[error] 48-48: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (6)
server/src/routes/storage.ts (3)

491-491: Verify browserId alignment with actual browser creation.
This line implies that the id of the recording in req.params.id is reused for the browser ID. Confirm that the front end or the rest of the system does not rely on a different ID for browsers, which could lead to mismatches.


500-504: Ensure consistent handling of the isScheduled flag.
The job is added with isScheduled: false, which triggers the unscheduled workflow path in the worker. Double-check that other parts of the code correctly handle this flag and that no re-scheduling logic is executed inadvertently.


508-508: Duplicate assignment of browserId as req.params.id.
This is the same assignment as on line 491, which may cause confusion between robot/recording IDs and browser IDs if the meaning differs. Ensure both references are intentional.

server/src/worker.ts (3)

4-5: Confirm distinction between scheduled vs. direct run recordings.
Using different function names for scheduled vs. direct runs can improve clarity. Ensure that both implementations stay synchronized if they share logic.
[approve]


26-26: Evaluate defaulting isScheduled to true.
This fallback is helpful, but confirm that manual runs always pass false to avoid erroneously calling the scheduled-run handler.


28-30: Conditional call effectively separates scheduled vs. on-demand runs.
This is a clean approach. Ensure that both functions handle unexpected job data states gracefully.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
server/src/routes/storage.ts (1)

500-504: LGTM! Consider adding error handling for the queue operation.

The job addition to the workflow queue is well-placed and contains all necessary identifiers. However, if the queue operation fails, the run record would still be created but the workflow wouldn't be queued.

Consider wrapping the queue operation in a try-catch block and updating the run status if queueing fails:

     });

+    try {
       const job = await workflowQueue.add(
         'run workflow',
         { id, runId, userId: req.user.id, isScheduled: false },
       );
+    } catch (error) {
+      await run.update({ status: 'failed', finishedAt: new Date().toLocaleString() });
+      throw new Error(`Failed to queue workflow: ${error.message}`);
+    }

     const plainRun = run.toJSON();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d3c55b and 302ccd1.

📒 Files selected for processing (1)
  • server/src/routes/storage.ts (1 hunks)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
server/src/worker.ts (4)

26-30: Add JSDoc to document the job data structure.

The job processing logic looks good, but it would benefit from documentation explaining the purpose of the isScheduled flag and when it should be set to false.

+/**
+ * Job data structure for workflow processing
+ * @typedef {Object} WorkflowJobData
+ * @property {string} runId - The unique identifier for the run
+ * @property {string} userId - The user initiating the run
+ * @property {string} id - The workflow identifier
+ * @property {boolean} [isScheduled=true] - Whether this is a scheduled run (true) or manual run (false)
+ */
 const { runId, userId, id, isScheduled = true } = job.data;

48-49: Use optional chaining for cleaner code.

The condition can be simplified using optional chaining.

-if (robot.schedule && robot.schedule.cronExpression && robot.schedule.timezone) {
-    const nextRunAt = computeNextRun(robot.schedule.cronExpression, robot.schedule.timezone) || undefined;
+if (robot.schedule?.cronExpression && robot.schedule?.timezone) {
+    const nextRunAt = computeNextRun(robot.schedule.cronExpression, robot.schedule.timezone) ?? undefined;
🧰 Tools
🪛 Biome (1.9.4)

[error] 48-48: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


58-58: Improve error message specificity.

The error message could be more helpful by indicating which specific property is missing.

-logger.error('Robot schedule, cronExpression, or timezone is missing.');
+logger.error(`Robot ${robot.id} missing required scheduling properties: ${[
+    !robot.schedule && 'schedule',
+    robot.schedule && !robot.schedule.cronExpression && 'cronExpression',
+    robot.schedule && !robot.schedule.timezone && 'timezone'
+].filter(Boolean).join(', ')}`);

50-56: Be explicit about schedule property updates.

Using the spread operator with the schedule object could inadvertently include unwanted properties. Consider explicitly listing the properties that should be updated.

 await robot.update({
     schedule: {
-        ...robot.schedule,
+        cronExpression: robot.schedule.cronExpression,
+        timezone: robot.schedule.timezone,
         lastRunAt,
         nextRunAt,
     },
 });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 302ccd1 and de2d828.

📒 Files selected for processing (1)
  • server/src/worker.ts (3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
server/src/worker.ts

[error] 48-48: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (2)
server/src/worker.ts (2)

4-5: LGTM! Clear separation of concerns.

The imports effectively distinguish between scheduled and unscheduled run recording handlers.


26-30: Verify integration with related components.

The changes look good but let's verify the integration points:

Also applies to: 40-59

✅ Verification successful

Integration verification completed successfully

The changes are properly integrated:

  • Queue jobs correctly set the isScheduled flag
  • Handlers are properly imported and used
  • Scheduling mechanism is consistently implemented
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the integration points with related components

# Check if handleRunRecording is properly exported from both modules
echo "Checking exports from workflow management modules..."
rg -A 1 "export.*handleRunRecording" "server/src/workflow-management/"

# Check if isScheduled flag is properly passed from all callers
echo "Checking job creation with isScheduled flag..."
rg -A 3 "workflowQueue.add"

# Check for any direct Robot model updates that might need the new conditional logic
echo "Checking Robot model updates..."
rg "Robot.update.*schedule" --type ts

Length of output: 1096


Script:

#!/bin/bash
# Get more context about the run recording handlers and their usage
echo "Checking handleScheduledRunRecording export and implementation..."
ast-grep --pattern 'export async function handleScheduledRunRecording($_) { $$$ }'

echo "\nChecking complete context of queue additions..."
rg -B 5 -A 10 'workflowQueue.add'

echo "\nChecking for any other related scheduling functions..."
rg "RunRecording.*schedule" --type ts

Length of output: 2303

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
server/src/workflow-management/record.ts (5)

52-55: Clarify or remove the function if not essential.
resetRecordingState simply reassigns browserId and id to empty strings. This has no side effects beyond modifying local variables. Unless there's a future plan to persist or propagate these changes, consider removing the function or making its purpose explicit.


105-105: Avoid assignment within an expression.
Passing (newPage: Page) => currentPage = newPage can be confusing. Consider moving the assignment into a separate statement or function for clarity.

Example refactor:

- (newPage: Page) => currentPage = newPage,
+ function handleNewPage(newPage: Page) {
+   currentPage = newPage;
+ },
🧰 Tools
🪛 Biome (1.9.4)

[error] 105-105: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


131-135: Consider optional chaining for safer checks.
Instead of performing repeated logical AND checks, optional chaining can enhance readability, e.g., updatedRun.dataValues.binaryOutput?.["item-0"]. However, this is purely stylistic and not critical.

🧰 Tools
🪛 Biome (1.9.4)

[error] 131-131: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 135-135: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


120-120: Use a consistent date format for storing timestamps.
Currently using new Date().toLocaleString() can produce time zone–dependent variations. Storing timestamps in an ISO format (new Date().toISOString()) is often more predictable and consistent across different systems.


198-224: userId is not used.
The function signature requires userId, but there's no usage within handleRunRecording. If it's not intended for future expansions, removing or handling it might improve clarity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de2d828 and 279612d.

📒 Files selected for processing (1)
  • server/src/workflow-management/record.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
server/src/workflow-management/record.ts

[error] 32-32: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 105-105: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 135-135: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (3)
server/src/workflow-management/record.ts (3)

23-50: Robust error handling and clear flow.
The readyForRunHandler function effectively captures errors and logs them, then cleans up the browser in case of failure. It emits the run status to the client, ensuring feedback loops are well-managed. Nice job!

🧰 Tools
🪛 Biome (1.9.4)

[error] 32-32: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


57-66: Declarative approach looks good.
AddGeneratedFlags function effectively clones the workflow and prepends the "generated" action. This is a straightforward approach, though it might involve extra memory usage for large workflows. Overall, it appears well-structured and clear.


68-142: Overall, this function is well-organized.
executeRun thoughtfully fetches run and recording data, handles browser interactions, and updates run status as needed. The callbacks for interpretation, logging, and storing binary outputs are cohesive. Good use of destroyRemoteBrowser on success or error.

🧰 Tools
🪛 Biome (1.9.4)

[error] 105-105: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 135-135: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Comment on lines +226 to +229
function cleanupSocketListeners(socket: Socket, browserId: string, id: string) {
socket.off('ready-for-run', () => readyForRunHandler(browserId, id));
logger.info(`Cleaned up listeners for browserId: ${browserId}, runId: ${id}`);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure event listeners are removed properly.
The callback passed to socket.off is a newly created arrow function, which doesn’t match the one registered with socket.on. This might prevent proper cleanup of the original listener. Consider storing a reference to the original callback.

Possible fix:

- socket.off('ready-for-run', () => readyForRunHandler(browserId, id));
+ const readyForRunCallback = () => readyForRunHandler(browserId, id);
// ...
+ socket.on('ready-for-run', readyForRunCallback);
// ...
+ socket.off('ready-for-run', readyForRunCallback);

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
server/src/workflow-management/record.ts (1)

228-231: ⚠️ Potential issue

Ensure event listeners are removed properly.

The callback passed to socket.off is a newly created arrow function, which doesn't match the one registered with socket.on. This might prevent proper cleanup of the original listener.

+const readyForRunCallback = (browserId: string, id: string) => () => readyForRunHandler(browserId, id);
+
 export async function handleRunRecording(id: string, userId: string, runId: string) {
     try {
         // ...
-        socket.on('ready-for-run', () => readyForRunHandler(id, runId));
+        const callback = readyForRunCallback(id, runId);
+        socket.on('ready-for-run', callback);
         // ...
         socket.on('disconnect', () => {
-            cleanupSocketListeners(socket, id, runId);
+            socket.off('ready-for-run', callback);
+            logger.info(`Cleaned up listeners for browserId: ${id}, runId: ${runId}`);
         });
     }
 }
-
-function cleanupSocketListeners(socket: Socket, browserId: string, id: string) {
-    socket.off('ready-for-run', () => readyForRunHandler(browserId, id));
-    logger.info(`Cleaned up listeners for browserId: ${browserId}, runId: ${id}`);
-}
🧹 Nitpick comments (4)
server/src/workflow-management/record.ts (4)

24-44: Consider using optional chaining and DRY up the code.

  1. Use optional chaining to safely access the success property
  2. Consider extracting the common cleanup code to avoid duplication
-        if (result && result.success) {
+        if (result?.success) {
             logger.info(`Interpretation of ${id} succeeded`);
-            resetRecordingState(browserId, id);
-            return result.interpretationInfo;
+            const interpretationInfo = result.interpretationInfo;
+            await cleanup();
+            return interpretationInfo;
         } else {
             logger.error(`Interpretation of ${id} failed`);
-            await destroyRemoteBrowser(browserId);
-            resetRecordingState(browserId, id);
-            return null;
+            await cleanup();
+            return null;
         }

+    async function cleanup() {
+        await destroyRemoteBrowser(browserId);
+        resetRecordingState(browserId, id);
+    }
🧰 Tools
🪛 Biome (1.9.4)

[error] 28-28: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


96-101: Avoid assignment in callback function.

The assignment to currentPage in the callback makes the code harder to understand and maintain.

-            (newPage: Page) => currentPage = newPage,
+            async (newPage: Page) => {
+                currentPage = newPage;
+                return currentPage;
+            },
🧰 Tools
🪛 Biome (1.9.4)

[error] 99-99: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


126-136: Use optional chaining for safer property access.

The nested property access could throw if any intermediate value is undefined.

-        if (updatedRun.dataValues.binaryOutput && updatedRun.dataValues.binaryOutput["item-0"]) {
+        if (updatedRun.dataValues?.binaryOutput?.["item-0"]) {
             extractedScreenshotsCount = 1;
         }
         
-        if (updatedRun.dataValues.serializableOutput && updatedRun.dataValues.serializableOutput["item-0"]) {
+        if (updatedRun.dataValues?.serializableOutput?.["item-0"]) {
             const itemsArray = updatedRun.dataValues.serializableOutput["item-0"];
🧰 Tools
🪛 Biome (1.9.4)

[error] 126-126: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 130-130: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


209-212: Consider using URL constructor for better URL handling.

The current URL construction using template literals could be improved using the URL constructor for better validation and handling.

-        const socket = io(`${process.env.BACKEND_URL ? process.env.BACKEND_URL : 'http://localhost:8080'}/${id}`, {
+        const baseUrl = process.env.BACKEND_URL || 'http://localhost:8080';
+        const url = new URL(id, baseUrl).toString();
+        const socket = io(url, {
             transports: ['websocket'],
             rejectUnauthorized: false
         });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 279612d and 13d52a4.

📒 Files selected for processing (1)
  • server/src/workflow-management/record.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
server/src/workflow-management/record.ts

[error] 28-28: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 99-99: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 126-126: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 130-130: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (1)
server/src/workflow-management/record.ts (1)

1-22: LGTM! Well-organized imports and setup.

The imports are logically grouped and the stealth plugin setup is properly configured.

Comment on lines +62 to +198
(newPage: Page) => currentPage = newPage,
plainRun.interpreterSettings
);

const binaryOutputService = new BinaryOutputService('maxun-run-screenshots');
const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(
run,
interpretationInfo.binaryOutput
);

await destroyRemoteBrowser(plainRun.browserId);

const updatedRun = await run.update({
...run,
status: 'success',
finishedAt: new Date().toLocaleString(),
browserId: plainRun.browserId,
log: interpretationInfo.log.join('\n'),
serializableOutput: interpretationInfo.serializableOutput,
binaryOutput: uploadedBinaryOutput,
});


let totalRowsExtracted = 0;
let extractedScreenshotsCount = 0;
let extractedItemsCount = 0;

if (updatedRun.dataValues.binaryOutput && updatedRun.dataValues.binaryOutput["item-0"]) {
extractedScreenshotsCount = 1;
}

if (updatedRun.dataValues.serializableOutput && updatedRun.dataValues.serializableOutput["item-0"]) {
const itemsArray = updatedRun.dataValues.serializableOutput["item-0"];
extractedItemsCount = itemsArray.length;
totalRowsExtracted = itemsArray.reduce((total: number, item: any) => {
return total + Object.keys(item).length;
}, 0);
}

logger.info(`Extracted Items Count: ${extractedItemsCount}`);
logger.info(`Extracted Screenshots Count: ${extractedScreenshotsCount}`);
logger.info(`Total Rows Extracted: ${totalRowsExtracted}`);


capture('maxun-oss-run-created-manual', {
runId: id,
created_at: new Date().toISOString(),
status: 'success',
extractedItemsCount,
totalRowsExtracted,
extractedScreenshotsCount,
});

// Handle Google Sheets integration
try {
googleSheetUpdateTasks[plainRun.runId] = {
robotId: plainRun.robotMetaId,
runId: plainRun.runId,
status: 'pending',
retries: 5,
};
await processGoogleSheetUpdates();
} catch (err: any) {
logger.error(`Failed to update Google Sheet for run: ${plainRun.runId}: ${err.message}`);
}

serverIo.of(plainRun.browserId).emit('run-completed', 'success');

return {
success: true,
interpretationInfo: updatedRun.toJSON()
};

} catch (error: any) {
logger.error(`Error running robot: ${error.message}`);
const run = await Run.findOne({ where: { runId: id } });
if (run) {
await run.update({
status: 'failed',
finishedAt: new Date().toLocaleString(),
});

const plainRun = run.toJSON();
serverIo.of(plainRun.browserId).emit('run-completed', 'failed');
}

capture('maxun-oss-run-created-manual', {
runId: id,
created_at: new Date().toISOString(),
status: 'failed',
error_message: error.message,
});


return {
success: false,
error: error.message,
};
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider breaking down this large function into smaller, focused functions.

The function handles multiple responsibilities including database operations, browser management, workflow interpretation, and analytics. Consider extracting these into separate functions for better maintainability.

Example structure:

async function executeRun(id: string) {
    try {
        const { run, recording } = await fetchRunData(id);
        const browser = await setupBrowser(run);
        const interpretationResult = await interpretWorkflow(recording, browser);
        await handleBinaryOutput(run, interpretationResult);
        await updateRunStatus(run, interpretationResult);
        await handleGoogleSheetsIntegration(run);
        await cleanup(run);
        return formatSuccess(interpretationResult);
    } catch (error) {
        await handleError(error, id);
        return formatError(error);
    }
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 99-99: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 126-126: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 130-130: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Comment on lines +46 to +49
function resetRecordingState(browserId: string, id: string) {
browserId = '';
id = '';
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix ineffective state reset logic.

The current implementation of resetRecordingState is ineffective as it only modifies the local parameter variables. These changes don't affect the original values passed to the function.

Consider one of these approaches:

  1. Return the reset values and update them in the caller:
-function resetRecordingState(browserId: string, id: string) {
-    browserId = '';
-    id = '';
+function resetRecordingState(): { browserId: string, id: string } {
+    return { browserId: '', id: '' };
}
  1. Use a state object that can be modified:
+interface RecordingState {
+    browserId: string;
+    id: string;
+}
+
+const recordingState: RecordingState = {
+    browserId: '',
+    id: ''
+};
+
+function resetRecordingState() {
+    recordingState.browserId = '';
+    recordingState.id = '';
+}

Committable suggestion skipped: line range outside the PR's diff.

@RohitR311 RohitR311 added Type: Bug Something isn't working Type: Enhancement Improvements to existing features Status: 🚫🚫 DO NOT MERGE 🚫🚫 Don't merge this PR. It is not perfect. and removed Status: 🚫🚫 DO NOT MERGE 🚫🚫 Don't merge this PR. It is not perfect. labels Feb 3, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (2)
server/src/routes/storage.ts (2)

698-703: Fix duplicate cron expressions for minutes and hours frequency.

The cron expressions for MINUTES and HOURS frequencies are identical, which could lead to incorrect scheduling behavior.

     switch (runEveryUnit) {
       case 'MINUTES':
-        cronExpression = `${startMinutes} */${runEvery} * * *`;
+        cronExpression = `*/${runEvery} * * * *`;
         break;
       case 'HOURS':
-        cronExpression = `${startMinutes} */${runEvery} * * *`;
+        cronExpression = `${startMinutes} */${runEvery} * * *`;
         break;

730-740: Clean up existing jobs before creating new schedule.

The endpoint should remove any existing scheduled jobs for this robot before creating a new schedule to prevent duplicate executions.

+    // Remove existing jobs before creating new schedule
+    const existingJobs = await workflowQueue.getJobs(['delayed', 'waiting']);
+    for (const job of existingJobs) {
+      if (job.data.id === id) {
+        await job.remove();
+      }
+    }
+
     // Create the job in the queue with the cron expression
     const job = await workflowQueue.add(
       'run workflow',
🧹 Nitpick comments (2)
server/src/worker.ts (1)

40-61: Add defensive programming and use optional chaining.

While the logic is correct, consider these improvements for better robustness:

  1. Use optional chaining for safer property access
  2. Add defensive checks for robot and schedule properties

Apply this diff to improve the code:

-    if (job.data.isScheduled) {
-        const robot = await Robot.findOne({ where: { 'recording_meta.id': job.data.id } });
-        if (robot) {
-            // Update `lastRunAt` to the current time
-            const lastRunAt = new Date();
-
-            // Compute the next run date
-            if (robot.schedule && robot.schedule.cronExpression && robot.schedule.timezone) {
-                const nextRunAt = computeNextRun(robot.schedule.cronExpression, robot.schedule.timezone) || undefined;
-                await robot.update({
-                    schedule: {
-                        ...robot.schedule,
-                        lastRunAt,
-                        nextRunAt,
-                    },
-                });
-            } else {
-                logger.error('Robot schedule, cronExpression, or timezone is missing.');
-            }
+    if (job.data?.isScheduled) {
+        const robot = await Robot.findOne({ where: { 'recording_meta.id': job.data?.id } });
+        if (!robot) {
+            logger.error('Robot not found.');
+            return;
+        }
+
+        // Update `lastRunAt` to the current time
+        const lastRunAt = new Date();
+
+        // Compute the next run date
+        if (!robot.schedule?.cronExpression || !robot.schedule?.timezone) {
+            logger.error('Robot schedule, cronExpression, or timezone is missing.');
+            return;
+        }
+
+        const nextRunAt = computeNextRun(robot.schedule.cronExpression, robot.schedule.timezone) || undefined;
+        await robot.update({
+            schedule: {
+                ...robot.schedule,
+                lastRunAt,
+                nextRunAt,
+            },
+        });
🧰 Tools
🪛 Biome (1.9.4)

[error] 48-48: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

server/src/routes/storage.ts (1)

499-502: Add error handling for the workflow queue operation.

While adding the job to the workflow queue is a good addition, consider:

  1. Adding specific error handling for queue operations
  2. Including the job ID in the response for better tracking
     const job = await workflowQueue.add(
       'run workflow',
       { id, runId, userId: req.user.id, isScheduled: false },
     );
+    
+    const plainRun = run.toJSON();
+    
+    return res.send({
+      browserId: id,
+      runId: plainRun.runId,
+      robotMetaId: recording.recording_meta.id,
+      jobId: job.id, // Add job ID to response
     });
   } catch (e) {
     const { message } = e as Error;
+    // Handle queue-specific errors
+    if (e instanceof Error && e.message.includes('queue')) {
+      logger.log('error', `Queue error while creating run: ${message}`);
+      return res.status(503).send({ error: 'Service temporarily unavailable' });
+    }
     logger.log('info', `Error while creating a run with robot id: ${req.params.id} - ${message}`);
     return res.send('');
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c0a832 and e077306.

📒 Files selected for processing (3)
  • server/src/routes/storage.ts (1 hunks)
  • server/src/worker.ts (3 hunks)
  • src/pages/MainPage.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/MainPage.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
server/src/worker.ts

[error] 48-48: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (3)
server/src/worker.ts (3)

4-5: LGTM! Clear distinction between scheduled and unscheduled run handlers.

The import aliasing effectively differentiates between the two types of run handlers.


26-26: LGTM! Backward compatible addition of isScheduled property.

The default value of true maintains backward compatibility with existing job data.


28-30: LGTM! Clear conditional handling of scheduled vs unscheduled runs.

The logic correctly routes to the appropriate handler and passes the required parameters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: 🚫🚫 DO NOT MERGE 🚫🚫 Don't merge this PR. It is not perfect. Type: Bug Something isn't working Type: Enhancement Improvements to existing features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants