import React, { useEffect, useState } from "react"; import { QueryBuilder } from "react-querybuilder"; import "react-querybuilder/dist/query-builder.css"; import { QueryBuilderDnD } from "@react-querybuilder/dnd"; import * as ReactDnD from "react-dnd"; import * as ReactDndHtml5Backend from "react-dnd-html5-backend"; import { Button, ButtonGroup } from "reactstrap"; import cx from "classnames"; import { uniqueId } from "lodash"; import ToastUtil from "@common-utils/ToastUtil"; import { DUPLICATE_CONFIG_ERROR_MSG } from "./constants"; //P.S, the component is not developed for nested rules const OfferRuleCreation = ({ preExistingRule = null, offerConfigs = [], offerRules, setOfferRules, closeSection }) => { const initialQueryState = { combinator: "and", rules: [] }; const [query, setQuery] = useState(initialQueryState); const [readableJSON, setReadableJSON] = useState(""); const [disableSave, setDisableSave] = useState(false); useEffect(() => { preExistingRule && updateQueryIfRuleAlreadyPresent(preExistingRule); }, []); useEffect(() => { fetchReadableJSONQuery(); }, [query]); const updateQueryIfRuleAlreadyPresent = () => { // console.log({ preExistingRule: preExistingRule }); setQuery({ ...getJSONQuery(preExistingRule) }); setReadableJSON(preExistingRule.readable); }; const getJSONQuery = preExistingRule => { //this needs to be changed for edit rule part if nested rules are added const { rule } = preExistingRule; const formattedRules = Object.keys(rule).map(ruleConfig => ({ field: ruleConfig, id: uniqueId("config"), //uniqueID for config rendering operator: "=", valueSource: "value", value: rule[ruleConfig].toString() })); return { combinator: "and", rules: [...formattedRules] }; }; const fetchReadableJSONQuery = () => { if (!isValidQuery(query)) return; const queryInJSONFormat = convertQueryToJSON(query); const readableString = ruleToReadableFormat(queryInJSONFormat); setReadableJSON(readableString); }; const checkIfValidConfigInput = rule => { const ruleConfig = offerConfigs.find(config => config.name === rule.field); let isValid = true; if (ruleConfig.inputValidation && rule.value !== "") { let { inputValidation, replacingValidation = "" } = ruleConfig; if (!validRegex(inputValidation) && !validRegex(replacingValidation)) return; inputValidation = new RegExp(inputValidation); if (!inputValidation.test(rule.value)) { if (replacingValidation !== "") { replacingValidation = stringToRegex(replacingValidation); const newVal = rule.value.replace(replacingValidation, ""); if (rule.value !== newVal) { const updatedQuery = updateRuleValueById(rule.id, newVal, query); setQuery(updatedQuery); ToastUtil.error(ruleConfig.errorToastMessage, "VALIDATION_ERROR"); } } } } return isValid; }; const validRegex = str => { let isValid = true; try { new RegExp(str); } catch (e) { isValid = false; console.error(str + " is not a valid regex"); } return isValid; }; //https://stackoverflow.com/questions/874709/converting-user-input-string-to-regular-expression const stringToRegex = str => { // Main regex const main = str.match(/\/(.+)\/.*/)[1]; // Regex options const options = str.match(/\/.+\/(.*)/)[1]; // Compiled regex return new RegExp(main, options); }; const updateRuleValueById = (id, newValue, currentQuery) => { //as only one level of query can be added return { ...currentQuery, rules: currentQuery.rules.map(rule => (rule.id === id ? { ...rule, value: newValue } : rule)) }; }; const isValidQuery = query => { let isValid = true; const { combinator, rules } = query; if (combinator && rules && rules.length >= 1 && rules.map(rule => checkIfValidConfigInput(rule))) { checkIfRepeatedConfigs(rules); rules.map(rule => { if (rule.hasOwnProperty("combinator")) { isValid = isValidQuery(rule); } }); } else { isValid = false; } return isValid; }; const checkIfRepeatedConfigs = rules => { let isRepeated = false; const configNames = []; rules.map(rule => { if (!rule.hasOwnProperty("combinator")) { if (configNames.includes(rule.field)) { isRepeated = true; } else { configNames.push(rule.field); } } }); if (isRepeated) ToastUtil.error(`${DUPLICATE_CONFIG_ERROR_MSG}`, "INVALID_RULE"); setDisableSave(isRepeated); }; const convertQueryToJSON = (jsonQuery = query) => { const COMBINATOR_LABELS = { and: "AND", or: "OR" //in offer scenario there is only "AND" combinator }; const { combinator, rules } = jsonQuery; // console.log({ combinator, rules }); //edge case, when only single rule is in the group if (rules.length === 1) { const config = offerConfigs.find(config => config.name === rules[0].field); const value = config.tagType.includes("List") ? rules[0].value .split(",") //check if List and send in array format .map(val => val.trim()) .filter(val => val !== "") : rules[0].value; return { [rules[0].field]: value }; } const queryInJSONFormat = { [COMBINATOR_LABELS[combinator]]: [ ...rules.map(rule => { if (rule.hasOwnProperty("combinator")) { return convertQueryToJSON(rule); } const config = offerConfigs.find(config => config.name === rule.field); const value = config.tagType.includes("List") ? rule.value .split(",") //check if List and send in array format .map(val => val.trim()) .filter(val => val !== "") : rule.value; return { [rule.field]: value }; }) ] }; return queryInJSONFormat; }; const ruleToReadableFormat = rules => { let readableString = ""; let newRule = {}; if (rules["AND"]) { newRule = rules["AND"]; newRule.map((rulePair, index) => { const key = Object.keys(rulePair)[0]; const value = JSON.stringify(rulePair[key]); readableString += `(${key} == ${value})`; if (index !== Object.keys(newRule).length - 1) readableString += " && "; }); } else { newRule = rules; Object.keys(newRule).map((rulePair, index) => { const value = JSON.stringify(newRule[rulePair]); readableString += `(${rulePair} == ${value})`; if (index !== Object.keys(newRule).length - 1) readableString += " && "; }); } // console.log({ rules, readableString, newRule }); return readableString; }; const saveRule = () => { const rulesInJSONFormat = convertQueryToJSON(query); let newRule = {}; if (rulesInJSONFormat["AND"]) { Object.keys(rulesInJSONFormat["AND"]).map(ruleIndex => { newRule = { ...newRule, ...rulesInJSONFormat["AND"][ruleIndex] }; }); } else { newRule = rulesInJSONFormat; } setOfferRules( { ruleId: preExistingRule ? preExistingRule.ruleId : offerRules.length, rule: { ...newRule }, readable: readableJSON }, preExistingRule ? preExistingRule.ruleId : null ); closeSection(); }; return ( <>
{readableJSON !== "" &&
{`Readable Rule : ${readableJSON}`}
} ( <>
{props.fieldData?.operatorLabel}
{props.rule?.value === "" && props.fieldData?.exampleMessage && ( {props.fieldData?.exampleMessage} )} ), addGroupAction: () => null, addRuleAction: props => ( ), removeRuleAction: props => ( ), removeGroupAction: () => null, combinatorSelector: props => { return ( ); } }} style={{ border: "1px solid #ccc", borderRadius: "0.25rem", padding: "10px" }} showCombinatorsBetweenRules query={query} onQueryChange={newQuery => setQuery(newQuery)} />
); }; export default OfferRuleCreation;