hunk.code_window:         <dependency>
               <groupId>io.sentry</groupId>
               <artifactId>sentry-spring-boot-starter-jakarta</artifactId>
               <version>6.23.0</version>
           </dependency>
           <dependency>
               <groupId>org.mockito</groupId>
               <artifactId>mockito-core</artifactId>
               <version>${mockito.version}</version>
           </dependency>
  
hunk.labels: keep keep keep keep add keep keep keep keep keep
hunk.after_edit:         <dependency>
               <groupId>org.mockito</groupId>
               <artifactId>mockito-inline</artifactId>
               <version>${mockito.version}</version>
           </dependency>
  
hunk.file_path: app/server/appsmith-server/pom.xml
hunk.type: add
file: import { DATA_BIND_REGEX_GLOBAL } from "constants/BindingsConstants";
  import { isBoolean, get, set, isString } from "lodash";
  import type {
    ConditionalOutput,
    FormConfigEvalObject,
    FormEvalOutput,
  } from "reducers/evaluationReducers/formEvaluationReducer";
  import type { FormConfigType, HiddenType } from "./BaseControl";
  import type { Diff } from "deep-diff";
  import { diff } from "deep-diff";
  import { MongoDefaultActionConfig } from "constants/DatasourceEditorConstants";
  import type { Action } from "@sentry/react/dist/types";
  import { klona } from "klona/full";
  import type { FeatureFlags } from "@appsmith/entities/FeatureFlag";
  import _ from "lodash";
  import { getType, Types } from "utils/TypeHelpers";
  import {
    FIELD_REQUIRED_ERROR,
    createMessage,
  } from "@appsmith/constants/messages";
  import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
  import { InputTypes } from "components/constants";
  
  // This function checks if the form is dirty
  // We needed this in the cases where datasources are created from APIs and the initial value
  // already has url set. If user presses back button, we need to show the confirmation dialog
  export const getIsFormDirty = (
    isFormDirty: boolean,
    formData: any,
    isNewDatasource: boolean,
    isRestPlugin: boolean,
    currentEditingEnvId: string,
  ) => {
    const url = isRestPlugin
      ? get(
          formData,
          `datastoreStorages.${currentEditingEnvId}.datasourceConfiguration.url`,
          "",
        )
      : "";
  
    if (!isFormDirty && isNewDatasource && isRestPlugin && url.length === 0) {
      return true;
    }
    return isFormDirty;
  };
  
  export const getTrimmedData = (formData: any) => {
    const dataType = getType(formData);
    const isArrayorObject = (type: ReturnType<typeof getType>) =>
      type === Types.ARRAY || type === Types.OBJECT;
  
    if (isArrayorObject(dataType)) {
      Object.keys(formData).map((key) => {
        const valueType = getType(formData[key]);
        if (isArrayorObject(valueType)) {
          getTrimmedData(formData[key]);
        } else if (valueType === Types.STRING) {
          _.set(formData, key, formData[key].trim());
        }
      });
    }
    return formData;
  };
  
  export const normalizeValues = (
    formData: any,
    configDetails: Record<string, string>,
  ) => {
    const checked: Record<string, any> = {};
    const configProperties = Object.keys(configDetails);
  
    for (const configProperty of configProperties) {
      const controlType = configDetails[configProperty];
  
      if (controlType === "KEYVALUE_ARRAY") {
        const properties = configProperty.split("[*].");
  
        if (checked[properties[0]]) continue;
  
        checked[properties[0]] = 1;
        const values = _.get(formData, properties[0], []);
        const newValues: ({ [s: string]: unknown } | ArrayLike<unknown>)[] = [];
  
        values.forEach(
          (object: { [s: string]: unknown } | ArrayLike<unknown>) => {
            const isEmpty = Object.values(object).every((x) => x === "");
  
            if (!isEmpty) {
              newValues.push(object);
            }
          },
        );
  
        if (newValues.length) {
          formData = _.set(formData, properties[0], newValues);
        } else {
          formData = _.set(formData, properties[0], []);
        }
      }
    }
  
    return formData;
  };
  
  export const validate = (
    requiredFields: Record<string, FormConfigType>,
    values: any,
    currentEnvId?: string,
  ) => {
    const errors = {} as any;
  
    Object.keys(requiredFields).forEach((fieldConfigProperty) => {
      // Do not check for required fields if the field is not part of the current environment
      if (
        !!currentEnvId &&
        currentEnvId.length > 0 &&
        !fieldConfigProperty.includes(currentEnvId)
      ) {
        return;
      }
      const fieldConfig = requiredFields[fieldConfigProperty];
      if (fieldConfig.controlType === "KEYVALUE_ARRAY") {
        const configProperty = (fieldConfig.configProperty as string).split(
          "[*].",
        );
        const arrayValues = _.get(values, configProperty[0], []);
        const keyValueArrayErrors: Record<string, string>[] = [];
  
        arrayValues.forEach((value: any, index: number) => {
          const objectKeys = Object.keys(value);
          const keyValueErrors: Record<string, string> = {};
  
          if (
            !value[objectKeys[0]] ||
            (isString(value[objectKeys[0]]) && !value[objectKeys[0]].trim())
          ) {
            keyValueErrors[objectKeys[0]] = createMessage(FIELD_REQUIRED_ERROR);
            keyValueArrayErrors[index] = keyValueErrors;
          }
          if (
            !value[objectKeys[1]] ||
            (isString(value[objectKeys[1]]) && !value[objectKeys[1]].trim())
          ) {
            keyValueErrors[objectKeys[1]] = createMessage(FIELD_REQUIRED_ERROR);
            keyValueArrayErrors[index] = keyValueErrors;
          }
        });
  
        if (keyValueArrayErrors.length) {
          _.set(errors, configProperty[0], keyValueArrayErrors);
        }
      } else {
        const value = _.get(values, fieldConfigProperty);
  
        if (_.isNil(value) || (isString(value) && _.isEmpty(value.trim()))) {
          _.set(errors, fieldConfigProperty, "This field is required");
        }
      }
    });
  
    return !_.isEmpty(errors);
  };
  
  export const evaluateCondtionWithType = (
    conditions: Array<boolean> | undefined,
    type: string | undefined,
  ) => {
    if (conditions) {
      let flag;
      //this is where each conditions gets evaluated
      if (conditions.length > 1) {
        if (type === "AND") {
          flag = conditions.reduce((acc: any, item: boolean) => {
            return acc && item;
          }, conditions[0]);
        } else if (type === "OR") {
          flag = conditions.reduce((acc: any, item: boolean) => {
            return acc || item;
          }, undefined);
        }
      } else {
        flag = conditions[0];
      }
      return flag;
    }
  };
  
  export const isHiddenConditionsEvaluation = (
    values: any,
    hidden?: HiddenType,
  ): any => {
    if (!!hidden && !isBoolean(hidden)) {
      //if nested condtions are there recursively from bottom to top call this function on each condtion
      let conditionType, conditions;
      if ("conditionType" in hidden) {
        conditionType = hidden.conditionType;
      }
      if ("conditions" in hidden) {
        conditions = hidden.conditions;
      }
      if (Array.isArray(conditions)) {
        conditions = conditions.map((rule: any) => {
          return isHiddenConditionsEvaluation(values, rule);
        });
      } else {
        return caculateIsHidden(values, hidden);
      }
      return evaluateCondtionWithType(conditions, conditionType);
    }
  };
  
  export const caculateIsHidden = (
    values: any,
    hiddenConfig?: HiddenType,
    featureFlags?: FeatureFlags,
    viewMode?: boolean,
  ) => {
    if (!!hiddenConfig && !isBoolean(hiddenConfig)) {
      let valueAtPath;
      let value, comparison;
      if ("path" in hiddenConfig) {
        valueAtPath = get(values, hiddenConfig.path);
      }
      if ("value" in hiddenConfig) {
        value = hiddenConfig.value;
      }
      if ("comparison" in hiddenConfig) {
        comparison = hiddenConfig.comparison;
      }
  
      let flagValue: keyof FeatureFlags = FEATURE_FLAG.TEST_FLAG;
      if ("flagValue" in hiddenConfig) {
        flagValue = hiddenConfig.flagValue;
      }
  
      switch (comparison) {
        case "EQUALS":
          return valueAtPath === value;
        case "NOT_EQUALS":
          return valueAtPath !== value;
        case "GREATER":
          return valueAtPath > value;
        case "LESSER":
          return valueAtPath < value;
        case "IN":
          return Array.isArray(value) && value.includes(valueAtPath);
        case "NOT_IN":
          return Array.isArray(value) && !value.includes(valueAtPath);
        case "FEATURE_FLAG":
          // FEATURE_FLAG comparision is used to hide previous configs,
          // and show new configs if feature flag is enabled, if disabled/ not present,
          // previous config would be shown as is
          return !!featureFlags && featureFlags[flagValue] === value;
        case "VIEW_MODE":
          // This can be used to decide which form controls to show in view mode or edit mode depending on the value.
          return viewMode === value;
        default:
          return true;
      }
    }
  };
  
  export const isHidden = (
    values: any,
    hiddenConfig?: HiddenType,
    featureFlags?: FeatureFlags,
    viewMode?: boolean,
  ) => {
    if (!!hiddenConfig && !isBoolean(hiddenConfig)) {
      if ("conditionType" in hiddenConfig) {
        //check if nested conditions exist
        return isHiddenConditionsEvaluation(values, hiddenConfig);
      } else {
        return caculateIsHidden(values, hiddenConfig, featureFlags, viewMode);
      }
    }
    return !!hiddenConfig;
  };
  
  export enum ViewTypes {
    JSON = "json",
    COMPONENT = "component",
  }
  
  export const alternateViewTypeInputConfig = () => {
    return {
      label: "",
      isValid: true,
      controlType: "QUERY_DYNAMIC_INPUT_TEXT",
      evaluationSubstitutionType: "TEMPLATE",
      inputType: "TEXT_WITH_BINDING",
      // showLineNumbers: true,
    };
  };
  
  export const getViewType = (values: any, configProperty: string) => {
    if (
      configProperty.startsWith("actionConfiguration.formData") &&
      configProperty.endsWith(".data")
    ) {
      const pathForViewType = configProperty.replace(".data", ".viewType");
      return get(values, pathForViewType, ViewTypes.COMPONENT);
    } else {
      return ViewTypes.COMPONENT;
    }
  };
  
  export const switchViewType = (
    values: any,
    configProperty: string,
    viewType: string,
    formName: string,
    changeFormValue: (formName: string, path: string, value: any) => void,
  ) => {
    const newViewType =
      viewType === ViewTypes.JSON ? ViewTypes.COMPONENT : ViewTypes.JSON;
    const pathForJsonData = configProperty.replace(".data", ".jsonData");
    const pathForComponentData = configProperty.replace(
      ".data",
      ".componentData",
    );
    const componentData = get(values, pathForComponentData);
    const currentData = get(values, configProperty, "");
    const stringifiedCurrentData = JSON.stringify(currentData, null, "\t");
  
    if (newViewType === ViewTypes.JSON) {
      changeFormValue(formName, pathForComponentData, currentData);
  
      // when switching to JSON, we always want a form to json conversion of the data.
      changeFormValue(
        formName,
        configProperty,
        isString(currentData)
          ? currentData
          : stringifiedCurrentData.replace(/\\/g, ""),
      );
    } else {
      changeFormValue(formName, pathForJsonData, currentData);
      if (!!componentData) {
        changeFormValue(formName, configProperty, componentData);
      }
    }
  
    changeFormValue(
      formName,
      configProperty.replace(".data", ".viewType"),
      newViewType,
    );
  };
  
  // Function that extracts the initial value from the JSON configs
  export const getConfigInitialValues = (
    config: Record<string, any>[],
    multipleViewTypesSupported = false,
    // Used in case when we want to not have encrypted fields in the response since we need to compare
    // the initial values with the server response and server response does not send encrypted fields.
    // With this param we can remove false negatives during comparison.
    includeEncryptedFields = true,
  ) => {
    const configInitialValues: Record<string, any> = {};
  
    // We expect the JSON configs to be an array of objects
    if (!Array.isArray(config)) return configInitialValues;
  
    // Function to loop through the configs and extract the initial values
    const parseConfig = (section: any): any => {
      if ("initialValue" in section) {
        if (section.controlType === "KEYVALUE_ARRAY") {
          section.initialValue.forEach(
            (initialValue: string | number, index: number) => {
              const configProperty = section.configProperty.replace("*", index);
  
              set(configInitialValues, configProperty, initialValue);
            },
          );
        } else {
          if (
            !includeEncryptedFields &&
            (section.encrypted || section.dataType === InputTypes.PASSWORD)
          ) {
            return;
          }
          set(configInitialValues, section.configProperty, section.initialValue);
        }
      } else if (section.controlType === "WHERE_CLAUSE") {
        let logicalTypes = [];
        if ("logicalTypes" in section && section.logicalTypes.length > 0) {
          logicalTypes = section.logicalTypes;
        } else {
          logicalTypes = [
            {
              label: "OR",
              value: "OR",
            },
            {
              label: "AND",
              value: "AND",
            },
          ];
        }
        set(
          configInitialValues,
          `${section.configProperty}.condition`,
          logicalTypes[0].value,
        );
        if (
          multipleViewTypesSupported &&
          section.configProperty.includes(".data")
        ) {
          set(
            configInitialValues,
            section.configProperty.replace(".data", ".viewType"),
            "component",
          );
          set(
            configInitialValues,
            section.configProperty.replace(".data", ".componentData.condition"),
            logicalTypes[0].value,
          );
        }
      }
      if ("children" in section) {
        section.children.forEach((childSection: any) => {
          parseConfig(childSection);
        });
      } else if ("schema" in section) {
        section.schema.forEach((childSection: any) => {
          parseConfig(childSection);
        });
      } else if (
        "configProperty" in section &&
        multipleViewTypesSupported &&
        section.configProperty.includes(".data")
      ) {
        set(
          configInitialValues,
          section.configProperty.replace(".data", ".viewType"),
          "component",
        );
        if (section.configProperty in configInitialValues) {
          set(
            configInitialValues,
            section.configProperty.replace(".data", ".componentData"),
            configInitialValues[section.configProperty],
          );
        }
      }
    };
  
    config.forEach((section: any) => {
      parseConfig(section);
    });
  
    return configInitialValues;
  };
  
  export const actionPathFromName = (
    actionName: string,
    name: string,
  ): string => {
    const ActionConfigStarts = "actionConfiguration.";
    let path = name;
    if (path.startsWith(ActionConfigStarts)) {
      path = "config." + path.slice(ActionConfigStarts.length);
    }
    return `${actionName}.${path}`;
  };
  
  export enum PaginationSubComponent {
    Limit = "limit",
    Offset = "offset",
    Cursor = "cursor",
  }
  
  export enum SortingSubComponent {
    Column = "column",
    Order = "order",
  }
  
  export enum WhereClauseSubComponent {
    Condition = "condition",
    Children = "children",
    Key = "key",
    Value = "value",
  }
  
  export const allowedControlTypes = ["DROP_DOWN", "QUERY_DYNAMIC_INPUT_TEXT"];
  
  const extractExpressionObject = (
    config: string,
    path: any,
    parentPath: string,
  ): FormConfigEvalObject => {
    const bindingPaths: FormConfigEvalObject = {};
    const expressions = config.match(DATA_BIND_REGEX_GLOBAL);
    if (Array.isArray(expressions) && expressions.length > 0) {
      const completePath = parentPath.length > 0 ? `${parentPath}.${path}` : path;
      expressions.forEach((exp) => {
        bindingPaths[completePath] = {
          expression: exp,
          output: "",
        };
      });
    }
    return bindingPaths;
  };
  
  export const extractEvalConfigFromFormConfig = (
    formConfig: FormConfigType,
    paths: string[],
    parentPath = "",
    bindingsFound: FormConfigEvalObject = {},
  ) => {
    paths.forEach((path: string) => {
      if (!(path in formConfig)) return;
      const config = get(formConfig, path, "");
      if (typeof config === "string") {
        bindingsFound = {
          ...bindingsFound,
          ...extractExpressionObject(config, path, parentPath),
        };
      } else if (typeof config === "object") {
        bindingsFound = {
          ...bindingsFound,
          ...extractEvalConfigFromFormConfig(
            config,
            Object.keys(config),
            parentPath.length > 0 ? `${parentPath}.${path}` : path,
            bindingsFound,
          ),
        };
      }
    });
  
    return bindingsFound;
  };
  
  // Extract the output of conditionals attached to the form from the state
  export const extractConditionalOutput = (
    section: any,
    formEvaluationState: FormEvalOutput,
  ): ConditionalOutput => {
    let conditionalOutput: ConditionalOutput = {};
    if (
      section.hasOwnProperty("propertyName") &&
      formEvaluationState.hasOwnProperty(section.propertyName)
    ) {
      conditionalOutput = formEvaluationState[section.propertyName];
    } else if (
      section.hasOwnProperty("configProperty") &&
      formEvaluationState.hasOwnProperty(section.configProperty)
    ) {
      conditionalOutput = formEvaluationState[section.configProperty];
    } else if (
      section.hasOwnProperty("identifier") &&
      !!section.identifier &&
      formEvaluationState.hasOwnProperty(section.identifier)
    ) {
      conditionalOutput = formEvaluationState[section.identifier];
    }
    return conditionalOutput;
  };
  
  // Function to check if the section config is allowed to render (Only for UQI forms)
  export const checkIfSectionCanRender = (
    conditionalOutput: ConditionalOutput,
  ) => {
    // By default, allow the section to render. This is to allow for the case where no conditional is provided.
    // The evaluation state disallows the section to render if the condition is not met. (Checkout formEval.ts)
    let allowToRender = true;
    if (
      conditionalOutput.hasOwnProperty("visible") &&
      typeof conditionalOutput.visible === "boolean"
    ) {
      allowToRender = conditionalOutput.visible;
    }
  
    if (
      conditionalOutput.hasOwnProperty("evaluateFormConfig") &&
      !!conditionalOutput.evaluateFormConfig &&
      conditionalOutput.evaluateFormConfig.hasOwnProperty(
        "updateEvaluatedConfig",
      ) &&
      typeof conditionalOutput.evaluateFormConfig.updateEvaluatedConfig ===
        "boolean"
    ) {
      allowToRender = conditionalOutput.evaluateFormConfig.updateEvaluatedConfig;
    }
    return allowToRender;
  };
  
  // Function to check if the section config is enabled (Only for UQI forms)
  export const checkIfSectionIsEnabled = (
    conditionalOutput: ConditionalOutput,
  ) => {
    // By default, the section is enabled. This is to allow for the case where no conditional is provided.
    // The evaluation state disables the section if the condition is not met. (Checkout formEval.ts)
    let enabled = true;
    if (
      conditionalOutput.hasOwnProperty("enabled") &&
      typeof conditionalOutput.enabled === "boolean"
    ) {
      enabled = conditionalOutput.enabled;
    }
    return enabled;
  };
  
  // Function to modify the section config based on the output of evaluations
  export const modifySectionConfig = (section: any, enabled: boolean): any => {
    if (!enabled) {
      section.disabled = true;
    } else {
      section.disabled = false;
    }
  
    return section;
  };
  
  export const updateEvaluatedSectionConfig = (
    section: any,
    conditionalOutput: ConditionalOutput,
    enabled = true,
  ) => {
    // we deep clone the section coming from the editorConfig to prevent any mutations of
    // the editorConfig in the redux state.
    // just spreading the object does a shallow clone(top level cloning), so we use the klona package to deep clone
    // klona is faster than deepClone from lodash.
  
    // leaving the commented code as a reminder of the above observation.
    // const updatedSection = { ...section };
    const updatedSection = klona(section);
    let evaluatedConfig: FormConfigEvalObject = {};
    if (
      conditionalOutput.hasOwnProperty("evaluateFormConfig") &&
      !!conditionalOutput.evaluateFormConfig &&
      conditionalOutput.evaluateFormConfig.hasOwnProperty(
        "updateEvaluatedConfig",
      ) &&
      typeof conditionalOutput.evaluateFormConfig.updateEvaluatedConfig ===
        "boolean" &&
      conditionalOutput.evaluateFormConfig.updateEvaluatedConfig
    ) {
      evaluatedConfig =
        conditionalOutput.evaluateFormConfig.evaluateFormConfigObject;
  
      const paths = Object.keys(evaluatedConfig);
      paths.forEach((path: string) => {
        set(updatedSection, path, evaluatedConfig[path].output);
      });
    }
  
    return modifySectionConfig(updatedSection, enabled);
  };
  
  export function fixActionPayloadForMongoQuery(
    action?: Action,
  ): Action | undefined {
    if (!action) return action;
  
    /* eslint-disable */
    //@ts-nocheck
    try {
      let actionObjectDiff: undefined | Diff<any, any>[] = diff(
        action,
        MongoDefaultActionConfig,
      );
      if (actionObjectDiff) {
        actionObjectDiff = actionObjectDiff.filter((diff) => diff.kind === "N");
        for (let i = 0; i < actionObjectDiff.length; i++) {
          let path = "";
          let value = "";
          //kind = N indicates a newly added property/element
          //This property is present in initialValues but not in action object
          if (
            actionObjectDiff &&
            actionObjectDiff[i].hasOwnProperty("kind") &&
            actionObjectDiff[i].path &&
            Array.isArray(actionObjectDiff[i].path) &&
            actionObjectDiff[i]?.path?.length &&
            actionObjectDiff[i]?.kind === "N"
          ) {
            // @ts-expect-error: Types are not available
            if (typeof actionObjectDiff[i]?.path[0] === "string") {
              // @ts-expect-error: Types are not available
              path = actionObjectDiff[i]?.path?.join(".");
            }
            // @ts-expect-error: Types are not available
            value = actionObjectDiff[i]?.rhs;
            // @ts-expect-error: Types are not available
            set(action, path, value);
          }
        }
      }
      return action;
      //@ts-check
    } catch (error) {
      console.error("Error adding default paths in Mongo query");
      return action;
    }
  }
  
  // Function to check if the config has KEYVALUE_ARRAY controlType with more than 1 dependent children
  export function isKVArray(children: Array<any>) {
    if (!Array.isArray(children) || children.length < 2) return false;
    return (
      children[0].controlType && children[0].controlType === "KEYVALUE_ARRAY"
    );
  }
  
  export const formatFileSize = (sizeInBytes: number) => {
    const FILE_SIZE = {
      KB: 1024,
      MB: 1024 * 1024,
      GB: 1024 * 1024 * 1024,
    };
  
    if (sizeInBytes < FILE_SIZE.KB) {
      return `${sizeInBytes} B`;
    } else if (sizeInBytes < FILE_SIZE.MB) {
      return `${Math.round(sizeInBytes / FILE_SIZE.KB)} KB`;
    } else if (sizeInBytes < FILE_SIZE.GB) {
      return `${Math.round(sizeInBytes / FILE_SIZE.MB)} MB`;
    }
  
    return `${Math.round(sizeInBytes / FILE_SIZE.GB)} GB`;
  };
  