@aglyn/plugins-workflows 1.0.0-beta.179 → 1.0.0-beta.181
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 +12 -11
- package/src/lib/components/automation-trigger-conditions.component.d.ts +43 -0
- package/src/lib/components/automation-trigger-conditions.component.js +189 -0
- package/src/lib/components/automation-trigger-conditions.component.js.map +1 -0
- package/src/lib/components/host-actions-card.component.js +27 -176
- package/src/lib/components/host-actions-card.component.js.map +1 -1
- package/src/lib/components/org-automation-editor.component.d.ts +51 -0
- package/src/lib/components/org-automation-editor.component.js +310 -0
- package/src/lib/components/org-automation-editor.component.js.map +1 -0
- package/src/lib/components/org-automations-card.component.d.ts +28 -0
- package/src/lib/components/org-automations-card.component.js +373 -0
- package/src/lib/components/org-automations-card.component.js.map +1 -0
- package/src/lib/components/org-site-automation-list.component.d.ts +43 -0
- package/src/lib/components/org-site-automation-list.component.js +289 -0
- package/src/lib/components/org-site-automation-list.component.js.map +1 -0
- package/src/lib/components/site-org-automations-panel.component.d.ts +22 -0
- package/src/lib/components/site-org-automations-panel.component.js +201 -0
- package/src/lib/components/site-org-automations-panel.component.js.map +1 -0
- package/src/lib/components/use-org-automation-step-pickers.d.ts +20 -0
- package/src/lib/components/use-org-automation-step-pickers.js +108 -0
- package/src/lib/components/use-org-automation-step-pickers.js.map +1 -0
- package/src/lib/components/use-org-automations-api.d.ts +16 -0
- package/src/lib/components/use-org-automations-api.js +55 -0
- package/src/lib/components/use-org-automations-api.js.map +1 -0
- package/src/lib/components/workflows-console-page.js +63 -1
- package/src/lib/components/workflows-console-page.js.map +1 -1
- package/src/lib/components/workflows-console-sections.d.ts +19 -1
- package/src/lib/components/workflows-console-sections.js +37 -2
- package/src/lib/components/workflows-console-sections.js.map +1 -1
- package/src/lib/components/workflows-org-mount.d.ts +52 -0
- package/src/lib/components/workflows-org-mount.js +49 -0
- package/src/lib/components/workflows-org-mount.js.map +1 -0
- package/src/lib/engine/flow-enrollments.d.ts +26 -10
- package/src/lib/engine/flow-enrollments.js +14 -7
- package/src/lib/engine/flow-enrollments.js.map +1 -1
- package/src/lib/engine/run-event-actions.d.ts +57 -4
- package/src/lib/engine/run-event-actions.js +119 -19
- package/src/lib/engine/run-event-actions.js.map +1 -1
- package/src/lib/engine/run-org-automations.d.ts +75 -0
- package/src/lib/engine/run-org-automations.js +154 -0
- package/src/lib/engine/run-org-automations.js.map +1 -0
- package/src/lib/model/org-automations.d.ts +232 -0
- package/src/lib/model/org-automations.js +423 -0
- package/src/lib/model/org-automations.js.map +1 -0
- package/src/lib/plugin.js +26 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/org-automations-routes.d.ts +88 -0
- package/src/lib/server/org-automations-routes.js +474 -0
- package/src/lib/server/org-automations-routes.js.map +1 -0
- package/src/lib/server-console.d.ts +2 -1
- package/src/lib/server-console.js +12 -2
- package/src/lib/server-console.js.map +1 -1
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { ACTION_MAX_CONDITIONS, ACTION_MAX_STEPS, HOST_ACTION_STEP_LABELS, TRIGGER_COMBINATORS, TRIGGER_CONDITION_OPS, validateHostAction } from "@aglyn/aglyn/app-utils/actions";
|
|
18
|
+
import { normalizeVisibleTo, visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
19
|
+
/**
|
|
20
|
+
* ORG AUTOMATIONS (AGL-3302): one automation the organization writes once and
|
|
21
|
+
* places on the sites it chooses.
|
|
22
|
+
*
|
|
23
|
+
* A site's own actions stay where they are, at `hosts/{hostId}/actions`. They
|
|
24
|
+
* are not moved here, and the reasons are the four things a site action can
|
|
25
|
+
* do that an org automation must not:
|
|
26
|
+
*
|
|
27
|
+
* - call the site's functions and variables through a workflow step — the
|
|
28
|
+
* working set a computed variable binds to, which is one site's;
|
|
29
|
+
* - run the steps only a visitor's page can run (overlays, drawers, class
|
|
30
|
+
* toggles, a redirect to a screen), which the `actions` collection also
|
|
31
|
+
* holds for element interactions;
|
|
32
|
+
* - be fired by `/api/events/dispatch`, which is unauthenticated and ties an
|
|
33
|
+
* action id to a site only by its path;
|
|
34
|
+
* - wait, with the resume beat reading a missing document as the kill
|
|
35
|
+
* switch, so a migration would stop every person already inside a flow.
|
|
36
|
+
*
|
|
37
|
+
* An org automation is therefore its own document with a narrower
|
|
38
|
+
* vocabulary: triggers only the server raises, and steps that mean the same
|
|
39
|
+
* thing on whichever site they run. Each run executes AS the site the event
|
|
40
|
+
* happened on — that site's sender, suppressions, consent group, meters and
|
|
41
|
+
* run allowance — so placing one automation on five sites is five sites each
|
|
42
|
+
* running it for themselves, never one site acting for the others.
|
|
43
|
+
*
|
|
44
|
+
* Pure and client-safe: the console's editor, the server routes and the
|
|
45
|
+
* engine read the same definitions from here.
|
|
46
|
+
*/ /** `orgs/{orgId}/automations/{automationId}`. */ export const ORG_AUTOMATIONS_COLLECTION = 'automations';
|
|
47
|
+
/**
|
|
48
|
+
* The two server doors onto the collection, under the plugin's `automations`
|
|
49
|
+
* API prefix: the organization's (`manage`) and a site's own pause
|
|
50
|
+
* (`pause`). Named here so the console's caller and the server's registration
|
|
51
|
+
* read one spelling.
|
|
52
|
+
*/ export const ORG_AUTOMATION_API_ROUTES = {
|
|
53
|
+
manage: 'automations/manage',
|
|
54
|
+
pause: 'automations/pause'
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* How many live org automations one organization may hold.
|
|
58
|
+
*
|
|
59
|
+
* A flat platform cap in the shape `ACTIONS_MAX_PER_HOST` takes, and smaller,
|
|
60
|
+
* because the two are different things: a site action is authored per site
|
|
61
|
+
* and per element, where an org automation is a shared building block placed
|
|
62
|
+
* on many sites at once. A hundred is far past what an organization writes by
|
|
63
|
+
* hand and keeps the org hub's list — which reads every live one — a single
|
|
64
|
+
* bounded window. Soft-deleted documents do not count.
|
|
65
|
+
*/ export const ORG_AUTOMATIONS_MAX = 100;
|
|
66
|
+
/**
|
|
67
|
+
* How many org automations one event may run on one site: the same fan-out
|
|
68
|
+
* bound the site's own actions take (`MAX_TRIGGERED_ACTIONS`), counted
|
|
69
|
+
* separately so the two cannot crowd each other out of the query.
|
|
70
|
+
*/ export const MAX_TRIGGERED_ORG_AUTOMATIONS = 10;
|
|
71
|
+
/**
|
|
72
|
+
* The run history's target type for a run of an org automation, on the
|
|
73
|
+
* activity feed of the site it ran on.
|
|
74
|
+
*/ export const ORG_AUTOMATION_RUN_TARGET = 'orgAutomation';
|
|
75
|
+
/**
|
|
76
|
+
* The org activity target an org automation's own lifecycle rows are filed
|
|
77
|
+
* under: the plugin's namespaced resource (`pluginId:noun`), which the org's
|
|
78
|
+
* feed reads as "Automation" — core's target list names only core's resources.
|
|
79
|
+
*/ export const ORG_AUTOMATION_ACTIVITY_TARGET = 'workflows:automation';
|
|
80
|
+
/**
|
|
81
|
+
* The events an org automation may start on: every host event the SERVER
|
|
82
|
+
* raises and a person can be the subject of.
|
|
83
|
+
*
|
|
84
|
+
* Listed rather than derived from `HOST_EVENT_TYPES`, deliberately. The engine
|
|
85
|
+
* queries the organization's automations on every event in this list, so a
|
|
86
|
+
* new host event must not become an org trigger — and a new read on every
|
|
87
|
+
* occurrence of it — by being added to the platform's list. Two are left out
|
|
88
|
+
* on purpose: `pageView` fires on every request to every published page, and
|
|
89
|
+
* an org query on it would be a read per visitor; `memberSignOut` is raised
|
|
90
|
+
* by no door today.
|
|
91
|
+
*/ export const ORG_AUTOMATION_TRIGGER_EVENTS = [
|
|
92
|
+
'formSubmission',
|
|
93
|
+
'lead',
|
|
94
|
+
'contactCreated',
|
|
95
|
+
'contactStageChanged',
|
|
96
|
+
'booking',
|
|
97
|
+
'memberSignUp',
|
|
98
|
+
'memberSignIn',
|
|
99
|
+
'dealStageChanged',
|
|
100
|
+
'dealWon',
|
|
101
|
+
'dealLost',
|
|
102
|
+
'taskCompleted'
|
|
103
|
+
];
|
|
104
|
+
const ORG_TRIGGER_SET = new Set(ORG_AUTOMATION_TRIGGER_EVENTS);
|
|
105
|
+
/** Whether an org automation may start on this event. */ export function isOrgAutomationTriggerEvent(event) {
|
|
106
|
+
return typeof event === 'string' && ORG_TRIGGER_SET.has(event);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The steps an org automation may hold: the server steps that read the same
|
|
110
|
+
* on every site.
|
|
111
|
+
*
|
|
112
|
+
* What is left out, and why:
|
|
113
|
+
* - every step the visitor's page runs — overlays, drawers, menus, class
|
|
114
|
+
* toggles, custom HTML and JavaScript, analytics, redirects, the site
|
|
115
|
+
* alert — because an org trigger is a server event with no page to act on;
|
|
116
|
+
* - `runWorkflow`, because a workflow is one site's and calls that site's
|
|
117
|
+
* functions and variables;
|
|
118
|
+
* - `webhookPost`, because a webhook's address and secret are one site's.
|
|
119
|
+
*
|
|
120
|
+
* `customEvent` stays: it fires a custom event ON the site the run is on,
|
|
121
|
+
* which that site's own actions may listen for — the documented way for an
|
|
122
|
+
* org automation to hand over to something site-specific.
|
|
123
|
+
*/ export const ORG_AUTOMATION_STEP_TYPES = [
|
|
124
|
+
'sendEmail',
|
|
125
|
+
'notifyAdmins',
|
|
126
|
+
'enrollList',
|
|
127
|
+
'assignCampaign',
|
|
128
|
+
'datasetAppend',
|
|
129
|
+
'updateDataset',
|
|
130
|
+
'setContactStage',
|
|
131
|
+
'addContactTag',
|
|
132
|
+
'assignContactOwner',
|
|
133
|
+
'createCrmTask',
|
|
134
|
+
'logCrmActivity',
|
|
135
|
+
'customEvent',
|
|
136
|
+
'wait',
|
|
137
|
+
'waitForEvent',
|
|
138
|
+
'exitFlow'
|
|
139
|
+
];
|
|
140
|
+
const ORG_STEP_SET = new Set(ORG_AUTOMATION_STEP_TYPES);
|
|
141
|
+
/** Whether an org automation may run a step of this type. */ export function isOrgAutomationStepType(type) {
|
|
142
|
+
return typeof type === 'string' && ORG_STEP_SET.has(type);
|
|
143
|
+
}
|
|
144
|
+
/** The org editor's "Do" picker: each allowed step with its label, in order. */ export const ORG_AUTOMATION_STEP_KINDS = ORG_AUTOMATION_STEP_TYPES.map((value)=>({
|
|
145
|
+
value,
|
|
146
|
+
label: HOST_ACTION_STEP_LABELS[value]
|
|
147
|
+
}));
|
|
148
|
+
/**
|
|
149
|
+
* Why an org automation cannot hold this step, or null when it can.
|
|
150
|
+
*
|
|
151
|
+
* Shared by the save route, which refuses it, and by the engine, which
|
|
152
|
+
* refuses to run one a writer stored anyway.
|
|
153
|
+
*/ export function orgAutomationStepRefusal(step) {
|
|
154
|
+
var _ref;
|
|
155
|
+
if (isOrgAutomationStepType(step == null ? void 0 : step.type)) return null;
|
|
156
|
+
const label = HOST_ACTION_STEP_LABELS[step == null ? void 0 : step.type];
|
|
157
|
+
if (!label) return `“${String((_ref = step == null ? void 0 : step.type) != null ? _ref : '')}” is not a step`;
|
|
158
|
+
return `“${label}” belongs to one site, so an org automation cannot run it — ` + 'build it as an action on that site';
|
|
159
|
+
}
|
|
160
|
+
/** Longest name an org automation may carry: the actions editor's cap. */ export const ORG_AUTOMATION_NAME_MAX = 60;
|
|
161
|
+
/** Longest trigger filter expression. */ const FILTER_MAX = 500;
|
|
162
|
+
/** Longest a condition's field or value may be. */ const CONDITION_FIELD_MAX = 64;
|
|
163
|
+
const CONDITION_VALUE_MAX = 200;
|
|
164
|
+
/**
|
|
165
|
+
* Every field a step of each type may carry beyond `type` and `when`, with
|
|
166
|
+
* the longest a string in it may be. What the save route stores is exactly
|
|
167
|
+
* this: a key a step's type does not list is dropped, so the document holds
|
|
168
|
+
* the vocabulary and nothing a caller made up.
|
|
169
|
+
*
|
|
170
|
+
* The caps follow what the engine reads — it cuts a subject at 200 and a body
|
|
171
|
+
* at 5,000 before sending — so nothing is stored that could not be run.
|
|
172
|
+
*/ const STEP_FIELDS = {
|
|
173
|
+
sendEmail: {
|
|
174
|
+
subject: 200,
|
|
175
|
+
body: 5000,
|
|
176
|
+
toField: 64,
|
|
177
|
+
topicId: 128
|
|
178
|
+
},
|
|
179
|
+
notifyAdmins: {
|
|
180
|
+
title: 200,
|
|
181
|
+
body: 500
|
|
182
|
+
},
|
|
183
|
+
enrollList: {
|
|
184
|
+
listId: 128,
|
|
185
|
+
listName: 200
|
|
186
|
+
},
|
|
187
|
+
assignCampaign: {
|
|
188
|
+
campaignId: 128,
|
|
189
|
+
campaignName: 200
|
|
190
|
+
},
|
|
191
|
+
datasetAppend: {
|
|
192
|
+
datasetId: 128,
|
|
193
|
+
datasetName: 200
|
|
194
|
+
},
|
|
195
|
+
updateDataset: {
|
|
196
|
+
datasetId: 128,
|
|
197
|
+
datasetName: 200
|
|
198
|
+
},
|
|
199
|
+
setContactStage: {
|
|
200
|
+
lifecycleStage: 64
|
|
201
|
+
},
|
|
202
|
+
addContactTag: {
|
|
203
|
+
tag: 200
|
|
204
|
+
},
|
|
205
|
+
assignContactOwner: {
|
|
206
|
+
ownerUid: 128,
|
|
207
|
+
ownerEmail: 320,
|
|
208
|
+
roundRobin: 'boolean'
|
|
209
|
+
},
|
|
210
|
+
createCrmTask: {
|
|
211
|
+
title: 200,
|
|
212
|
+
kind: 64,
|
|
213
|
+
dueInDays: 'number',
|
|
214
|
+
assigneeUid: 128,
|
|
215
|
+
assigneeEmail: 320
|
|
216
|
+
},
|
|
217
|
+
logCrmActivity: {
|
|
218
|
+
kind: 64,
|
|
219
|
+
body: 2000
|
|
220
|
+
},
|
|
221
|
+
customEvent: {
|
|
222
|
+
eventName: 64
|
|
223
|
+
},
|
|
224
|
+
wait: {
|
|
225
|
+
delayMinutes: 'number'
|
|
226
|
+
},
|
|
227
|
+
waitForEvent: {
|
|
228
|
+
eventName: 64,
|
|
229
|
+
timeoutMinutes: 'number'
|
|
230
|
+
},
|
|
231
|
+
exitFlow: {}
|
|
232
|
+
};
|
|
233
|
+
const asObject = (value)=>value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
234
|
+
/** A condition as it is stored, or null for a row that is not one. */ function readCondition(raw) {
|
|
235
|
+
var _row_field, _row_value;
|
|
236
|
+
const row = asObject(raw);
|
|
237
|
+
if (!row) return null;
|
|
238
|
+
const op = row['op'];
|
|
239
|
+
if (!TRIGGER_CONDITION_OPS.includes(op)) {
|
|
240
|
+
var _row_field1;
|
|
241
|
+
// Kept as written so the validator names the missing operator rather
|
|
242
|
+
// than the row silently vanishing.
|
|
243
|
+
return {
|
|
244
|
+
field: String((_row_field1 = row['field']) != null ? _row_field1 : '').trim().slice(0, CONDITION_FIELD_MAX),
|
|
245
|
+
op: op
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return _extends({
|
|
249
|
+
field: String((_row_field = row['field']) != null ? _row_field : '').trim().slice(0, CONDITION_FIELD_MAX),
|
|
250
|
+
op: op
|
|
251
|
+
}, op !== 'notEmpty' ? {
|
|
252
|
+
value: String((_row_value = row['value']) != null ? _row_value : '').trim().slice(0, CONDITION_VALUE_MAX)
|
|
253
|
+
} : {});
|
|
254
|
+
}
|
|
255
|
+
function readConditions(raw) {
|
|
256
|
+
return (Array.isArray(raw) ? raw : []).slice(0, ACTION_MAX_CONDITIONS + 1).map(readCondition).filter((row)=>row !== null);
|
|
257
|
+
}
|
|
258
|
+
function readCombinator(raw) {
|
|
259
|
+
return TRIGGER_COMBINATORS.includes(raw) ? raw : null;
|
|
260
|
+
}
|
|
261
|
+
/** One step as it would be stored: its own fields only, strings capped. */ function readStep(raw) {
|
|
262
|
+
var _asObject;
|
|
263
|
+
const source = (_asObject = asObject(raw)) != null ? _asObject : {};
|
|
264
|
+
const type = source['type'];
|
|
265
|
+
if (!isOrgAutomationStepType(type)) return {
|
|
266
|
+
type
|
|
267
|
+
};
|
|
268
|
+
const step = {
|
|
269
|
+
type
|
|
270
|
+
};
|
|
271
|
+
for (const [key, kind] of Object.entries(STEP_FIELDS[type])){
|
|
272
|
+
const value = source[key];
|
|
273
|
+
if (value === undefined || value === null) continue;
|
|
274
|
+
if (kind === 'boolean') {
|
|
275
|
+
if (typeof value === 'boolean') step[key] = value;
|
|
276
|
+
} else if (kind === 'number') {
|
|
277
|
+
if (typeof value === 'number' && Number.isFinite(value)) step[key] = value;
|
|
278
|
+
} else if (typeof value === 'string') {
|
|
279
|
+
step[key] = value.slice(0, Number(kind));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const when = asObject(source['when']);
|
|
283
|
+
if (when) {
|
|
284
|
+
const conditions = readConditions(when['conditions']);
|
|
285
|
+
if (conditions.length) {
|
|
286
|
+
var _readCombinator;
|
|
287
|
+
step['when'] = {
|
|
288
|
+
conditions,
|
|
289
|
+
combinator: (_readCombinator = readCombinator(when['combinator'])) != null ? _readCombinator : 'and'
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return step;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Reads an org automation out of an untrusted request body: the fields it may
|
|
297
|
+
* hold, in the shape they are stored, or the first reason it cannot be saved.
|
|
298
|
+
*
|
|
299
|
+
* The rules are the actions builder's own — `validateHostAction` checks the
|
|
300
|
+
* name, the trigger's conditions and every step's fields — and on top of them
|
|
301
|
+
* the two this vocabulary adds: the trigger must be an org trigger and every
|
|
302
|
+
* step an org step. The placement must be a scope a save can store:
|
|
303
|
+
* `normalizeVisibleTo` refuses an empty one and one over thirty sites rather
|
|
304
|
+
* than guessing between sharing wider and taking access away.
|
|
305
|
+
*
|
|
306
|
+
* Whether each named site belongs to the organization is the route's to ask,
|
|
307
|
+
* because it takes a read.
|
|
308
|
+
*/ export function readOrgAutomation(input) {
|
|
309
|
+
var _asObject, _body_name, _asObject1, _trigger_event, _trigger_filter;
|
|
310
|
+
const body = (_asObject = asObject(input)) != null ? _asObject : {};
|
|
311
|
+
const name = String((_body_name = body['name']) != null ? _body_name : '').trim().slice(0, ORG_AUTOMATION_NAME_MAX);
|
|
312
|
+
if (!name) return {
|
|
313
|
+
ok: false,
|
|
314
|
+
problem: 'Name the automation'
|
|
315
|
+
};
|
|
316
|
+
const trigger = (_asObject1 = asObject(body['trigger'])) != null ? _asObject1 : {};
|
|
317
|
+
const event = String((_trigger_event = trigger['event']) != null ? _trigger_event : '').trim();
|
|
318
|
+
if (!isOrgAutomationTriggerEvent(event)) {
|
|
319
|
+
return {
|
|
320
|
+
ok: false,
|
|
321
|
+
problem: 'Pick a trigger an org automation can start on — a form, a lead, a ' + 'booking, a member or a CRM event'
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const filter = String((_trigger_filter = trigger['filter']) != null ? _trigger_filter : '').trim().slice(0, FILTER_MAX);
|
|
325
|
+
const conditions = readConditions(trigger['conditions']);
|
|
326
|
+
const combinator = readCombinator(trigger['combinator']);
|
|
327
|
+
const rawSteps = Array.isArray(body['steps']) ? body['steps'] : [];
|
|
328
|
+
if (rawSteps.length > ACTION_MAX_STEPS) {
|
|
329
|
+
return {
|
|
330
|
+
ok: false,
|
|
331
|
+
problem: `Org automations are capped at ${ACTION_MAX_STEPS} steps`
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const steps = rawSteps.map(readStep);
|
|
335
|
+
for (const [index, step] of steps.entries()){
|
|
336
|
+
const refusal = orgAutomationStepRefusal(step);
|
|
337
|
+
if (refusal) return {
|
|
338
|
+
ok: false,
|
|
339
|
+
problem: `Step ${index + 1}: ${refusal}`
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const problem = validateHostAction({
|
|
343
|
+
name,
|
|
344
|
+
trigger: _extends({
|
|
345
|
+
event
|
|
346
|
+
}, filter ? {
|
|
347
|
+
filter
|
|
348
|
+
} : {}, conditions.length ? {
|
|
349
|
+
conditions
|
|
350
|
+
} : {}, combinator ? {
|
|
351
|
+
combinator
|
|
352
|
+
} : {}),
|
|
353
|
+
steps: steps
|
|
354
|
+
});
|
|
355
|
+
if (problem) return {
|
|
356
|
+
ok: false,
|
|
357
|
+
problem
|
|
358
|
+
};
|
|
359
|
+
const visibleTo = normalizeVisibleTo(Array.isArray(body['visibleTo']) ? body['visibleTo'].filter((token)=>typeof token === 'string') : []);
|
|
360
|
+
if (!visibleTo) {
|
|
361
|
+
return {
|
|
362
|
+
ok: false,
|
|
363
|
+
problem: 'Choose every site, or up to 30 sites, for it to run on'
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
ok: true,
|
|
368
|
+
value: {
|
|
369
|
+
name,
|
|
370
|
+
trigger: _extends({
|
|
371
|
+
event
|
|
372
|
+
}, filter ? {
|
|
373
|
+
filter
|
|
374
|
+
} : {}, {
|
|
375
|
+
conditions: conditions.length ? conditions : null,
|
|
376
|
+
combinator: conditions.length ? combinator != null ? combinator : 'and' : null
|
|
377
|
+
}),
|
|
378
|
+
steps: steps,
|
|
379
|
+
enabled: body['enabled'] !== false,
|
|
380
|
+
visibleTo
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/** A stored document's pause list, read defensively. */ export function orgAutomationPausedHostIds(automation) {
|
|
385
|
+
const paused = automation == null ? void 0 : automation.pausedHostIds;
|
|
386
|
+
return Array.isArray(paused) ? paused.filter((id)=>typeof id === 'string') : [];
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Why a stored org automation will not run on this site, or null when it
|
|
390
|
+
* will — the one answer the engine asks when an event arrives and again when
|
|
391
|
+
* a person waiting inside the automation is due.
|
|
392
|
+
*
|
|
393
|
+
* Deleted, switched off, taken off the site and paused there are the four
|
|
394
|
+
* ways an organization or a site says "not here", and every one of them
|
|
395
|
+
* stops the people already waiting on this site as well as the next run: the
|
|
396
|
+
* kill switch a site action's resume applies, for the same reason.
|
|
397
|
+
*/ export function orgAutomationStopReason(automation, hostId) {
|
|
398
|
+
if (!automation || automation.deletedAt) {
|
|
399
|
+
return 'the org automation was deleted';
|
|
400
|
+
}
|
|
401
|
+
if (automation.enabled === false) {
|
|
402
|
+
return 'the org automation was switched off';
|
|
403
|
+
}
|
|
404
|
+
if (!visibleToHost(automation.visibleTo, hostId)) {
|
|
405
|
+
return 'the org automation no longer runs on this site';
|
|
406
|
+
}
|
|
407
|
+
if (orgAutomationPausedHostIds(automation).includes(hostId)) {
|
|
408
|
+
return 'the org automation is paused on this site';
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
/** Whether a stored org automation runs on this site. */ export function orgAutomationRunsOnHost(automation, hostId) {
|
|
413
|
+
return orgAutomationStopReason(automation, hostId) === null;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* A pause list after the placement changed: a site the automation no longer
|
|
417
|
+
* runs on has nothing left to pause, so its entry goes with it, and a site
|
|
418
|
+
* placed on it again later starts unpaused.
|
|
419
|
+
*/ export function prunePausedHostIds(pausedHostIds, visibleTo) {
|
|
420
|
+
return pausedHostIds.filter((hostId)=>visibleToHost(visibleTo, hostId));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
//# sourceMappingURL=org-automations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/org-automations.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ACTION_MAX_CONDITIONS,\n ACTION_MAX_STEPS,\n HOST_ACTION_STEP_LABELS,\n type HostActionStep,\n type HostActionStepType,\n type HostActionTriggerCondition,\n TRIGGER_COMBINATORS,\n TRIGGER_CONDITION_OPS,\n type TriggerCombinator,\n validateHostAction,\n} from '@aglyn/aglyn/app-utils/actions'\nimport {\n normalizeVisibleTo,\n type ScopeToken,\n visibleToHost,\n} from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type { HostEventType } from '@aglyn/aglyn/app-utils/workflows'\n\n/**\n * ORG AUTOMATIONS (AGL-3302): one automation the organization writes once and\n * places on the sites it chooses.\n *\n * A site's own actions stay where they are, at `hosts/{hostId}/actions`. They\n * are not moved here, and the reasons are the four things a site action can\n * do that an org automation must not:\n *\n * - call the site's functions and variables through a workflow step — the\n * working set a computed variable binds to, which is one site's;\n * - run the steps only a visitor's page can run (overlays, drawers, class\n * toggles, a redirect to a screen), which the `actions` collection also\n * holds for element interactions;\n * - be fired by `/api/events/dispatch`, which is unauthenticated and ties an\n * action id to a site only by its path;\n * - wait, with the resume beat reading a missing document as the kill\n * switch, so a migration would stop every person already inside a flow.\n *\n * An org automation is therefore its own document with a narrower\n * vocabulary: triggers only the server raises, and steps that mean the same\n * thing on whichever site they run. Each run executes AS the site the event\n * happened on — that site's sender, suppressions, consent group, meters and\n * run allowance — so placing one automation on five sites is five sites each\n * running it for themselves, never one site acting for the others.\n *\n * Pure and client-safe: the console's editor, the server routes and the\n * engine read the same definitions from here.\n */\n\n/** `orgs/{orgId}/automations/{automationId}`. */\nexport const ORG_AUTOMATIONS_COLLECTION = 'automations'\n\n/**\n * The two server doors onto the collection, under the plugin's `automations`\n * API prefix: the organization's (`manage`) and a site's own pause\n * (`pause`). Named here so the console's caller and the server's registration\n * read one spelling.\n */\nexport const ORG_AUTOMATION_API_ROUTES = {\n manage: 'automations/manage',\n pause: 'automations/pause',\n} as const\n\n/**\n * How many live org automations one organization may hold.\n *\n * A flat platform cap in the shape `ACTIONS_MAX_PER_HOST` takes, and smaller,\n * because the two are different things: a site action is authored per site\n * and per element, where an org automation is a shared building block placed\n * on many sites at once. A hundred is far past what an organization writes by\n * hand and keeps the org hub's list — which reads every live one — a single\n * bounded window. Soft-deleted documents do not count.\n */\nexport const ORG_AUTOMATIONS_MAX = 100\n\n/**\n * How many org automations one event may run on one site: the same fan-out\n * bound the site's own actions take (`MAX_TRIGGERED_ACTIONS`), counted\n * separately so the two cannot crowd each other out of the query.\n */\nexport const MAX_TRIGGERED_ORG_AUTOMATIONS = 10\n\n/**\n * The run history's target type for a run of an org automation, on the\n * activity feed of the site it ran on.\n */\nexport const ORG_AUTOMATION_RUN_TARGET = 'orgAutomation'\n\n/**\n * The org activity target an org automation's own lifecycle rows are filed\n * under: the plugin's namespaced resource (`pluginId:noun`), which the org's\n * feed reads as \"Automation\" — core's target list names only core's resources.\n */\nexport const ORG_AUTOMATION_ACTIVITY_TARGET = 'workflows:automation'\n\n/**\n * The events an org automation may start on: every host event the SERVER\n * raises and a person can be the subject of.\n *\n * Listed rather than derived from `HOST_EVENT_TYPES`, deliberately. The engine\n * queries the organization's automations on every event in this list, so a\n * new host event must not become an org trigger — and a new read on every\n * occurrence of it — by being added to the platform's list. Two are left out\n * on purpose: `pageView` fires on every request to every published page, and\n * an org query on it would be a read per visitor; `memberSignOut` is raised\n * by no door today.\n */\nexport const ORG_AUTOMATION_TRIGGER_EVENTS = [\n 'formSubmission',\n 'lead',\n 'contactCreated',\n 'contactStageChanged',\n 'booking',\n 'memberSignUp',\n 'memberSignIn',\n 'dealStageChanged',\n 'dealWon',\n 'dealLost',\n 'taskCompleted',\n] as const satisfies readonly HostEventType[]\n\nexport type OrgAutomationTriggerEvent =\n (typeof ORG_AUTOMATION_TRIGGER_EVENTS)[number]\n\nconst ORG_TRIGGER_SET: ReadonlySet<string> = new Set(\n ORG_AUTOMATION_TRIGGER_EVENTS,\n)\n\n/** Whether an org automation may start on this event. */\nexport function isOrgAutomationTriggerEvent(\n event: unknown,\n): event is OrgAutomationTriggerEvent {\n return typeof event === 'string' && ORG_TRIGGER_SET.has(event)\n}\n\n/**\n * The steps an org automation may hold: the server steps that read the same\n * on every site.\n *\n * What is left out, and why:\n * - every step the visitor's page runs — overlays, drawers, menus, class\n * toggles, custom HTML and JavaScript, analytics, redirects, the site\n * alert — because an org trigger is a server event with no page to act on;\n * - `runWorkflow`, because a workflow is one site's and calls that site's\n * functions and variables;\n * - `webhookPost`, because a webhook's address and secret are one site's.\n *\n * `customEvent` stays: it fires a custom event ON the site the run is on,\n * which that site's own actions may listen for — the documented way for an\n * org automation to hand over to something site-specific.\n */\nexport const ORG_AUTOMATION_STEP_TYPES = [\n 'sendEmail',\n 'notifyAdmins',\n 'enrollList',\n 'assignCampaign',\n 'datasetAppend',\n 'updateDataset',\n 'setContactStage',\n 'addContactTag',\n 'assignContactOwner',\n 'createCrmTask',\n 'logCrmActivity',\n 'customEvent',\n 'wait',\n 'waitForEvent',\n 'exitFlow',\n] as const satisfies readonly HostActionStepType[]\n\nexport type OrgAutomationStepType = (typeof ORG_AUTOMATION_STEP_TYPES)[number]\n\nconst ORG_STEP_SET: ReadonlySet<string> = new Set(ORG_AUTOMATION_STEP_TYPES)\n\n/** Whether an org automation may run a step of this type. */\nexport function isOrgAutomationStepType(\n type: unknown,\n): type is OrgAutomationStepType {\n return typeof type === 'string' && ORG_STEP_SET.has(type)\n}\n\n/** The org editor's \"Do\" picker: each allowed step with its label, in order. */\nexport const ORG_AUTOMATION_STEP_KINDS: ReadonlyArray<{\n value: OrgAutomationStepType\n label: string\n}> = ORG_AUTOMATION_STEP_TYPES.map((value) => ({\n value,\n label: HOST_ACTION_STEP_LABELS[value],\n}))\n\n/**\n * Why an org automation cannot hold this step, or null when it can.\n *\n * Shared by the save route, which refuses it, and by the engine, which\n * refuses to run one a writer stored anyway.\n */\nexport function orgAutomationStepRefusal(step: {\n type?: unknown\n}): string | null {\n if (isOrgAutomationStepType(step?.type)) return null\n const label = HOST_ACTION_STEP_LABELS[step?.type as HostActionStepType]\n if (!label) return `“${String(step?.type ?? '')}” is not a step`\n return (\n `“${label}” belongs to one site, so an org automation cannot run it — ` +\n 'build it as an action on that site'\n )\n}\n\n/** The trigger an org automation starts on, as it is stored. */\nexport interface OrgAutomationTrigger {\n event: OrgAutomationTriggerEvent\n /** Optional expression over the payload; runs only when truthy. */\n filter?: string\n conditions: HostActionTriggerCondition[] | null\n combinator: TriggerCombinator | null\n}\n\n/**\n * The fields an org automation is written with — by the save route, and by\n * nothing else: the collection is closed to client writes.\n */\nexport interface OrgAutomationFields {\n name: string\n trigger: OrgAutomationTrigger\n steps: HostActionStep[]\n enabled: boolean\n /**\n * The sites it runs on, as scope tokens: `['org']` is every site of the\n * organization, now and later; `['host:{id}', …]` names up to thirty.\n */\n visibleTo: ScopeToken[]\n}\n\n/** `orgs/{orgId}/automations/{id}`, as it is stored. */\nexport interface OrgAutomation extends OrgAutomationFields {\n /**\n * The sites that have paused it for themselves — each a site id, never a\n * scope token. The \"host level control\": a site's admin or editor adds or\n * removes their own site here, and only their own.\n */\n pausedHostIds: string[]\n createdBy?: string\n updatedBy?: string\n /**\n * Set when it was deleted. Always written — `null` while live — so the\n * org hub's list can ask for the live ones by equality.\n */\n deletedAt: unknown\n}\n\n/** A stored org automation with its id, as the console lists it. */\nexport type OrgAutomationRow = OrgAutomation & { $id: string }\n\n/** Longest name an org automation may carry: the actions editor's cap. */\nexport const ORG_AUTOMATION_NAME_MAX = 60\n\n/** Longest trigger filter expression. */\nconst FILTER_MAX = 500\n\n/** Longest a condition's field or value may be. */\nconst CONDITION_FIELD_MAX = 64\nconst CONDITION_VALUE_MAX = 200\n\n/**\n * Every field a step of each type may carry beyond `type` and `when`, with\n * the longest a string in it may be. What the save route stores is exactly\n * this: a key a step's type does not list is dropped, so the document holds\n * the vocabulary and nothing a caller made up.\n *\n * The caps follow what the engine reads — it cuts a subject at 200 and a body\n * at 5,000 before sending — so nothing is stored that could not be run.\n */\nconst STEP_FIELDS: Record<\n OrgAutomationStepType,\n Readonly<Record<string, 'string' | 'number' | 'boolean' | number>>\n> = {\n sendEmail: { subject: 200, body: 5000, toField: 64, topicId: 128 },\n notifyAdmins: { title: 200, body: 500 },\n enrollList: { listId: 128, listName: 200 },\n assignCampaign: { campaignId: 128, campaignName: 200 },\n datasetAppend: { datasetId: 128, datasetName: 200 },\n updateDataset: { datasetId: 128, datasetName: 200 },\n setContactStage: { lifecycleStage: 64 },\n addContactTag: { tag: 200 },\n assignContactOwner: {\n ownerUid: 128,\n ownerEmail: 320,\n roundRobin: 'boolean',\n },\n createCrmTask: {\n title: 200,\n kind: 64,\n dueInDays: 'number',\n assigneeUid: 128,\n assigneeEmail: 320,\n },\n logCrmActivity: { kind: 64, body: 2000 },\n customEvent: { eventName: 64 },\n wait: { delayMinutes: 'number' },\n waitForEvent: { eventName: 64, timeoutMinutes: 'number' },\n exitFlow: {},\n}\n\nconst asObject = (value: unknown): Record<string, unknown> | null =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\n/** A condition as it is stored, or null for a row that is not one. */\nfunction readCondition(raw: unknown): HostActionTriggerCondition | null {\n const row = asObject(raw)\n if (!row) return null\n const op = row['op']\n if (!(TRIGGER_CONDITION_OPS as readonly unknown[]).includes(op)) {\n // Kept as written so the validator names the missing operator rather\n // than the row silently vanishing.\n return {\n field: String(row['field'] ?? '').trim().slice(0, CONDITION_FIELD_MAX),\n op: op as never,\n }\n }\n return {\n field: String(row['field'] ?? '').trim().slice(0, CONDITION_FIELD_MAX),\n op: op as HostActionTriggerCondition['op'],\n ...(op !== 'notEmpty'\n ? { value: String(row['value'] ?? '').trim().slice(0, CONDITION_VALUE_MAX) }\n : {}),\n }\n}\n\nfunction readConditions(raw: unknown): HostActionTriggerCondition[] {\n return (Array.isArray(raw) ? raw : [])\n .slice(0, ACTION_MAX_CONDITIONS + 1)\n .map(readCondition)\n .filter((row): row is HostActionTriggerCondition => row !== null)\n}\n\nfunction readCombinator(raw: unknown): TriggerCombinator | null {\n return (TRIGGER_COMBINATORS as readonly unknown[]).includes(raw)\n ? (raw as TriggerCombinator)\n : null\n}\n\n/** One step as it would be stored: its own fields only, strings capped. */\nfunction readStep(raw: unknown): HostActionStep | { type: unknown } {\n const source = asObject(raw) ?? {}\n const type = source['type']\n if (!isOrgAutomationStepType(type)) return { type }\n const step: Record<string, unknown> = { type }\n for (const [key, kind] of Object.entries(STEP_FIELDS[type])) {\n const value = source[key]\n if (value === undefined || value === null) continue\n if (kind === 'boolean') {\n if (typeof value === 'boolean') step[key] = value\n } else if (kind === 'number') {\n if (typeof value === 'number' && Number.isFinite(value)) step[key] = value\n } else if (typeof value === 'string') {\n step[key] = value.slice(0, Number(kind))\n }\n }\n const when = asObject(source['when'])\n if (when) {\n const conditions = readConditions(when['conditions'])\n if (conditions.length) {\n step['when'] = {\n conditions,\n combinator: readCombinator(when['combinator']) ?? 'and',\n }\n }\n }\n return step as HostActionStep\n}\n\n/** What a caller sends to create or replace an org automation. */\nexport type OrgAutomationInput = Record<string, unknown>\n\n/**\n * Reads an org automation out of an untrusted request body: the fields it may\n * hold, in the shape they are stored, or the first reason it cannot be saved.\n *\n * The rules are the actions builder's own — `validateHostAction` checks the\n * name, the trigger's conditions and every step's fields — and on top of them\n * the two this vocabulary adds: the trigger must be an org trigger and every\n * step an org step. The placement must be a scope a save can store:\n * `normalizeVisibleTo` refuses an empty one and one over thirty sites rather\n * than guessing between sharing wider and taking access away.\n *\n * Whether each named site belongs to the organization is the route's to ask,\n * because it takes a read.\n */\nexport function readOrgAutomation(\n input: unknown,\n): { ok: true; value: OrgAutomationFields } | { ok: false; problem: string } {\n const body = asObject(input) ?? {}\n const name = String(body['name'] ?? '')\n .trim()\n .slice(0, ORG_AUTOMATION_NAME_MAX)\n if (!name) return { ok: false, problem: 'Name the automation' }\n const trigger = asObject(body['trigger']) ?? {}\n const event = String(trigger['event'] ?? '').trim()\n if (!isOrgAutomationTriggerEvent(event)) {\n return {\n ok: false,\n problem:\n 'Pick a trigger an org automation can start on — a form, a lead, a ' +\n 'booking, a member or a CRM event',\n }\n }\n const filter = String(trigger['filter'] ?? '')\n .trim()\n .slice(0, FILTER_MAX)\n const conditions = readConditions(trigger['conditions'])\n const combinator = readCombinator(trigger['combinator'])\n const rawSteps = Array.isArray(body['steps']) ? body['steps'] : []\n if (rawSteps.length > ACTION_MAX_STEPS) {\n return {\n ok: false,\n problem: `Org automations are capped at ${ACTION_MAX_STEPS} steps`,\n }\n }\n const steps = rawSteps.map(readStep)\n for (const [index, step] of steps.entries()) {\n const refusal = orgAutomationStepRefusal(step)\n if (refusal) return { ok: false, problem: `Step ${index + 1}: ${refusal}` }\n }\n const problem = validateHostAction({\n name,\n trigger: {\n event,\n ...(filter ? { filter } : {}),\n ...(conditions.length ? { conditions } : {}),\n ...(combinator ? { combinator } : {}),\n },\n steps: steps as HostActionStep[],\n })\n if (problem) return { ok: false, problem }\n const visibleTo = normalizeVisibleTo(\n Array.isArray(body['visibleTo'])\n ? (body['visibleTo'] as unknown[]).filter(\n (token): token is string => typeof token === 'string',\n )\n : [],\n )\n if (!visibleTo) {\n return {\n ok: false,\n problem: 'Choose every site, or up to 30 sites, for it to run on',\n }\n }\n return {\n ok: true,\n value: {\n name,\n trigger: {\n event,\n ...(filter ? { filter } : {}),\n conditions: conditions.length ? conditions : null,\n combinator: conditions.length ? (combinator ?? 'and') : null,\n },\n steps: steps as HostActionStep[],\n enabled: body['enabled'] !== false,\n visibleTo,\n },\n }\n}\n\n/** A stored document's pause list, read defensively. */\nexport function orgAutomationPausedHostIds(\n automation: { pausedHostIds?: unknown } | null | undefined,\n): string[] {\n const paused = automation?.pausedHostIds\n return Array.isArray(paused)\n ? paused.filter((id): id is string => typeof id === 'string')\n : []\n}\n\n/**\n * Why a stored org automation will not run on this site, or null when it\n * will — the one answer the engine asks when an event arrives and again when\n * a person waiting inside the automation is due.\n *\n * Deleted, switched off, taken off the site and paused there are the four\n * ways an organization or a site says \"not here\", and every one of them\n * stops the people already waiting on this site as well as the next run: the\n * kill switch a site action's resume applies, for the same reason.\n */\nexport function orgAutomationStopReason(\n automation:\n | {\n deletedAt?: unknown\n enabled?: unknown\n visibleTo?: unknown\n pausedHostIds?: unknown\n }\n | null\n | undefined,\n hostId: string,\n): string | null {\n if (!automation || automation.deletedAt) {\n return 'the org automation was deleted'\n }\n if (automation.enabled === false) {\n return 'the org automation was switched off'\n }\n if (!visibleToHost(automation.visibleTo as string[] | undefined, hostId)) {\n return 'the org automation no longer runs on this site'\n }\n if (orgAutomationPausedHostIds(automation).includes(hostId)) {\n return 'the org automation is paused on this site'\n }\n return null\n}\n\n/** Whether a stored org automation runs on this site. */\nexport function orgAutomationRunsOnHost(\n automation: Parameters<typeof orgAutomationStopReason>[0],\n hostId: string,\n): boolean {\n return orgAutomationStopReason(automation, hostId) === null\n}\n\n/**\n * A pause list after the placement changed: a site the automation no longer\n * runs on has nothing left to pause, so its entry goes with it, and a site\n * placed on it again later starts unpaused.\n */\nexport function prunePausedHostIds(\n pausedHostIds: readonly string[],\n visibleTo: readonly string[],\n): string[] {\n return pausedHostIds.filter((hostId) => visibleToHost(visibleTo, hostId))\n}\n"],"names":["ACTION_MAX_CONDITIONS","ACTION_MAX_STEPS","HOST_ACTION_STEP_LABELS","TRIGGER_COMBINATORS","TRIGGER_CONDITION_OPS","validateHostAction","normalizeVisibleTo","visibleToHost","ORG_AUTOMATIONS_COLLECTION","ORG_AUTOMATION_API_ROUTES","manage","pause","ORG_AUTOMATIONS_MAX","MAX_TRIGGERED_ORG_AUTOMATIONS","ORG_AUTOMATION_RUN_TARGET","ORG_AUTOMATION_ACTIVITY_TARGET","ORG_AUTOMATION_TRIGGER_EVENTS","ORG_TRIGGER_SET","Set","isOrgAutomationTriggerEvent","event","has","ORG_AUTOMATION_STEP_TYPES","ORG_STEP_SET","isOrgAutomationStepType","type","ORG_AUTOMATION_STEP_KINDS","map","value","label","orgAutomationStepRefusal","step","String","ORG_AUTOMATION_NAME_MAX","FILTER_MAX","CONDITION_FIELD_MAX","CONDITION_VALUE_MAX","STEP_FIELDS","sendEmail","subject","body","toField","topicId","notifyAdmins","title","enrollList","listId","listName","assignCampaign","campaignId","campaignName","datasetAppend","datasetId","datasetName","updateDataset","setContactStage","lifecycleStage","addContactTag","tag","assignContactOwner","ownerUid","ownerEmail","roundRobin","createCrmTask","kind","dueInDays","assigneeUid","assigneeEmail","logCrmActivity","customEvent","eventName","wait","delayMinutes","waitForEvent","timeoutMinutes","exitFlow","asObject","Array","isArray","readCondition","raw","row","op","includes","field","trim","slice","readConditions","filter","readCombinator","readStep","source","key","Object","entries","undefined","Number","isFinite","when","conditions","length","combinator","readOrgAutomation","input","trigger","name","ok","problem","rawSteps","steps","index","refusal","visibleTo","token","enabled","orgAutomationPausedHostIds","automation","paused","pausedHostIds","id","orgAutomationStopReason","hostId","deletedAt","orgAutomationRunsOnHost","prunePausedHostIds"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,qBAAqB,EACrBC,gBAAgB,EAChBC,uBAAuB,EAIvBC,mBAAmB,EACnBC,qBAAqB,EAErBC,kBAAkB,QACb,iCAAgC;AACvC,SACEC,kBAAkB,EAElBC,aAAa,QACR,sCAAqC;AAG5C;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BC,GAED,+CAA+C,GAC/C,OAAO,MAAMC,6BAA6B,cAAa;AAEvD;;;;;CAKC,GACD,OAAO,MAAMC,4BAA4B;IACvCC,QAAQ;IACRC,OAAO;AACT,EAAU;AAEV;;;;;;;;;CASC,GACD,OAAO,MAAMC,sBAAsB,IAAG;AAEtC;;;;CAIC,GACD,OAAO,MAAMC,gCAAgC,GAAE;AAE/C;;;CAGC,GACD,OAAO,MAAMC,4BAA4B,gBAAe;AAExD;;;;CAIC,GACD,OAAO,MAAMC,iCAAiC,uBAAsB;AAEpE;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,gCAAgC;IAC3C;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAA4C;AAK7C,MAAMC,kBAAuC,IAAIC,IAC/CF;AAGF,uDAAuD,GACvD,OAAO,SAASG,4BACdC,KAAc;IAEd,OAAO,OAAOA,UAAU,YAAYH,gBAAgBI,GAAG,CAACD;AAC1D;AAEA;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAME,4BAA4B;IACvC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAiD;AAIlD,MAAMC,eAAoC,IAAIL,IAAII;AAElD,2DAA2D,GAC3D,OAAO,SAASE,wBACdC,IAAa;IAEb,OAAO,OAAOA,SAAS,YAAYF,aAAaF,GAAG,CAACI;AACtD;AAEA,8EAA8E,GAC9E,OAAO,MAAMC,4BAGRJ,0BAA0BK,GAAG,CAAC,CAACC,QAAW,CAAA;QAC7CA;QACAC,OAAO3B,uBAAuB,CAAC0B,MAAM;IACvC,CAAA,GAAG;AAEH;;;;;CAKC,GACD,OAAO,SAASE,yBAAyBC,IAExC;;IACC,IAAIP,wBAAwBO,wBAAAA,KAAMN,IAAI,GAAG,OAAO;IAChD,MAAMI,QAAQ3B,uBAAuB,CAAC6B,wBAAAA,KAAMN,IAAI,CAAuB;IACvE,IAAI,CAACI,OAAO,OAAO,CAAC,CAAC,EAAEG,eAAOD,wBAAAA,KAAMN,IAAI,mBAAI,IAAI,eAAe,CAAC;IAChE,OACE,CAAC,CAAC,EAAEI,MAAM,4DAA4D,CAAC,GACvE;AAEJ;AA+CA,wEAAwE,GACxE,OAAO,MAAMI,0BAA0B,GAAE;AAEzC,uCAAuC,GACvC,MAAMC,aAAa;AAEnB,iDAAiD,GACjD,MAAMC,sBAAsB;AAC5B,MAAMC,sBAAsB;AAE5B;;;;;;;;CAQC,GACD,MAAMC,cAGF;IACFC,WAAW;QAAEC,SAAS;QAAKC,MAAM;QAAMC,SAAS;QAAIC,SAAS;IAAI;IACjEC,cAAc;QAAEC,OAAO;QAAKJ,MAAM;IAAI;IACtCK,YAAY;QAAEC,QAAQ;QAAKC,UAAU;IAAI;IACzCC,gBAAgB;QAAEC,YAAY;QAAKC,cAAc;IAAI;IACrDC,eAAe;QAAEC,WAAW;QAAKC,aAAa;IAAI;IAClDC,eAAe;QAAEF,WAAW;QAAKC,aAAa;IAAI;IAClDE,iBAAiB;QAAEC,gBAAgB;IAAG;IACtCC,eAAe;QAAEC,KAAK;IAAI;IAC1BC,oBAAoB;QAClBC,UAAU;QACVC,YAAY;QACZC,YAAY;IACd;IACAC,eAAe;QACbnB,OAAO;QACPoB,MAAM;QACNC,WAAW;QACXC,aAAa;QACbC,eAAe;IACjB;IACAC,gBAAgB;QAAEJ,MAAM;QAAIxB,MAAM;IAAK;IACvC6B,aAAa;QAAEC,WAAW;IAAG;IAC7BC,MAAM;QAAEC,cAAc;IAAS;IAC/BC,cAAc;QAAEH,WAAW;QAAII,gBAAgB;IAAS;IACxDC,UAAU,CAAC;AACb;AAEA,MAAMC,WAAW,CAAChD,QAChBA,SAAS,OAAOA,UAAU,YAAY,CAACiD,MAAMC,OAAO,CAAClD,SAChDA,QACD;AAEN,oEAAoE,GACpE,SAASmD,cAAcC,GAAY;QAajBC,YAGMA;IAftB,MAAMA,MAAML,SAASI;IACrB,IAAI,CAACC,KAAK,OAAO;IACjB,MAAMC,KAAKD,GAAG,CAAC,KAAK;IACpB,IAAI,CAAC,AAAC7E,sBAA6C+E,QAAQ,CAACD,KAAK;YAI/CD;QAHhB,qEAAqE;QACrE,mCAAmC;QACnC,OAAO;YACLG,OAAOpD,QAAOiD,cAAAA,GAAG,CAAC,QAAQ,YAAZA,cAAgB,IAAII,IAAI,GAAGC,KAAK,CAAC,GAAGnD;YAClD+C,IAAIA;QACN;IACF;IACA,OAAO;QACLE,OAAOpD,QAAOiD,aAAAA,GAAG,CAAC,QAAQ,YAAZA,aAAgB,IAAII,IAAI,GAAGC,KAAK,CAAC,GAAGnD;QAClD+C,IAAIA;OACAA,OAAO,aACP;QAAEtD,OAAOI,QAAOiD,aAAAA,GAAG,CAAC,QAAQ,YAAZA,aAAgB,IAAII,IAAI,GAAGC,KAAK,CAAC,GAAGlD;IAAqB,IACzE,CAAC;AAET;AAEA,SAASmD,eAAeP,GAAY;IAClC,OAAO,AAACH,CAAAA,MAAMC,OAAO,CAACE,OAAOA,MAAM,EAAE,AAAD,EACjCM,KAAK,CAAC,GAAGtF,wBAAwB,GACjC2B,GAAG,CAACoD,eACJS,MAAM,CAAC,CAACP,MAA2CA,QAAQ;AAChE;AAEA,SAASQ,eAAeT,GAAY;IAClC,OAAO,AAAC7E,oBAA2CgF,QAAQ,CAACH,OACvDA,MACD;AACN;AAEA,yEAAyE,GACzE,SAASU,SAASV,GAAY;QACbJ;IAAf,MAAMe,UAASf,YAAAA,SAASI,gBAATJ,YAAiB,CAAC;IACjC,MAAMnD,OAAOkE,MAAM,CAAC,OAAO;IAC3B,IAAI,CAACnE,wBAAwBC,OAAO,OAAO;QAAEA;IAAK;IAClD,MAAMM,OAAgC;QAAEN;IAAK;IAC7C,KAAK,MAAM,CAACmE,KAAK5B,KAAK,IAAI6B,OAAOC,OAAO,CAACzD,WAAW,CAACZ,KAAK,EAAG;QAC3D,MAAMG,QAAQ+D,MAAM,CAACC,IAAI;QACzB,IAAIhE,UAAUmE,aAAanE,UAAU,MAAM;QAC3C,IAAIoC,SAAS,WAAW;YACtB,IAAI,OAAOpC,UAAU,WAAWG,IAAI,CAAC6D,IAAI,GAAGhE;QAC9C,OAAO,IAAIoC,SAAS,UAAU;YAC5B,IAAI,OAAOpC,UAAU,YAAYoE,OAAOC,QAAQ,CAACrE,QAAQG,IAAI,CAAC6D,IAAI,GAAGhE;QACvE,OAAO,IAAI,OAAOA,UAAU,UAAU;YACpCG,IAAI,CAAC6D,IAAI,GAAGhE,MAAM0D,KAAK,CAAC,GAAGU,OAAOhC;QACpC;IACF;IACA,MAAMkC,OAAOtB,SAASe,MAAM,CAAC,OAAO;IACpC,IAAIO,MAAM;QACR,MAAMC,aAAaZ,eAAeW,IAAI,CAAC,aAAa;QACpD,IAAIC,WAAWC,MAAM,EAAE;gBAGPX;YAFd1D,IAAI,CAAC,OAAO,GAAG;gBACboE;gBACAE,UAAU,GAAEZ,kBAAAA,eAAeS,IAAI,CAAC,aAAa,aAAjCT,kBAAsC;YACpD;QACF;IACF;IACA,OAAO1D;AACT;AAKA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASuE,kBACdC,KAAc;QAED3B,WACOpC,YAIJoC,YACK4B,gBASCA;IAftB,MAAMhE,QAAOoC,YAAAA,SAAS2B,kBAAT3B,YAAmB,CAAC;IACjC,MAAM6B,OAAOzE,QAAOQ,aAAAA,IAAI,CAAC,OAAO,YAAZA,aAAgB,IACjC6C,IAAI,GACJC,KAAK,CAAC,GAAGrD;IACZ,IAAI,CAACwE,MAAM,OAAO;QAAEC,IAAI;QAAOC,SAAS;IAAsB;IAC9D,MAAMH,WAAU5B,aAAAA,SAASpC,IAAI,CAAC,UAAU,aAAxBoC,aAA6B,CAAC;IAC9C,MAAMxD,QAAQY,QAAOwE,iBAAAA,OAAO,CAAC,QAAQ,YAAhBA,iBAAoB,IAAInB,IAAI;IACjD,IAAI,CAAClE,4BAA4BC,QAAQ;QACvC,OAAO;YACLsF,IAAI;YACJC,SACE,uEACA;QACJ;IACF;IACA,MAAMnB,SAASxD,QAAOwE,kBAAAA,OAAO,CAAC,SAAS,YAAjBA,kBAAqB,IACxCnB,IAAI,GACJC,KAAK,CAAC,GAAGpD;IACZ,MAAMiE,aAAaZ,eAAeiB,OAAO,CAAC,aAAa;IACvD,MAAMH,aAAaZ,eAAee,OAAO,CAAC,aAAa;IACvD,MAAMI,WAAW/B,MAAMC,OAAO,CAACtC,IAAI,CAAC,QAAQ,IAAIA,IAAI,CAAC,QAAQ,GAAG,EAAE;IAClE,IAAIoE,SAASR,MAAM,GAAGnG,kBAAkB;QACtC,OAAO;YACLyG,IAAI;YACJC,SAAS,CAAC,8BAA8B,EAAE1G,iBAAiB,MAAM,CAAC;QACpE;IACF;IACA,MAAM4G,QAAQD,SAASjF,GAAG,CAAC+D;IAC3B,KAAK,MAAM,CAACoB,OAAO/E,KAAK,IAAI8E,MAAMf,OAAO,GAAI;QAC3C,MAAMiB,UAAUjF,yBAAyBC;QACzC,IAAIgF,SAAS,OAAO;YAAEL,IAAI;YAAOC,SAAS,CAAC,KAAK,EAAEG,QAAQ,EAAE,EAAE,EAAEC,SAAS;QAAC;IAC5E;IACA,MAAMJ,UAAUtG,mBAAmB;QACjCoG;QACAD,SAAS;YACPpF;WACIoE,SAAS;YAAEA;QAAO,IAAI,CAAC,GACvBW,WAAWC,MAAM,GAAG;YAAED;QAAW,IAAI,CAAC,GACtCE,aAAa;YAAEA;QAAW,IAAI,CAAC;QAErCQ,OAAOA;IACT;IACA,IAAIF,SAAS,OAAO;QAAED,IAAI;QAAOC;IAAQ;IACzC,MAAMK,YAAY1G,mBAChBuE,MAAMC,OAAO,CAACtC,IAAI,CAAC,YAAY,IAC3B,AAACA,IAAI,CAAC,YAAY,CAAegD,MAAM,CACrC,CAACyB,QAA2B,OAAOA,UAAU,YAE/C,EAAE;IAER,IAAI,CAACD,WAAW;QACd,OAAO;YACLN,IAAI;YACJC,SAAS;QACX;IACF;IACA,OAAO;QACLD,IAAI;QACJ9E,OAAO;YACL6E;YACAD,SAAS;gBACPpF;eACIoE,SAAS;gBAAEA;YAAO,IAAI,CAAC;gBAC3BW,YAAYA,WAAWC,MAAM,GAAGD,aAAa;gBAC7CE,YAAYF,WAAWC,MAAM,GAAIC,qBAAAA,aAAc,QAAS;;YAE1DQ,OAAOA;YACPK,SAAS1E,IAAI,CAAC,UAAU,KAAK;YAC7BwE;QACF;IACF;AACF;AAEA,sDAAsD,GACtD,OAAO,SAASG,2BACdC,UAA0D;IAE1D,MAAMC,SAASD,8BAAAA,WAAYE,aAAa;IACxC,OAAOzC,MAAMC,OAAO,CAACuC,UACjBA,OAAO7B,MAAM,CAAC,CAAC+B,KAAqB,OAAOA,OAAO,YAClD,EAAE;AACR;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASC,wBACdJ,UAQa,EACbK,MAAc;IAEd,IAAI,CAACL,cAAcA,WAAWM,SAAS,EAAE;QACvC,OAAO;IACT;IACA,IAAIN,WAAWF,OAAO,KAAK,OAAO;QAChC,OAAO;IACT;IACA,IAAI,CAAC3G,cAAc6G,WAAWJ,SAAS,EAA0BS,SAAS;QACxE,OAAO;IACT;IACA,IAAIN,2BAA2BC,YAAYjC,QAAQ,CAACsC,SAAS;QAC3D,OAAO;IACT;IACA,OAAO;AACT;AAEA,uDAAuD,GACvD,OAAO,SAASE,wBACdP,UAAyD,EACzDK,MAAc;IAEd,OAAOD,wBAAwBJ,YAAYK,YAAY;AACzD;AAEA;;;;CAIC,GACD,OAAO,SAASG,mBACdN,aAAgC,EAChCN,SAA4B;IAE5B,OAAOM,cAAc9B,MAAM,CAAC,CAACiC,SAAWlH,cAAcyG,WAAWS;AACnE"}
|
package/src/lib/plugin.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { mdiSitemap } from "@aglyn/shared-data-mdi";
|
|
18
18
|
import { lazy } from "react";
|
|
19
19
|
import { HostActivityCard } from "./components/host-activity-card.component.js";
|
|
20
|
-
import { WORKFLOWS_CONSOLE_SECTIONS } from "./components/workflows-console-sections.js";
|
|
20
|
+
import { WORKFLOWS_CONSOLE_SECTIONS, WORKFLOWS_ORG_CONSOLE_SECTIONS } from "./components/workflows-console-sections.js";
|
|
21
21
|
import { registerPluginZone } from "@aglyn/aglyn/plugin-manager/plugin-zones";
|
|
22
22
|
import { WORKFLOW_USAGE_ZONE } from "./components/workflow-zones.js";
|
|
23
23
|
import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
@@ -85,6 +85,31 @@ import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
|
85
85
|
},
|
|
86
86
|
Component: WorkflowsConsolePage
|
|
87
87
|
}
|
|
88
|
+
],
|
|
89
|
+
/*
|
|
90
|
+
* The ORGANIZATION's Automation hub (AGL-3302), at `/[orgSlug]/automation`:
|
|
91
|
+
* the org automations placed across the organization's sites, and every
|
|
92
|
+
* site's own workflows, actions and webhooks side by side. The same page
|
|
93
|
+
* component, mounted with no site; the site tab's release flag gates both
|
|
94
|
+
* levels, so one flag rolls the surface out at once.
|
|
95
|
+
*/ orgNavItems: [
|
|
96
|
+
{
|
|
97
|
+
label: 'Automation',
|
|
98
|
+
href: '/automation',
|
|
99
|
+
sections: WORKFLOWS_ORG_CONSOLE_SECTIONS,
|
|
100
|
+
navTabId: 'nav-tab-workflows',
|
|
101
|
+
icon: {
|
|
102
|
+
path: mdiSitemap.path
|
|
103
|
+
},
|
|
104
|
+
header: {
|
|
105
|
+
title: 'Automation',
|
|
106
|
+
icon: {
|
|
107
|
+
path: mdiSitemap.path
|
|
108
|
+
},
|
|
109
|
+
docsTopic: 'workflows'
|
|
110
|
+
},
|
|
111
|
+
Component: WorkflowsConsolePage
|
|
112
|
+
}
|
|
88
113
|
]
|
|
89
114
|
});
|
|
90
115
|
}
|
package/src/lib/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/workflows/src/lib/plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiSitemap } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport { HostActivityCard } from './components/host-activity-card.component'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/workflows/src/lib/plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiSitemap } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport { HostActivityCard } from './components/host-activity-card.component'\nimport {\n WORKFLOWS_CONSOLE_SECTIONS,\n WORKFLOWS_ORG_CONSOLE_SECTIONS,\n} from './components/workflows-console-sections'\nimport { registerPluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport { WORKFLOW_USAGE_ZONE } from './components/workflow-zones'\nimport { BUNDLE_ID } from './constants/bundle-common'\n\n/** Code-split: the Automation console page only loads when opened. */\nconst WorkflowsConsolePage = lazy(\n () => import('./components/workflows-console-page'),\n)\n\n/**\n * Workflows feature plugin (AGL-395). Console-only — workflows, actions,\n * and webhooks run server-side (event pipelines, `/api/hooks`), not through\n * a canvas element, so there is no UI bundle. The console half declares the\n * Automation nav + page through the ConsoleExtension registry, gated by the\n * `workflows` entitlement (the Actions/Webhooks tabs run their own per-plan\n * checks off the passed `org`). Where a workflow is used is asked of the\n * platform's scan and drawn in a zone this plugin hosts.\n *\n * `pluginId`, `featureFlag` and `navTabId` all still read `workflows`: they\n * are stored — an org's enabled-plugin ids, the plan's entitlement flags, the\n * Remote Config release flag — and a section's name is not a reason to orphan\n * them. Only what a reader sees carries the new name.\n */\nexport function registerWorkflowsConsole(): void {\n registerPluginZone(\n {\n zone: WORKFLOW_USAGE_ZONE,\n label: 'Where a workflow is used',\n surface: 'console',\n description:\n 'On the Automation page, after a reader asks where a workflow is used. A widget here lays out the scan the page already ran; it is handed the site, the answer and a way to close, and it writes nothing.',\n },\n // Named, because a spec calls this registrar without the loader.\n { pluginId: BUNDLE_ID },\n )\n Aglyn.registerConsoleExtension({\n // Host activity feed (AGL-419): rendered by the shell's\n // 'hostActivity' widget slot (dashboard + editor view page).\n widgets: [\n {\n slot: 'hostActivity',\n widgetId: 'workflows-host-activity',\n title: 'Recent Activity',\n Component: HostActivityCard,\n },\n ],\n pluginId: BUNDLE_ID,\n displayName: 'Automation',\n featureFlag: 'workflows',\n navItems: [\n {\n label: 'Automation',\n // Workflows, Actions and Webhooks are the tabs INSIDE this section, so\n // the section is named for what they have in common. `/workflows` —\n // the address before the section had its own name — is redirected here\n // by the console (see `Route.HOST_WORKFLOWS`).\n href: '/automation',\n // Sections as ROUTES (AGL-2501): each is a real URL the shell\n // resolves and gates, so the page mounts the one being read.\n sections: WORKFLOWS_CONSOLE_SECTIONS,\n // Reuse the existing release-flag nav-tab so staff-preview gating is\n // unchanged now that the tab comes from the plugin (AGL-395).\n navTabId: 'nav-tab-workflows',\n icon: { path: mdiSitemap.path },\n header: {\n title: 'Automation',\n icon: { path: mdiSitemap.path },\n docsTopic: 'workflows',\n },\n Component: WorkflowsConsolePage,\n },\n ],\n /*\n * The ORGANIZATION's Automation hub (AGL-3302), at `/[orgSlug]/automation`:\n * the org automations placed across the organization's sites, and every\n * site's own workflows, actions and webhooks side by side. The same page\n * component, mounted with no site; the site tab's release flag gates both\n * levels, so one flag rolls the surface out at once.\n */\n orgNavItems: [\n {\n label: 'Automation',\n href: '/automation',\n sections: WORKFLOWS_ORG_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-workflows',\n icon: { path: mdiSitemap.path },\n header: {\n title: 'Automation',\n icon: { path: mdiSitemap.path },\n docsTopic: 'workflows',\n },\n Component: WorkflowsConsolePage,\n },\n ],\n })\n}\n\n// Activity feed consumed directly by the app (dashboard + screen view).\nexport { default as HostActivityCard } from './components/host-activity-card.component'\nexport type { HostActivityCardProps } from './components/host-activity-card.component'\n"],"names":["Aglyn","mdiSitemap","lazy","HostActivityCard","WORKFLOWS_CONSOLE_SECTIONS","WORKFLOWS_ORG_CONSOLE_SECTIONS","registerPluginZone","WORKFLOW_USAGE_ZONE","BUNDLE_ID","WorkflowsConsolePage","registerWorkflowsConsole","zone","label","surface","description","pluginId","registerConsoleExtension","widgets","slot","widgetId","title","Component","displayName","featureFlag","navItems","href","sections","navTabId","icon","path","header","docsTopic","orgNavItems","default"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,UAAU,QAAQ,yBAAwB;AACnD,SAASC,IAAI,QAAQ,QAAO;AAC5B,SAASC,gBAAgB,QAAQ,+CAA2C;AAC5E,SACEC,0BAA0B,EAC1BC,8BAA8B,QACzB,6CAAyC;AAChD,SAASC,kBAAkB,QAAQ,2CAA0C;AAC7E,SAASC,mBAAmB,QAAQ,iCAA6B;AACjE,SAASC,SAAS,QAAQ,+BAA2B;AAErD,oEAAoE,GACpE,MAAMC,uBAAuBP,KAC3B,IAAM,MAAM,CAAC;AAGf;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASQ;IACdJ,mBACE;QACEK,MAAMJ;QACNK,OAAO;QACPC,SAAS;QACTC,aACE;IACJ,GACA,iEAAiE;IACjE;QAAEC,UAAUP;IAAU;IAExBR,MAAMgB,wBAAwB,CAAC;QAC7B,wDAAwD;QACxD,6DAA6D;QAC7DC,SAAS;YACP;gBACEC,MAAM;gBACNC,UAAU;gBACVC,OAAO;gBACPC,WAAWlB;YACb;SACD;QACDY,UAAUP;QACVc,aAAa;QACbC,aAAa;QACbC,UAAU;YACR;gBACEZ,OAAO;gBACP,uEAAuE;gBACvE,oEAAoE;gBACpE,uEAAuE;gBACvE,+CAA+C;gBAC/Ca,MAAM;gBACN,8DAA8D;gBAC9D,6DAA6D;gBAC7DC,UAAUtB;gBACV,qEAAqE;gBACrE,8DAA8D;gBAC9DuB,UAAU;gBACVC,MAAM;oBAAEC,MAAM5B,WAAW4B,IAAI;gBAAC;gBAC9BC,QAAQ;oBACNV,OAAO;oBACPQ,MAAM;wBAAEC,MAAM5B,WAAW4B,IAAI;oBAAC;oBAC9BE,WAAW;gBACb;gBACAV,WAAWZ;YACb;SACD;QACD;;;;;;KAMC,GACDuB,aAAa;YACX;gBACEpB,OAAO;gBACPa,MAAM;gBACNC,UAAUrB;gBACVsB,UAAU;gBACVC,MAAM;oBAAEC,MAAM5B,WAAW4B,IAAI;gBAAC;gBAC9BC,QAAQ;oBACNV,OAAO;oBACPQ,MAAM;wBAAEC,MAAM5B,WAAW4B,IAAI;oBAAC;oBAC9BE,WAAW;gBACb;gBACAV,WAAWZ;YACb;SACD;IACH;AACF;AAEA,wEAAwE;AACxE,SAASwB,WAAW9B,gBAAgB,QAAQ,+CAA2C"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type AglynOrgMember, type PluginApiHandler, type PluginApiRequestSubject } from '@aglyn/aglyn/server';
|
|
18
|
+
import { ORG_AUTOMATION_ACTIVITY_TARGET, ORG_AUTOMATION_API_ROUTES } from '../model/org-automations';
|
|
19
|
+
export { ORG_AUTOMATION_API_ROUTES };
|
|
20
|
+
/** What `automations/manage` may be asked to do. */
|
|
21
|
+
export declare const ORG_AUTOMATION_MANAGE_ACTIONS: readonly ["create", "update", "setEnabled", "delete"];
|
|
22
|
+
/** What the doors reach outside this module; specs hand in their own. */
|
|
23
|
+
export interface OrgAutomationRouteDeps {
|
|
24
|
+
firestore(): FirebaseFirestore.Firestore;
|
|
25
|
+
verifyIdToken(token: string): Promise<{
|
|
26
|
+
uid: string;
|
|
27
|
+
email?: string | null;
|
|
28
|
+
}>;
|
|
29
|
+
resolveOrgMembership(uid: string, orgId: string): Promise<{
|
|
30
|
+
orgId: string;
|
|
31
|
+
member: Partial<AglynOrgMember>;
|
|
32
|
+
} | null>;
|
|
33
|
+
getOrgDoc(orgId: string): Promise<Record<string, unknown> | null>;
|
|
34
|
+
resolveOrgIdForHost(hostId: string): Promise<string | null>;
|
|
35
|
+
logOrgActivity(orgId: string, actor: {
|
|
36
|
+
uid: string;
|
|
37
|
+
email?: string | null;
|
|
38
|
+
}, action: string, target: {
|
|
39
|
+
type: typeof ORG_AUTOMATION_ACTIVITY_TARGET;
|
|
40
|
+
id: string;
|
|
41
|
+
name?: string;
|
|
42
|
+
}): Promise<void>;
|
|
43
|
+
logHostActivity(hostId: string, actor: {
|
|
44
|
+
uid: string;
|
|
45
|
+
email?: string | null;
|
|
46
|
+
}, action: string, target: {
|
|
47
|
+
type: 'workflow';
|
|
48
|
+
id: string;
|
|
49
|
+
name?: string;
|
|
50
|
+
}): Promise<void>;
|
|
51
|
+
newId(): string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The organization a `automations/manage` request is for, read off its JSON
|
|
55
|
+
* body, so the dispatchers' release gate asks about the right organization.
|
|
56
|
+
* Consulted only for a request that names no site. Unverified, exactly as a
|
|
57
|
+
* `hostId` is: the handler refuses anyone who is not an org-wide member.
|
|
58
|
+
*/
|
|
59
|
+
export declare function orgAutomationsManageSubject(request: Request): Promise<PluginApiRequestSubject | null>;
|
|
60
|
+
/**
|
|
61
|
+
* `automations/manage`: create, edit, switch on or off, and delete one of the
|
|
62
|
+
* organization's automations.
|
|
63
|
+
*
|
|
64
|
+
* A DELETE is soft — `deletedAt` stamped and the automation switched off —
|
|
65
|
+
* because the id is what every run history row and every waiting person
|
|
66
|
+
* names: the rows keep a name to show, and the people waiting inside it are
|
|
67
|
+
* stopped by the resume's kill switch on their next beat. Deleting and
|
|
68
|
+
* switching off are never refused for want of a plan, so an organization
|
|
69
|
+
* that downgraded can always stop what it built.
|
|
70
|
+
*/
|
|
71
|
+
export declare function createOrgAutomationsManageHandler(overrides?: Partial<OrgAutomationRouteDeps>): PluginApiHandler;
|
|
72
|
+
/**
|
|
73
|
+
* `automations/pause`: pause or resume one org automation on ONE site.
|
|
74
|
+
*
|
|
75
|
+
* `pausedHostIds` is changed with `arrayUnion`/`arrayRemove` of the named
|
|
76
|
+
* site alone, inside a transaction that first confirms the automation runs
|
|
77
|
+
* there — so a site's editor can take their own site out of an org
|
|
78
|
+
* automation and put it back, and can never touch another site's entry or
|
|
79
|
+
* the automation itself. Pausing stops the people already waiting inside it
|
|
80
|
+
* on this site as well, on their next beat: the resume reads a pause as the
|
|
81
|
+
* kill switch.
|
|
82
|
+
*
|
|
83
|
+
* Never refused for want of a plan: a pause is how a site stops something,
|
|
84
|
+
* and stopping is always allowed.
|
|
85
|
+
*/
|
|
86
|
+
export declare function createOrgAutomationPauseHandler(overrides?: Partial<OrgAutomationRouteDeps>): PluginApiHandler;
|
|
87
|
+
export declare const orgAutomationsManageHandler: PluginApiHandler;
|
|
88
|
+
export declare const orgAutomationPauseHandler: PluginApiHandler;
|