Skip to content

Runset Optimization#3436

Merged
Maheshkale447 merged 12 commits into
masterfrom
Enhancement/RunsetOptimization
Jan 30, 2024
Merged

Runset Optimization#3436
Maheshkale447 merged 12 commits into
masterfrom
Enhancement/RunsetOptimization

Conversation

@manas-droid

@manas-droid manas-droid commented Jan 10, 2024

Copy link
Copy Markdown
Contributor
  1. Removed Thread.Sleep(1000) from RunsetExecutor and StartDriver in AgentOperations.cs
  2. Added Synchronization block while setting up virtual agent.
  3. Changed how CheckIfPageLoaded function.
  4. Changed the recursive code to iterative

Thank you for your contribution.
Before submitting this PR, please make sure:

  • PR description and commit message should describe the changes done in this PR
  • Verify the PR is pointing to correct branch i.e. Release or Beta branch if the code fix is for specific release , else point it to master
  • Latest Code from master or specific release branch is merged to your branch
  • No unwanted\commented\junk code is included
  • No new warning upon build solution
  • Code Summary\Comments are added to my code which explains what my code is doing
  • Existing unit test cases are passed
  • New Unit tests are added for your development
  • Sanity Tests are successfully executed for New and Existing Functionality
  • Verify that changes are compatible with all relevant browsers and platforms.
  • After creating pull request there should not be any conflicts
  • Resolve all Codacy comments
  • Builds and checks are passed before PR is sent for review
  • Resolve code review comments
  • Update the Help Library document to match any feature changes

Summary by CodeRabbit

  • Refactor
    • Improved stability and reliability of Selenium interactions within the application.
    • Enhanced virtual agent creation process and execution feedback mechanisms.
    • Streamlined the run set execution logic for better performance and maintainability.

@coderabbitai

coderabbitai Bot commented Jan 10, 2024

Copy link
Copy Markdown
Contributor

Walkthrough

The recent updates aim to enhance the robustness and reliability of the Ginger automation framework. The changes include improved Selenium integration with better page load verification, refined exception handling, and more efficient agent management. Additionally, there are adjustments to the execution feedback mechanism for clarity.

Changes

File Path Change Summary
.../Web/Selenium/SeleniumDriver.cs Enhanced IsRunning with retries and exception handling; improved CheckifPageLoaded using WebDriverWait; refactored file handling.
.../Run/GingerExecutionEngine.cs,
.../Run/RunsetExecutor.cs
Added semaphore for agent control; updated agent creation logic; refined execution with time limit and feedback; streamlined RunRunset control flow and runner management.
.../AutomatePageLib/ExecutionSummaryPage.xaml,
.../AutomatePageLib/NewAutomatePage.xaml
Adjusted XAML attributes for improved UI display.
.../Dictionaries/Skins/GingerDefaultSkinDictionary.xaml,
.../UserControlsLib/UCListView/UcListViewItem.xaml
Made UI layout and styling modifications for improved user experience.
.../DataSource/LiteDB.cs,
.../LiteDBFolder/LiteDbConnector.cs
Implemented SemaphoreSlim for thread safety in LiteDB access; refactored exception handling and file access logic.

Poem

🐇
In the burrow of code, the changes are sown,

Selenium's garden, more robustly grown.

Virtual agents now come and go with grace,

And the Ginger runs with a steadier pace.

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>.
    • Generate unit-tests for this file.
  • 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 tests 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 generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

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 as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • 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/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 6

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between cc7a6fa and 9307d44.
Files selected for processing (4)
  • Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs (4 hunks)
  • Ginger/GingerCoreNET/Run/GingerExecutionEngine.cs (4 hunks)
  • Ginger/GingerCoreNET/Run/RunsetExecutor.cs (10 hunks)
  • Ginger/GingerCoreNET/RunLib/AgentOperations.cs (2 hunks)
Files skipped from review due to trivial changes (1)
  • Ginger/GingerCoreNET/RunLib/AgentOperations.cs
Additional comments: 12
Ginger/GingerCoreNET/Run/RunsetExecutor.cs (10)
  • 178-178: The method InitRunner is responsible for configuring the runner for execution. It checks if the executor is not null and is of type GingerExecutionEngine, then clears bindings. This is a good practice to ensure that the runner is in a clean state before configuration.

  • 453-453: Setting mRunSetConfig.IsRunning to true at the start of the RunRunset method is correct to indicate that the runset execution has started.

  • 460-460: The else block starting here is checking if the execution logger configuration is set to publish to a central database and if the endpoint URL is not empty. It then validates the execution ID. This is a good check to prevent duplicate execution IDs.

  • 483-483: The method ResetRunnersExecutionDetails is called conditionally based on the ReRunConfigurations settings. This is a good use of conditional logic to control the flow of the application.

  • 509-509: The RunSetStart method of the Centeralized_Logger is called asynchronously, which is a good practice for potentially long-running operations that could block the UI thread.

  • 520-520: The DisableTestsExecution method is called with the testsToExclude array and the RunSetConfig object. This method is likely responsible for disabling the execution of certain tests based on external input, which is a flexible feature.

  • 561-561: The commented-out Thread.Sleep(1000) is likely the line that was removed as part of the optimizations mentioned in the PR objectives. It's good to see that unnecessary delays are being removed.

  • 625-630: The RunSetEnd method of the Centeralized_Logger is called asynchronously, which is consistent with the earlier call to RunSetStart. This ensures that the end of the runset is logged without blocking the main thread.

  • 664-664: Setting mRunSetConfig.IsRunning to false in the finally block is a good practice to ensure that the state is correctly updated regardless of how the try block exits.

  • 735-735: The method ResetRunsetActions correctly resets the status and errors of each RunSetActionBase in the RunSetConfig. This is important for ensuring that the state is clean for the next execution.

Ginger/GingerCoreNET/Run/GingerExecutionEngine.cs (2)
  • 742-742: The addition of the SemaphoreSlim semaphore is a good practice for managing concurrent access to a resource. Ensure that it is used consistently throughout the codebase for similar synchronization needs.

  • 759-794: The implementation of synchronization using SemaphoreSlim in the SetupVirtualAgents method appears correct. However, ensure that all possible exceptions are handled properly to avoid a situation where semaphoreSlim.Release() might not be called due to an unhandled exception, which would lead to a deadlock.

Verification successful

The search results confirm that semaphoreSlim is declared and used within the GingerExecutionEngine.cs file. It is used in the SetupVirtualAgents method, with a Wait() call before entering a critical section and a Release() call in the finally block to ensure the semaphore is released even if an exception occurs.

Given this information, the use of SemaphoreSlim for synchronization in the SetupVirtualAgents method is implemented correctly, with proper exception handling to avoid deadlocks.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that semaphoreSlim is used consistently and correctly throughout the codebase.
rg --type cs "semaphoreSlim"

Length of output: 362

Comment on lines +4410 to +4426
int maxAttempts = 5;
int attemptCount = 0;

int count = 0;
///IAsyncResult result;
//Action action = () =>
var action = Task.Run(() =>
while (attemptCount <= maxAttempts)
{
try
{
int count = 0;

try
{
Thread.Sleep(100);
//Thread.Sleep(100);
count = Driver.WindowHandles.Count;
}
catch (System.InvalidCastException ex)
{
exceptioncount = 0;
attemptCount = 0;

@coderabbitai coderabbitai Bot Jan 10, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The logic for resetting attemptCount to 0 in the catch block for InvalidCastException seems incorrect. This would cause an infinite loop if this exception is thrown because attemptCount will never reach maxAttempts. Consider incrementing attemptCount instead.

- attemptCount = 0;
+ attemptCount++;

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
int maxAttempts = 5;
int attemptCount = 0;
int count = 0;
///IAsyncResult result;
//Action action = () =>
var action = Task.Run(() =>
while (attemptCount <= maxAttempts)
{
try
{
int count = 0;
try
{
Thread.Sleep(100);
//Thread.Sleep(100);
count = Driver.WindowHandles.Count;
}
catch (System.InvalidCastException ex)
{
exceptioncount = 0;
attemptCount = 0;
int maxAttempts = 5;
int attemptCount = 0;
while (attemptCount <= maxAttempts)
{
try
{
int count = 0;
try
{
//Thread.Sleep(100);
count = Driver.WindowHandles.Count;
}
catch (System.InvalidCastException ex)
{
attemptCount++;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok , will remove the attemptCount = 0 line from the code as it does not really make sense

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@manas-droid Yes, removing the attemptCount = 0 line will prevent the possibility of an infinite loop. If you need further assistance or have more questions, feel free to ask.

Comment on lines +6702 to +6749
/* void CheckifPageLoaded()
{
Thread.Sleep(300);
//TODO: slow function, try to check alternatives or let the user config wait for
try
{
bool DomElementIncreasing = true;
int CurrentDomElementSize = 0;
int SameSizzeCounter = 0;
while (DomElementIncreasing)
{
Thread.Sleep(300);

int instanceSize = Driver.FindElements(By.CssSelector("*")).Count;
int instanceSize = Driver.FindElements(By.CssSelector("*")).Count;

if (instanceSize > CurrentDomElementSize)
{
CurrentDomElementSize = instanceSize;
SameSizzeCounter = 0;
continue;
if (instanceSize > CurrentDomElementSize)
{
CurrentDomElementSize = instanceSize;
SameSizzeCounter = 0;
continue;
}
else
{
SameSizzeCounter++;
if (SameSizzeCounter == 5)
{
DomElementIncreasing = false;
}
}
}
}
else
catch
{
SameSizzeCounter++;
if (SameSizzeCounter == 5)
{
DomElementIncreasing = false;
}
// Do nothing...
}
}
}
catch
*/
void CheckifPageLoaded()
{
WebDriverWait webDriverWait = new WebDriverWait(Driver, TimeSpan.FromSeconds(ImplicitWait));
webDriverWait.Until((driver) =>
{
// Do nothing...
}
object jQuery = ((IJavaScriptExecutor)driver).ExecuteScript("return window.jQuery && jQuery.active == 0");

bool IsJqueryCompleted = jQuery == null ? true : jQuery.Equals(true);
return
((IJavaScriptExecutor)driver).ExecuteScript("return document.readyState").Equals("complete") && IsJqueryCompleted;
});

@coderabbitai coderabbitai Bot Jan 10, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The refactoring of the CheckifPageLoaded method to use WebDriverWait is a good improvement for reliability. However, the old method implementation is commented out rather than removed. It's best practice to remove unused code to keep the codebase clean. If this code needs to be preserved for historical reasons, it should be stored in version control, not commented out in the source.

- /* void CheckifPageLoaded()
- {
-     // Old implementation...
- }
- */

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
/* void CheckifPageLoaded()
{
Thread.Sleep(300);
//TODO: slow function, try to check alternatives or let the user config wait for
try
{
bool DomElementIncreasing = true;
int CurrentDomElementSize = 0;
int SameSizzeCounter = 0;
while (DomElementIncreasing)
{
Thread.Sleep(300);
int instanceSize = Driver.FindElements(By.CssSelector("*")).Count;
int instanceSize = Driver.FindElements(By.CssSelector("*")).Count;
if (instanceSize > CurrentDomElementSize)
{
CurrentDomElementSize = instanceSize;
SameSizzeCounter = 0;
continue;
if (instanceSize > CurrentDomElementSize)
{
CurrentDomElementSize = instanceSize;
SameSizzeCounter = 0;
continue;
}
else
{
SameSizzeCounter++;
if (SameSizzeCounter == 5)
{
DomElementIncreasing = false;
}
}
}
}
else
catch
{
SameSizzeCounter++;
if (SameSizzeCounter == 5)
{
DomElementIncreasing = false;
}
// Do nothing...
}
}
}
catch
*/
void CheckifPageLoaded()
{
WebDriverWait webDriverWait = new WebDriverWait(Driver, TimeSpan.FromSeconds(ImplicitWait));
webDriverWait.Until((driver) =>
{
// Do nothing...
}
object jQuery = ((IJavaScriptExecutor)driver).ExecuteScript("return window.jQuery && jQuery.active == 0");
bool IsJqueryCompleted = jQuery == null ? true : jQuery.Equals(true);
return
((IJavaScriptExecutor)driver).ExecuteScript("return document.readyState").Equals("complete") && IsJqueryCompleted;
});
void CheckifPageLoaded()
{
WebDriverWait webDriverWait = new WebDriverWait(Driver, TimeSpan.FromSeconds(ImplicitWait));
webDriverWait.Until((driver) =>
{
object jQuery = ((IJavaScriptExecutor)driver).ExecuteScript("return window.jQuery && jQuery.active == 0");
bool IsJqueryCompleted = jQuery == null ? true : jQuery.Equals(true);
return
((IJavaScriptExecutor)driver).ExecuteScript("return document.readyState").Equals("complete") && IsJqueryCompleted;
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We need this commented code for later use

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I understand that you wish to keep the commented code for future reference or use. While it's typically recommended to use version control systems for maintaining historical code, I respect your decision to keep it within the source files for convenience. If you need to access this code frequently or quickly revert changes, having it commented might be practical for your workflow.

(\\(\\
(-.-)
o_(")(")

Keeping the old ways close to heart.


New Learnings Added
User: manas-droid
PR: Ginger-Automation/Ginger#3436
File: Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs:6702-6749
Timestamp: 2024-01-10T08:58:17.226Z
Learning: The user has indicated that the commented `CheckifPageLoaded` method is needed for later use, suggesting a requirement to retain the code within the source files for reference or potential re-implementation.

public class RunsetExecutor : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler PropertyChanged;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The implementation of OnPropertyChanged follows the standard pattern for INotifyPropertyChanged. However, the null check can be simplified using the null-conditional operator.

- PropertyChangedEventHandler handler = PropertyChanged;
- if (handler != null)
- {
-     handler(this, new PropertyChangedEventArgs(name));
- }
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler PropertyChanged;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

Comment on lines +546 to +555
if (doContinueRun == false)
{
GR.Executor.RunRunner();
}
else
if (GR.Status == Amdocs.Ginger.CoreNET.Execution.eRunStatus.Stopped)//we continue only Stopped Runners
{
GR.Executor.ResetRunnerExecutionDetails(doNotResetBusFlows: true);//reset stopped runners only and not their BF's
GR.Executor.ContinueRun(eContinueLevel.Runner, eContinueFrom.LastStoppedAction);
}

@coderabbitai coderabbitai Bot Jan 10, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The use of TaskCreationOptions.LongRunning is appropriate for tasks that are expected to run for an extended period. However, the nested if-else blocks could be refactored for better readability.

- if (doContinueRun == false)
- {
-     GR.Executor.RunRunner();
- }
- else
-     if (GR.Status == Amdocs.Ginger.CoreNET.Execution.eRunStatus.Stopped)
- {
-     GR.Executor.ResetRunnerExecutionDetails(doNotResetBusFlows: true);
-     GR.Executor.ContinueRun(eContinueLevel.Runner, eContinueFrom.LastStoppedAction);
- }
+ if (!doContinueRun)
+ {
+     GR.Executor.RunRunner();
+ }
+ else if (GR.Status == Amdocs.Ginger.CoreNET.Execution.eRunStatus.Stopped)
+ {
+     GR.Executor.ResetRunnerExecutionDetails(doNotResetBusFlows: true);
+     GR.Executor.ContinueRun(eContinueLevel.Runner, eContinueFrom.LastStoppedAction);
+ }

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if (doContinueRun == false)
{
GR.Executor.RunRunner();
}
else
if (GR.Status == Amdocs.Ginger.CoreNET.Execution.eRunStatus.Stopped)//we continue only Stopped Runners
{
GR.Executor.ResetRunnerExecutionDetails(doNotResetBusFlows: true);//reset stopped runners only and not their BF's
GR.Executor.ContinueRun(eContinueLevel.Runner, eContinueFrom.LastStoppedAction);
}
if (!doContinueRun)
{
GR.Executor.RunRunner();
}
else if (GR.Status == Amdocs.Ginger.CoreNET.Execution.eRunStatus.Stopped)//we continue only Stopped Runners
{
GR.Executor.ResetRunnerExecutionDetails(doNotResetBusFlows: true);//reset stopped runners only and not their BF's
GR.Executor.ContinueRun(eContinueLevel.Runner, eContinueFrom.LastStoppedAction);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok, will make this change

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@manas-droid Great, I'm glad the suggestion was helpful. If you need any further assistance, feel free to ask. Happy coding!


If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!

Comment on lines +2444 to +2452
codeBlock();
}
);

while (!task.IsCompleted && st.ElapsedMilliseconds < timeSpan.TotalMilliseconds && !mStopRun)
{
task.Wait(500); // Give user feedback every 500ms
act.Elapsed = st.ElapsedMilliseconds;
GiveUserFeedback();
act.Elapsed = st.ElapsedMilliseconds;
GiveUserFeedback();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The review of the ExecuteActionWithTimeLimit method in GingerExecutionEngine.cs shows that while exceptions are caught and handled, there is no evidence of a cancellation token being used with the task. This means that the task may not be set up to respond to cancellation requests through the standard .NET task cancellation mechanism. The code does provide user feedback every 500ms and handles a stop condition with the mStopRun flag.

Given the absence of a cancellation token, the review comment's concern about potentially ignoring task cancellation requests is valid. However, exceptions are not ignored, as there is a catch block to handle them.

Analysis chain

The changes in the ExecuteActionWithTimeLimit method seem to adjust the feedback mechanism by providing user feedback every 500ms. This is a good practice for long-running tasks. However, ensure that the task.Wait(500) does not introduce any unintended consequences, such as ignoring task cancellation requests or exceptions that should be handled immediately.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that task.Wait is used correctly and does not ignore cancellation requests or exceptions.
rg --type cs "task.Wait"

Length of output: 442



Script:

#!/bin/bash
# Examine the complete implementation of the ExecuteActionWithTimeLimit method to verify handling of cancellations and exceptions.
rg --type cs -C 30 "ExecuteActionWithTimeLimit" Ginger/GingerCoreNET/Run/GingerExecutionEngine.cs

Length of output: 5795



Script:

#!/bin/bash
# Search for task creation and cancellation token logic related to the ExecuteActionWithTimeLimit method.
rg --type cs -A 10 "Task.Factory.StartNew" Ginger/GingerCoreNET/Run/GingerExecutionEngine.cs

Length of output: 542

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 9307d44 and db9b530.
Files selected for processing (2)
  • Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs (4 hunks)
  • Ginger/GingerCoreNET/Run/RunsetExecutor.cs (12 hunks)
Files skipped from review as they are similar to previous changes (2)
  • Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs
  • Ginger/GingerCoreNET/Run/RunsetExecutor.cs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 11

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between db9b530 and bcc2d93.
Files selected for processing (11)
  • Ginger/Ginger/Activities/ActivitiesListViewHelper.cs (5 hunks)
  • Ginger/Ginger/AutomatePageLib/ExecutionSummaryPage.xaml (1 hunks)
  • Ginger/Ginger/AutomatePageLib/NewAutomatePage.xaml (2 hunks)
  • Ginger/Ginger/Dictionaries/Skins/GingerDefaultSkinDictionary.xaml (1 hunks)
  • Ginger/Ginger/SolutionWindows/TreeViewItems/NewTreeViewItemBase.cs (1 hunks)
  • Ginger/Ginger/UserControlsLib/UCListView/UcListViewItem.xaml (2 hunks)
  • Ginger/Ginger/UserControlsLib/UCListView/UcListViewItem.xaml.cs (6 hunks)
  • Ginger/GingerCoreNET/DataSource/LiteDB.cs (12 hunks)
  • Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs (6 hunks)
  • Ginger/GingerCoreNET/LiteDBFolder/LiteDbConnector.cs (3 hunks)
  • Ginger/GingerCoreNET/LiteDBFolder/LiteDbManager.cs (2 hunks)
Files skipped from review due to trivial changes (1)
  • Ginger/Ginger/Dictionaries/Skins/GingerDefaultSkinDictionary.xaml
Additional comments: 27
Ginger/Ginger/AutomatePageLib/ExecutionSummaryPage.xaml (1)
  • 6-6: The Height and Width of ExecutionSummaryPage have been changed to 600 each. Please ensure that all elements within the page are still displayed correctly and that this change does not negatively impact the user interface or the layout of any contained elements.
Ginger/GingerCoreNET/LiteDBFolder/LiteDbConnector.cs (3)
  • 28-31: The addition of comments regarding the future handling of multi-access to database files is a good practice for providing guidance for future development.

  • 46-60: Refactoring of exception handling in the GetCollection method is done correctly by logging the error and rethrowing the exception, which is a good practice for error handling.

  • 137-138: The change from a using statement to a using declaration in the SetCollection method simplifies the code and ensures the disposal of the object at the end of the enclosing scope, which is a feature of C# 8.0.

Ginger/GingerCoreNET/LiteDBFolder/LiteDbManager.cs (1)
  • 142-155: The introduction of a SemaphoreSlim in the WriteToLiteDb method is a good practice to ensure thread safety during write operations. Please verify that the semaphore is used correctly throughout the codebase to prevent potential deadlocks or race conditions.
Ginger/Ginger/UserControlsLib/UCListView/UcListViewItem.xaml (1)
  • 93-150: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [96-159]

Modifications to the layout and styling of UI elements in UcListViewItem.xaml, such as text block alignment and font size, have been made. Please ensure that these changes do not disrupt the intended appearance and behavior of the UI elements when selected or in their default state.

Ginger/Ginger/SolutionWindows/TreeViewItems/NewTreeViewItemBase.cs (1)
  • 101-103: Storing the result of the user confirmation in a variable and adding a condition for eUserMsgSelection.None enhances the readability and robustness of the deletion logic in NewTreeViewItemBase.cs.
Ginger/Ginger/AutomatePageLib/NewAutomatePage.xaml (2)
  • 321-321: The width of a column in the Grid has been changed from 650 to 550. This change should be verified to ensure it aligns with the intended design and does not negatively impact the layout or user experience.

  • 330-330: The margin of the GridSplitter has been changed from "0,0,0,0" to "0,0,0,10". This change adds spacing at the bottom of the GridSplitter, which could improve visual separation. Ensure this change is consistent with the design standards of the application.

Ginger/Ginger/UserControlsLib/UCListView/UcListViewItem.xaml.cs (1)
  • 760-779: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [700-770]

The methods AlignGridandTextWidth and XDetailsGrid_SizeChanged have been added to dynamically adjust the width of the text blocks based on the actual width of the grid column. This is a good approach to ensure that the UI adapts to different sizes and maintains a good user experience.

Ginger/GingerCoreNET/DataSource/LiteDB.cs (7)
  • 40-40: The introduction of a SemaphoreSlim for thread safety is a good practice when dealing with potential concurrent database access.

  • 65-111: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [52-71]

The use of LiteDBLock.Wait() and LiteDBLock.Release() in AddColumn method ensures that the database operations are thread-safe. However, it's important to ensure that LiteDBLock.Release() is called in a finally block to guarantee the release even if an exception occurs.

  • 78-107: Similar to the previous comment, the AddTable method correctly implements the SemaphoreSlim to manage concurrent access.

  • 200-210: The DeleteTable method correctly uses the SemaphoreSlim to ensure thread safety.

  • 585-627: The GetTablesList method correctly uses the SemaphoreSlim to ensure thread safety.

  • 632-641: The IsTableExist method correctly uses the SemaphoreSlim to ensure thread safety.

  • 646-663: The RemoveColumn method correctly uses the SemaphoreSlim to ensure thread safety.

Ginger/Ginger/Activities/ActivitiesListViewHelper.cs (5)
  • 675-675: The tooltip for the delete operation has been updated to improve clarity. The change from "Delete all" to "Delete" followed by the group and its activities is more precise.

  • 685-685: The tooltip for the disable operation has been updated to improve clarity. The change from "Disable all" to "Disable" followed by the group and its activities is more precise.

  • 695-695: The tooltip for the activate operation has been updated to improve clarity. The change from "Activate all" to "Activate" followed by the group and its activities is more precise.

  • 734-734: The tooltip for the export operation has been updated to improve clarity. The change from "Export" with incorrect grammar "and it" to "and its" corrects the grammatical mistake and is more precise.

  • 753-753: The tooltip for the add to shared repository operation has been updated to improve clarity. The change from "Add" with incorrect grammar "and it" to "and its" corrects the grammatical mistake and is more precise.

Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs (5)
  • 1581-1581: The addition of string filepath = null; before the try block is a good practice to ensure the variable is initialized and can be used after the try-catch block.

  • 1590-1603: Moving the filepath assignment inside the using block ensures that the file path is only set if the screenshots are successfully taken and merged. This is a good change for better error handling and resource management.

  • 1606-1609: Adding a check for !string.IsNullOrEmpty(filepath) before adding to act.ScreenShotsNames and act.ScreenShots is a good defensive programming practice to avoid null or empty paths being added to the action's screenshots.

  • 4409-4417: The introduction of a while loop with a maximum attempt count is a good refactor to replace the previous recursive approach. This change should improve the reliability and maintainability of the IsRunning method.

  • 6699-6746: The refactoring of the CheckifPageLoaded method to use WebDriverWait and IJavaScriptExecutor for improved page load checking is a significant improvement. However, as per the learning from the user, the old implementation is commented out for future reference. This decision should be respected, and no further action is required regarding the commented code.

Comment on lines 760 to 779
});
}

private void AlignGridandTextWidth()
{
if (xItemNameColumn.ActualWidth > 200)
{
xItemNameTxtBlock.MaxWidth = xItemNameColumn.ActualWidth - 100;
xItemDescriptionTxtBlock.MaxWidth = xItemNameTxtBlock.MaxWidth;
}
}

private void XDetailsGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
AlignGridandTextWidth();
}

private void SetItemDescription()
{
this.Dispatcher.Invoke(() =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [700-770]

It's important to note that the SizeChanged event handler is being added every time SetItemFullName is called. This could potentially lead to multiple event subscriptions if SetItemFullName is called multiple times. Consider checking if the handler is already attached before adding it or ensure that SetItemFullName is only called once during the lifecycle of the control.

- xDetailsGrid.SizeChanged += XDetailsGrid_SizeChanged;
+ if (!isSizeChangedHandlerAttached) {
+     xDetailsGrid.SizeChanged += XDetailsGrid_SizeChanged;
+     isSizeChangedHandlerAttached = true;
+ }

Comment on lines +4452 to +4536
if(attemptCount < maxAttempts)
{
exceptioncount++;
return (IsRunning());
attemptCount++;
continue;
}

var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
{
return true;
}

if (count == 0)
{
return false;
}
}
if (count == 0)
catch (OpenQA.Selenium.UnhandledAlertException)
{
return false;
}
}
catch (OpenQA.Selenium.UnhandledAlertException)
{
return true;
}
catch (OpenQA.Selenium.NoSuchWindowException ex)
{
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex);
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
catch (OpenQA.Selenium.NoSuchWindowException ex)
{
return true;
}
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex);
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
{

return true;
}

if (exceptioncount < 5)
{
exceptioncount++;
return (IsRunning());
if (attemptCount < maxAttempts)
{
attemptCount++;
continue;
}
}
}
catch (OpenQA.Selenium.WebDriverTimeoutException ex)
{
Reporter.ToLog(eLogLevel.DEBUG, "Timeout exception occurred when we are checking IsRunning", ex);
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
catch (OpenQA.Selenium.WebDriverTimeoutException ex)
{
return true;
}
Reporter.ToLog(eLogLevel.DEBUG, "Timeout exception occurred when we are checking IsRunning", ex);
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
{
return true;
}

if (exceptioncount < 5)
{
exceptioncount++;
return (IsRunning());
if (attemptCount < maxAttempts)
{
attemptCount++;
continue;
}
}
}
catch (OpenQA.Selenium.WebDriverException ex)
{
Reporter.ToLog(eLogLevel.DEBUG, "Webdriver exception occurred when we are checking IsRunning", ex);

if (PreviousRunStopped && ex.Message == "Unexpected error. Error 404: Not Found\r\nNot Found")
catch (OpenQA.Selenium.WebDriverException ex)
{
return true;
}
Reporter.ToLog(eLogLevel.DEBUG, "Webdriver exception occurred when we are checking IsRunning", ex);

if (exceptioncount < 5)
{
exceptioncount++;
return (IsRunning());
if (PreviousRunStopped && ex.Message == "Unexpected error. Error 404: Not Found\r\nNot Found")
{
return true;
}

if (attemptCount < maxAttempts)
{
attemptCount++;
continue;
}

CloseDriver();
break;
}
CloseDriver();
return false;
}
catch (Exception ex2)
{
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex2);
if (ex2.Message.ToString().ToUpper().Contains("DIALOG"))
catch (Exception ex2)
{
return true;
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex2);
if (ex2.Message.ToString().ToUpper().Contains("DIALOG"))
{
return true;
}

CloseDriver();
break;
}
CloseDriver();
return false;

}
return true;

return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The refactoring of the exception handling in the IsRunning method to use a loop with a maximum attempt count and improved exception handling is a good change. However, there is a potential issue with the logic: the attemptCount is incremented inside each catch block, which could lead to multiple increments per loop iteration if multiple exceptions are caught. This needs to be verified and potentially corrected to ensure that attemptCount is only incremented once per iteration.

- if (attemptCount < maxAttempts)
- {
-     attemptCount++;
-     continue;
- }
+ // Move the attemptCount incrementation and continue logic outside the catch blocks to ensure it only happens once per iteration.

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if(attemptCount < maxAttempts)
{
exceptioncount++;
return (IsRunning());
attemptCount++;
continue;
}
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
{
return true;
}
if (count == 0)
{
return false;
}
}
if (count == 0)
catch (OpenQA.Selenium.UnhandledAlertException)
{
return false;
}
}
catch (OpenQA.Selenium.UnhandledAlertException)
{
return true;
}
catch (OpenQA.Selenium.NoSuchWindowException ex)
{
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex);
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
catch (OpenQA.Selenium.NoSuchWindowException ex)
{
return true;
}
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex);
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
{
return true;
}
if (exceptioncount < 5)
{
exceptioncount++;
return (IsRunning());
if (attemptCount < maxAttempts)
{
attemptCount++;
continue;
}
}
}
catch (OpenQA.Selenium.WebDriverTimeoutException ex)
{
Reporter.ToLog(eLogLevel.DEBUG, "Timeout exception occurred when we are checking IsRunning", ex);
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
catch (OpenQA.Selenium.WebDriverTimeoutException ex)
{
return true;
}
Reporter.ToLog(eLogLevel.DEBUG, "Timeout exception occurred when we are checking IsRunning", ex);
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
{
return true;
}
if (exceptioncount < 5)
{
exceptioncount++;
return (IsRunning());
if (attemptCount < maxAttempts)
{
attemptCount++;
continue;
}
}
}
catch (OpenQA.Selenium.WebDriverException ex)
{
Reporter.ToLog(eLogLevel.DEBUG, "Webdriver exception occurred when we are checking IsRunning", ex);
if (PreviousRunStopped && ex.Message == "Unexpected error. Error 404: Not Found\r\nNot Found")
catch (OpenQA.Selenium.WebDriverException ex)
{
return true;
}
Reporter.ToLog(eLogLevel.DEBUG, "Webdriver exception occurred when we are checking IsRunning", ex);
if (exceptioncount < 5)
{
exceptioncount++;
return (IsRunning());
if (PreviousRunStopped && ex.Message == "Unexpected error. Error 404: Not Found\r\nNot Found")
{
return true;
}
if (attemptCount < maxAttempts)
{
attemptCount++;
continue;
}
CloseDriver();
break;
}
CloseDriver();
return false;
}
catch (Exception ex2)
{
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex2);
if (ex2.Message.ToString().ToUpper().Contains("DIALOG"))
catch (Exception ex2)
{
return true;
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex2);
if (ex2.Message.ToString().ToUpper().Contains("DIALOG"))
{
return true;
}
CloseDriver();
break;
}
CloseDriver();
return false;
}
return true;
return false;
if(attemptCount < maxAttempts)
{
attemptCount++;
continue;
}
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
{
return true;
}
if (count == 0)
{
return false;
}
}
catch (OpenQA.Selenium.UnhandledAlertException)
{
return false;
}
catch (OpenQA.Selenium.NoSuchWindowException ex)
{
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex);
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
{
return true;
}
}
catch (OpenQA.Selenium.WebDriverTimeoutException ex)
{
Reporter.ToLog(eLogLevel.DEBUG, "Timeout exception occurred when we are checking IsRunning", ex);
var currentWindow = Driver.CurrentWindowHandle;
if (!string.IsNullOrEmpty(currentWindow))
{
return true;
}
}
catch (OpenQA.Selenium.WebDriverException ex)
{
Reporter.ToLog(eLogLevel.DEBUG, "Webdriver exception occurred when we are checking IsRunning", ex);
if (PreviousRunStopped && ex.Message == "Unexpected error. Error 404: Not Found\r\nNot Found")
{
return true;
}
CloseDriver();
break;
}
catch (Exception ex2)
{
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex2);
if (ex2.Message.ToString().ToUpper().Contains("DIALOG"))
{
return true;
}
CloseDriver();
break;
}
}
return false;

Comment on lines +123 to +136
LiteDBLock.Wait();
using (var db = new LiteDatabase(ConnectionString))
{
var CopyTable = db.GetCollection(CopyTableName);
var table = db.GetCollection(tableName);

DataTable dtChange = new DataTable(CopyTableName);
dtChange = datatable(table.FindAll() , CopyTableName);
SaveTable(dtChange , CopyTable);
DataTable dtChange = new DataTable(CopyTableName);
dtChange = datatable(table.FindAll(), CopyTableName);
SaveTable(dtChange, CopyTable);
}
}
finally
{
LiteDBLock.Release();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The CopyTable method also correctly uses the SemaphoreSlim. However, it's important to note that the method increments the table name with "_Copy" until a non-existing name is found. This could potentially lead to a large number of iterations if there are many copies. Consider adding a limit to the number of iterations or a different naming strategy.

Comment on lines +257 to +275
try
{
if (tableName == "")
{ return mColumnNames; }
LiteDBLock.Wait();
List<string> mColumnNames = new();
using (var db = new LiteDatabase(ConnectionString))
{
if (tableName == "")
{ return mColumnNames; }

var results = db.GetCollection(tableName).Find(Query.All(), 0).ToList();
mColumnNames = GetColumnList(results, tableName);
//var name = mColumnNames.RemoveAll(i => i.Contains("Name")); Commented this as we are not able to see columnNames which contain "Name" keyword in it.
var results = db.GetCollection(tableName).Find(Query.All(), 0).ToList();
mColumnNames = GetColumnList(results, tableName);
//var name = mColumnNames.RemoveAll(i => i.Contains("Name")); Commented this as we are not able to see columnNames which contain "Name" keyword in it.

}
return mColumnNames;
}
finally
{
LiteDBLock.Release();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The GetColumnList method is properly synchronized with SemaphoreSlim. However, the commented line //var name = mColumnNames.RemoveAll(i => i.Contains("Name")); suggests there was an intention to remove certain column names. If this is no longer needed, it's best to remove the commented code to avoid confusion.

Comment on lines +360 to +549
try
{
var results = db.GetCollection(query).Find(Query.All(), 0).ToList();
try
LiteDBLock.Wait();
List<string> mColumnNames = new List<string>();
DataTable dataTable = new DataTable();
bool duplicate = false;
using (var db = new LiteDatabase(ConnectionString))
{
if (results.Count > 0)
var results = db.GetCollection(query).Find(Query.All(), 0).ToList();
try
{
var result = db.GetCollection<BsonDocument>(query);

var dt = new LiteDataTable(results.ToString());
foreach (var doc in results)
if (results.Count > 0)
{
var dr = dt.NewRow() as LiteDataRow;
if (dr != null)
var result = db.GetCollection<BsonDocument>(query);

var dt = new LiteDataTable(results.ToString());
foreach (var doc in results)
{
dr.UnderlyingValue = doc;
foreach (var property in doc.RawValue)
var dr = dt.NewRow() as LiteDataRow;
if (dr != null)
{
if (!property.Value.IsMaxValue && !property.Value.IsMinValue)
dr.UnderlyingValue = doc;
foreach (var property in doc.RawValue)
{
if (!dt.Columns.Contains(property.Key))
{
dt.Columns.Add(property.Key, typeof(string));
}
if(property.Value.RawValue != null)
if (!property.Value.IsMaxValue && !property.Value.IsMinValue)
{
if (property.Value.RawValue.ToString() == "System.Collections.Generic.Dictionary`2[System.String,BsonValue]" || property.Value.RawValue.ToString() == "System.Collections.Generic.Dictionary`2[System.String,LiteDB.BsonValue]")
if (!dt.Columns.Contains(property.Key))
{
dr[property.Key] = "";
dt.Columns.Add(property.Key, typeof(string));
}
else if (property.Value.RawValue.ToString() == "System.Data.DataRowCollection" || property.Value.RawValue.ToString() == "System.Collections.Generic.Dictionary`2[System.String,LiteDB.BsonValue]")
if (property.Value.RawValue != null)
{
duplicate = true;
if (property.Value.RawValue.ToString() == "System.Collections.Generic.Dictionary`2[System.String,BsonValue]" || property.Value.RawValue.ToString() == "System.Collections.Generic.Dictionary`2[System.String,LiteDB.BsonValue]")
{
dr[property.Key] = "";
}
else if (property.Value.RawValue.ToString() == "System.Data.DataRowCollection" || property.Value.RawValue.ToString() == "System.Collections.Generic.Dictionary`2[System.String,LiteDB.BsonValue]")
{
duplicate = true;
}
else
{
dr[property.Key] = property.Value.RawValue.ToString();
}
}
else
{
dr[property.Key] = property.Value.RawValue.ToString();
}
}
else
{
dr[property.Key] = string.Empty;
dr[property.Key] = string.Empty;

}
}
}
dt.Rows.Add(dr);
}
dt.Rows.Add(dr);
}
}

DataTable aa = dt;
bool dosort = true;
if (duplicate)
{
dt.Rows.RemoveAt(dt.Rows.Count - 1);
}
DataTable dt2 = dt.Clone();
dt2.Columns["GINGER_ID"].DataType = Type.GetType("System.Int32");
DataTable aa = dt;
bool dosort = true;
if (duplicate)
{
dt.Rows.RemoveAt(dt.Rows.Count - 1);
}
DataTable dt2 = dt.Clone();
dt2.Columns["GINGER_ID"].DataType = Type.GetType("System.Int32");

foreach (DataRow dr in dt.Rows)
{
if (Convert.ToString(dr["GINGER_ID"]) == "")
foreach (DataRow dr in dt.Rows)
{
dosort = false;
if (Convert.ToString(dr["GINGER_ID"]) == "")
{
dosort = false;
}
else
{
dt2.ImportRow(dr);
}
}
if (dosort)
{
dt2.AcceptChanges();
DataView dv = dt2.DefaultView;
dv.Sort = "GINGER_ID ASC";

aa = dv.ToTable();
}
else
{
dt2.ImportRow(dr);
aa.Rows.RemoveAt(0);
}
}
if (dosort)
{
dt2.AcceptChanges();
DataView dv = dt2.DefaultView;
dv.Sort = "GINGER_ID ASC";

aa = dv.ToTable();
aa.TableName = query;
dataTable = aa;
}
else
{
aa.Rows.RemoveAt(0);
}

aa.TableName = query;
dataTable = aa;
}
else
{
// If we need to run a direct query
try
{
// Converting BSON to JSON
JArray array = new ();
// This query needs SQL Command
BsonValue[] result = db.Execute(query).ToArray();
foreach (BsonValue bs in result)
// If we need to run a direct query
try
{
string js = LiteDB.JsonSerializer.Serialize(bs);
if (js == "0" || js == "1")
// Converting BSON to JSON
JArray array = new();
// This query needs SQL Command
BsonValue[] result = db.Execute(query).ToArray();
foreach (BsonValue bs in result)
{
return dataTable;
}
JObject jo = JObject.Parse(js);
JObject jo2 = new JObject();
foreach (JToken jt in jo.Children())
{
if ((jt as JProperty).Name != "_id")
string js = LiteDB.JsonSerializer.Serialize(bs);
if (js == "0" || js == "1")
{
return dataTable;
}
JObject jo = JObject.Parse(js);
JObject jo2 = new JObject();
foreach (JToken jt in jo.Children())
{
string sData = jt.ToString();
Regex regex = new Regex(@": {(\r|\n| )*""_type"": ""System.DBNull*");
Match match = regex.Match(sData);
if (match.Success)
if ((jt as JProperty).Name != "_id")
{
if (jt.HasValues)
string sData = jt.ToString();
Regex regex = new Regex(@": {(\r|\n| )*""_type"": ""System.DBNull*");
Match match = regex.Match(sData);
if (match.Success)
{
string name = (jt as JProperty).Name;
var aa = jt as JProperty;
aa.Value = "";
if (jt.HasValues)
{
string name = (jt as JProperty).Name;
var aa = jt as JProperty;
aa.Value = "";
}
jo2.Add(jt);
}
else
{
jo2.Add(jt);
}
jo2.Add(jt);
}
}
array.Add(jo2);
}
// JSON to Datatable
dataTable = JsonConvert.DeserializeObject<DataTable>(array.ToString());
if (dataTable != null && dataTable.Columns.Count > 0)
{
DataTable dt = dataTable;
bool dosort = true;
DataTable dt2 = dataTable.Clone();
foreach (DataRow dr in dataTable.Rows)
{
if (Convert.ToString(dr["GINGER_ID"]) == "")
{
dosort = false;
}
else
{
jo2.Add(jt);
dt2.ImportRow(dr);
}
}
}
array.Add(jo2);
}
// JSON to Datatable
dataTable = JsonConvert.DeserializeObject<DataTable>(array.ToString());
if (dataTable != null && dataTable.Columns.Count > 0)
{
DataTable dt = dataTable;
bool dosort = true;
DataTable dt2 = dataTable.Clone();
foreach (DataRow dr in dataTable.Rows)
{
if (Convert.ToString(dr["GINGER_ID"]) == "")
if (dosort)
{
dosort = false;
dt2.AcceptChanges();
DataView dv = dt2.DefaultView;
dv.Sort = "GINGER_ID ASC";

dt = dv.ToTable();
}
else
{
dt2.ImportRow(dr);
dt.Rows.RemoveAt(0);
}
dt.TableName = query;
dataTable = dt;
var json = JsonConvert.SerializeObject(array);
}
if (dosort)
{
dt2.AcceptChanges();
DataView dv = dt2.DefaultView;
dv.Sort = "GINGER_ID ASC";

dt = dv.ToTable();
}
else
{
dt.Rows.RemoveAt(0);
}
dt.TableName = query;
dataTable = dt;
var json = JsonConvert.SerializeObject(array);
}
}
catch (Exception ex)
{
Reporter.ToLog(eLogLevel.WARN, "Exception Occurred while doing LiteDB GetQueryOutput", ex);
db.Dispose();
catch (Exception ex)
{
Reporter.ToLog(eLogLevel.WARN, "Exception Occurred while doing LiteDB GetQueryOutput", ex);
db.Dispose();
}
}
}
catch (Exception ex)
{
Reporter.ToLog(eLogLevel.ERROR, "Exception Occurred while doing LiteDB GetQueryOutput", ex);
db.Dispose();

}
}
catch (Exception ex)
{
Reporter.ToLog(eLogLevel.ERROR, "Exception Occurred while doing LiteDB GetQueryOutput", ex);
db.Dispose();
}
dataTable.AcceptChanges();
return dataTable;
}
finally
{
LiteDBLock.Release();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The GetQueryOutput method is quite complex and could benefit from refactoring for clarity and maintainability. It contains nested try-catch blocks and multiple paths that could be simplified. Additionally, the method uses direct SQL execution, which could be prone to SQL injection if not properly sanitized. Ensure that the input query is sanitized before execution.

Comment on lines +702 to +716
try
{
// SQL Command needed here:
var result = db.Execute(query);
}
LiteDBLock.Wait();
using (LiteDatabase db = new LiteDatabase(ConnectionString))
{
// SQL Command needed here:
var result = db.Execute(query);
}

return true;
return true;
}
finally
{
LiteDBLock.Release();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The RunQuery method correctly uses the SemaphoreSlim to ensure thread safety. However, the method returns true unconditionally, even if the execution of the query fails. Consider returning the result of the query execution or handling exceptions to provide accurate feedback.

Comment on lines +781 to +802
try
{
// SQL query needed here
var resultdxs = db.Execute(query).ToArray();
foreach (BsonValue bs in resultdxs)
LiteDBLock.Wait();
string result = null;
using (LiteDatabase db = new LiteDatabase(ConnectionString))
{
BsonDocument aa = bs.AsDocument;
foreach (KeyValuePair<string, BsonValue> keyval in aa.RawValue)
// SQL query needed here
var resultdxs = db.Execute(query).ToArray();
foreach (BsonValue bs in resultdxs)
{
result = keyval.Value.RawValue.ToString();
BsonDocument aa = bs.AsDocument;
foreach (KeyValuePair<string, BsonValue> keyval in aa.RawValue)
{
result = keyval.Value.RawValue.ToString();
}
}
}
return result;
}
finally
{
LiteDBLock.Release();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The GetResultString method correctly uses the SemaphoreSlim to ensure thread safety. However, the method iterates over the results and overwrites the result variable in each iteration, which means only the last result will be returned. If this is the intended behavior, it should be documented; otherwise, consider if a different approach is needed.

Comment on lines +808 to +827
try
{
//SQL command needed here

var resultdxs = db.Execute(query).ToArray();
foreach (BsonValue bs in resultdxs)
LiteDBLock.Wait();
object result = null;
using (LiteDatabase db = new LiteDatabase(ConnectionString))
{
result = bs.RawValue;
//SQL command needed here

var resultdxs = db.Execute(query).ToArray();
foreach (BsonValue bs in resultdxs)
{
result = bs.RawValue;
}
}
return result;

}
finally
{
LiteDBLock.Release();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The GetResult method correctly uses the SemaphoreSlim to ensure thread safety. Similar to GetResultString, it overwrites the result variable in each iteration, which may not be the intended behavior.

Comment on lines +855 to +867
try
{
dataTable.DefaultView.Sort = "GINGER_ID";
var table = db.GetCollection(dataTable.ToString());
SaveTable(dataTable , table);
LiteDBLock.Wait();
using (LiteDatabase db = new LiteDatabase(ConnectionString))
{
dataTable.DefaultView.Sort = "GINGER_ID";
var table = db.GetCollection(dataTable.ToString());
SaveTable(dataTable, table);
}
}
finally
{
LiteDBLock.Release();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The SaveTable method correctly uses the SemaphoreSlim to ensure thread safety. However, the method does not handle the case where dataTable.GetChanges() returns null, which would result in a NullReferenceException when calling changed.Rows.Count. Ensure that this case is handled properly.

Comment on lines +1262 to +1289
try
{
var table = db.GetCollection(TName);
LiteDBLock.Wait();
using (LiteDatabase db = new LiteDatabase(ConnectionString))
{
var table = db.GetCollection(TName);

List<string> ColumnList = GetColumnList(table.FindAll().ToList() , TName);
table.DeleteAll();
List<BsonDocument> batch = new List<BsonDocument>();
List<string> ColumnList = GetColumnList(table.FindAll().ToList(), TName);
table.DeleteAll();
List<BsonDocument> batch = new List<BsonDocument>();

var doc = new BsonDocument();
var doc = new BsonDocument();

for (int i = 0; i < ColumnList.Count; i++)
{
doc[ColumnList[i]] = "";
}
table.Insert(doc);

for (int i = 0; i < ColumnList.Count; i++)
{
doc[ColumnList[i]] = "";
}
table.Insert(doc);

isDeleteAllExecuted = false;
}
finally
{
LiteDBLock.Release();

isDeleteAllExecuted = false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The DeleteDBTableContents method correctly uses the SemaphoreSlim to ensure thread safety. However, the method inserts a new document with empty values for all columns after deleting all documents. This behavior should be documented or reconsidered if it's not necessary.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 3

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between bcc2d93 and 695c509.
Files selected for processing (1)
  • Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs (4 hunks)
Additional comments: 5
Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs (5)
  • 4409-4417: The introduction of maxAttempts and attemptCount variables, along with the nested try-catch block for counting window handles, is a good approach for improving the robustness of the IsRunning method. However, ensure that the logic for incrementing attemptCount is consistent and error-proof across the entire method to avoid premature exits or infinite loops.
  • 4473-4487: Catching specific exceptions like NoSuchWindowException and logging them is a good practice for debugging and understanding the flow of execution. However, ensure that the logic for handling these exceptions, including incrementing attemptCount and continuing the loop, is consistent and does not lead to logic errors or infinite loops.
  • 4489-4502: Handling WebDriverTimeoutException with specific logging and decision-making based on the current window handle is appropriate. Again, pay attention to the incrementation of attemptCount to ensure it's handled correctly across all exception types.
  • 4504-4518: The handling of WebDriverException with specific checks for PreviousRunStopped and the message content is a good example of detailed error handling. However, ensure that the logic for incrementing attemptCount and potentially closing the driver is clear and does not introduce unintended behavior.
  • 6699-6746: The refactoring of CheckifPageLoaded to use WebDriverWait is a significant improvement in reliability and performance over the previous loop-based method. Keeping the old method commented out for future reference, as per the user's request, is acknowledged. However, ensure that this approach aligns with your team's code maintenance and documentation practices.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 3

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 695c509 and c61e599.
Files selected for processing (1)
  • Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs (4 hunks)
Additional comments: 4
Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs (4)
  • 4409-4410: The introduction of maxAttempts and attemptCount variables is a good practice for managing retries in a loop. This approach allows for a clear and adjustable retry mechanism.
  • 4412-4417: The loop structure for retrying based on attemptCount and maxAttempts is correctly implemented. However, ensure that the catch blocks inside this loop correctly manage attemptCount to prevent multiple increments per iteration.
  • 6699-6734: The commented-out CheckifPageLoaded method is retained for future reference or use, as per the user's request. While it's typically recommended to use version control systems for maintaining historical code, the decision to keep it within the source files for convenience is respected.
  • 6735-6746: The refactoring of the CheckifPageLoaded method to use WebDriverWait is a significant improvement for reliability and performance. This change aligns with best practices for Selenium WebDriver, ensuring that the page load is properly waited for before proceeding.

Comment on lines +4447 to +4455
else if (count > 0)
{
return true;
}
}
else
{
if (exceptioncount < 5)

if(attemptCount < maxAttempts)
{
exceptioncount++;
return (IsRunning());
attemptCount++;
continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The logic to return true if count > 0 is straightforward and effective. However, the incrementation of attemptCount and the continuation of the loop should be carefully managed to ensure that it only happens once per iteration, as previously discussed. Consider refactoring this logic to avoid duplication and potential errors.

- if(attemptCount < maxAttempts)
- {
-     attemptCount++;
-     continue;
- }
+ // Ensure attemptCount is incremented once per iteration outside of catch blocks.

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
else if (count > 0)
{
return true;
}
}
else
{
if (exceptioncount < 5)
if(attemptCount < maxAttempts)
{
exceptioncount++;
return (IsRunning());
attemptCount++;
continue;
else if (count > 0)
{
return true;
}
// Ensure attemptCount is incremented once per iteration outside of catch blocks.

Comment on lines +4463 to +4467

if (count == 0)
{
return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The check for count == 0 to return false is redundant given the earlier check in the method. This could be simplified or removed to improve code readability and efficiency.

- if (count == 0)
- {
-     return false;
- }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if (count == 0)
{
return false;
}

Comment on lines +4522 to +4531
catch (Exception ex2)
{
return true;
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex2);
if (ex2.Message.ToString().ToUpper().Contains("DIALOG"))
{
return true;
}

CloseDriver();
break;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Catching a generic Exception and making decisions based on the message content is a risky approach due to potential localization issues or changes in the exception messages. Consider more robust ways to determine the nature of the exception.

- if (ex2.Message.ToString().ToUpper().Contains("DIALOG"))
+ // Consider using specific exception types or other indicators rather than relying on message content.

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
catch (Exception ex2)
{
return true;
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex2);
if (ex2.Message.ToString().ToUpper().Contains("DIALOG"))
{
return true;
}
CloseDriver();
break;
catch (Exception ex2)
{
Reporter.ToLog(eLogLevel.DEBUG, "Exception occurred when we are checking IsRunning", ex2);
// Consider using specific exception types or other indicators rather than relying on message content.
CloseDriver();
break;

@Maheshkale447
Maheshkale447 merged commit 250bc0d into master Jan 30, 2024
@Maheshkale447
Maheshkale447 deleted the Enhancement/RunsetOptimization branch January 30, 2024 10:24
@coderabbitai coderabbitai Bot mentioned this pull request Jan 29, 2025
15 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Jul 16, 2025
15 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants