PromucFlow_constructor/app/client/src/workers/validations.ts
2021-03-05 00:28:43 +05:30

860 lines
22 KiB
TypeScript

import {
ISO_DATE_FORMAT,
VALIDATION_TYPES,
ValidationResponse,
ValidationType,
Validator,
} from "../constants/WidgetValidation";
import { DataTree } from "../entities/DataTree/dataTreeFactory";
import _, {
every,
isBoolean,
isNumber,
isObject,
isPlainObject,
isString,
isUndefined,
toNumber,
toString,
} from "lodash";
import { WidgetProps } from "../widgets/BaseWidget";
import { WIDGET_TYPE_VALIDATION_ERROR } from "../constants/messages";
import moment from "moment";
export const VALIDATORS: Record<ValidationType, Validator> = {
[VALIDATION_TYPES.TEXT]: (value: any): ValidationResponse => {
let parsed = value;
if (isUndefined(value) || value === null) {
return {
isValid: true,
parsed: value,
message: "",
};
}
if (isObject(value)) {
return {
isValid: false,
parsed: JSON.stringify(value, null, 2),
message: `${WIDGET_TYPE_VALIDATION_ERROR}: text`,
};
}
let isValid = isString(value);
if (!isValid) {
try {
parsed = toString(value);
isValid = true;
} catch (e) {
console.error(`Error when parsing ${value} to string`);
console.error(e);
return {
isValid: false,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: text`,
};
}
}
return { isValid, parsed };
},
[VALIDATION_TYPES.REGEX]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed, message } = VALIDATORS[VALIDATION_TYPES.TEXT](
value,
props,
dataTree,
);
if (isValid) {
try {
new RegExp(parsed);
} catch (e) {
return {
isValid: false,
parsed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: regex`,
};
}
}
return { isValid, parsed, message };
},
[VALIDATION_TYPES.NUMBER]: (value: any): ValidationResponse => {
let parsed = value;
if (isUndefined(value)) {
return {
isValid: false,
parsed: 0,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
};
}
let isValid = isNumber(value);
if (!isValid) {
try {
parsed = toNumber(value);
if (isNaN(parsed)) {
return {
isValid: false,
parsed: 0,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
};
}
isValid = true;
} catch (e) {
console.error(`Error when parsing ${value} to number`);
console.error(e);
return {
isValid: false,
parsed: 0,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
};
}
}
return { isValid, parsed };
},
[VALIDATION_TYPES.BOOLEAN]: (value: any): ValidationResponse => {
let parsed = value;
if (isUndefined(value)) {
return {
isValid: false,
parsed: false,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
};
}
const isABoolean = isBoolean(value);
const isStringTrueFalse = value === "true" || value === "false";
const isValid = isABoolean || isStringTrueFalse;
if (isStringTrueFalse) parsed = value !== "false";
if (!isValid) {
return {
isValid: isValid,
parsed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.OBJECT]: (value: any): ValidationResponse => {
let parsed = value;
if (isUndefined(value)) {
return {
isValid: false,
parsed: {},
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Object`,
};
}
let isValid = isObject(value);
if (!isValid) {
try {
parsed = JSON.parse(value);
isValid = true;
} catch (e) {
console.error(`Error when parsing ${value} to object`);
console.error(e);
return {
isValid: false,
parsed: {},
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Object`,
};
}
}
return { isValid, parsed };
},
[VALIDATION_TYPES.ARRAY]: (value: any): ValidationResponse => {
let parsed = value;
try {
if (isUndefined(value)) {
return {
isValid: false,
parsed: [],
transformed: undefined,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
};
}
if (isString(value)) {
parsed = JSON.parse(parsed as string);
}
if (!Array.isArray(parsed)) {
return {
isValid: false,
parsed: [],
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
};
}
return { isValid: true, parsed, transformed: parsed };
} catch (e) {
console.error(e);
return {
isValid: false,
parsed: [],
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
};
}
},
[VALIDATION_TYPES.TABS_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Tabs Data`,
};
} else if (
!every(
parsed,
(datum: {
id: string;
label: string;
widgetId: string;
isVisible?: boolean;
}) =>
isObject(datum) &&
!isUndefined(datum.id) &&
!isUndefined(datum.label) &&
!isUndefined(datum.widgetId),
)
) {
return {
isValid: false,
parsed: [],
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Tabs Data`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.TABLE_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, transformed, parsed } = VALIDATORS.ARRAY(
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed: [],
transformed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "Col1" : "val1", "Col2" : "val2" }]`,
};
}
const isValidTableData = every(parsed, (datum) => {
return (
isPlainObject(datum) &&
Object.keys(datum).filter((key) => isString(key) && key.length === 0)
.length === 0
);
});
if (!isValidTableData) {
return {
isValid: false,
parsed: [],
transformed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "Col1" : "val1", "Col2" : "val2" }]`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.CHART_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Chart Data`,
};
}
let validationMessage = "";
let index = 0;
const parsedChartData = [];
let isValidChart = true;
for (const seriesData of parsed) {
let isValidSeries = false;
try {
const validatedResponse: {
isValid: boolean;
parsed: Array<unknown>;
message?: string;
} = VALIDATORS[VALIDATION_TYPES.ARRAY](
seriesData.data,
props,
dataTree,
);
if (validatedResponse.isValid) {
isValidSeries = every(
validatedResponse.parsed,
(chartPoint: { x: string; y: any }) => {
return (
isObject(chartPoint) &&
isString(chartPoint.x) &&
!isUndefined(chartPoint.y)
);
},
);
}
if (!isValidSeries) {
isValidChart = false;
parsedChartData.push({
...seriesData,
data: [],
});
validationMessage = `${index}##${WIDGET_TYPE_VALIDATION_ERROR}: [{ "x": "val", "y": "val" }]`;
} else {
parsedChartData.push({
...seriesData,
data: validatedResponse.parsed,
});
}
} catch (e) {
console.error(e);
}
index++;
}
if (!isValidChart) {
return {
isValid: false,
parsed: parsedChartData,
transformed: parsed,
message: validationMessage,
};
}
return { isValid, parsed: parsedChartData, transformed: parsedChartData };
},
[VALIDATION_TYPES.MARKERS]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Marker Data`,
};
} else if (
!every(
parsed,
(datum) => VALIDATORS[VALIDATION_TYPES.LAT_LONG](datum, props).isValid,
)
) {
return {
isValid: false,
parsed: [],
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Marker Data`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.OPTIONS_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Options Data`,
};
}
try {
const isValidOption = (option: { label: any; value: any }) =>
_.isObject(option) &&
_.isString(option.label) &&
_.isString(option.value) &&
!_.isEmpty(option.label) &&
!_.isEmpty(option.value);
const hasOptions = every(parsed, isValidOption);
const validOptions = parsed.filter(isValidOption);
const uniqValidOptions = _.uniqBy(validOptions, "value");
if (!hasOptions || uniqValidOptions.length !== validOptions.length) {
return {
isValid: false,
parsed: uniqValidOptions,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Options Data`,
};
}
return { isValid, parsed };
} catch (e) {
console.error(e);
return {
isValid: false,
parsed: [],
transformed: parsed,
};
}
},
[VALIDATION_TYPES.DATE]: (
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const dateFormat =
props.version === 2
? ISO_DATE_FORMAT
: props.dateFormat || ISO_DATE_FORMAT;
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
? props.dateFormat
: "",
};
}
const isValid = moment(dateString, dateFormat).isValid();
if (!isValid) {
return {
isValid: isValid,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date`,
};
}
return {
isValid,
parsed: dateString,
message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date`,
};
},
[VALIDATION_TYPES.DEFAULT_DATE]: (
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const dateFormat =
props.version === 2
? ISO_DATE_FORMAT
: props.dateFormat || ISO_DATE_FORMAT;
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + dateFormat
? dateFormat
: "",
};
}
const parsedCurrentDate = moment(dateString, dateFormat);
const isValid = parsedCurrentDate.isValid();
if (!isValid) {
return {
isValid: isValid,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`,
};
}
return {
isValid: isValid,
parsed: dateString,
message: "",
};
},
[VALIDATION_TYPES.MIN_DATE]: (
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const dateFormat =
props.version === 2
? ISO_DATE_FORMAT
: props.dateFormat || ISO_DATE_FORMAT;
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + dateFormat
? dateFormat
: "",
};
}
const parsedMinDate = moment(dateString, dateFormat);
let isValid = parsedMinDate.isValid();
if (!props.defaultDate) {
return {
isValid: isValid,
parsed: dateString,
message: "",
};
}
const parsedDefaultDate = moment(props.defaultDate, dateFormat);
if (
isValid &&
parsedDefaultDate.isValid() &&
parsedDefaultDate.isBefore(parsedMinDate)
) {
isValid = false;
}
if (!isValid) {
return {
isValid: isValid,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`,
};
}
return {
isValid: isValid,
parsed: dateString,
message: "",
};
},
[VALIDATION_TYPES.MAX_DATE]: (
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const dateFormat =
props.version === 2
? ISO_DATE_FORMAT
: props.dateFormat || ISO_DATE_FORMAT;
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + dateFormat
? dateFormat
: "",
};
}
const parsedMaxDate = moment(dateString, dateFormat);
let isValid = parsedMaxDate.isValid();
if (!props.defaultDate) {
return {
isValid: isValid,
parsed: dateString,
message: "",
};
}
const parsedDefaultDate = moment(props.defaultDate, dateFormat);
if (
isValid &&
parsedDefaultDate.isValid() &&
parsedDefaultDate.isAfter(parsedMaxDate)
) {
isValid = false;
}
if (!isValid) {
return {
isValid: isValid,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`,
};
}
return {
isValid: isValid,
parsed: dateString,
message: "",
};
},
[VALIDATION_TYPES.ACTION_SELECTOR]: (value: any): ValidationResponse => {
if (Array.isArray(value) && value.length) {
return {
isValid: true,
parsed: undefined,
transformed: "Function Call",
};
}
/*
if (_.isString(value)) {
if (value.indexOf("navigateTo") !== -1) {
const pageNameOrUrl = modalGetter(value);
if (dataTree) {
if (isDynamicValue(pageNameOrUrl)) {
return {
isValid: true,
parsed: value,
};
}
const isPage =
(dataTree.pageList as PageListPayload).findIndex(
page => page.pageName === pageNameOrUrl,
) !== -1;
const isValidUrl = URL_REGEX.test(pageNameOrUrl);
if (!(isValidUrl || isPage)) {
return {
isValid: false,
parsed: value,
message: `${NAVIGATE_TO_VALIDATION_ERROR}`,
};
}
}
}
}
*/
return {
isValid: false,
parsed: undefined,
transformed: "undefined",
message: "Not a function call",
};
},
[VALIDATION_TYPES.ARRAY_ACTION_SELECTOR]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed, message } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
let isValidFinal = isValid;
let finalParsed = parsed.slice();
if (isValid) {
finalParsed = parsed.map((value: any) => {
const { isValid, message } = VALIDATORS[
VALIDATION_TYPES.ACTION_SELECTOR
](value.dynamicTrigger, props, dataTree);
isValidFinal = isValidFinal && isValid;
return {
...value,
message: message,
isValid: isValid,
};
});
}
return {
isValid: isValidFinal,
parsed: finalParsed,
message: message,
};
},
[VALIDATION_TYPES.SELECTED_TAB]: (
value: any,
props: WidgetProps,
): ValidationResponse => {
const tabs =
props.tabs && isString(props.tabs)
? JSON.parse(props.tabs)
: props.tabs && Array.isArray(props.tabs)
? props.tabs
: [];
const tabNames = tabs.map((i: { label: string; id: string }) => i.label);
const isValidTabName = tabNames.includes(value);
return {
isValid: isValidTabName,
parsed: value,
message: isValidTabName
? ""
: `${WIDGET_TYPE_VALIDATION_ERROR}: Invalid tab name.`,
};
},
[VALIDATION_TYPES.DEFAULT_OPTION_VALUE]: (
value: string | string[],
props: WidgetProps,
dataTree?: DataTree,
) => {
let values = value;
if (props) {
if (props.selectionType === "SINGLE_SELECT") {
return VALIDATORS[VALIDATION_TYPES.TEXT](value, props, dataTree);
} else if (props.selectionType === "MULTI_SELECT") {
if (typeof value === "string") {
try {
values = JSON.parse(value);
if (!Array.isArray(values)) {
throw new Error();
}
} catch {
values = value.length ? value.split(",") : [];
if (values.length > 0) {
values = values.map((value) => value.trim());
}
}
}
}
}
if (Array.isArray(values)) {
values = _.uniq(values);
}
return {
isValid: true,
parsed: values,
};
},
[VALIDATION_TYPES.DEFAULT_SELECTED_ROW]: (
value: string | string[],
props: WidgetProps,
) => {
let values = value;
if (props) {
if (props.multiRowSelection) {
if (typeof value === "string") {
try {
values = JSON.parse(value);
if (!Array.isArray(values)) {
throw new Error();
}
} catch {
values = value.length ? value.split(",") : [];
if (values.length > 0) {
let numericValues = values.map((value) => {
return isNumber(value.trim()) ? -1 : Number(value.trim());
});
numericValues = _.uniq(numericValues);
return {
isValid: true,
parsed: numericValues,
};
}
}
}
} else {
try {
if (value === "") {
return {
isValid: true,
parsed: -1,
};
}
const parsed = toNumber(value);
return {
isValid: true,
parsed: parsed,
};
} catch (e) {
return {
isValid: true,
parsed: -1,
};
}
}
}
return {
isValid: true,
parsed: values,
};
},
[VALIDATION_TYPES.COLUMN_PROPERTIES_ARRAY]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
) => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
transformed: parsed,
message: "",
};
}
const isValidProperty = (data: any) =>
isString(data) || isNumber(data) || isBoolean(data);
const isValidColumns = every(parsed, (datum: any) => {
const validatedResponse: {
isValid: boolean;
parsed: Record<string, unknown>;
message?: string;
} = VALIDATORS[VALIDATION_TYPES.OBJECT](datum, props, dataTree);
const isValidColumn = validatedResponse.isValid;
if (isValidColumn) {
for (const key in validatedResponse.parsed) {
const columnProperty = validatedResponse.parsed[key];
let isValidColumnProperty = true;
if (Array.isArray(columnProperty)) {
isValidColumnProperty = every(columnProperty, (data: any) => {
return isValidProperty(data);
});
} else if (!isObject(columnProperty)) {
isValidColumnProperty = isValidProperty(columnProperty);
}
if (!isValidColumnProperty) {
validatedResponse.parsed[key] = "";
}
}
}
return isValidColumn;
});
if (!isValidColumns) {
return {
isValid: isValidColumns,
parsed: [],
transformed: parsed,
message: "",
};
}
return { isValid, parsed, transformed: parsed };
},
[VALIDATION_TYPES.LAT_LONG]: (unparsedValue: {
lat?: number;
long?: number;
[x: string]: any;
}): ValidationResponse => {
let value = unparsedValue;
const invalidResponse = {
isValid: false,
parsed: undefined,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: { lat: number, long: number }`,
};
if (isString(unparsedValue)) {
try {
value = JSON.parse(unparsedValue);
} catch (e) {
console.error(`Error when parsing string as object`);
}
}
const { lat, long } = value || {};
const validLat = typeof lat === "number" && lat <= 90 && lat >= -90;
const validLong = typeof long === "number" && long <= 180 && long >= -180;
if (!validLat || !validLong) {
return invalidResponse;
}
return {
isValid: true,
parsed: value,
};
},
};