diff --git a/Common/Server/Types/Workflow/Components/Conditions/IfElse.ts b/Common/Server/Types/Workflow/Components/Conditions/IfElse.ts index a5d6807a72..6291418e1f 100644 --- a/Common/Server/Types/Workflow/Components/Conditions/IfElse.ts +++ b/Common/Server/Types/Workflow/Components/Conditions/IfElse.ts @@ -9,6 +9,7 @@ import ComponentMetadata, { import ComponentID from "../../../../../Types/Workflow/ComponentID"; import Components, { ConditionOperator, + ConditionValueType, } from "../../../../../Types/Workflow/Components/Condition"; import CaptureSpan from "../../../../Utils/Telemetry/CaptureSpan"; @@ -61,30 +62,69 @@ export default class IfElse extends ComponentCode { * Inject dependencies */ - for (const key in args) { - if (key === "operator") { - continue; + // Get explicit types from dropdowns, default to text + let input1Type: ConditionValueType = + (args["input-1-type"] as ConditionValueType) || + ConditionValueType.Text; + let input2Type: ConditionValueType = + (args["input-2-type"] as ConditionValueType) || + ConditionValueType.Text; + + // When types differ, coerce both to the more specific type + // so comparisons like text "true" == boolean true work correctly. + // Priority: Null/Undefined keep as-is, Boolean > Number > Text. + if (input1Type !== input2Type) { + const isNullish = (t: ConditionValueType): boolean => + t === ConditionValueType.Null || + t === ConditionValueType.Undefined; + + if (!isNullish(input1Type) && !isNullish(input2Type)) { + const typePriority: Record = { + [ConditionValueType.Boolean]: 2, + [ConditionValueType.Number]: 1, + [ConditionValueType.Text]: 0, + }; + + const p1: number = typePriority[input1Type] ?? 0; + const p2: number = typePriority[input2Type] ?? 0; + const commonType: ConditionValueType = + p1 >= p2 ? input1Type : input2Type; + input1Type = commonType; + input2Type = commonType; } - - const value: JSONValue = args[key]; - - let shouldHaveQuotes: boolean = false; - - if ( - typeof value === "string" && - value !== "null" && - value !== "undefined" - ) { - shouldHaveQuotes = true; - } - - if (typeof value === "object") { - args[key] = JSON.stringify(args[key]); - } - - args[key] = shouldHaveQuotes ? `"${args[key]}"` : args[key]; } + type FormatValueFunction = ( + value: JSONValue, + valueType: ConditionValueType, + ) => string; + + const formatValue: FormatValueFunction = ( + value: JSONValue, + valueType: ConditionValueType, + ): string => { + const strValue: string = typeof value === "object" + ? JSON.stringify(value) + : String(value ?? ""); + + switch (valueType) { + case ConditionValueType.Boolean: + return strValue === "true" ? "true" : "false"; + case ConditionValueType.Number: + return isNaN(Number(strValue)) ? "0" : String(Number(strValue)); + case ConditionValueType.Null: + return "null"; + case ConditionValueType.Undefined: + return "undefined"; + case ConditionValueType.Text: + default: + return `"${strValue}"`; + } + }; + + args["input-1"] = formatValue(args["input-1"], input1Type); + args["input-2"] = formatValue(args["input-2"], input2Type); + type SerializeFunction = (arg: string) => string; const serialize: SerializeFunction = (arg: string): string => { @@ -107,13 +147,13 @@ export default class IfElse extends ComponentCode { `; if (args["operator"] === ConditionOperator.Contains) { - code += `return input1.includes(input2);`; + code += `return String(input1).includes(String(input2));`; } else if (args["operator"] === ConditionOperator.DoesNotContain) { - code += `return !input1.includes(input2);`; + code += `return !String(input1).includes(String(input2));`; } else if (args["operator"] === ConditionOperator.StartsWith) { - code += `return input1.startsWith(input2);`; + code += `return String(input1).startsWith(String(input2));`; } else if (args["operator"] === ConditionOperator.EndsWith) { - code += `return input1.endsWith(input2);`; + code += `return String(input1).endsWith(String(input2));`; } else { code += `return input1 ${(args["operator"] as string) || "=="} input2;`; } diff --git a/Common/Types/Workflow/Component.ts b/Common/Types/Workflow/Component.ts index e1cd97dfcb..cfa7c6720c 100644 --- a/Common/Types/Workflow/Component.ts +++ b/Common/Types/Workflow/Component.ts @@ -26,6 +26,7 @@ export enum ComponentInputType { HTML = "HTML", Operator = "Operator", Markdown = "Markdown", + ValueType = "Value Type", } export enum ComponentType { diff --git a/Common/Types/Workflow/Components/Condition.ts b/Common/Types/Workflow/Components/Condition.ts index 6d89c08956..081ebd3613 100644 --- a/Common/Types/Workflow/Components/Condition.ts +++ b/Common/Types/Workflow/Components/Condition.ts @@ -5,6 +5,14 @@ import ComponentMetadata, { ComponentType, } from "./../Component"; +export enum ConditionValueType { + Text = "text", + Boolean = "boolean", + Number = "number", + Null = "null", + Undefined = "undefined", +} + export enum ConditionOperator { EqualTo = "==", NotEqualTo = "!=", @@ -27,6 +35,15 @@ const components: Array = [ iconProp: IconProp.Condition, componentType: ComponentType.Component, arguments: [ + { + type: ComponentInputType.ValueType, + name: "Input 1 Type", + description: + "Type of Input 1. Defaults to Text if not selected.", + placeholder: "Text", + required: false, + id: "input-1-type", + }, { type: ComponentInputType.Text, name: "Input 1", @@ -43,6 +60,15 @@ const components: Array = [ required: true, id: "operator", }, + { + type: ComponentInputType.ValueType, + name: "Input 2 Type", + description: + "Type of Input 2. Defaults to Text if not selected.", + placeholder: "Text", + required: false, + id: "input-2-type", + }, { type: ComponentInputType.Text, name: "Input 2", diff --git a/Common/UI/Components/Workflow/Utils.ts b/Common/UI/Components/Workflow/Utils.ts index 5454ee9580..d82ab1d477 100644 --- a/Common/UI/Components/Workflow/Utils.ts +++ b/Common/UI/Components/Workflow/Utils.ts @@ -8,7 +8,10 @@ import ComponentMetadata, { import Components, { Categories } from "../../../Types/Workflow/Components"; import BaseModelComponentFactory from "../../../Types/Workflow/Components/BaseModel"; import Entities from "../../../Models/DatabaseModels/Index"; -import { ConditionOperator } from "../../../Types/Workflow/Components/Condition"; +import { + ConditionOperator, + ConditionValueType, +} from "../../../Types/Workflow/Components/Condition"; type LoadComponentsAndCategoriesFunction = () => { components: Array; @@ -230,6 +233,34 @@ export const componentInputTypeToFormFieldType: ComponentInputTypeToFormFieldTyp }; } + if (componentInputType === ComponentInputType.ValueType) { + return { + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: [ + { + label: "Text", + value: ConditionValueType.Text, + }, + { + label: "Boolean", + value: ConditionValueType.Boolean, + }, + { + label: "Number", + value: ConditionValueType.Number, + }, + { + label: "Null", + value: ConditionValueType.Null, + }, + { + label: "Undefined", + value: ConditionValueType.Undefined, + }, + ], + }; + } + if (componentInputType === ComponentInputType.Date) { return { fieldType: FormFieldSchemaType.Date,