Skip to content

Conversation

@harshtandiya
Copy link
Collaborator

@harshtandiya harshtandiya commented Jan 14, 2026

Summary by CodeRabbit

  • New Features

    • Conditional logic for fields: show/hide fields and make answers required based on other responses.
    • New conditional-logic editor in the field properties sidebar to create and edit rules.
    • Two new field types: Checkbox and Rating.
    • Forms and submission validation now respect dynamic visibility and requiredness.
  • Bug Fixes

    • Improved click-away handling in the form builder to avoid closing when interacting with dropdowns/popovers.

✏️ Tip: You can customize this high-level summary in your review settings.

* Added 'display_depends_on' field to the form field JSON and Python definitions.
* Updated field order to include 'reqd' and 'fieldtype' for better organization.
* Moved FieldPropertiesForm.vue to the field-editor directory for better organization.
* Updated import paths in FieldEditorSidebar.vue to reflect the new location.
@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

📝 Walkthrough

Walkthrough

Adds conditional field logic: a new conditional_logic field in backend schema and Python typing, new frontend types and utilities for parsing/evaluating conditional rules, UI components to edit rules, and runtime changes so renderer and validator respect conditional visibility and requiredness.

Changes

Cohort / File(s) Summary
Backend: form field schema & types
forms_pro/forms_pro/doctype/form_field/form_field.json, forms_pro/forms_pro/doctype/form_field/form_field.py
Added conditional_logic (Code) to DocType JSON; updated field_order and fieldtype options; added `conditional_logic: DF.Code
Field editor import path
frontend/src/components/FieldEditorSidebar.vue
Updated import path to the relocated FieldPropertiesForm component.
Field editor components (moved + new)
frontend/src/components/builder/FieldPropertiesForm.vue, frontend/src/components/builder/field-editor/FieldPropertiesForm.vue, frontend/src/components/builder/field-editor/ConditionalLogicSection.vue
Removed old top-level FieldPropertiesForm, added new FieldPropertiesForm under builder/field-editor/ and a ConditionalLogicSection.vue to create/edit/sync conditional_logic with the edit form store.
Form builder interaction / click-away handling
frontend/src/components/FormBuilderContent.vue
Added isDropdownOrPopover() helper; improved click-away logic to ignore dropdowns/portals and moved fieldContentRef to outer wrapper.
Form rendering & validation
frontend/src/components/submission/FormRenderer.vue, frontend/src/stores/submissionForm.ts
Renderer now computes visibleFields via conditional logic; fields passed to FieldRenderer have dynamic reqd using shouldFieldBeRequired. Validation only checks visible fields and uses conditional-required checks.
Conditional types
frontend/src/types/conditional-render.types.ts
New exported enums/types: ConditionalOperators, LogicOperators, Actions, Condition, and ConditionalLogic.
Form field type updates
frontend/src/types/formfield.ts
Added FormFieldTypes enum; changed FormField to use that enum for fieldtype and added optional conditional_logic?: string.
Conditional evaluation utilities
frontend/src/utils/conditionals.ts
New comprehensive module providing parseConditionalLogic, shouldFieldBeVisible, shouldFieldBeRequired, getVisibleFields, plus helpers to evaluate many operators and normalize values.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Editor as Field Properties Editor
    participant Conditional as ConditionalLogicSection
    participant Store as Edit Form Store
    participant Renderer as FormRenderer
    participant Utils as conditionals.ts

    User->>Editor: Select field to edit
    Editor->>Store: read selectedField
    Editor->>Conditional: render conditional UI

    User->>Conditional: add/modify conditions
    Conditional->>Store: update selectedField.conditional_logic
    Store-->>Conditional: emit updated selectedField

    User->>Renderer: open form / change values
    Renderer->>Utils: getVisibleFields(formFields, formValues)
    Utils->>Utils: evaluate conditions
    Utils-->>Renderer: visibleFields
    Renderer->>Utils: shouldFieldBeRequired(field, formValues, allFields)
    Utils-->>Renderer: required boolean
    Renderer->>Renderer: render & validate visible fields
Loading
sequenceDiagram
    participant Conditional as ConditionalLogicSection
    participant Store as Edit Form Store
    participant Local as Component State

    Conditional->>Store: watch selectedField.conditional_logic
    Store-->>Conditional: emit changes
    Conditional->>Local: parse into local state

    Local->>Conditional: user edits conditions
    Conditional->>Store: stringify and set selectedField.conditional_logic
    Conditional->>Local: reset update guard (microtask)
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • feat: manage form pages #8 — Related refactor of FieldPropertiesForm/field-editor UI and import path changes touching the same components/files.

Poem

🐰
I nibble rules where fields may hide,
I hop through logic, swing open gates wide.
Show, hide, require — a carrot-coded song,
Forms hum softly as conditions belong.
Hop on, tweak a rule, watch the fields abide.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: conditional logic v1' accurately describes the main changes across the PR, which introduce conditional logic features for form field rendering and visibility.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
forms_pro/forms_pro/doctype/form_field/form_field.py (1)

62-63: LGTM!

Correctly maps both "Switch" and "Checkbox" to Frappe's "Check" fieldtype.

💅 Optional: Use `in` for slightly cleaner comparison
-        elif self.fieldtype == "Switch" or self.fieldtype == "Checkbox":
+        elif self.fieldtype in ("Switch", "Checkbox"):
             _fieldtype = "Check"
frontend/src/utils/conditionals.ts (1)

12-25: Consider adding runtime validation for parsed JSON.

JSON.parse with type assertion doesn't validate the structure at runtime. Malformed JSON that parses successfully but doesn't match ConditionalLogic structure could cause runtime errors in evaluateConditions.

♻️ Proposed fix with basic validation
 export function parseConditionalLogic(
   conditionalLogic: string | undefined | null
 ): ConditionalLogic | null {
   if (!conditionalLogic || conditionalLogic.trim() === "") {
     return null;
   }

   try {
-    return JSON.parse(conditionalLogic) as ConditionalLogic;
+    const parsed = JSON.parse(conditionalLogic);
+    // Basic structure validation
+    if (
+      typeof parsed !== "object" ||
+      !Array.isArray(parsed.conditions) ||
+      typeof parsed.action !== "string"
+    ) {
+      console.error("Invalid conditional_logic structure:", parsed);
+      return null;
+    }
+    return parsed as ConditionalLogic;
   } catch (e) {
     console.error("Failed to parse conditional_logic:", e);
     return null;
   }
 }

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2d9c01 and ba09ba3.

📒 Files selected for processing (5)
  • forms_pro/forms_pro/doctype/form_field/form_field.json
  • forms_pro/forms_pro/doctype/form_field/form_field.py
  • frontend/src/components/builder/field-editor/ConditionalLogicSection.vue
  • frontend/src/types/conditional-render.types.ts
  • frontend/src/utils/conditionals.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/builder/field-editor/ConditionalLogicSection.vue
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/utils/conditionals.ts (2)
frontend/src/types/conditional-render.types.ts (2)
  • ConditionalLogic (36-40)
  • Condition (24-28)
frontend/src/types/formfield.ts (1)
  • FormField (20-30)
🪛 Ruff (0.14.11)
forms_pro/forms_pro/doctype/form_field/form_field.py

36-36: Undefined name Checkbox

(F821)


37-37: Undefined name Rating

(F821)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Server
🔇 Additional comments (10)
forms_pro/forms_pro/doctype/form_field/form_field.py (2)

17-17: LGTM!

The conditional_logic field is correctly typed as DF.Code | None, consistent with the JSON schema definition mentioned in the summary.


36-37: LGTM! Static analysis warnings are false positives.

The Ruff F821 warnings for "Checkbox" and "Rating" are incorrect—these are quoted string literals inside DF.Literal[...], not undefined variable names. This is valid Python typing syntax.

forms_pro/forms_pro/doctype/form_field/form_field.json (2)

67-71: LGTM - New conditional_logic field added appropriately.

Using Code fieldtype is suitable for storing the JSON-serialized conditional logic structure. This aligns well with the frontend ConditionalLogic type that will be parsed from this field.


39-39: LGTM - Fieldtype options expanded.

Good addition of Checkbox and Rating to the available field types. The Checkbox addition resolves the previous inconsistency where the frontend handled Checkbox but the backend didn't define it.

frontend/src/types/conditional-render.types.ts (1)

1-17: LGTM - Well-structured conditional logic types.

The operators cover a comprehensive set of comparison operations, and the Actions enum provides clear visibility/requirement control. The type definitions are clean and provide good type safety for the conditional logic feature.

Also applies to: 30-40

frontend/src/utils/conditionals.ts (5)

122-126: Verify intended behavior: negated operators return true for null values.

The negated operators (DoesNotContain, DoesNotStartWith, DoesNotEndWith) return true when the field value is null. This means conditions like "Field X does not contain 'abc'" will evaluate to true for empty/unset fields.

This is logically consistent but may cause unexpected rule triggers on unfilled fields. Confirm this matches the intended UX.

Also applies to: 170-174, 182-186


247-275: Confirm rule precedence: ShowField takes priority over HideField.

When both ShowField and HideField conditions are met simultaneously, ShowField wins (lines 263-266 return true before checking hasHideRule). This implicit precedence could cause confusion if a user defines conflicting rules.

Consider adding a comment documenting this precedence, or warning in the console when conflicting rules are detected.


280-313: LGTM - Required status logic is sound.

The additive approach (base required OR conditional required) is appropriate. A field marked as required in the schema will always be required, and conditional logic can only add the requirement, not remove it.


225-245: LGTM - Visibility logic correctly refactored.

The previous early-return issue has been addressed. The function now properly scans all fields for targeting rules before determining visibility.


318-325: LGTM - Clean helper function.

Simple and effective filter using shouldFieldBeVisible.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
forms_pro/forms_pro/doctype/form_field/form_field.py (1)

65-73: Add display_depends_on to the to_frappe_field property output.

The display_depends_on field is defined in the type hints but is missing from the to_frappe_field property (lines 65-73). Since this property is used in form.py to serialize field data when synchronizing with DocType, the display_depends_on value will not be available for field updates or creation. Include it in the returned dictionary:

Updated to_frappe_field method
return {
    "fieldname": self.fieldname,
    "fieldtype": _fieldtype,
    "label": self.label,
    "reqd": self.reqd,
    "options": self.options,
    "description": self.description,
    "default": self.default,
    "display_depends_on": self.display_depends_on,
}
🤖 Fix all issues with AI agents
In `@frontend/src/components/builder/field-editor/ConditionalLogicSection.vue`:
- Around line 1-12: The template uses the Button component but it isn't
imported; update the top import from "frappe-ui" to include Button alongside
FormControl and Popover (so import Button, FormControl, Popover from
"frappe-ui") so the Button symbol used in the ConditionalLogicSection component
is defined.
- Around line 196-215: The action and target_field selectors are rendered inside
the v-for loop over conditions causing duplicates; move the FormControl blocks
bound to conditionalLogic.action and conditionalLogic.target_field (the selects
using actionOptions and availableFieldOptions) out of the loop that iterates
conditions so they render once per conditionalLogic instead of once per
condition; locate the v-for that iterates conditions and remove the "Then" div
plus the two FormControl elements from inside it and place them after (or
before) the conditions loop so the controls still bind to
conditionalLogic.action and conditionalLogic.target_field but are not repeated.

In `@frontend/src/types/conditional-render.types.ts`:
- Around line 21-24: The LogicOperators enum is declared but unused; add a
logic_operator field to the ConditionalLogic type so callers can express how the
conditions array should be combined (AND vs OR). Update the ConditionalLogic
definition to include a new optional or required property (e.g., logic_operator:
LogicOperators) and ensure any construction/validation of ConditionalLogic
(places that build or consume ConditionalLogic) are updated to set or handle
this field; use the existing LogicOperators enum values (And, Or) as the allowed
options.
- Around line 26-30: The Condition type's value currently only allows a single
primitive (string | number | boolean) but ConditionalOperators includes range
operators IsBetween/IsNotBetween which need two values; update the Condition
definition (and any consumers) so value can represent ranges—either change value
to accept a tuple/array like [string|number, string|number] or union it with a
{minValue: string|number; maxValue: string|number} shape, and ensure code
handling Condition (e.g., evaluation/validation logic) checks the operator and
reads the tuple or min/max fields for IsBetween/IsNotBetween while preserving
existing single-value behavior for other operators.

In `@frontend/src/types/formfield.ts`:
- Around line 1-18: The FormFieldTypes enum includes unsupported values
'Checkbox' and 'Rating' that don't exist in the backend form_field.json; remove
'Checkbox' and 'Rating' from the export in the FormFieldTypes enum so the
front-end only exposes backend-supported types (Attach, Data, Number, Email,
Date, Date Time, Date Range, Time Picker, Password, Select, Switch, Textarea,
Text Editor, Link), and update any code that references FormFieldTypes.Checkbox
or FormFieldTypes.Rating (forms, validators, tests) to use a supported type or a
clear fallback/mapping to avoid sending invalid field types to the backend.

In `@frontend/src/utils/conditionals.ts`:
- Around line 256-311: The early return that checks the current field's own
display_depends_on (the block that returns true when field.display_depends_on is
missing or empty) prevents any targeting rules on other fields from being
considered; remove that initial check so visibility is determined solely by
scanning allFields for ConditionalLogic entries targeting this field (keep the
existing allFields scan, the targetingRules length check, and the rest of the
evaluation logic), and update the comment above shouldFieldBeVisible to reflect
that visibility is driven by rules on other fields rather than a property on the
field itself.
🧹 Nitpick comments (9)
frontend/src/components/FormBuilderContent.vue (1)

81-86: Remove duplicate selector.

Lines 83 and 85 are functionally identical selectors (attribute order doesn't matter in CSS selectors). Remove one to avoid redundancy.

Suggested fix
     const hasOpenDropdown = !!(
         document.querySelector('[role="listbox"]:not([hidden]):not([style*="display: none"])') ||
         document.querySelector('[role="combobox"][aria-expanded="true"]') ||
-        document.querySelector('[data-headlessui-state="open"]') ||
-        document.querySelector('[aria-expanded="true"][role="combobox"]')
+        document.querySelector('[data-headlessui-state="open"]')
     );
frontend/src/components/submission/FormRenderer.vue (1)

38-51: Consider memoizing required status computation.

Calling shouldFieldBeRequired inline in the template means it executes on every render for each field. For typical form sizes this is acceptable, but for forms with many fields and complex conditional rules, consider computing required statuses in a memoized computed property alongside visibleFields.

Optional optimization
 const visibleFields = computed(() => {
     const fields = submissionFormStore.formResource.data?.fields || [];
     return fields.filter((field: FormField) =>
         shouldFieldBeVisible(field, submissionFormStore.fields, fields)
-    );
+    ).map((field: FormField) => ({
+        ...field,
+        reqd: shouldFieldBeRequired(field, submissionFormStore.fields, fields),
+    }));
 });

Then simplify the template:

             <FieldRenderer
                 :disabled="disabled"
                 v-model="submissionFormStore.fields[field.fieldname]"
-                :field="{
-                    ...field,
-                    reqd: shouldFieldBeRequired(
-                        field,
-                        submissionFormStore.fields,
-                        submissionFormStore.formResource.data?.fields || []
-                    ),
-                }"
+                :field="field"
                 :inEditMode="false"
             />
frontend/src/components/builder/field-editor/FieldPropertiesForm.vue (1)

91-95: ConditionalLogicSection ignores the v-model binding.

The ConditionalLogicSection component manages state internally via the store rather than through the v-model prop passed here. While this works because both components share the same store, this creates an inconsistency in the data flow pattern compared to other field properties.

Consider either:

  1. Making ConditionalLogicSection accept and use the v-model prop for consistency
  2. Or separating this component from the fieldProperties array since it uses a different data flow pattern
frontend/src/components/builder/field-editor/ConditionalLogicSection.vue (2)

70-78: Potential race condition with setTimeout for flag reset.

Using setTimeout(..., 0) to reset isUpdatingFromStore could cause race conditions if the store updates rapidly. Consider using queueMicrotask or Vue's nextTick for more predictable timing within Vue's reactivity cycle.

♻️ Proposed fix
+import { ref, watch, computed, nextTick } from "vue";
...
         } finally {
-            // Reset flag after a microtask to allow the other watcher to process
-            setTimeout(() => {
-                isUpdatingFromStore.value = false;
-            }, 0);
+            // Reset flag after Vue's next tick to allow the other watcher to process
+            nextTick(() => {
+                isUpdatingFromStore.value = false;
+            });
         }

80-94: Default target_field set to null may cause validation issues.

When adding a condition, target_field is initialized to null. If the user saves without selecting a target field, this could result in invalid conditional logic. Consider validating before save or providing a more explicit default.

frontend/src/utils/conditionals.ts (3)

38-51: Type comparison uses string literals instead of enum values.

field.fieldtype is now typed as FormFieldTypes enum, but the comparisons use string literals. While this works due to TypeScript's structural typing with string enums, using the enum values would be more type-safe and maintainable.

♻️ Proposed fix
+import { FormField, FormFieldTypes } from "@/types/formfield";
...
   // Handle boolean/switch fields
-  if (fieldType === "Switch" || fieldType === "Checkbox") {
+  if (fieldType === FormFieldTypes.Switch || fieldType === FormFieldTypes.Checkbox) {
     return Boolean(fieldValue);
   }

   // Handle number fields
-  if (fieldType === "Number") {
+  if (fieldType === FormFieldTypes.Number) {
     const num = Number(fieldValue);
     return isNaN(num) ? null : num;
   }

97-109: Redundant undefined check after normalizeValue.

normalizeValue never returns undefined (only null, boolean, number, or string), so checking for undefined on lines 101-102 and 108 is unnecessary.

♻️ Proposed fix
     case ConditionalOperators.IsEmpty:
       return (
-        normalizedFieldValue === null ||
-        normalizedFieldValue === "" ||
-        normalizedFieldValue === undefined
+        normalizedFieldValue === null ||
+        normalizedFieldValue === ""
       );

     case ConditionalOperators.IsNotEmpty:
       return (
         normalizedFieldValue !== null &&
-        normalizedFieldValue !== "" &&
-        normalizedFieldValue !== undefined
+        normalizedFieldValue !== ""
       );

188-218: Document expected format for IsBetween / IsNotBetween operators.

The "min,max" format expected for IsBetween and IsNotBetween operators is parsed inline but not documented in the Condition type. Consider adding a JSDoc comment or updating the type definition to clarify this requirement.

frontend/src/types/conditional-render.types.ts (1)

38-42: Clarify when target_field can be null.

If target_field represents the field affected by this conditional logic, when would it legitimately be null? If it's always required at runtime, consider making it non-nullable to avoid unnecessary null checks in consuming code. If there's a valid use case for null, a comment documenting it would be helpful.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34adb8f and 041d055.

📒 Files selected for processing (12)
  • forms_pro/forms_pro/doctype/form_field/form_field.json
  • forms_pro/forms_pro/doctype/form_field/form_field.py
  • frontend/src/components/FieldEditorSidebar.vue
  • frontend/src/components/FormBuilderContent.vue
  • frontend/src/components/builder/FieldPropertiesForm.vue
  • frontend/src/components/builder/field-editor/ConditionalLogicSection.vue
  • frontend/src/components/builder/field-editor/FieldPropertiesForm.vue
  • frontend/src/components/submission/FormRenderer.vue
  • frontend/src/stores/submissionForm.ts
  • frontend/src/types/conditional-render.types.ts
  • frontend/src/types/formfield.ts
  • frontend/src/utils/conditionals.ts
💤 Files with no reviewable changes (1)
  • frontend/src/components/builder/FieldPropertiesForm.vue
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/utils/conditionals.ts (1)
frontend/src/types/conditional-render.types.ts (2)
  • ConditionalLogic (38-42)
  • Condition (26-30)
frontend/src/stores/submissionForm.ts (1)
frontend/src/utils/conditionals.ts (2)
  • shouldFieldBeVisible (256-311)
  • shouldFieldBeRequired (316-349)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Server
🔇 Additional comments (9)
frontend/src/components/FormBuilderContent.vue (2)

17-63: Good defensive approach for portal-based dropdown detection.

The DOM traversal approach handles various UI library patterns well. This ensures dropdown interactions don't accidentally deselect the currently edited field.


99-108: LGTM!

The multi-condition check properly prevents field deselection during dropdown interactions, which is essential for the new conditional logic editing workflow.

frontend/src/components/FieldEditorSidebar.vue (1)

3-3: LGTM!

Import path updated to reflect the new field-editor folder structure for the conditional logic feature.

frontend/src/components/submission/FormRenderer.vue (1)

20-27: LGTM!

The computed property correctly filters fields based on conditional visibility and will reactively update when form values change.

frontend/src/stores/submissionForm.ts (1)

168-186: The integration of conditional logic in this function is correct, but the underlying utility design needs clarification.

The validateValues() function correctly uses shouldFieldBeVisible and shouldFieldBeRequired. However, there is a logical inconsistency in the shouldFieldBeVisible utility (line 262 of conditionals.ts): the early return comment suggests a field's own display_depends_on controls its visibility, but the actual implementation below searches for OTHER fields' rules that target this field. The early check at line 262–263 returns true for fields without display_depends_on, yet this comment contradicts the actual behavior where visibility is determined by targeting rules from other fields. This design pattern (storing visibility rules on the controlling field rather than the controlled field) should be verified as intentional and properly documented to avoid confusion.

frontend/src/components/builder/field-editor/FieldPropertiesForm.vue (1)

106-113: LGTM!

The conditional rendering with v-if and dynamic component iteration pattern is clean and correctly guards against null selectedField.

frontend/src/types/formfield.ts (1)

20-30: LGTM!

The FormField type is well-structured with appropriate optional properties. The addition of display_depends_on as an optional string aligns with the backend schema.

forms_pro/forms_pro/doctype/form_field/form_field.json (1)

67-71: LGTM!

The display_depends_on field is appropriately defined as a Code fieldtype, which is suitable for storing JSON-formatted conditional logic data.

frontend/src/utils/conditionals.ts (1)

12-25: LGTM!

The parseConditionalLogic and getVisibleFields functions are clean utilities with proper error handling for JSON parsing failures.

Also applies to: 354-361

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +1 to +12
<script setup lang="ts">
import { useEditForm } from "@/stores/editForm";
import { ref, watch, computed } from "vue";
import {
ConditionalLogic,
ConditionalOperators,
type Condition,
Actions,
} from "@/types/conditional-render.types";
import { EllipsisVertical, Trash, Workflow, Zap } from "lucide-vue-next";
import { FormControl, Popover } from "frappe-ui";
import type { FormField } from "@/types/formfield";
Copy link

@coderabbitai coderabbitai bot Jan 14, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing Button import.

The Button component is used in the template (lines 149, 161, 217, 224) but is not imported from frappe-ui.

🐛 Proposed fix
-import { FormControl, Popover } from "frappe-ui";
+import { FormControl, Popover, Button } from "frappe-ui";
📝 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
<script setup lang="ts">
import { useEditForm } from "@/stores/editForm";
import { ref, watch, computed } from "vue";
import {
ConditionalLogic,
ConditionalOperators,
type Condition,
Actions,
} from "@/types/conditional-render.types";
import { EllipsisVertical, Trash, Workflow, Zap } from "lucide-vue-next";
import { FormControl, Popover } from "frappe-ui";
import type { FormField } from "@/types/formfield";
<script setup lang="ts">
import { useEditForm } from "@/stores/editForm";
import { ref, watch, computed } from "vue";
import {
ConditionalLogic,
ConditionalOperators,
type Condition,
Actions,
} from "@/types/conditional-render.types";
import { EllipsisVertical, Trash, Workflow, Zap } from "lucide-vue-next";
import { FormControl, Popover, Button } from "frappe-ui";
import type { FormField } from "@/types/formfield";
🤖 Prompt for AI Agents
In `@frontend/src/components/builder/field-editor/ConditionalLogicSection.vue`
around lines 1 - 12, The template uses the Button component but it isn't
imported; update the top import from "frappe-ui" to include Button alongside
FormControl and Popover (so import Button, FormControl, Popover from
"frappe-ui") so the Button symbol used in the ConditionalLogicSection component
is defined.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's imported globally

Copy link

Choose a reason for hiding this comment

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

@harshtandiya, thank you for the clarification! If Button is globally imported in your project setup, then no change is needed here. The code is correct as-is.

Comment on lines +21 to +24
export enum LogicOperators {
And = "And",
Or = "Or",
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

LogicOperators enum is defined but never used.

The ConditionalLogic type has a conditions array but no field to specify how multiple conditions should be combined (AND vs OR). Consider adding a logic_operator field to ConditionalLogic:

Proposed fix
 export type ConditionalLogic = {
   conditions: Condition[];
+  logic_operator: LogicOperators;
   action: Actions;
   target_field: string | null;
 };
🤖 Prompt for AI Agents
In `@frontend/src/types/conditional-render.types.ts` around lines 21 - 24, The
LogicOperators enum is declared but unused; add a logic_operator field to the
ConditionalLogic type so callers can express how the conditions array should be
combined (AND vs OR). Update the ConditionalLogic definition to include a new
optional or required property (e.g., logic_operator: LogicOperators) and ensure
any construction/validation of ConditionalLogic (places that build or consume
ConditionalLogic) are updated to set or handle this field; use the existing
LogicOperators enum values (And, Or) as the allowed options.

Comment on lines +26 to +30
export type Condition = {
fieldname: string;
operator: ConditionalOperators;
value: string | number | boolean;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

IsBetween/IsNotBetween operators require two values but Condition.value only supports a single value.

The value field is typed as string | number | boolean, which cannot represent a range. Consider supporting an array or tuple for range-based operators:

Proposed fix
 export type Condition = {
   fieldname: string;
   operator: ConditionalOperators;
-  value: string | number | boolean;
+  value: string | number | boolean | [number, number];
 };

Alternatively, add separate minValue/maxValue fields, or remove IsBetween/IsNotBetween if they're not yet supported.

📝 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
export type Condition = {
fieldname: string;
operator: ConditionalOperators;
value: string | number | boolean;
};
export type Condition = {
fieldname: string;
operator: ConditionalOperators;
value: string | number | boolean | [number, number];
};
🤖 Prompt for AI Agents
In `@frontend/src/types/conditional-render.types.ts` around lines 26 - 30, The
Condition type's value currently only allows a single primitive (string | number
| boolean) but ConditionalOperators includes range operators
IsBetween/IsNotBetween which need two values; update the Condition definition
(and any consumers) so value can represent ranges—either change value to accept
a tuple/array like [string|number, string|number] or union it with a {minValue:
string|number; maxValue: string|number} shape, and ensure code handling
Condition (e.g., evaluation/validation logic) checks the operator and reads the
tuple or min/max fields for IsBetween/IsNotBetween while preserving existing
single-value behavior for other operators.

Comment on lines +1 to +18
export enum FormFieldTypes {
Attach = "Attach",
Data = "Data",
Number = "Number",
Email = "Email",
Date = "Date",
DateTime = "Date Time",
DateRange = "Date Range",
TimePicker = "Time Picker",
Password = "Password",
Select = "Select",
Switch = "Switch",
Textarea = "Textarea",
TextEditor = "Text Editor",
Link = "Link",
Checkbox = "Checkbox",
Rating = "Rating",
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, locate form_field.json to verify the backend claim
find . -name "form_field.json" -type f 2>/dev/null | head -20

Repository: BuildWithHussain/forms_pro

Length of output: 126


🏁 Script executed:

#!/bin/bash
# Check the current formfield.ts file to confirm enum contents
cat -n frontend/src/types/formfield.ts

Repository: BuildWithHussain/forms_pro

Length of output: 900


🏁 Script executed:

#!/bin/bash
# Run the verification script from the review to check usage
echo "=== Checking for Checkbox field type usage ==="
rg -n "Checkbox" --type=ts --type=vue -g '!*.spec.*' -g '!*.test.*' 2>/dev/null | head -30

echo "=== Checking for Rating field type usage ==="
rg -n "Rating" --type=ts --type=vue -g '!*.spec.*' -g '!*.test.*' 2>/dev/null | head -30

Repository: BuildWithHussain/forms_pro

Length of output: 161


🏁 Script executed:

#!/bin/bash
# Read the form_field.json to check what field types are supported in backend
cat ./forms_pro/forms_pro/doctype/form_field/form_field.json | head -200

Repository: BuildWithHussain/forms_pro

Length of output: 1799


Enum includes field types not supported by backend.

The FormFieldTypes enum includes Checkbox and Rating field types that are not listed in the backend form_field.json fieldtype options. The backend supports only: Attach, Data, Number, Email, Date, Date Time, Date Range, Time Picker, Password, Select, Switch, Textarea, Text Editor, and Link. This mismatch could cause issues if these types are selected in the UI but rejected by backend validation.

🤖 Prompt for AI Agents
In `@frontend/src/types/formfield.ts` around lines 1 - 18, The FormFieldTypes enum
includes unsupported values 'Checkbox' and 'Rating' that don't exist in the
backend form_field.json; remove 'Checkbox' and 'Rating' from the export in the
FormFieldTypes enum so the front-end only exposes backend-supported types
(Attach, Data, Number, Email, Date, Date Time, Date Range, Time Picker,
Password, Select, Switch, Textarea, Text Editor, Link), and update any code that
references FormFieldTypes.Checkbox or FormFieldTypes.Rating (forms, validators,
tests) to use a supported type or a clear fallback/mapping to avoid sending
invalid field types to the backend.

…he codebase

* Updated JSON, Python, and Vue components to reflect the new field name.
* Adjusted parsing and evaluation functions to accommodate the change.
* Ensured consistency in the handling of conditional logic throughout the application.
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

🤖 Fix all issues with AI agents
In `@frontend/src/utils/conditionals.ts`:
- Around line 39-41: getFieldValue references a non-existent backend field type
"Checkbox", causing inconsistency with form_field.json which only defines
"Switch"; either remove "Checkbox" from the boolean branch or normalize it to
"Switch". Fix by editing getFieldValue to stop checking for "Checkbox" (only
treat fieldType === "Switch" as boolean) or add a short normalization step (map
"Checkbox" to "Switch" before the existing checks) and update any callers/tests
to use the normalized type; reference getFieldValue, fieldType, "Checkbox", and
"Switch" when making the change.
- Around line 188-218: The IsBetween and IsNotBetween branches in evaluate
(ConditionalOperators.IsBetween / ConditionalOperators.IsNotBetween) use
inconsistent defaults for malformed input—IsBetween returns false while
IsNotBetween returns true; make them consistent by treating malformed inputs as
unevaluable and returning false in both cases: update the IsNotBetween checks
that currently return true (the initial typeof conditionValue !== "string" /
normalizedFieldValue === null check and the isNaN/min/max/type checks) to return
false, matching the IsBetween behavior so both operators behave symmetrically
when conditionValue or normalizedFieldValue is invalid.
♻️ Duplicate comments (4)
frontend/src/types/formfield.ts (1)

16-17: Enum includes field types not supported by backend.

Checkbox and Rating are not in the backend's form_field.json fieldtype options. This mismatch could cause validation failures if these types are selected in the UI.

frontend/src/utils/conditionals.ts (1)

256-311: Logic confusion in shouldFieldBeVisible - checks two different concepts.

Lines 261-264 check if the current field has conditional_logic, but lines 269-276 search for rules in OTHER fields that target this field. These are unrelated checks:

  1. A field's own conditional_logic defines rules that affect OTHER fields (via target_field)
  2. This function should only check if OTHER fields have rules targeting THIS field

The early return creates a bug: fields without their own conditional_logic will be visible regardless of whether other fields have rules hiding them.

🐛 Proposed fix - remove the early return
 export function shouldFieldBeVisible(
   field: FormField,
   formValues: Record<string, any>,
   allFields: FormField[]
 ): boolean {
-  // If field has no conditional_logic, it's always visible
-  if (!field.conditional_logic || field.conditional_logic.trim() === "") {
-    return true;
-  }
-
   // Find all conditional logic rules that target this field
   const targetingRules: ConditionalLogic[] = [];
frontend/src/components/builder/field-editor/ConditionalLogicSection.vue (2)

11-11: Missing Button import causes runtime error.

The Button component is used in the template (lines 149, 161, 217, 224) but is not imported from frappe-ui.

🐛 Proposed fix
-import { FormControl, Popover } from "frappe-ui";
+import { FormControl, Popover, Button } from "frappe-ui";

196-214: Action and target field controls are duplicated inside the condition loop.

The "Then" section (action and target_field selectors) is inside the v-for loop over conditions (lines 137-215). Since action and target_field are properties of conditionalLogic (not per-condition), this creates duplicate controls when multiple conditions exist.

🔧 Proposed fix - move outside the loop
             </div>
+        </div>
+        <!-- Action section - outside the condition loop -->
+        <div class="flex flex-col gap-2 mt-4 pt-4 border-t">
+            <div class="flex items-center gap-2 text-ink-gray-5 text-sm">
+                <Zap class="w-4 h-4" />
+                <span>Then</span>
+            </div>
+            <FormControl
+                type="select"
+                placeholder="Select Action"
+                variant="outline"
+                :options="actionOptions"
+                v-model="conditionalLogic.action"
+            />
+            <FormControl
+                type="select"
+                placeholder="Select Field"
+                variant="outline"
+                :options="availableFieldOptions"
+                v-model="conditionalLogic.target_field"
+            />
         </div>
-            <!-- Actions -->
-            <div class="flex items-center gap-2 ...">
-            ...
-            </div>
-        </div>

Move lines 196-214 outside the v-for block that ends at line 215.

🧹 Nitpick comments (2)
frontend/src/components/builder/field-editor/ConditionalLogicSection.vue (2)

70-78: Consider using queueMicrotask instead of setTimeout.

Using setTimeout(..., 0) to reset the flag works but queueMicrotask is more semantically correct for deferring to the next microtask and avoids timer overhead.

♻️ Proposed refactor
         } finally {
             // Reset flag after a microtask to allow the other watcher to process
-            setTimeout(() => {
+            queueMicrotask(() => {
                 isUpdatingFromStore.value = false;
-            }, 0);
+            });
         }

101-106: Field options include the currently selected field.

availableFieldOptions includes all fields, which means a field's conditional logic could target itself. Consider filtering out the current field to prevent self-referencing rules.

♻️ Proposed fix
 const availableFieldOptions = computed(() => {
-    return editFormStore.fields.map((field: FormField) => ({
+    return editFormStore.fields
+        .filter((field: FormField) => field.fieldname !== editFormStore.selectedField?.fieldname)
+        .map((field: FormField) => ({
         label: field.label,
         value: field.fieldname,
     }));
 });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 041d055 and b2d9c01.

📒 Files selected for processing (6)
  • forms_pro/forms_pro/doctype/form_field/form_field.json
  • forms_pro/forms_pro/doctype/form_field/form_field.py
  • frontend/src/components/builder/field-editor/ConditionalLogicSection.vue
  • frontend/src/components/builder/field-editor/FieldPropertiesForm.vue
  • frontend/src/types/formfield.ts
  • frontend/src/utils/conditionals.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/builder/field-editor/FieldPropertiesForm.vue
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/types/formfield.ts (1)
forms_pro/forms_pro/doctype/form_field/form_field.py (1)
  • FormField (8-73)
frontend/src/utils/conditionals.ts (2)
frontend/src/types/conditional-render.types.ts (2)
  • ConditionalLogic (38-42)
  • Condition (26-30)
frontend/src/types/formfield.ts (1)
  • FormField (20-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Server
🔇 Additional comments (6)
forms_pro/forms_pro/doctype/form_field/form_field.json (1)

67-71: LGTM - New conditional_logic field added correctly.

The Code fieldtype is appropriate for storing JSON-formatted conditional logic strings.

forms_pro/forms_pro/doctype/form_field/form_field.py (2)

17-17: Type annotation added correctly.

The conditional_logic: DF.Code | None type annotation aligns with the JSON field definition.


65-72: No action needed. The conditional_logic field is correctly excluded from to_frappe_field. It is a frontend-only feature used to control field visibility and conditional requirements during form submission, and is not part of the Frappe field schema. The backend properly syncs only the field properties that Frappe DocField supports (label, fieldtype, reqd, options, description, default).

Likely an incorrect or invalid review comment.

frontend/src/types/formfield.ts (1)

20-30: LGTM - FormField type updated correctly.

The conditional_logic?: string property aligns with the backend's DF.Code field, and using FormFieldTypes enum for fieldtype improves type safety.

frontend/src/utils/conditionals.ts (2)

12-25: LGTM - JSON parsing with error handling.

The function correctly handles null/undefined/empty strings and catches JSON parse errors.


316-349: LGTM - shouldFieldBeRequired correctly evaluates targeting rules.

Unlike shouldFieldBeVisible, this function correctly scans all fields for rules targeting the current field without an erroneous early return.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +39 to +41
if (fieldType === "Switch" || fieldType === "Checkbox") {
return Boolean(fieldValue);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Checkbox field type referenced but not in backend.

getFieldValue handles Checkbox field type, but the backend form_field.json only defines Switch for boolean fields. Consider removing Checkbox or mapping it to Switch for consistency.

🔧 Proposed fix
   // Handle boolean/switch fields
-  if (fieldType === "Switch" || fieldType === "Checkbox") {
+  if (fieldType === "Switch") {
     return Boolean(fieldValue);
   }
📝 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
if (fieldType === "Switch" || fieldType === "Checkbox") {
return Boolean(fieldValue);
}
// Handle boolean/switch fields
if (fieldType === "Switch") {
return Boolean(fieldValue);
}
🤖 Prompt for AI Agents
In `@frontend/src/utils/conditionals.ts` around lines 39 - 41, getFieldValue
references a non-existent backend field type "Checkbox", causing inconsistency
with form_field.json which only defines "Switch"; either remove "Checkbox" from
the boolean branch or normalize it to "Switch". Fix by editing getFieldValue to
stop checking for "Checkbox" (only treat fieldType === "Switch" as boolean) or
add a short normalization step (map "Checkbox" to "Switch" before the existing
checks) and update any callers/tests to use the normalized type; reference
getFieldValue, fieldType, "Checkbox", and "Switch" when making the change.

Comment on lines 188 to 218
case ConditionalOperators.IsBetween:
// For "Is Between", value should be a string like "min,max"
if (normalizedFieldValue === null || typeof conditionValue !== "string") {
return false;
}
const [min, max] = conditionValue.split(",").map((v) => Number(v.trim()));
if (
isNaN(min) ||
isNaN(max) ||
typeof normalizedFieldValue !== "number"
) {
return false;
}
return normalizedFieldValue >= min && normalizedFieldValue <= max;

case ConditionalOperators.IsNotBetween:
// For "Is Not Between", value should be a string like "min,max"
if (normalizedFieldValue === null || typeof conditionValue !== "string") {
return true;
}
const [minNot, maxNot] = conditionValue
.split(",")
.map((v) => Number(v.trim()));
if (
isNaN(minNot) ||
isNaN(maxNot) ||
typeof normalizedFieldValue !== "number"
) {
return true;
}
return normalizedFieldValue < minNot || normalizedFieldValue > maxNot;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

IsBetween and IsNotBetween return inconsistent defaults for malformed input.

  • IsBetween returns false when conditionValue is not a string (line 190-192)
  • IsNotBetween returns true when conditionValue is not a string (line 205-207)

This asymmetry could cause unexpected behavior. If the condition value is malformed, both should probably return false (condition not evaluable) or the same default.

🔧 Proposed fix for consistent behavior
     case ConditionalOperators.IsNotBetween:
       // For "Is Not Between", value should be a string like "min,max"
       if (normalizedFieldValue === null || typeof conditionValue !== "string") {
-        return true;
+        return false; // Malformed condition should not affect field
       }
📝 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
case ConditionalOperators.IsBetween:
// For "Is Between", value should be a string like "min,max"
if (normalizedFieldValue === null || typeof conditionValue !== "string") {
return false;
}
const [min, max] = conditionValue.split(",").map((v) => Number(v.trim()));
if (
isNaN(min) ||
isNaN(max) ||
typeof normalizedFieldValue !== "number"
) {
return false;
}
return normalizedFieldValue >= min && normalizedFieldValue <= max;
case ConditionalOperators.IsNotBetween:
// For "Is Not Between", value should be a string like "min,max"
if (normalizedFieldValue === null || typeof conditionValue !== "string") {
return true;
}
const [minNot, maxNot] = conditionValue
.split(",")
.map((v) => Number(v.trim()));
if (
isNaN(minNot) ||
isNaN(maxNot) ||
typeof normalizedFieldValue !== "number"
) {
return true;
}
return normalizedFieldValue < minNot || normalizedFieldValue > maxNot;
case ConditionalOperators.IsBetween:
// For "Is Between", value should be a string like "min,max"
if (normalizedFieldValue === null || typeof conditionValue !== "string") {
return false;
}
const [min, max] = conditionValue.split(",").map((v) => Number(v.trim()));
if (
isNaN(min) ||
isNaN(max) ||
typeof normalizedFieldValue !== "number"
) {
return false;
}
return normalizedFieldValue >= min && normalizedFieldValue <= max;
case ConditionalOperators.IsNotBetween:
// For "Is Not Between", value should be a string like "min,max"
if (normalizedFieldValue === null || typeof conditionValue !== "string") {
return false; // Malformed condition should not affect field
}
const [minNot, maxNot] = conditionValue
.split(",")
.map((v) => Number(v.trim()));
if (
isNaN(minNot) ||
isNaN(maxNot) ||
typeof normalizedFieldValue !== "number"
) {
return true;
}
return normalizedFieldValue < minNot || normalizedFieldValue > maxNot;
🤖 Prompt for AI Agents
In `@frontend/src/utils/conditionals.ts` around lines 188 - 218, The IsBetween and
IsNotBetween branches in evaluate (ConditionalOperators.IsBetween /
ConditionalOperators.IsNotBetween) use inconsistent defaults for malformed
input—IsBetween returns false while IsNotBetween returns true; make them
consistent by treating malformed inputs as unevaluable and returning false in
both cases: update the IsNotBetween checks that currently return true (the
initial typeof conditionValue !== "string" / normalizedFieldValue === null check
and the isNaN/min/max/type checks) to return false, matching the IsBetween
behavior so both operators behave symmetrically when conditionValue or
normalizedFieldValue is invalid.

* Added missing commas for consistency in function parameters.
* Removed deprecated "Is Between" and "Is Not Between" conditional checks.
* Improved visibility determination logic for fields based on conditional rules.
@harshtandiya harshtandiya merged commit 848eb28 into develop Jan 14, 2026
4 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 14, 2026
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