@dev.smartpricing/message-composer-utils 4.5.1-next.3 → 5.0.0-automations.0
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/dist/schemas/automation-catalog.js +47 -0
- package/dist/schemas/automation-catalog.js.map +1 -0
- package/dist/schemas/automation.js +191 -0
- package/dist/schemas/automation.js.map +1 -0
- package/dist/schemas/condition.js +17 -0
- package/dist/schemas/condition.js.map +1 -0
- package/dist/schemas/condition.test.js +73 -0
- package/dist/schemas/condition.test.js.map +1 -0
- package/dist/schemas/index.js +3 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/message-group.js +10 -8
- package/dist/schemas/message-group.js.map +1 -1
- package/dist/types/app.js +3 -0
- package/dist/types/app.js.map +1 -1
- package/dist/types/automation-catalog.js +16 -0
- package/dist/types/automation-catalog.js.map +1 -0
- package/dist/types/automation.js +9 -0
- package/dist/types/automation.js.map +1 -0
- package/dist/types/condition.js +51 -0
- package/dist/types/condition.js.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/src/schemas/automation-catalog.ts +62 -0
- package/src/schemas/automation.ts +230 -0
- package/src/schemas/condition.test.ts +92 -0
- package/src/schemas/condition.ts +27 -0
- package/src/schemas/index.ts +3 -0
- package/src/schemas/message-group.ts +15 -11
- package/src/types/app.ts +6 -2
- package/src/types/automation-catalog.ts +79 -0
- package/src/types/automation.ts +216 -0
- package/src/types/condition.ts +114 -0
- package/src/types/index.ts +3 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
AUTOMATION_ACTION_STATUSES,
|
|
4
|
+
AUTOMATION_CHANNELS,
|
|
5
|
+
AUTOMATION_MESSAGE_GROUP_KEPT_REASONS,
|
|
6
|
+
Automation,
|
|
7
|
+
AutomationAction,
|
|
8
|
+
AutomationActionInput,
|
|
9
|
+
AutomationActionsInvalidDetails,
|
|
10
|
+
AutomationDeleteResult,
|
|
11
|
+
AutomationPartlySavedDetails,
|
|
12
|
+
CloneAutomationBody,
|
|
13
|
+
CreateAutomationBody,
|
|
14
|
+
MESSAGE_GROUP_PRODUCTS,
|
|
15
|
+
UpdateAutomationBody,
|
|
16
|
+
} from "../types/index.js";
|
|
17
|
+
import { conditionOperatorSchema, conditionSchema } from "./condition.js";
|
|
18
|
+
import { messageGroupValidationErrorsSchema } from "./message-group.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* GET /automations: the Automations of one Organization for one Product.
|
|
22
|
+
* Both are required: the Automation Engine lists one Organization at a time
|
|
23
|
+
* (ADR 0016, a deviation from ADR 0014) and the Product is never read from
|
|
24
|
+
* the `sm-product` header (ADR 0011).
|
|
25
|
+
*/
|
|
26
|
+
export const automationListQuerySchema = z.object({
|
|
27
|
+
organization_id: z.uuid(),
|
|
28
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/** GET /automations/:id: an Automation of another Product is not found. */
|
|
32
|
+
export const automationQuerySchema = z.object({
|
|
33
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* DELETE /automations/:id: the Message Groups to delete with the Automation,
|
|
38
|
+
* one repeated `message_group_ids` each (ADR 0016). A single one reaches the
|
|
39
|
+
* schema as a string; the route always reads a list. The Product is in the
|
|
40
|
+
* query too: a DELETE has no body to name it in.
|
|
41
|
+
*/
|
|
42
|
+
export const deleteAutomationQuerySchema = automationQuerySchema.extend({
|
|
43
|
+
message_group_ids: z
|
|
44
|
+
.union([z.uuid(), z.array(z.uuid())])
|
|
45
|
+
.optional()
|
|
46
|
+
.transform((ids) => (ids === undefined ? [] : [ids].flat())),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const messageGroupRef = { id: z.uuid(), name: z.string() };
|
|
50
|
+
|
|
51
|
+
/** 200 of a DELETE /automations/:id that selected Message Groups. */
|
|
52
|
+
export const automationDeleteResponseSchema = z.object({
|
|
53
|
+
deleted: z.array(z.object(messageGroupRef)),
|
|
54
|
+
kept: z.array(
|
|
55
|
+
z.object({
|
|
56
|
+
...messageGroupRef,
|
|
57
|
+
reason: z.enum(AUTOMATION_MESSAGE_GROUP_KEPT_REASONS),
|
|
58
|
+
}),
|
|
59
|
+
),
|
|
60
|
+
}) satisfies z.ZodType<AutomationDeleteResult>;
|
|
61
|
+
|
|
62
|
+
/** Automation ids are the engine's (`aut_<uuid>`), not UUIDs. */
|
|
63
|
+
export const automationIdParamsSchema = z.object({
|
|
64
|
+
id: z.string().min(1),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const automationActionStatusSchema = z.enum(AUTOMATION_ACTION_STATUSES);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* An Action as served: the engine's `config` as stored, loose because the
|
|
71
|
+
* engine owns its shape (ADR 0016).
|
|
72
|
+
*/
|
|
73
|
+
export const automationActionSchema = z.object({
|
|
74
|
+
id: z.string(),
|
|
75
|
+
type: z.string(),
|
|
76
|
+
status: automationActionStatusSchema,
|
|
77
|
+
created_at: z.string(),
|
|
78
|
+
updated_at: z.string(),
|
|
79
|
+
config: z.looseObject({
|
|
80
|
+
template: z.string(),
|
|
81
|
+
channel: z.string(),
|
|
82
|
+
recipient: z.string(),
|
|
83
|
+
delay: z.number(),
|
|
84
|
+
}),
|
|
85
|
+
}) satisfies z.ZodType<AutomationAction>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* An Automation with its Actions embedded (ADR 0016). `settings` and
|
|
89
|
+
* `metadata` keep the engine's shapes, loose so a key the engine adds is
|
|
90
|
+
* served, not stripped; `active` is derived by the backend.
|
|
91
|
+
*/
|
|
92
|
+
export const automationResponseSchema = z.object({
|
|
93
|
+
id: z.string(),
|
|
94
|
+
organization_id: z.uuid(),
|
|
95
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS),
|
|
96
|
+
name: z.string(),
|
|
97
|
+
created_at: z.string(),
|
|
98
|
+
updated_at: z.string(),
|
|
99
|
+
active: z.boolean(),
|
|
100
|
+
settings: z.looseObject({
|
|
101
|
+
trigger: z.looseObject({
|
|
102
|
+
source: z.union([z.string(), z.array(z.string())]).optional(),
|
|
103
|
+
type: z.union([z.string(), z.array(z.string())]),
|
|
104
|
+
}),
|
|
105
|
+
conditions: z.array(
|
|
106
|
+
z.looseObject({
|
|
107
|
+
field: z.string(),
|
|
108
|
+
operator: z.string(),
|
|
109
|
+
value: z.unknown().optional(),
|
|
110
|
+
}),
|
|
111
|
+
),
|
|
112
|
+
}),
|
|
113
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
114
|
+
actions: z.array(automationActionSchema),
|
|
115
|
+
}) satisfies z.ZodType<Automation>;
|
|
116
|
+
|
|
117
|
+
export const automationListResponseSchema = z.array(automationResponseSchema);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* An Action as submitted: `config` keeps the engine's shape, loose for the
|
|
121
|
+
* rest of the Schedule. The recipient and the sender are not the client's
|
|
122
|
+
* to choose (ADR 0017).
|
|
123
|
+
*/
|
|
124
|
+
export const automationActionInputSchema = z.object({
|
|
125
|
+
status: automationActionStatusSchema,
|
|
126
|
+
config: z.looseObject({
|
|
127
|
+
template: z.string().min(1),
|
|
128
|
+
channel: z.enum(AUTOMATION_CHANNELS),
|
|
129
|
+
delay: z.number(),
|
|
130
|
+
dateTimeReference: z.string().min(1),
|
|
131
|
+
}),
|
|
132
|
+
}) satisfies z.ZodType<AutomationActionInput>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* POST /automations: an Automation and its Actions in one request
|
|
136
|
+
* (ADR 0016). The Organization and the Product are explicit in the body
|
|
137
|
+
* (ADR 0014, ADR 0011).
|
|
138
|
+
*/
|
|
139
|
+
export const createAutomationBodySchema = z.object({
|
|
140
|
+
organization_id: z.uuid(),
|
|
141
|
+
product: z.enum(MESSAGE_GROUP_PRODUCTS),
|
|
142
|
+
name: z.string().trim().min(1),
|
|
143
|
+
settings: z.looseObject({
|
|
144
|
+
trigger: z.looseObject({ type: z.string().min(1) }),
|
|
145
|
+
conditions: z.array(conditionSchema),
|
|
146
|
+
}),
|
|
147
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
148
|
+
actions: z.array(automationActionInputSchema).min(1),
|
|
149
|
+
}) satisfies z.ZodType<CreateAutomationBody>;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* PUT /automations/:id: the full list of Actions, each stored one named by
|
|
153
|
+
* its `id` (ADR 0016). The Product is explicit (ADR 0011); the Organization
|
|
154
|
+
* is the stored one and is not sent.
|
|
155
|
+
*/
|
|
156
|
+
export const updateAutomationBodySchema = createAutomationBodySchema
|
|
157
|
+
.omit({ organization_id: true })
|
|
158
|
+
.extend({
|
|
159
|
+
actions: z
|
|
160
|
+
.array(
|
|
161
|
+
automationActionInputSchema.extend({
|
|
162
|
+
id: z.string().min(1).optional(),
|
|
163
|
+
}),
|
|
164
|
+
)
|
|
165
|
+
.min(1),
|
|
166
|
+
}) satisfies z.ZodType<UpdateAutomationBody>;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* POST /automations/:id/clone: the name of the copy, and the Product as on
|
|
170
|
+
* every write (ADR 0011). Everything else is read from the source.
|
|
171
|
+
*/
|
|
172
|
+
export const cloneAutomationBodySchema = createAutomationBodySchema.pick({
|
|
173
|
+
product: true,
|
|
174
|
+
name: true,
|
|
175
|
+
}) satisfies z.ZodType<CloneAutomationBody>;
|
|
176
|
+
|
|
177
|
+
/** `details` of the 502 of an update that was saved only in part. */
|
|
178
|
+
export const automationPartlySavedDetailsSchema = z.object({
|
|
179
|
+
automation: automationResponseSchema,
|
|
180
|
+
action_ids: z.array(z.string().nullable()),
|
|
181
|
+
}) satisfies z.ZodType<AutomationPartlySavedDetails>;
|
|
182
|
+
|
|
183
|
+
const position = z.number().int().nonnegative();
|
|
184
|
+
|
|
185
|
+
const actionProblem = { target: z.literal("action"), position };
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* `details` of the 422 `AUTOMATION_ACTIONS_INVALID`: a flat list, each
|
|
189
|
+
* entry naming its `target`. A new kind of problem is a new entry shape,
|
|
190
|
+
* never a change to an existing one.
|
|
191
|
+
*/
|
|
192
|
+
export const automationActionsInvalidDetailsSchema = z.object({
|
|
193
|
+
problems: z.array(
|
|
194
|
+
z.union([
|
|
195
|
+
z.object({
|
|
196
|
+
target: z.literal("trigger"),
|
|
197
|
+
reason: z.literal("not_in_catalog"),
|
|
198
|
+
type: z.string(),
|
|
199
|
+
}),
|
|
200
|
+
z.object({
|
|
201
|
+
target: z.literal("condition"),
|
|
202
|
+
position,
|
|
203
|
+
reason: z.enum(["field_not_in_catalog", "operator_not_allowed"]),
|
|
204
|
+
field: z.string(),
|
|
205
|
+
operator: conditionOperatorSchema,
|
|
206
|
+
}),
|
|
207
|
+
z.object({
|
|
208
|
+
...actionProblem,
|
|
209
|
+
reason: z.enum(["not_found", "other_organization"]),
|
|
210
|
+
message_group_id: z.string(),
|
|
211
|
+
}),
|
|
212
|
+
z.object({
|
|
213
|
+
...actionProblem,
|
|
214
|
+
reason: z.literal("invalid"),
|
|
215
|
+
message_group_id: z.string(),
|
|
216
|
+
errors: messageGroupValidationErrorsSchema,
|
|
217
|
+
}),
|
|
218
|
+
z.object({
|
|
219
|
+
...actionProblem,
|
|
220
|
+
reason: z.literal("date_reference_not_in_catalog"),
|
|
221
|
+
date_reference: z.string(),
|
|
222
|
+
}),
|
|
223
|
+
z.object({
|
|
224
|
+
...actionProblem,
|
|
225
|
+
reason: z.literal("channel_not_in_catalog"),
|
|
226
|
+
channel: z.enum(AUTOMATION_CHANNELS),
|
|
227
|
+
}),
|
|
228
|
+
]),
|
|
229
|
+
),
|
|
230
|
+
}) satisfies z.ZodType<AutomationActionsInvalidDetails>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
CONDITION_OPERATORS_BY_FIELD_TYPE,
|
|
4
|
+
conditionFieldTypeSchema,
|
|
5
|
+
conditionOperatorSchema,
|
|
6
|
+
conditionSchema,
|
|
7
|
+
operatorFitsFieldType,
|
|
8
|
+
} from "./condition.js";
|
|
9
|
+
|
|
10
|
+
describe("conditionSchema", () => {
|
|
11
|
+
it.each([
|
|
12
|
+
"eq",
|
|
13
|
+
"neq",
|
|
14
|
+
"gt",
|
|
15
|
+
"gte",
|
|
16
|
+
"lt",
|
|
17
|
+
"lte",
|
|
18
|
+
"in",
|
|
19
|
+
"not_in",
|
|
20
|
+
"contains",
|
|
21
|
+
"exclude",
|
|
22
|
+
"exists",
|
|
23
|
+
])("accepts the engine operator %s", (operator) => {
|
|
24
|
+
const condition = { field: "guest.language", operator, value: "it" };
|
|
25
|
+
expect(conditionSchema.parse(condition)).toEqual(condition);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("knows eleven operators and no other", () => {
|
|
29
|
+
expect(conditionOperatorSchema.options).toHaveLength(11);
|
|
30
|
+
expect(
|
|
31
|
+
conditionSchema.safeParse({ field: "status", operator: "is", value: 1 })
|
|
32
|
+
.success,
|
|
33
|
+
).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("accepts a Condition with no value", () => {
|
|
37
|
+
const condition = { field: "totalPrice", operator: "exists" };
|
|
38
|
+
expect(conditionSchema.parse(condition)).toEqual(condition);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("keeps the value as it is, whatever its type", () => {
|
|
42
|
+
const condition = {
|
|
43
|
+
field: "guest.marketingConsent",
|
|
44
|
+
operator: "in",
|
|
45
|
+
value: [1, 2],
|
|
46
|
+
};
|
|
47
|
+
expect(conditionSchema.parse(condition)).toEqual(condition);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("rejects a Condition with no field", () => {
|
|
51
|
+
expect(
|
|
52
|
+
conditionSchema.safeParse({ field: "", operator: "eq", value: 1 })
|
|
53
|
+
.success,
|
|
54
|
+
).toBe(false);
|
|
55
|
+
expect(
|
|
56
|
+
conditionSchema.safeParse({ operator: "eq", value: 1 }).success,
|
|
57
|
+
).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("the field types and their operators (ADR 0018)", () => {
|
|
62
|
+
it("has six field types", () => {
|
|
63
|
+
expect([...conditionFieldTypeSchema.options].sort()).toEqual([
|
|
64
|
+
"date",
|
|
65
|
+
"multiselect",
|
|
66
|
+
"number",
|
|
67
|
+
"price",
|
|
68
|
+
"select",
|
|
69
|
+
"text",
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("gives each field type the operators of the ADR's table", () => {
|
|
74
|
+
const comparison = ["eq", "neq", "gt", "gte", "lt", "lte", "exists"];
|
|
75
|
+
expect(CONDITION_OPERATORS_BY_FIELD_TYPE).toEqual({
|
|
76
|
+
select: ["eq", "neq", "exists"],
|
|
77
|
+
multiselect: ["in", "not_in", "exists"],
|
|
78
|
+
number: comparison,
|
|
79
|
+
price: comparison,
|
|
80
|
+
date: comparison,
|
|
81
|
+
text: ["eq", "neq", "contains", "exclude", "exists"],
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("tells whether an operator fits a field type", () => {
|
|
86
|
+
expect(operatorFitsFieldType("gte", "price")).toBe(true);
|
|
87
|
+
expect(operatorFitsFieldType("in", "multiselect")).toBe(true);
|
|
88
|
+
expect(operatorFitsFieldType("in", "select")).toBe(false);
|
|
89
|
+
expect(operatorFitsFieldType("contains", "number")).toBe(false);
|
|
90
|
+
expect(operatorFitsFieldType("exists", "text")).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
CONDITION_FIELD_TYPES,
|
|
4
|
+
CONDITION_OPERATORS,
|
|
5
|
+
Condition,
|
|
6
|
+
} from "../types/index.js";
|
|
7
|
+
|
|
8
|
+
export const conditionOperatorSchema = z.enum(CONDITION_OPERATORS);
|
|
9
|
+
|
|
10
|
+
export const conditionFieldTypeSchema = z.enum(CONDITION_FIELD_TYPES);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A Condition in the Automation Engine's shape (ADR 0018). The value is not
|
|
14
|
+
* checked: what fits depends on the field, which only a catalog knows.
|
|
15
|
+
*/
|
|
16
|
+
export const conditionSchema = z.object({
|
|
17
|
+
field: z.string().min(1),
|
|
18
|
+
operator: conditionOperatorSchema,
|
|
19
|
+
value: z.unknown().optional(),
|
|
20
|
+
}) satisfies z.ZodType<Condition>;
|
|
21
|
+
|
|
22
|
+
// The type-to-operator table sits with the vocabulary it is made of, so the
|
|
23
|
+
// layer can read it without the schemas.
|
|
24
|
+
export {
|
|
25
|
+
CONDITION_OPERATORS_BY_FIELD_TYPE,
|
|
26
|
+
operatorFitsFieldType,
|
|
27
|
+
} from "../types/index.js";
|
package/src/schemas/index.ts
CHANGED
|
@@ -13,3 +13,6 @@ export * from "./message-compilation.js";
|
|
|
13
13
|
export * from "./meta-integration.js";
|
|
14
14
|
export * from "./onboarding.js";
|
|
15
15
|
export * from "./tracking.js";
|
|
16
|
+
export * from "./automation.js";
|
|
17
|
+
export * from "./condition.js";
|
|
18
|
+
export * from "./automation-catalog.js";
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
DuplicateMessageGroupBody,
|
|
4
4
|
ListMessageGroupsQuery,
|
|
5
5
|
MessageGroupFromDb,
|
|
6
|
+
MessageGroupValidationErrors,
|
|
6
7
|
MessageGroupWithMessageSummary,
|
|
7
8
|
MESSAGE_GROUP_CATEGORIES,
|
|
8
9
|
MESSAGE_GROUP_LIST_SORT,
|
|
@@ -123,17 +124,20 @@ export const messageGroupSummaryListResponseSchema = paginatedResponseSchema(
|
|
|
123
124
|
messageGroupWithMessageSummaryResponseSchema,
|
|
124
125
|
);
|
|
125
126
|
|
|
127
|
+
/** The errors of the Message Group validation, by what they are about. */
|
|
128
|
+
export const messageGroupValidationErrorsSchema = z.object({
|
|
129
|
+
status: z.array(z.string()),
|
|
130
|
+
languages: z.array(z.string()),
|
|
131
|
+
content: z.record(
|
|
132
|
+
z.string(),
|
|
133
|
+
z.object({
|
|
134
|
+
type: z.enum(MESSAGE_RENDER_TYPES),
|
|
135
|
+
errors: z.array(z.string()),
|
|
136
|
+
}),
|
|
137
|
+
),
|
|
138
|
+
}) satisfies z.ZodType<MessageGroupValidationErrors>;
|
|
139
|
+
|
|
126
140
|
export const messageGroupValidationResponseSchema = z.object({
|
|
127
141
|
valid: z.boolean(),
|
|
128
|
-
errors:
|
|
129
|
-
status: z.array(z.string()),
|
|
130
|
-
languages: z.array(z.string()),
|
|
131
|
-
content: z.record(
|
|
132
|
-
z.string(),
|
|
133
|
-
z.object({
|
|
134
|
-
type: z.string(),
|
|
135
|
-
errors: z.array(z.string()),
|
|
136
|
-
}),
|
|
137
|
-
),
|
|
138
|
-
}),
|
|
142
|
+
errors: messageGroupValidationErrorsSchema,
|
|
139
143
|
});
|
package/src/types/app.ts
CHANGED
|
@@ -21,6 +21,9 @@ export const errorCodes = {
|
|
|
21
21
|
UNSUPPORTED_RENDER_TYPE: "UNSUPPORTED_RENDER_TYPE",
|
|
22
22
|
NOT_IMPLEMENTED: "NOT_IMPLEMENTED",
|
|
23
23
|
TRANSLATION_TOKEN_MISMATCH: "TRANSLATION_TOKEN_MISMATCH",
|
|
24
|
+
UPSTREAM_ERROR: "UPSTREAM_ERROR",
|
|
25
|
+
AUTOMATION_ACTIONS_INVALID: "AUTOMATION_ACTIONS_INVALID",
|
|
26
|
+
SYSTEM_AUTOMATION_NOT_DELETABLE: "SYSTEM_AUTOMATION_NOT_DELETABLE",
|
|
24
27
|
} as const;
|
|
25
28
|
|
|
26
29
|
type ErrorCode = keyof typeof errorCodes;
|
|
@@ -40,8 +43,9 @@ export interface AppContextConfig {
|
|
|
40
43
|
languages?: string[];
|
|
41
44
|
mainLanguage?: string;
|
|
42
45
|
/**
|
|
43
|
-
* The WABA the Host works on
|
|
44
|
-
* it is one of the Organization's
|
|
46
|
+
* The WABA the Host works on. Only a preselection when the user picks the
|
|
47
|
+
* WABA of a Message Group, valid if it is one of the Organization's
|
|
48
|
+
* (ADR 0015).
|
|
45
49
|
*/
|
|
46
50
|
metaWabaId?: string;
|
|
47
51
|
organization_id?: string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Condition, ConditionFieldType } from "./condition.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Automation Catalog (ADR 0017): what an Automation of a Product can be
|
|
5
|
+
* built from. The backend keeps one config per Product with a label per
|
|
6
|
+
* language and serves it resolved to the requested one.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** The languages a catalog holds a label in. */
|
|
10
|
+
export const AUTOMATION_CATALOG_LANGUAGES = [
|
|
11
|
+
"en",
|
|
12
|
+
"it",
|
|
13
|
+
"de",
|
|
14
|
+
"es",
|
|
15
|
+
"fr",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export type AutomationCatalogLanguage =
|
|
19
|
+
(typeof AUTOMATION_CATALOG_LANGUAGES)[number];
|
|
20
|
+
|
|
21
|
+
export const AUTOMATION_CHANNELS = ["email", "whatsapp"] as const;
|
|
22
|
+
|
|
23
|
+
export type AutomationChannel = (typeof AUTOMATION_CHANNELS)[number];
|
|
24
|
+
|
|
25
|
+
export const DATE_REFERENCE_DIRECTIONS = ["after", "before"] as const;
|
|
26
|
+
|
|
27
|
+
export type DateReferenceDirection = (typeof DATE_REFERENCE_DIRECTIONS)[number];
|
|
28
|
+
|
|
29
|
+
/** A label in every language of the catalog, as the backend keeps it. */
|
|
30
|
+
export type AutomationCatalogLabels = Record<AutomationCatalogLanguage, string>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The shapes below take the type of a label: a string as the route serves
|
|
34
|
+
* them, in one language, or `AutomationCatalogLabels` as the backend keeps
|
|
35
|
+
* them.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export interface ConditionField<Label = string> {
|
|
39
|
+
/** Dot path into the Trigger Event's data. */
|
|
40
|
+
key: string;
|
|
41
|
+
label: Label;
|
|
42
|
+
type: ConditionFieldType;
|
|
43
|
+
/**
|
|
44
|
+
* The name of the option list the Host supplies for this field. The
|
|
45
|
+
* catalog holds no option of its own.
|
|
46
|
+
*/
|
|
47
|
+
option_list?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DateReference<Label = string> {
|
|
51
|
+
key: string;
|
|
52
|
+
/** The directions a delay can count in, in display order. */
|
|
53
|
+
directions: { direction: DateReferenceDirection; label: Label }[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A Channel a Trigger can send over, with whom its Actions send to and from. */
|
|
57
|
+
export interface AutomationCatalogChannel {
|
|
58
|
+
channel: AutomationChannel;
|
|
59
|
+
recipient: string;
|
|
60
|
+
sender: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AutomationTrigger<Label = string> {
|
|
64
|
+
type: string;
|
|
65
|
+
label: Label;
|
|
66
|
+
date_references: DateReference<Label>[];
|
|
67
|
+
condition_fields: ConditionField<Label>[];
|
|
68
|
+
default_conditions: Condition[];
|
|
69
|
+
channels: AutomationCatalogChannel[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface AutomationCatalog<Label = string> {
|
|
73
|
+
triggers: AutomationTrigger<Label>[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** `catalog` is null for a Product that has no Automation Catalog. */
|
|
77
|
+
export interface AutomationCatalogResponse {
|
|
78
|
+
catalog: AutomationCatalog | null;
|
|
79
|
+
}
|