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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 1037x 117x 117x 117x 33x 117x 117x 117x 111x 117x 117x 117x 203x 117x 117x 117x 271x 271x 271x 271x 271x 271x 117x 117x 117x 117x 117x 117x 117x 11x 11x 11x 22x 22x 20x 9x 9x 7x 7x 7x 11x 11x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 117x 8x 8x 117x 117x 117x 117x 117x 117x 117x 117x 117x 123x 123x 123x 123x 117x 117x 117x 117x 117x 117x | /**
* Shared helpers and constants for router category handling.
*
* This module is the single source of truth for:
* - reserved category names (forbidden to users across all node types)
* - system category names (auto-generated; filtered from user-editable rules)
* - case-insensitive category name comparison and lookup
*/
/**
* Names reserved for system-generated categories. Users may not create
* categories whose names collide with these (matching is case-insensitive).
*/
export const RESERVED_CATEGORY_NAMES = [
'Other',
'All Responses',
'No Response',
'Failure',
'Success',
'Timeout'
] as const;
/**
* Categories that are auto-generated by the system and should be filtered out
* when converting router data back into user-editable form rules.
*/
export const SYSTEM_CATEGORY_NAMES = [
'Other',
'All Responses',
'No Response',
'Timeout'
] as const;
/**
* System categories permitted to be localized even on nodes that otherwise
* block translation of system categories.
*/
export const SYSTEM_CATEGORIES_ALLOWED_FOR_TRANSLATION: ReadonlySet<string> =
new Set(['Other', 'No Response']);
const RESERVED_LOWER = new Set<string>(
RESERVED_CATEGORY_NAMES.map((n) => n.toLowerCase())
);
const SYSTEM_LOWER = new Set<string>(
SYSTEM_CATEGORY_NAMES.map((n) => n.toLowerCase())
);
const normalize = (name: string | undefined | null): string =>
(name || '').trim().toLowerCase();
/** Case-insensitive check for reserved category names. */
export const isReservedCategoryName = (name: string): boolean =>
RESERVED_LOWER.has(normalize(name));
/** Case-insensitive check for auto-generated system categories. */
export const isSystemCategory = (name: string): boolean =>
SYSTEM_LOWER.has(normalize(name));
/** Case-insensitive equality test for two category names. */
export const categoryNamesEqual = (a: string, b: string): boolean =>
normalize(a) === normalize(b);
/** Case-insensitive lookup of a category by name. */
export const findCategoryByName = <T extends { name: string }>(
categories: T[],
name: string
): T | undefined => {
const target = normalize(name);
return categories.find((cat) => normalize(cat.name) === target);
};
/**
* Returns the subset of the provided names that collide with a reserved
* category name. Originals (with their casing) are preserved in the output;
* duplicates are de-duplicated.
*/
export const findReservedNames = (names: string[]): string[] => {
const seen = new Set<string>();
const result: string[] = [];
for (const name of names) {
const trimmed = (name || '').trim();
if (!trimmed) continue;
if (!isReservedCategoryName(trimmed)) continue;
const key = trimmed.toLowerCase();
if (seen.has(key)) continue;
seen.add(key);
result.push(trimmed);
}
return result;
};
/**
* Form fields whose entries become router category names. Kept in one place so
* the reserved-name validator can be applied uniformly across node types.
*
* - categories[].name (split_by_random)
* - groups[].name (split_by_groups — the group name becomes the
* category name, so a group literally named "Other"
* must be rejected)
* - rules[].category (split_by_expression, wait_for_response)
*/
const CATEGORY_FIELD_SHAPES: Array<{
fieldName: string;
getName: (item: any) => unknown;
}> = [
{ fieldName: 'categories', getName: (item) => item?.name },
{ fieldName: 'groups', getName: (item) => item?.name },
{ fieldName: 'rules', getName: (item) => item?.category }
];
/**
* Scans form data for user-authored category names that collide with reserved
* system names, returning an `errors` map keyed by form field name. Checks all
* known category-bearing field shapes so callers don't have to know which one
* a given node uses.
*/
export const collectReservedCategoryErrors = (formData: {
[key: string]: any;
}): { [fieldName: string]: string } => {
const errors: { [fieldName: string]: string } = {};
for (const { fieldName, getName } of CATEGORY_FIELD_SHAPES) {
const value = formData[fieldName];
if (!Array.isArray(value)) continue;
const names = value
.map((item) => getName(item))
.filter((name): name is string => typeof name === 'string');
const reservedUsed = findReservedNames(names);
if (reservedUsed.length > 0) {
errors[fieldName] =
`Reserved category names cannot be used: ${reservedUsed.join(', ')}`;
}
}
return errors;
};
|