@escalated-dev/escalated 0.11.5 → 0.11.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/pages.json +766 -3
- package/src/components/WorkflowActionConfig.vue +73 -174
- package/src/components/WorkflowActionList.vue +65 -106
- package/src/components/WorkflowConditionBuilder.vue +56 -48
- package/src/components/WorkflowTriggerSelector.vue +34 -38
- package/src/pages/Admin/Workflows/Builder.vue +24 -10
- package/src/pages/Admin/Workflows/Form.vue +21 -9
- package/src/pages/Admin/Workflows/Index.vue +28 -22
- package/src/utils/workflowContract.js +143 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Workflows admin wire format, as fixed in escalated-developer-context
|
|
3
|
+
* `domain-model/workflow-admin-contract.md`.
|
|
4
|
+
*
|
|
5
|
+
* Every backend engine reads `trigger_event` with dotted names, conditions as
|
|
6
|
+
* `{all: [...]}` or `{any: [...]}`, and actions as `{type, value}`. The builder
|
|
7
|
+
* used to send `trigger: 'ticket_created'`, `{match, conditions}` and
|
|
8
|
+
* `{type, config}`, none of which any backend reads. The older shapes are still
|
|
9
|
+
* read here, so a workflow saved that way, or stored by Laravel as
|
|
10
|
+
* `{match, rules}`, opens in the builder and is saved back in the contract shape.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_TRIGGER_EVENTS = [
|
|
14
|
+
{ value: 'ticket.created', label: 'Ticket Created' },
|
|
15
|
+
{ value: 'ticket.updated', label: 'Ticket Updated' },
|
|
16
|
+
{ value: 'ticket.assigned', label: 'Ticket Assigned' },
|
|
17
|
+
{ value: 'ticket.status_changed', label: 'Status Changed' },
|
|
18
|
+
{ value: 'reply.created', label: 'Reply Added' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export const CORE_ACTION_TYPES = [
|
|
22
|
+
{ value: 'change_status', label: 'Change Status' },
|
|
23
|
+
{ value: 'change_priority', label: 'Change Priority' },
|
|
24
|
+
{ value: 'add_tag', label: 'Add Tag' },
|
|
25
|
+
{ value: 'remove_tag', label: 'Remove Tag' },
|
|
26
|
+
{ value: 'set_department', label: 'Set Department' },
|
|
27
|
+
{ value: 'assign_agent', label: 'Assign Agent' },
|
|
28
|
+
{ value: 'add_note', label: 'Add Internal Note' },
|
|
29
|
+
{ value: 'insert_canned_reply', label: 'Insert Canned Reply' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const DEFAULT_OPERATORS = [
|
|
33
|
+
'equals',
|
|
34
|
+
'not_equals',
|
|
35
|
+
'contains',
|
|
36
|
+
'not_contains',
|
|
37
|
+
'starts_with',
|
|
38
|
+
'ends_with',
|
|
39
|
+
'greater_than',
|
|
40
|
+
'less_than',
|
|
41
|
+
'greater_or_equal',
|
|
42
|
+
'less_or_equal',
|
|
43
|
+
'is_empty',
|
|
44
|
+
'is_not_empty',
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const ACRONYMS = { sla: 'SLA' };
|
|
48
|
+
|
|
49
|
+
/** `ticket.status_changed` -> `Ticket Status Changed`. */
|
|
50
|
+
export function humanize(value) {
|
|
51
|
+
return String(value ?? '')
|
|
52
|
+
.split(/[._\s]+/)
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.map((word) => ACRONYMS[word.toLowerCase()] ?? word.charAt(0).toUpperCase() + word.slice(1))
|
|
55
|
+
.join(' ');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A backend option list in any of the shapes the contract allows, as
|
|
60
|
+
* `[{value, label}]`: a list of `{value, label}`, a list of plain values, or a
|
|
61
|
+
* value -> label map. An absent or empty list means `fallback`.
|
|
62
|
+
*/
|
|
63
|
+
export function normalizeOptions(input, fallback = []) {
|
|
64
|
+
const known = new Map(fallback.map((option) => [option.value, option.label]));
|
|
65
|
+
const labelFor = (value) => known.get(value) ?? humanize(value);
|
|
66
|
+
|
|
67
|
+
let options = [];
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(input)) {
|
|
70
|
+
options = input.map((item) =>
|
|
71
|
+
item !== null && typeof item === 'object'
|
|
72
|
+
? { value: item.value, label: item.label ?? labelFor(item.value) }
|
|
73
|
+
: { value: item, label: labelFor(item) },
|
|
74
|
+
);
|
|
75
|
+
} else if (input !== null && typeof input === 'object') {
|
|
76
|
+
options = Object.entries(input).map(([value, label]) => ({ value, label: label || labelFor(value) }));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return options.length ? options : fallback;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Stored conditions, in any shape, as the editor's `{match, conditions}`. */
|
|
83
|
+
export function toConditionState(conditions) {
|
|
84
|
+
if (Array.isArray(conditions)) {
|
|
85
|
+
return { match: 'all', conditions };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (conditions === null || typeof conditions !== 'object') {
|
|
89
|
+
return { match: 'all', conditions: [] };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (Array.isArray(conditions.all)) {
|
|
93
|
+
return { match: 'all', conditions: conditions.all };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (Array.isArray(conditions.any)) {
|
|
97
|
+
return { match: 'any', conditions: conditions.any };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const match = conditions.match === 'any' ? 'any' : 'all';
|
|
101
|
+
const list = conditions.rules ?? conditions.conditions;
|
|
102
|
+
|
|
103
|
+
return { match, conditions: Array.isArray(list) ? list : [] };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The editor's `{match, conditions}` as the contract's `{all}` or `{any}`. */
|
|
107
|
+
export function toWireConditions(state) {
|
|
108
|
+
return { [state?.match === 'any' ? 'any' : 'all']: state?.conditions ?? [] };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// The builder's old `{type, config}` kept the one value an action needs under
|
|
112
|
+
// one of these keys.
|
|
113
|
+
const LEGACY_CONFIG_KEYS = ['status', 'priority', 'tag', 'department', 'agent', 'body', 'url', 'duration', 'macro'];
|
|
114
|
+
|
|
115
|
+
/** Stored actions as `{type, value}`. */
|
|
116
|
+
export function toActionState(actions) {
|
|
117
|
+
if (!Array.isArray(actions)) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return actions.map((action) => {
|
|
122
|
+
if (Object.prototype.hasOwnProperty.call(action, 'value')) {
|
|
123
|
+
return { type: action.type, value: action.value };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const config = action.config ?? {};
|
|
127
|
+
const key = LEGACY_CONFIG_KEYS.find((candidate) => config[candidate] !== undefined && config[candidate] !== '');
|
|
128
|
+
|
|
129
|
+
return { type: action.type, value: key ? config[key] : '' };
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The create/update request body. */
|
|
134
|
+
export function toWirePayload(form) {
|
|
135
|
+
return {
|
|
136
|
+
name: form.name,
|
|
137
|
+
description: form.description ? form.description : null,
|
|
138
|
+
trigger_event: form.trigger_event,
|
|
139
|
+
conditions: toWireConditions(form.conditions),
|
|
140
|
+
actions: (form.actions ?? []).map(({ type, value }) => ({ type, value: value ?? '' })),
|
|
141
|
+
is_active: Boolean(form.is_active),
|
|
142
|
+
};
|
|
143
|
+
}
|