Skip to content

feat/CUS--1469-Added utils to support bracket conditons - #362

Merged
akhil-testsigma merged 1 commit into
devfrom
feat/CUS-11469-Added-utils-to-support-bracket-conditons
Mar 24, 2026
Merged

akhil-testsigma merged 1 commit into
devfrom
feat/CUS-11469-Added-utils-to-support-bracket-conditons

Conversation

@akhil-testsigma

@akhil-testsigma akhil-testsigma commented Mar 23, 2026 •

Copy link
Copy Markdown
Contributor

Publish this addon as PUBLIC (EU Region)

Addon Name: If Condition Evaluator
Jarvis Link: https://jarvis-eu.testsigma.com/ui/tenants/66076/addons
Jira : https://testsigma.atlassian.net/browse/CUS-11469
Added utils to support bracket conditons

Summary by CodeRabbit

  • New Features

    • Added support for CONTAINS (A OR B) and CONTAINS (A AND B) syntax in conditional expressions across all platforms (Android, iOS, mobile web, REST API, web, and Windows).
  • Chores

    • Updated project version to 1.0.1
    • Added runtime dependency on Nashorn JavaScript engine for improved condition evaluation

@coderabbitai

coderabbitai Bot commented Mar 23, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Extracted condition evaluation logic from six platform-specific IfConditionEvaluatorAction classes into a new shared ConditionEvaluatorUtils utility class. Updated pom.xml with Nashorn dependency for JavaScript engine fallback. All action classes now delegate conversion and evaluation to the centralized utility.

Changes

Cohort / File(s) Summary
Dependency & Utility
pom.xml, src/main/java/com/testsigma/addons/utils/ConditionEvaluatorUtils.java
Added Nashorn runtime dependency (version 15.4). Created new utility class with static methods for converting custom condition syntax to JavaScript and evaluating expressions via ScriptEngine with fallback strategies and type coercion.
Platform Action Refactors
src/main/java/com/testsigma/addons/.../IfConditionEvaluatorAction.java (android, ios, web, mobileweb, restapi, windows)
Removed inline evaluateCondition(String) implementations from all six platform classes. Delegated to ConditionEvaluatorUtils.toJavaScriptExpression() and ConditionEvaluatorUtils.evaluateCondition(). Updated @Action annotations to document CONTAINS (A OR B) and CONTAINS (A AND B) expansion behavior. Removed unused imports.
Configuration
src/main/resources/testsigma-sdk.properties
Updated testsigma-sdk.api.key property value.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • vigneshtestsigma
  • Ganesh-Testsigma

Poem

🐰 With nimble paws, I've hopped along,

Extracting logic, where it's strong,

Six paths now share one rabbit's way,

ConditionEvaluatorUtils saves the day! 🌟

No more duplication in the code,

Shared utility eases every load! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title mentions 'bracket conditons' but the actual changes focus on extracting condition evaluation logic into a utility class (ConditionEvaluatorUtils) and refactoring multiple IfConditionEvaluatorAction classes across different platforms to use this utility. Consider revising the title to be more specific about the main change, such as 'Refactor condition evaluation into shared utility class' or 'Extract ConditionEvaluatorUtils for centralized condition evaluation'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/CUS-11469-Added-utils-to-support-bracket-conditons

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@if_condition_evaluator/src/main/java/com/testsigma/addons/utils/ConditionEvaluatorUtils.java`:
- Around line 184-187: allPartsValid currently rejects single-character tokens
because it returns false when p.trim().length() < 2; change the validation in
allPartsValid(List<String> parts) to only reject empty/blank tokens (e.g., use
p.trim().isEmpty() or p.trim().length() == 0) so single-character terms like "A"
or "B" are accepted and the expression expansion (e.g., "X CONTAINS (A OR B)")
works correctly; update the loop that iterates over parts and replace the length
check with an emptiness check on p.trim().
- Around line 145-170: expandInner currently expands grouped terms assuming
positive CONTAINS; when notPart indicates a NOT (i.e., handling "NOT CONTAINS"),
the boolean join must be inverted per De Morgan (X NOT CONTAINS (A OR B) -> X
NOT CONTAINS A AND X NOT CONTAINS B, and X NOT CONTAINS (A AND B) -> X NOT
CONTAINS A OR X NOT CONTAINS B) rather than leaving the join as-is. Update
expandInner (and the logic that sets op/uses expandTerm) to detect notPart (the
NOT case) and flip the joiners when building the expansion for splitByTopLevel
results: for orTerms with a NOT use " AND " between expanded terms; for andTerms
with a NOT use " OR ". Preserve the existing parenthesis-wrapping behavior
around terms that contain the opposite join and keep the allPartsValid checks;
alternatively, if you prefer rejecting grouped NOT CONTAINS, return null early
when notPart is present and there are grouped terms.

In
`@if_condition_evaluator/src/main/java/com/testsigma/addons/web/IfConditionEvaluatorAction.java`:
- Around line 25-29: Add explicit null checks for testData and
testData.getValue() before using them in IfConditionEvaluatorAction (the try
block where condition is assigned and logger.info("Original Condition: " +
condition) is executed); if testData or testData.getValue() is null, log a clear
error via logger.error (including which variable is null) and return or throw a
meaningful exception/result rather than letting a NullPointerException be caught
with an unhelpful message—update the null guard near the existing condition
assignment to validate testData and its value and provide a descriptive error
path.

In `@if_condition_evaluator/src/main/resources/testsigma-sdk.properties`:
- Line 1: The file contains a committed live credential under the property key
testsigma-sdk.api.key; remove the secret value from testsigma-sdk.properties,
replace it with a placeholder (e.g.
testsigma-sdk.api.key=${TESTSIGMA_SDK_API_KEY}) and update the code that reads
this property to load from an environment variable or your secret manager (env
var TESTSIGMA_SDK_API_KEY or secret service) instead of the repo; revoke/rotate
the exposed key immediately, purge it from history (or run git-filter-repo/BFG)
and ensure CI/build injects the secret at release time via your secrets service
or pipeline variables.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 28c7a7ff-1925-492a-b0e1-8a2eb9f91173

📥 Commits

Reviewing files that changed from the base of the PR and between 360c124 and cd46924.

📒 Files selected for processing (9)
  • if_condition_evaluator/pom.xml
  • if_condition_evaluator/src/main/java/com/testsigma/addons/android/IfConditionEvaluatorAction.java
  • if_condition_evaluator/src/main/java/com/testsigma/addons/ios/IfConditionEvaluatorAction.java
  • if_condition_evaluator/src/main/java/com/testsigma/addons/mobileweb/IfConditionEvaluatorAction.java
  • if_condition_evaluator/src/main/java/com/testsigma/addons/restapi/IfConditionEvaluatorAction.java
  • if_condition_evaluator/src/main/java/com/testsigma/addons/utils/ConditionEvaluatorUtils.java
  • if_condition_evaluator/src/main/java/com/testsigma/addons/web/IfConditionEvaluatorAction.java
  • if_condition_evaluator/src/main/java/com/testsigma/addons/windows/IfConditionEvaluatorAction.java
  • if_condition_evaluator/src/main/resources/testsigma-sdk.properties

Comment on lines +145 to +170
private static String expandInner(String left, String notPart, String inner) {
inner = stripOuterParens(inner);
String op = "CONTAINS";
// Keep grouped CONTAINS terms together: "High OR Risk AND Override" -> (High OR Risk) AND Override
List<String> andTerms = splitByTopLevel(inner, "AND");
if (andTerms.size() > 1) {
if (!allPartsValid(andTerms)) return null;
StringBuilder expansion = new StringBuilder();
for (int i = 0; i < andTerms.size(); i++) {
if (i > 0) expansion.append(" AND ");
String term = expandTerm(left, notPart, op, andTerms.get(i));
expansion.append(term.contains(" OR ") ? "(" + term + ")" : term);
}
return expansion.toString();
}
List<String> orTerms = splitByTopLevel(inner, "OR");
if (orTerms.size() > 1) {
if (!allPartsValid(orTerms)) return null;
StringBuilder expansion = new StringBuilder();
for (int i = 0; i < orTerms.size(); i++) {
if (i > 0) expansion.append(" OR ");
String term = expandTerm(left, notPart, op, orTerms.get(i));
expansion.append(term.contains(" AND ") ? "(" + term + ")" : term);
}
return expansion.toString();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Grouped NOT CONTAINS uses the wrong boolean join.

X NOT CONTAINS (AB OR CD) currently expands to X NOT CONTAINS AB OR X NOT CONTAINS CD, which becomes true as soon as either token is absent. If grouped NOT CONTAINS is supported, this needs De Morgan's law; otherwise it should be rejected instead of silently changing the meaning.

🐛 Proposed fix
   private static String expandInner(String left, String notPart, String inner) {
     inner = stripOuterParens(inner);
     String op = "CONTAINS";
+    boolean negated = !notPart.isEmpty();
     // Keep grouped CONTAINS terms together: "High OR Risk AND Override" -> (High OR Risk) AND Override
     List<String> andTerms = splitByTopLevel(inner, "AND");
     if (andTerms.size() > 1) {
       if (!allPartsValid(andTerms)) return null;
       StringBuilder expansion = new StringBuilder();
       for (int i = 0; i < andTerms.size(); i++) {
-        if (i > 0) expansion.append(" AND ");
+        if (i > 0) expansion.append(negated ? " OR " : " AND ");
         String term = expandTerm(left, notPart, op, andTerms.get(i));
         expansion.append(term.contains(" OR ") ? "(" + term + ")" : term);
       }
       return expansion.toString();
     }
     List<String> orTerms = splitByTopLevel(inner, "OR");
     if (orTerms.size() > 1) {
       if (!allPartsValid(orTerms)) return null;
       StringBuilder expansion = new StringBuilder();
       for (int i = 0; i < orTerms.size(); i++) {
-        if (i > 0) expansion.append(" OR ");
+        if (i > 0) expansion.append(negated ? " AND " : " OR ");
         String term = expandTerm(left, notPart, op, orTerms.get(i));
         expansion.append(term.contains(" AND ") ? "(" + term + ")" : term);
       }
       return expansion.toString();
     }
📝 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. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static String expandInner(String left, String notPart, String inner) {
inner = stripOuterParens(inner);
String op = "CONTAINS";
// Keep grouped CONTAINS terms together: "High OR Risk AND Override" -> (High OR Risk) AND Override
List<String> andTerms = splitByTopLevel(inner, "AND");
if (andTerms.size() > 1) {
if (!allPartsValid(andTerms)) return null;
StringBuilder expansion = new StringBuilder();
for (int i = 0; i < andTerms.size(); i++) {
if (i > 0) expansion.append(" AND ");
String term = expandTerm(left, notPart, op, andTerms.get(i));
expansion.append(term.contains(" OR ") ? "(" + term + ")" : term);
}
return expansion.toString();
}
List<String> orTerms = splitByTopLevel(inner, "OR");
if (orTerms.size() > 1) {
if (!allPartsValid(orTerms)) return null;
StringBuilder expansion = new StringBuilder();
for (int i = 0; i < orTerms.size(); i++) {
if (i > 0) expansion.append(" OR ");
String term = expandTerm(left, notPart, op, orTerms.get(i));
expansion.append(term.contains(" AND ") ? "(" + term + ")" : term);
}
return expansion.toString();
}
private static String expandInner(String left, String notPart, String inner) {
inner = stripOuterParens(inner);
String op = "CONTAINS";
boolean negated = !notPart.isEmpty();
// Keep grouped CONTAINS terms together: "High OR Risk AND Override" -> (High OR Risk) AND Override
List<String> andTerms = splitByTopLevel(inner, "AND");
if (andTerms.size() > 1) {
if (!allPartsValid(andTerms)) return null;
StringBuilder expansion = new StringBuilder();
for (int i = 0; i < andTerms.size(); i++) {
if (i > 0) expansion.append(negated ? " OR " : " AND ");
String term = expandTerm(left, notPart, op, andTerms.get(i));
expansion.append(term.contains(" OR ") ? "(" + term + ")" : term);
}
return expansion.toString();
}
List<String> orTerms = splitByTopLevel(inner, "OR");
if (orTerms.size() > 1) {
if (!allPartsValid(orTerms)) return null;
StringBuilder expansion = new StringBuilder();
for (int i = 0; i < orTerms.size(); i++) {
if (i > 0) expansion.append(negated ? " AND " : " OR ");
String term = expandTerm(left, notPart, op, orTerms.get(i));
expansion.append(term.contains(" AND ") ? "(" + term + ")" : term);
}
return expansion.toString();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@if_condition_evaluator/src/main/java/com/testsigma/addons/utils/ConditionEvaluatorUtils.java`
around lines 145 - 170, expandInner currently expands grouped terms assuming
positive CONTAINS; when notPart indicates a NOT (i.e., handling "NOT CONTAINS"),
the boolean join must be inverted per De Morgan (X NOT CONTAINS (A OR B) -> X
NOT CONTAINS A AND X NOT CONTAINS B, and X NOT CONTAINS (A AND B) -> X NOT
CONTAINS A OR X NOT CONTAINS B) rather than leaving the join as-is. Update
expandInner (and the logic that sets op/uses expandTerm) to detect notPart (the
NOT case) and flip the joiners when building the expansion for splitByTopLevel
results: for orTerms with a NOT use " AND " between expanded terms; for andTerms
with a NOT use " OR ". Preserve the existing parenthesis-wrapping behavior
around terms that contain the opposite join and keep the allPartsValid checks;
alternatively, if you prefer rejecting grouped NOT CONTAINS, return null early
when notPart is present and there are grouped terms.

Comment on lines +184 to +187
private static boolean allPartsValid(List<String> parts) {
for (String p : parts) {
if (p.trim().length() < 2) return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Single-character bracket terms are rejected right now.

allPartsValid() returns false for tokens like A or B, so the documented case X CONTAINS (A OR B) never expands. It falls through to a literal search for "A OR B" instead of evaluating two CONTAINS clauses.

🐛 Proposed fix
   private static boolean allPartsValid(List<String> parts) {
     for (String p : parts) {
-      if (p.trim().length() < 2) return false;
+      if (p.trim().isEmpty()) return false;
     }
     return true;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@if_condition_evaluator/src/main/java/com/testsigma/addons/utils/ConditionEvaluatorUtils.java`
around lines 184 - 187, allPartsValid currently rejects single-character tokens
because it returns false when p.trim().length() < 2; change the validation in
allPartsValid(List<String> parts) to only reject empty/blank tokens (e.g., use
p.trim().isEmpty() or p.trim().length() == 0) so single-character terms like "A"
or "B" are accepted and the expression expansion (e.g., "X CONTAINS (A OR B)")
works correctly; update the loop that iterates over parts and replace the length
check with an emptiness check on p.trim().

Comment on lines 25 to 29
try {

if (testData == null || testData.getValue() == null) {
throw new Exception("Condition cannot be empty");
}

String condition = testData.getValue().toString().trim();
logger.info("Original Condition: " + condition);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add null check for testData and its value to provide a clear error message.

The prior null/empty guard was removed. If testData is null or testData.getValue() returns null, an NPE will be thrown and caught, but the error message will be cryptic (e.g., "Exception Occurred: null") rather than actionable.

🛡️ Proposed fix to add validation
     try {
 
+      if (testData == null || testData.getValue() == null) {
+        result = com.testsigma.sdk.Result.FAILED;
+        setErrorMessage("Condition input is required but was not provided");
+        return result;
+      }
 
       String condition = testData.getValue().toString().trim();
+      if (condition.isEmpty()) {
+        result = com.testsigma.sdk.Result.FAILED;
+        setErrorMessage("Condition cannot be empty");
+        return result;
+      }
       logger.info("Original Condition: " + condition);
📝 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. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
if (testData == null || testData.getValue() == null) {
throw new Exception("Condition cannot be empty");
}
String condition = testData.getValue().toString().trim();
logger.info("Original Condition: " + condition);
try {
if (testData == null || testData.getValue() == null) {
result = com.testsigma.sdk.Result.FAILED;
setErrorMessage("Condition input is required but was not provided");
return result;
}
String condition = testData.getValue().toString().trim();
if (condition.isEmpty()) {
result = com.testsigma.sdk.Result.FAILED;
setErrorMessage("Condition cannot be empty");
return result;
}
logger.info("Original Condition: " + condition);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@if_condition_evaluator/src/main/java/com/testsigma/addons/web/IfConditionEvaluatorAction.java`
around lines 25 - 29, Add explicit null checks for testData and
testData.getValue() before using them in IfConditionEvaluatorAction (the try
block where condition is assigned and logger.info("Original Condition: " +
condition) is executed); if testData or testData.getValue() is null, log a clear
error via logger.error (including which variable is null) and return or throw a
meaningful exception/result rather than letting a NullPointerException be caught
with an unhelpful message—update the null guard near the existing condition
assignment to validate testData and its value and provide a descriptive error
path.

@@ -1 +1 @@
testsigma-sdk.api.key=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyMjMyMmM2Ni04NWYzLWIyN2UtN2FiOS0zM2U2M2Q4OWM1MGIiLCJ1bmlxdWVJZCI6IjU5OTYiLCJpZGVudGl0eUFjY291bnRVVUlkIjoiNDMifQ.GoblsmuHFdMOKiEL9HBx7J2Q74MnkMYjdKzyDFXA4YamryBO_5Nr_ajHygSf6Cc4G2X4Y36jtgcJVHhiYOBf7g No newline at end of file
testsigma-sdk.api.key=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyNTgxZDNlYy02Zjk2LTg3NDktZThmNS1hYjEwMWIwZDA1NTQiLCJ1bmlxdWVJZCI6IjM0NzYiLCJpZGVudGl0eUFjY291bnRVVUlkIjoiODZlMGQ1ODUtZTVlYi05NmIxLTAyZDktOTRkODM3N2RiMzlmIn0.--JD8x3hpn4abLzuvVhdDg0eprohDvweUFkSNSAphQT59oTFDZswlg9ABTxRZHGjfbDXUHuFtDLfj_Pu-sluhw No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove the committed API key immediately.

This is a live credential in a tracked resource file, so it will be exposed through the repo, CI artifacts, and the published addon jar. Please revoke/rotate it and inject the key from secret storage at build/release time instead of packaging it here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@if_condition_evaluator/src/main/resources/testsigma-sdk.properties` at line
1, The file contains a committed live credential under the property key
testsigma-sdk.api.key; remove the secret value from testsigma-sdk.properties,
replace it with a placeholder (e.g.
testsigma-sdk.api.key=${TESTSIGMA_SDK_API_KEY}) and update the code that reads
this property to load from an environment variable or your secret manager (env
var TESTSIGMA_SDK_API_KEY or secret service) instead of the repo; revoke/rotate
the exposed key immediately, purge it from history (or run git-filter-repo/BFG)
and ensure CI/build injects the secret at release time via your secrets service
or pipeline variables.

@akhil-testsigma
akhil-testsigma merged commit f638428 into dev Mar 24, 2026
2 checks passed
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