## Description - Fix XSS issue by upgrading packages such that the `serialize-javascript` dependency resolves to `v6.0.2` Fixes https://github.com/appsmithorg/appsmith/security/dependabot/376 - Fix XSS issue by upgrading `esbuild` to `v0.25.1` Fixes https://github.com/appsmithorg/appsmith/security/dependabot/367 - Fix vite vulnerability by upgrading `vite` to `v6.2.1` (this is a major version upgrade and effects the `storybook` package) Fixes https://github.com/appsmithorg/appsmith/security/dependabot/364 Fixes https://github.com/appsmithorg/appsmith/security/dependabot/334 Fixes https://github.com/appsmithorg/appsmith/security/dependabot/336 - Fixes TinyMCE XSS vulnerabilities by upgrading `tinymce` to `v7.7.1` and `tinymce-react` to `v6.0.0` (Major version upgrade) Fixes https://github.com/appsmithorg/appsmith/security/dependabot/347 Fixes https://github.com/appsmithorg/appsmith/security/dependabot/348 Fixes https://github.com/appsmithorg/appsmith/security/dependabot/290 - Fix vulnerability in `webpack` by upgrading to `v5.98.0` Fixes https://github.com/appsmithorg/appsmith/security/dependabot/324 - Fix vulnerability in `@sentry/browser` by upgrading `@sentry/react` to `v7.120.3` (Major version upgrade) _Note: [`Severity` enum has been deprecated](https://docs.sentry.io/platforms/javascript/migration/v7-to-v8/#removal-of-severity-enum)_ Fixes https://github.com/appsmithorg/appsmith/security/dependabot/345 - Fix vulnerability in `axios` by upgrading to `v1.8.3` Fixes https://github.com/appsmithorg/appsmith/security/dependabot/391 - Fix vulnerability in `@babel/runtime` by upgrading to `v7.26.10` Fixes https://github.com/appsmithorg/appsmith/security/dependabot/393 - Fix vulnerability in `@babel/helper` by upgrading `@babel/core` to `v7.26.10` Fixes https://github.com/appsmithorg/appsmith/security/dependabot/392 - Fix vulnerability in `prismjs` by upgrading to `v1.30.0` Fixes https://github.com/appsmithorg/appsmith/security/dependabot/390 - Fix vulnerability in `cookie` by upgrading to `v0.7.0` Fixes https://github.com/appsmithorg/appsmith/security/dependabot/346 ## Automation /ok-to-test tags="@tag.All" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/13967528524> > Commit: 6a36c9755e7df9e22c1c109876c127b963127a71 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13967528524&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.All` > Spec: > <hr>Thu, 20 Mar 2025 12:03:08 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [x] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Enhanced the text editor experience by introducing quick markdown-style shortcuts for headings, lists, and quotes. - **Chores** - Upgraded numerous underlying libraries and tools for improved performance and stability. - Streamlined error logging by refining how errors are categorized and reported. - Improved accessibility by updating element selectors to use `aria-label` attributes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
821 lines
25 KiB
TypeScript
821 lines
25 KiB
TypeScript
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 { klona } from "klona/full";
|
|
import type { FeatureFlags } from "ee/entities/FeatureFlag";
|
|
import _ from "lodash";
|
|
import { getType, Types } from "utils/TypeHelpers";
|
|
import { FIELD_REQUIRED_ERROR, createMessage } from "ee/constants/messages";
|
|
import { FEATURE_FLAG } from "ee/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,
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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;
|
|
};
|
|
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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 = (
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
formData: any,
|
|
configDetails: Record<string, string>,
|
|
) => {
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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>,
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
values: any,
|
|
currentEnvId?: string,
|
|
) => {
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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>[] = [];
|
|
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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") {
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
flag = conditions.reduce((acc: any, item: boolean) => {
|
|
return acc && item;
|
|
}, conditions[0]);
|
|
} else if (type === "OR") {
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
flag = conditions.reduce((acc: any, item: boolean) => {
|
|
return acc || item;
|
|
}, undefined);
|
|
}
|
|
} else {
|
|
flag = conditions[0];
|
|
}
|
|
|
|
return flag;
|
|
}
|
|
};
|
|
|
|
export const isHiddenConditionsEvaluation = (
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
values: any,
|
|
hidden?: HiddenType,
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
): 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)) {
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
conditions = conditions.map((rule: any) => {
|
|
return isHiddenConditionsEvaluation(values, rule);
|
|
});
|
|
} else {
|
|
return caculateIsHidden(values, hidden);
|
|
}
|
|
|
|
return evaluateCondtionWithType(conditions, conditionType);
|
|
}
|
|
};
|
|
|
|
export const caculateIsHidden = (
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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;
|
|
case "DEFINED_AND_NOT_EQUALS":
|
|
return !!valueAtPath && valueAtPath !== value;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
|
|
export const isHidden = (
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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,
|
|
};
|
|
};
|
|
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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 = (
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
values: any,
|
|
configProperty: string,
|
|
viewType: string,
|
|
formName: string,
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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 = (
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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,
|
|
) => {
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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) {
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
section.children.forEach((childSection: any) => {
|
|
parseConfig(childSection);
|
|
});
|
|
} else if ("schema" in section) {
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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],
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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,
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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 = (
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
export const modifySectionConfig = (section: any, enabled: boolean): any => {
|
|
if (!enabled) {
|
|
section.disabled = true;
|
|
} else {
|
|
section.disabled = false;
|
|
}
|
|
|
|
return section;
|
|
};
|
|
|
|
export const updateEvaluatedSectionConfig = (
|
|
// TODO: Fix this the next time the file is edited
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
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?: unknown,
|
|
): unknown | 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;
|
|
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`;
|
|
};
|