@aglyn/plugins-workflows 1.0.0-beta.218 → 1.0.0-beta.220
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/README.md +33 -4
- package/package.json +13 -13
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/components/host-actions-card.component.js +3 -2
- package/src/lib/components/host-actions-card.component.js.map +1 -1
- package/src/lib/components/host-run-history-card.component.d.ts +2 -2
- package/src/lib/components/host-run-history-card.component.js +3 -2
- package/src/lib/components/host-run-history-card.component.js.map +1 -1
- package/src/lib/components/host-webhooks-card.component.js +2 -1
- package/src/lib/components/host-webhooks-card.component.js.map +1 -1
- package/src/lib/components/host-workflows-card.component.js +4 -3
- package/src/lib/components/host-workflows-card.component.js.map +1 -1
- package/src/lib/components/use-automation-step-pickers.js +50 -42
- package/src/lib/components/use-automation-step-pickers.js.map +1 -1
- package/src/lib/components/use-org-automation-step-pickers.js +23 -11
- package/src/lib/components/use-org-automation-step-pickers.js.map +1 -1
- package/src/lib/components/workflow-zones.d.ts +49 -0
- package/src/lib/components/workflow-zones.js +3 -0
- package/src/lib/components/workflow-zones.js.map +1 -1
- package/src/lib/declarations.server.d.ts +6 -0
- package/src/lib/declarations.server.js +39 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/engine/crm-action-steps.d.ts +14 -1
- package/src/lib/engine/crm-action-steps.js +15 -0
- package/src/lib/engine/crm-action-steps.js.map +1 -1
- package/src/lib/engine/run-event-actions.d.ts +2 -1
- package/src/lib/engine/run-event-actions.js +66 -25
- package/src/lib/engine/run-event-actions.js.map +1 -1
- package/src/lib/engine/run-event-workflows.js +2 -1
- package/src/lib/engine/run-event-workflows.js.map +1 -1
- package/src/lib/engine/workflow-steps.d.ts +1 -1
- package/src/lib/engine/workflow-steps.js +1 -1
- package/src/lib/engine/workflow-steps.js.map +1 -1
- package/src/lib/model/automation-record.d.ts +47 -0
- package/src/lib/model/automation-record.js +79 -0
- package/src/lib/model/automation-record.js.map +1 -0
- package/src/lib/model/org-automations.js.map +1 -1
- package/src/lib/model/step-outcomes.d.ts +42 -0
- package/src/lib/model/step-outcomes.js +70 -0
- package/src/lib/model/step-outcomes.js.map +1 -0
- package/src/lib/model/webhooks.d.ts +36 -0
- package/src/lib/model/webhooks.js +24 -0
- package/src/lib/model/webhooks.js.map +1 -0
- package/src/lib/model/workflows-record-lists.d.ts +18 -0
- package/src/lib/model/workflows-record-lists.js +45 -0
- package/src/lib/model/workflows-record-lists.js.map +1 -0
- package/src/lib/model/workflows-record-routes.d.ts +22 -0
- package/src/lib/model/workflows-record-routes.js +73 -0
- package/src/lib/model/workflows-record-routes.js.map +1 -0
- package/src/lib/model/workflows-search-sources.d.ts +20 -0
- package/src/lib/model/workflows-search-sources.js +35 -0
- package/src/lib/model/workflows-search-sources.js.map +1 -0
- package/src/lib/model/workflows.d.ts +102 -0
- package/src/lib/model/workflows.js +167 -0
- package/src/lib/model/workflows.js.map +1 -0
- package/src/lib/plan-entitlements.d.ts +36 -0
- package/src/lib/plan-entitlements.js +44 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +37 -2
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/automation-record-index.d.ts +20 -0
- package/src/lib/server/automation-record-index.js +59 -0
- package/src/lib/server/automation-record-index.js.map +1 -0
- package/src/lib/server/workflow-dependents.d.ts +28 -0
- package/src/lib/server/workflow-dependents.js +61 -0
- package/src/lib/server/workflow-dependents.js.map +1 -0
- package/src/lib/server/workflow-variables.d.ts +20 -0
- package/src/lib/server/workflow-variables.js +75 -0
- package/src/lib/server/workflow-variables.js.map +1 -0
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js +2 -1
- package/src/lib/server.js.map +1 -1
- package/src/lib/tenant-emails.d.ts +29 -0
- package/src/lib/tenant-emails.js +41 -0
- package/src/lib/tenant-emails.js.map +1 -0
- package/src/lib/testing/stand-in-dataset-list.d.ts +28 -0
- package/src/lib/testing/stand-in-dataset-list.js +55 -0
- package/src/lib/testing/stand-in-dataset-list.js.map +1 -0
- package/src/lib/testing/stand-in-overlay-list.d.ts +26 -0
- package/src/lib/testing/stand-in-overlay-list.js +53 -0
- package/src/lib/testing/stand-in-overlay-list.js.map +1 -0
- package/src/lib/usage-axes.d.ts +28 -0
- package/src/lib/usage-axes.js +72 -0
- package/src/lib/usage-axes.js.map +1 -0
|
@@ -14,7 +14,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import {
|
|
17
|
+
*/ import { ACTION_MAX_STEPS, checkEntitlement, consentGroupForHost, planLabelGrantingFeature, checkQuota, evaluateExpression, evaluateStepGuard, evaluateTriggerConditions, FLOW_TIMED_OUT_FIELD, flowSubscriptionTopicId, hostPublicOrigin, isClientActionStep, isFlowSuspendingStep, datasetDisplayName, describeDatasetRecordErrors, contactContainerFieldPath, datasetIntegrityFields, datasetIntegrityUpdate, effectiveDatasetModel, ensureDeclaredCustomFieldTypes, normalizeTriggerConditions, prepareDatasetRecordWrite, resolveOrgEntitlements } from "@aglyn/aglyn/server";
|
|
18
18
|
import { isDeferrableSendResult, isEmailConfigured, sendEmail, sendFailureReason } from "@aglyn/shared-util-email";
|
|
19
19
|
import { dataStorageRefusal, enrollListMember, firebaseAdmin, flowEmailRefusal, getOrgForHost, hostSendingIdentity, meterHostEmail, notifyHostManagers, consentGroupForSite, orgDataCollectionForHost, orgDataQueryForHost, resolveOrgIdForHost } from "@aglyn/tenant-data-admin";
|
|
20
20
|
import { announceDatasetRecordChange } from "@aglyn/tenant-data-admin/server/dataset-live-pages";
|
|
@@ -23,6 +23,7 @@ import { visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
|
23
23
|
// The leaf, not the barrel: this library's specs substitute the barrel
|
|
24
24
|
// wholesale, and the lookup must reach the real index logic under them.
|
|
25
25
|
import { findContactByEmail } from "@aglyn/tenant-data-admin/server/contact-email-index";
|
|
26
|
+
import { findOrgContainersByName, readOrgContainers } from "@aglyn/tenant-data-admin/server/org-containers";
|
|
26
27
|
// The leaf again, for the phishing screen's hold (AGL-3356): the control
|
|
27
28
|
// that stops a send must be the real one under a spec that mocks the barrel.
|
|
28
29
|
import { screenOutboundSend } from "@aglyn/tenant-data-admin/server/outbound-send-review";
|
|
@@ -30,9 +31,12 @@ import { renderSiteTextEmail } from "@aglyn/tenant-data-admin/server/host-email-
|
|
|
30
31
|
import { createHmac } from "crypto";
|
|
31
32
|
import { FieldValue } from "firebase-admin/firestore";
|
|
32
33
|
import { runSummaryFields } from "../model/run-history.js";
|
|
34
|
+
import { ACTION_MAX_EVENT_DEPTH, runWorkflow, WORKFLOW_MAX_STEPS } from "../model/workflows.js";
|
|
35
|
+
import { describeStepOutcome } from "../model/step-outcomes.js";
|
|
36
|
+
import { WEBHOOK_URL_PATTERN } from "../model/webhooks.js";
|
|
33
37
|
import { eventRunSuspension } from "./site-suspension.js";
|
|
34
38
|
import { advanceFlowEnrollment, claimFlowEnrollment, deferFlowEnrollment, endFlowEnrollment, enrollInFlow, findFlowEnrollmentsAwaiting, sweepDueFlowEnrollments } from "./flow-enrollments.js";
|
|
35
|
-
import { logCrmEmailActivity, prepareCrmEmailActivity, runCrmActionStep } from "./crm-action-steps.js";
|
|
39
|
+
import { isCrmActionStep, logCrmEmailActivity, prepareCrmEmailActivity, runCrmActionStep } from "./crm-action-steps.js";
|
|
36
40
|
import { resolveDatasetDoc } from "@aglyn/tenant-runtime/resolve-dataset";
|
|
37
41
|
import { resumeWorkflowEnrollment, runEventWorkflows } from "./run-event-workflows.js";
|
|
38
42
|
import { ORG_AUTOMATION_RUN_TARGET, orgAutomationStepRefusal } from "../model/org-automations.js";
|
|
@@ -384,7 +388,11 @@ async function datasetAppendRefusal(env, datasetRef) {
|
|
|
384
388
|
model: datasetDoc.get('model'),
|
|
385
389
|
fields: Array.isArray(datasetDoc.get('fields')) ? datasetDoc.get('fields') : []
|
|
386
390
|
};
|
|
387
|
-
const
|
|
391
|
+
const appendModel = effectiveDatasetModel(appendDataset);
|
|
392
|
+
// A plugin's field validator runs only once its plugin registered it.
|
|
393
|
+
await ensureDeclaredCustomFieldTypes(appendModel);
|
|
394
|
+
const write = prepareDatasetRecordWrite(appendDataset, payload);
|
|
395
|
+
const values = write.values;
|
|
388
396
|
// Same name precedence `findDatasetByName` resolves in.
|
|
389
397
|
const appendLabel = (datasetDisplayName({
|
|
390
398
|
displayName: datasetDoc.get('displayName'),
|
|
@@ -394,14 +402,23 @@ async function datasetAppendRefusal(env, datasetRef) {
|
|
|
394
402
|
// to write. An error rather than a quiet success: a run history that
|
|
395
403
|
// says `saved to Leads` while nothing saves is how a mismatched field
|
|
396
404
|
// name goes unnoticed.
|
|
397
|
-
if (!
|
|
405
|
+
if (!write.matched.length) {
|
|
398
406
|
return failed(`no event field matches a field in dataset "${appendLabel || step.datasetId}"`);
|
|
399
407
|
}
|
|
408
|
+
// Held to the model like every other record write (AGL-2773): a value
|
|
409
|
+
// its field cannot hold refuses the whole record, and the run says
|
|
410
|
+
// which field and why instead of `saved to Leads`.
|
|
411
|
+
if (Object.keys(write.errors).length) {
|
|
412
|
+
return failed(`record failed validation for dataset "${appendLabel || step.datasetId}": ${describeDatasetRecordErrors(write.errors)}`);
|
|
413
|
+
}
|
|
414
|
+
if (!Object.keys(values).length) {
|
|
415
|
+
return failed(`every event field matching dataset "${appendLabel || step.datasetId}" is empty`);
|
|
416
|
+
}
|
|
400
417
|
const refusal = await datasetAppendRefusal(env, datasetDoc.ref);
|
|
401
418
|
if (refusal) return failed(refusal);
|
|
402
419
|
await datasetDoc.ref.collection('records').add(_extends({
|
|
403
420
|
values
|
|
404
|
-
}, datasetIntegrityFields(
|
|
421
|
+
}, datasetIntegrityFields(appendModel, values), {
|
|
405
422
|
createdAt: FieldValue.serverTimestamp()
|
|
406
423
|
}));
|
|
407
424
|
await announceDatasetStepWrite(env, datasetDoc.id);
|
|
@@ -421,15 +438,22 @@ async function datasetAppendRefusal(env, datasetRef) {
|
|
|
421
438
|
fields: Array.isArray(datasetDoc.get('fields')) ? datasetDoc.get('fields') : []
|
|
422
439
|
};
|
|
423
440
|
const updateModel = effectiveDatasetModel(updateDataset);
|
|
424
|
-
|
|
441
|
+
await ensureDeclaredCustomFieldTypes(updateModel);
|
|
442
|
+
const updateLabel = String(datasetDisplayName({
|
|
443
|
+
displayName: datasetDoc.get('displayName'),
|
|
444
|
+
name: datasetDoc.get('name')
|
|
445
|
+
}) || step.datasetName || step.datasetId || '').slice(0, 60);
|
|
446
|
+
// Checked before the lookup, as an append: the fields this write
|
|
447
|
+
// supplies are held to the model whichever leg runs below, and a
|
|
448
|
+
// refusal costs no read.
|
|
449
|
+
const incoming = prepareDatasetRecordWrite(updateDataset, payload);
|
|
425
450
|
// Nothing to merge or append — an error, for the reason the append
|
|
426
451
|
// branch above gives.
|
|
427
|
-
if (!
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return failed(`no event field matches a field in dataset "${String(updateLabel != null ? updateLabel : '').slice(0, 60)}"`);
|
|
452
|
+
if (!incoming.matched.length) {
|
|
453
|
+
return failed(`no event field matches a field in dataset "${updateLabel}"`);
|
|
454
|
+
}
|
|
455
|
+
if (!Object.keys(incoming.values).length) {
|
|
456
|
+
return failed(`every event field matching dataset "${updateLabel}" is empty`);
|
|
433
457
|
}
|
|
434
458
|
const email = String((_payload_email = payload.email) != null ? _payload_email : '').trim();
|
|
435
459
|
// `records.values` is exempt from indexing, so this lookup is served
|
|
@@ -439,7 +463,16 @@ async function datasetAppendRefusal(env, datasetRef) {
|
|
|
439
463
|
const existing = email ? await datasetDoc.ref.collection('records').where('values.email', '==', email).limit(1).get() : null;
|
|
440
464
|
if (existing && !existing.empty) {
|
|
441
465
|
var _existing_docs__get;
|
|
442
|
-
|
|
466
|
+
// A merge holds only the fields this write sent to the model: a row
|
|
467
|
+
// stored as text before AGL-2773 is not refused for a legacy value
|
|
468
|
+
// this run never touched.
|
|
469
|
+
const write = prepareDatasetRecordWrite(updateDataset, payload, {
|
|
470
|
+
existing: (_existing_docs__get = existing.docs[0].get('values')) != null ? _existing_docs__get : {}
|
|
471
|
+
});
|
|
472
|
+
if (Object.keys(write.errors).length) {
|
|
473
|
+
return failed(`record failed validation for dataset "${updateLabel}": ${describeDatasetRecordErrors(write.errors)}`);
|
|
474
|
+
}
|
|
475
|
+
const merged = write.values;
|
|
443
476
|
await existing.docs[0].ref.set(_extends({
|
|
444
477
|
values: merged
|
|
445
478
|
}, datasetIntegrityUpdate(updateModel, merged, FieldValue.delete()), {
|
|
@@ -460,12 +493,16 @@ async function datasetAppendRefusal(env, datasetRef) {
|
|
|
460
493
|
} else {
|
|
461
494
|
// The APPEND leg of update-or-append, and the only one of the two
|
|
462
495
|
// that adds a row — the merge above rewrites a record that already
|
|
463
|
-
// counts against the band.
|
|
496
|
+
// counts against the band. A new row is held to the whole model,
|
|
497
|
+
// required fields included, like any other append.
|
|
498
|
+
if (Object.keys(incoming.errors).length) {
|
|
499
|
+
return failed(`record failed validation for dataset "${updateLabel}": ${describeDatasetRecordErrors(incoming.errors)}`);
|
|
500
|
+
}
|
|
464
501
|
const refusal = await datasetAppendRefusal(env, datasetDoc.ref);
|
|
465
502
|
if (refusal) return failed(refusal);
|
|
466
503
|
await datasetDoc.ref.collection('records').add(_extends({
|
|
467
|
-
values
|
|
468
|
-
}, datasetIntegrityFields(updateModel, values), {
|
|
504
|
+
values: incoming.values
|
|
505
|
+
}, datasetIntegrityFields(updateModel, incoming.values), {
|
|
469
506
|
createdAt: FieldValue.serverTimestamp()
|
|
470
507
|
}));
|
|
471
508
|
}
|
|
@@ -546,7 +583,7 @@ async function datasetAppendRefusal(env, datasetRef) {
|
|
|
546
583
|
* so the opt-out link the seam mints names it. Without that the
|
|
547
584
|
* preference page opens on a list of every stream the site has, and
|
|
548
585
|
* the recipient has to find the one they were trying to leave.
|
|
549
|
-
*/ const topicId =
|
|
586
|
+
*/ const topicId = flowSubscriptionTopicId(step.topicId, scope);
|
|
550
587
|
const gate = await flowEmailRefusal({
|
|
551
588
|
hostId,
|
|
552
589
|
email: to,
|
|
@@ -636,7 +673,7 @@ async function datasetAppendRefusal(env, datasetRef) {
|
|
|
636
673
|
priority: 'bulk'
|
|
637
674
|
} : {}, {
|
|
638
675
|
// `topicId` is `''` for a step that belongs to no stream, which
|
|
639
|
-
// every reader of it treats as absent — see `
|
|
676
|
+
// every reader of it treats as absent — see `flowSubscriptionTopicId`.
|
|
640
677
|
// The consent group comes off the org the run already holds, so the
|
|
641
678
|
// gate honors an unsubscribe from any site of a declared group —
|
|
642
679
|
// the sender this site mails as — and the org's confirmation switch,
|
|
@@ -753,17 +790,21 @@ async function datasetAppendRefusal(env, datasetRef) {
|
|
|
753
790
|
*/ const campaignLabel = step.campaignName || step.campaignId;
|
|
754
791
|
const campaignOrgId = (_env_orgId = env.orgId) != null ? _env_orgId : await resolveOrgIdForHost(hostId);
|
|
755
792
|
if (!campaignOrgId) return failed(`unknown campaign "${campaignLabel}"`);
|
|
756
|
-
|
|
757
|
-
|
|
793
|
+
// The org's containers of the `campaign` kind, read where the kind's
|
|
794
|
+
// declaration says they are stored.
|
|
795
|
+
const containerStore = firebaseAdmin.app().firestore();
|
|
796
|
+
const usable = (container)=>Boolean((container == null ? void 0 : container.live) && visibleToHost(container.visibleTo, hostId));
|
|
758
797
|
const namedId = (_ref7 = (_step_campaignId = step.campaignId) == null ? void 0 : _step_campaignId.trim()) != null ? _ref7 : '';
|
|
759
|
-
const
|
|
760
|
-
|
|
798
|
+
const campaign = namedId ? (await readOrgContainers(containerStore, 'campaign', campaignOrgId, [
|
|
799
|
+
namedId
|
|
800
|
+
]))[0] : (await findOrgContainersByName(containerStore, 'campaign', campaignOrgId, (_ref8 = (_step_campaignName = step.campaignName) == null ? void 0 : _step_campaignName.trim()) != null ? _ref8 : '', 10)).find(usable);
|
|
801
|
+
if (!(campaign == null ? void 0 : campaign.exists)) {
|
|
761
802
|
return failed(`unknown campaign "${campaignLabel}"`);
|
|
762
803
|
}
|
|
763
|
-
if (
|
|
804
|
+
if (!campaign.live) {
|
|
764
805
|
return failed(`campaign "${campaignLabel}" was deleted`);
|
|
765
806
|
}
|
|
766
|
-
if (!usable(
|
|
807
|
+
if (!usable(campaign)) {
|
|
767
808
|
return failed(`campaign "${campaignLabel}" is not placed on this site`);
|
|
768
809
|
}
|
|
769
810
|
/*
|
|
@@ -781,7 +822,7 @@ async function datasetAppendRefusal(env, datasetRef) {
|
|
|
781
822
|
* update cannot fail for absence.
|
|
782
823
|
*/ const group = await consentGroupForSite(hostId);
|
|
783
824
|
await contact.ref.update({
|
|
784
|
-
[
|
|
825
|
+
[contactContainerFieldPath(group.groupId, 'campaign')]: FieldValue.arrayUnion(campaign.id),
|
|
785
826
|
updatedAt: FieldValue.serverTimestamp()
|
|
786
827
|
});
|
|
787
828
|
} else if (isCrmActionStep(step)) {
|