testConditions.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import cloneDeep from 'lodash.clonedeep';
  2. import objectPath from 'object-path';
  3. import { parseJSON } from '@/utils/helper';
  4. import { conditionBuilder } from '@/utils/shared';
  5. import renderString from '../templating/renderString';
  6. const isBoolStr = (str) => {
  7. if (str === 'true') return true;
  8. if (str === 'false') return false;
  9. return str;
  10. };
  11. const isNumStr = (str) => (Number.isNaN(+str) ? str : +str);
  12. const comparisons = {
  13. eq: (a, b) => a === b,
  14. eqi: (a, b) => a?.toLocaleLowerCase() === b?.toLocaleLowerCase(),
  15. nq: (a, b) => a !== b,
  16. gt: (a, b) => isNumStr(a) > isNumStr(b),
  17. gte: (a, b) => isNumStr(a) >= isNumStr(b),
  18. lt: (a, b) => isNumStr(a) < isNumStr(b),
  19. lte: (a, b) => isNumStr(a) <= isNumStr(b),
  20. cnt: (a, b) => a?.includes(b) ?? false,
  21. cni: (a, b) =>
  22. a?.toLocaleLowerCase().includes(b?.toLocaleLowerCase()) ?? false,
  23. nct: (a, b) => !comparisons.cnt(a, b),
  24. nci: (a, b) => !comparisons.cni(a, b),
  25. stw: (a, b) => a?.startsWith(b) ?? false,
  26. enw: (a, b) => a?.endsWith(b) ?? false,
  27. rgx: (a, b) => {
  28. const match = b.match(/^\/(.*?)\/([gimy]*)$/);
  29. const regex = match ? new RegExp(match[1], match[2]) : new RegExp(b);
  30. return regex.test(a);
  31. },
  32. itr: (a) => Boolean(isBoolStr(a)),
  33. ifl: (a) => !isBoolStr(a),
  34. };
  35. const convertDataType = {
  36. string: (val) => `${val}`,
  37. number: (val) => +val,
  38. json: (val) => parseJSON(val, null),
  39. boolean: (val) => Boolean(isBoolStr(val)),
  40. };
  41. export default async function (conditionsArr, workflowData) {
  42. const result = {
  43. isMatch: false,
  44. replacedValue: {},
  45. };
  46. async function getConditionItemValue({ type, data }) {
  47. if (type.startsWith('data')) {
  48. let dataPath = data.dataPath.trim().replace('@', '.');
  49. const isInsideBrackets =
  50. dataPath.startsWith('{{') && dataPath.endsWith('}}');
  51. if (isInsideBrackets) {
  52. dataPath = dataPath.slice(2, -2).trim();
  53. }
  54. return objectPath.has(workflowData.refData, dataPath);
  55. }
  56. const copyData = cloneDeep(data);
  57. for (const key of Object.keys(data)) {
  58. const { value, list } = await renderString(
  59. copyData[key],
  60. workflowData.refData,
  61. workflowData.isPopup
  62. );
  63. copyData[key] = parseJSON(value, value);
  64. Object.assign(result.replacedValue, list);
  65. }
  66. if (type === 'value') {
  67. const regex = /^(json|string|number|boolean)::/;
  68. if (regex.test(copyData.value)) {
  69. const [dataType, value] = copyData.value.split(/::(.*)/s);
  70. return convertDataType[dataType](value);
  71. }
  72. return copyData.value;
  73. }
  74. if (type.startsWith('code')) {
  75. let conditionValue;
  76. const newRefData = {};
  77. Object.keys(workflowData.refData).forEach((keyword) => {
  78. if (!copyData.code.includes(keyword)) return;
  79. newRefData[keyword] = workflowData.refData[keyword];
  80. });
  81. if (workflowData.isMV2 && data.context !== 'background') {
  82. conditionValue = await workflowData.sendMessage({
  83. type: 'condition-builder',
  84. data: {
  85. type,
  86. data: copyData,
  87. refData: newRefData,
  88. },
  89. });
  90. } else {
  91. conditionValue = await workflowData.checkCodeCondition({
  92. data: copyData,
  93. refData: newRefData,
  94. isPopup: workflowData.isPopup,
  95. });
  96. }
  97. return conditionValue;
  98. }
  99. if (type.startsWith('element')) {
  100. const conditionValue = await workflowData.sendMessage({
  101. type: 'condition-builder',
  102. data: {
  103. type,
  104. data: copyData,
  105. },
  106. });
  107. return conditionValue;
  108. }
  109. return '';
  110. }
  111. async function checkConditions(items) {
  112. let conditionResult = true;
  113. const condition = {
  114. value: '',
  115. operator: '',
  116. };
  117. for (const { category, data, type } of items) {
  118. if (!conditionResult) return conditionResult;
  119. if (category === 'compare') {
  120. const { needValue } = conditionBuilder.compareTypes.find(
  121. ({ id }) => id === type
  122. );
  123. if (!needValue) {
  124. conditionResult = comparisons[type](condition.value);
  125. return conditionResult;
  126. }
  127. condition.operator = type;
  128. } else if (category === 'value') {
  129. const conditionValue = await getConditionItemValue({ data, type });
  130. const { compareable } = conditionBuilder.valueTypes.find(
  131. ({ id }) => id === type
  132. );
  133. if (!compareable) {
  134. conditionResult = conditionValue;
  135. } else if (condition.operator) {
  136. conditionResult = comparisons[condition.operator](
  137. condition.value,
  138. conditionValue
  139. );
  140. condition.operator = '';
  141. }
  142. condition.value = conditionValue;
  143. }
  144. }
  145. return conditionResult;
  146. }
  147. for (const { conditions } of conditionsArr) {
  148. if (result.isMatch) return result;
  149. let isAllMatch = false;
  150. for (const { items } of conditions) {
  151. isAllMatch = await checkConditions(items, workflowData);
  152. if (!isAllMatch) break;
  153. }
  154. result.isMatch = isAllMatch;
  155. }
  156. return result;
  157. }