PromucFlow_constructor/app/client/src/workers/formEval.ts
Ayush Pahwa fe2d625f5e
fix: Updated drop down control memo usage (#11218)
* Stopped props drilling of eval state

* Connect drop down to redux state

* Added extra check to formcontrol memo function

* Reduced modification of section at top

* Stopped mutating the initial state

* Created selector to get dynamic fetched values

* <refactor> Added comments and refactors

- Added key to the ES fragment
- Cleaned drop down component from redundant code
- Added comments

* <refactor> Removed test files

- Removed testing JSON configs

* <fix> Added null check for form eval output

- Added check to prevent null evalOutput in forms

* <chore> Removed console error

- Removed console error which is causing the vercel builds to fail
2022-02-26 22:41:38 +05:30

196 lines
7.0 KiB
TypeScript

import {
DynamicValues,
FormEvalOutput,
FormEvaluationState,
} from "../reducers/evaluationReducers/formEvaluationReducer";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import { ActionConfig } from "entities/Action";
import { FormEvalActionPayload } from "sagas/FormEvaluationSaga";
import { FormConfig } from "components/formControls/BaseControl";
import { isEmpty, merge } from "lodash";
export enum ConditionType {
HIDE = "hide", // When set, the component will be shown until condition is true
SHOW = "show", // When set, the component will be hidden until condition is true
ENABLE = "enable", // When set, the component will be enabled until condition is true
DISABLE = "disable", // When set, the component will be disabled until condition is true
FETCH_DYNAMIC_VALUES = "fetchDynamicValues", // When set, the component will fetch the values dynamically
}
// Object to hold the initial eval object
let finalEvalObj: FormEvalOutput;
// Recursive function to generate the evaluation state for form config
const generateInitialEvalState = (formConfig: FormConfig) => {
const conditionals: Record<string, any> = {};
const conditionTypes: Record<string, any> = {};
// Any element is only added to the eval state if they have a conditional statement present, if not they are allowed to be rendered
if ("conditionals" in formConfig && !!formConfig.conditionals) {
let key = "unknowns";
// A unique key is used to refer the object in the eval state, can be propertyName, configProperty or identifier
if ("propertyName" in formConfig && !!formConfig.propertyName) {
key = formConfig.propertyName;
} else if ("configProperty" in formConfig && !!formConfig.configProperty) {
key = formConfig.configProperty;
} else if ("identifier" in formConfig && !!formConfig.identifier) {
key = formConfig.identifier;
}
const allConditionTypes = Object.keys(formConfig.conditionals);
if (
allConditionTypes.includes(ConditionType.HIDE) ||
allConditionTypes.includes(ConditionType.SHOW)
) {
conditionTypes.visible = false;
merge(conditionals, formConfig.conditionals);
}
if (
allConditionTypes.includes(ConditionType.ENABLE) ||
allConditionTypes.includes(ConditionType.DISABLE)
) {
conditionTypes.enabled = true;
merge(conditionals, formConfig.conditionals);
}
if (allConditionTypes.includes(ConditionType.FETCH_DYNAMIC_VALUES)) {
const dynamicValues: DynamicValues = {
allowedToFetch: false,
isLoading: false,
hasStarted: false,
hasFetchFailed: false,
data: [],
config: {
url: formConfig.conditionals.fetchDynamicValues.url,
method: formConfig.conditionals.fetchDynamicValues.method,
params: formConfig.conditionals.fetchDynamicValues.params,
},
};
conditionTypes.fetchDynamicValues = dynamicValues;
conditionals.fetchDynamicValues =
formConfig.conditionals.fetchDynamicValues.condition;
}
// Conditionals are stored in the eval state itself for quick access
finalEvalObj[key] = {
...conditionTypes,
conditionals,
};
}
if ("children" in formConfig && !!formConfig.children)
formConfig.children.forEach((config: FormConfig) =>
generateInitialEvalState(config),
);
if ("schema" in formConfig && !!formConfig.schema)
formConfig.schema.forEach((config: FormConfig) =>
generateInitialEvalState({ ...config }),
);
};
// Function to run the eval for the whole form when data changes
function evaluate(
actionConfiguration: ActionConfig,
currentEvalState: FormEvalOutput,
) {
Object.keys(currentEvalState).forEach((key: string) => {
try {
if (currentEvalState[key].hasOwnProperty("conditionals")) {
const conditionBlock = currentEvalState[key].conditionals;
if (!!conditionBlock) {
Object.keys(conditionBlock).forEach((conditionType: string) => {
const output = eval(conditionBlock[conditionType]);
if (conditionType === ConditionType.HIDE) {
currentEvalState[key].visible = !output;
} else if (conditionType === ConditionType.SHOW) {
currentEvalState[key].visible = output;
} else if (conditionType === ConditionType.DISABLE) {
currentEvalState[key].enabled = !output;
} else if (conditionType === ConditionType.ENABLE) {
currentEvalState[key].enabled = output;
} else if (
conditionType === ConditionType.FETCH_DYNAMIC_VALUES &&
currentEvalState[key].hasOwnProperty("fetchDynamicValues") &&
!!currentEvalState[key].fetchDynamicValues
) {
(currentEvalState[key]
.fetchDynamicValues as DynamicValues).allowedToFetch = output;
(currentEvalState[key]
.fetchDynamicValues as DynamicValues).isLoading = output;
}
});
}
}
} catch (e) {}
});
return currentEvalState;
}
// Fetches current evaluation and runs a new one based on the new data
function getFormEvaluation(
formId: string,
actionConfiguration: ActionConfig,
currentEvalState: FormEvaluationState,
): FormEvaluationState {
// Only change the form evaluation state if the form ID is same or the evaluation state is present
if (!!currentEvalState && currentEvalState.hasOwnProperty(formId)) {
currentEvalState[formId] = evaluate(
actionConfiguration,
currentEvalState[formId],
);
}
return currentEvalState;
}
// Filter function to assign a function to the Action dispatched
export function setFormEvaluationSaga(
type: string,
payload: FormEvalActionPayload,
currentEvalState: FormEvaluationState,
) {
if (type === ReduxActionTypes.INIT_FORM_EVALUATION) {
finalEvalObj = {};
// Config is extracted from the editor json first
if (
"editorConfig" in payload &&
!!payload.editorConfig &&
payload.editorConfig.length > 0
) {
payload.editorConfig.forEach((config: FormConfig) => {
generateInitialEvalState(config);
});
}
// Then the form config is extracted from the settings json
if (
"settingConfig" in payload &&
!!payload.settingConfig &&
payload.settingConfig.length > 0
) {
payload.settingConfig.forEach((config: FormConfig) => {
generateInitialEvalState(config);
});
}
// if the evaluations are empty, then the form is not valid, don't mutate the state
if (isEmpty(finalEvalObj)) {
return currentEvalState;
}
// This is the initial evaluation state, evaluations can now be run on top of this
return { [payload.formId]: finalEvalObj };
} else {
const { actionConfiguration, formId } = payload;
// In case the formData is not ready or the form is not of type UQI, return empty state
if (!actionConfiguration || !actionConfiguration.formData) {
return currentEvalState;
} else {
return getFormEvaluation(formId, actionConfiguration, currentEvalState);
}
}
}