All files / src/flow/nodes split_by_random.ts

66.66% Statements 72/108
43.75% Branches 7/16
100% Functions 2/2
66.66% Lines 72/108

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 10998x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 184x 184x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x                                                                         98x 12x 12x 12x 12x 12x 12x 12x 12x 12x 98x 98x 98x 98x 109x 97x 109x 98x 98x 98x 97x 98x 98x 98x 98x 98x 98x 98x 98x 98x 109x 109x 98x 98x 98x 98x 98x 98x 98x  
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
        }
      }
    }
  },
  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'
};