All files / src/flow/nodes split_by_random.ts

66.97% Statements 73/109
43.75% Branches 7/16
100% Functions 2/2
66.97% Lines 73/109

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 208x 208x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x 132x                                                                         132x 12x 12x 12x 12x 12x 12x 12x 12x 12x 132x 132x 132x 132x 143x 131x 143x 132x 132x 132x 131x 132x 132x 132x 132x 132x 132x 132x 132x 132x 143x 143x 132x 132x 132x 132x 132x 132x 132x  
import { SPLIT_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
import { Node } from '../../store/flow-definition.d';
import { validateWith } from '../utils';
import { buildCategoriesExitsCases } from './shared';
 
export const split_by_random: NodeConfig = {
  type: 'split_by_random',
  name: 'Random Split',
  group: SPLIT_GROUPS.split,
  flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],
  form: {
    categories: {
      type: 'array',
      helpText: 'Define the buckets to randomly split contacts into',
      required: true,
      itemLabel: 'Bucket',
      sortable: true,
      minItems: 2,
      maxItems: 10,
      isEmptyItem: (item: any) => {
        return !item.name || item.name.trim() === '';
      },
      itemConfig: {
        name: {
          type: 'text',
          placeholder: 'Bucket name',
          required: true,
          maxLength: 36
        }
      }
    }
  },
  layout: ['categories'],
  validate: validateWith((formData, errors) => {
    if (!formData.categories || !Array.isArray(formData.categories)) return;

    const categories = formData.categories.filter(
      (item: any) => item?.name && item.name.trim() !== ''
    );

    if (categories.length < 2) {
      errors.categories = 'At least 2 buckets are required for random split';
    }

    const duplicateCategories: string[] = [];
    const lowerCaseMap = new Map<string, string[]>();

    categories.forEach((category) => {
      const lowerName = category.name.trim().toLowerCase();
      if (!lowerCaseMap.has(lowerName)) {
        lowerCaseMap.set(lowerName, []);
      }
      lowerCaseMap.get(lowerName).push(category.name.trim());
    });

    lowerCaseMap.forEach((originalNames) => {
      if (originalNames.length > 1) {
        duplicateCategories.push(...originalNames);
      }
    });

    if (duplicateCategories.length > 0) {
      const uniqueDuplicates = [...new Set(duplicateCategories)];
      errors.categories = `Duplicate bucket names found: ${uniqueDuplicates.join(
        ', '
      )}`;
    }
  }),
  toFormData: (node: Node) => {
    // Extract categories from the existing node structure
    const categories =
      node.router?.categories?.map((cat) => ({ name: cat.name })) || [];
 
    return {
      uuid: node.uuid,
      categories: categories
    };
  },
  fromFormData: (formData: FormData, originalNode: Node): Node => {
    const userCategories = (formData.categories || [])
      .filter((item: any) => item?.name?.trim())
      .map((item: any) => item.name.trim());
 
    const existingCategories = originalNode.router?.categories || [];
    const existingExits = originalNode.exits || [];
 
    const { categories, exits } = buildCategoriesExitsCases(
      userCategories.map((name) => ({ name })),
      existingCategories,
      existingExits
    );
 
    return {
      uuid: originalNode.uuid,
      actions: originalNode.actions || [],
      router: {
        type: 'random',
        categories
      },
      exits
    };
  },
  router: {
    type: 'random'
  },
 
  // Localization support for categories
  localizable: 'categories'
};