@aglyn/plugins-workflows 1.0.0-beta.232 → 1.0.0-beta.233
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 -12
- package/src/lib/components/host-run-history-card.component.js +31 -7
- package/src/lib/components/host-run-history-card.component.js.map +1 -1
- package/src/lib/components/host-webhooks-card.component.js +2 -2
- package/src/lib/components/host-webhooks-card.component.js.map +1 -1
- package/src/lib/components/host-workflows-card.component.js +1 -1
- package/src/lib/components/host-workflows-card.component.js.map +1 -1
- package/src/lib/components/staff-automations-card.component.js +38 -3
- package/src/lib/components/staff-automations-card.component.js.map +1 -1
- package/src/lib/engine/run-event-actions.js +4 -0
- package/src/lib/engine/run-event-actions.js.map +1 -1
- package/src/lib/model/run-history.js +37 -2
- package/src/lib/model/run-history.js.map +1 -1
- package/src/lib/model/webhook-text.d.ts +17 -0
- package/src/lib/model/webhook-text.js +78 -0
- package/src/lib/model/webhook-text.js.map +1 -0
- package/src/lib/model/webhooks.d.ts +9 -0
- package/src/lib/model/webhooks.js +18 -0
- package/src/lib/model/webhooks.js.map +1 -1
|
@@ -88,11 +88,46 @@
|
|
|
88
88
|
]
|
|
89
89
|
}
|
|
90
90
|
],
|
|
91
|
-
|
|
91
|
+
/*
|
|
92
|
+
* The headers (AGL-3680): Time both ways, and Trigger, Result and What
|
|
93
|
+
* happened — on every run, `backfill-activity-run-fields.mjs` completing
|
|
94
|
+
* the old ones — `alone`, one direction each: beside the `target.id` and
|
|
95
|
+
* `result` base every order is a composite per base field.
|
|
96
|
+
* Who is read from the actor fields and sorts the page.
|
|
97
|
+
*/ sorts: [
|
|
92
98
|
{
|
|
93
99
|
path: 'createdAt',
|
|
94
100
|
direction: 'desc',
|
|
95
|
-
column: 'createdAtMs'
|
|
101
|
+
column: 'createdAtMs',
|
|
102
|
+
label: 'Time'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
path: 'createdAt',
|
|
106
|
+
direction: 'asc',
|
|
107
|
+
column: 'createdAtMs',
|
|
108
|
+
label: 'Time',
|
|
109
|
+
alone: true
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
path: 'trigger',
|
|
113
|
+
direction: 'asc',
|
|
114
|
+
column: 'trigger',
|
|
115
|
+
label: 'Trigger',
|
|
116
|
+
alone: true
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
path: 'result',
|
|
120
|
+
direction: 'asc',
|
|
121
|
+
column: 'result',
|
|
122
|
+
label: 'Result',
|
|
123
|
+
alone: true
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
path: 'summary',
|
|
127
|
+
direction: 'asc',
|
|
128
|
+
column: 'summary',
|
|
129
|
+
label: 'What happened',
|
|
130
|
+
alone: true
|
|
96
131
|
}
|
|
97
132
|
],
|
|
98
133
|
search: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/run-history.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 { nameSearchTokens } from '@aglyn/aglyn/app-utils/name-search'\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport type {\n ListQueryDeclaration,\n ListQueryFilter,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\n\n/*\n * THE RUN HISTORY, AS A QUERY (AGL-3321).\n *\n * A run is an entry in `hosts/{hostId}/activity` that carries a verdict:\n * `result` is `succeeded`, `failed` or `skipped`, beside the `trigger` event\n * and the `summary` of what each step did. The activity feed also holds\n * publishes, media saves and member changes, so the history's query is\n * scoped to the automation (`target.id`) AND to runs (`result`) — and every\n * clause the Filters panel accepts, and the search word, is another\n * predicate on that same query. Pages come from the query, so a page of ten\n * is ten runs and a filtered page is ten matches, however far back they sit.\n *\n * Every run writer stamps `summaryTokens` beside the summary\n * ({@link runSummaryFields}), and `tools/scripts/backfill-activity-run-fields.mjs`\n * completes the entries written before: `result`, `trigger` and `summary`\n * from the prose line of a run recorded before those fields existed, and the\n * tokens on every run. A run without a `result` is invisible to the query,\n * which is why the backfill is part of shipping this.\n */\n\n/** A run's verdict, as every run writer stores it. */\nexport const RUN_RESULTS = ['succeeded', 'failed', 'skipped'] as const\nexport type RunResultValue = (typeof RUN_RESULTS)[number]\n\n/** The written word-prefix tokens of a run's summary, for the search box. */\nexport const RUN_SUMMARY_TOKENS = 'summaryTokens'\n\n/**\n * What a run writer spreads beside `summary`: the summary itself and its\n * search tokens, so the two cannot be written apart.\n */\nexport function runSummaryFields(summary: string): {\n summary: string\n summaryTokens: string[]\n} {\n return { summary, summaryTokens: nameSearchTokens(summary) }\n}\n\n/**\n * What the Filters panel offers, each a predicate the query serves beneath\n * its newest-first order: Time is that order's own field, so its ranges cost\n * no index of their own; Trigger and Result are equalities.\n *\n * `What happened` has no column filter: its words are the search box's,\n * which reads the same tokens, and a `contains` beside the search would be a\n * second array clause the query cannot hold.\n */\nexport const RUN_HISTORY_QUERY: ListQueryDeclaration = {\n fields: [\n {\n column: 'createdAtMs',\n kind: 'date',\n path: 'createdAt',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n {\n column: 'trigger',\n kind: 'exact',\n path: 'trigger',\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: 'result',\n kind: 'exact',\n path: 'result',\n operators: ['equals', 'isAnyOf'],\n },\n ] satisfies ListFilterField[],\n sorts: [{ path: 'createdAt', direction: 'desc', column: 'createdAtMs' }],\n search: { tokensPath: RUN_SUMMARY_TOKENS },\n}\n\nexport const RUN_HISTORY_HEADERS: Readonly<Record<string, string>> = {\n createdAtMs: 'Time',\n trigger: 'Trigger',\n result: 'Result',\n}\n\n/** A clause on Result the query serves — which is then what keeps it to runs. */\nconst namesResult = (clause: ListFilterClause): boolean =>\n clause.field === 'result' &&\n (clause.op === 'equals' || clause.op === 'isAnyOf') &&\n clause.value.trim() !== ''\n\n/**\n * The history's scope: this automation's entries, and only runs.\n *\n * \"Only runs\" is `result in [all three]` — unless the reader has asked for\n * a Result, whose own equality already names nothing but runs. The two are\n * never both on the query: a second predicate on the same field would only\n * repeat the first, and the plan serves an `equals` or `isAnyOf` on Result\n * unconditionally, so dropping the scope never lets a non-run through.\n */\nexport function runHistoryBase(\n targetId: string | undefined,\n clauses: readonly ListFilterClause[],\n): ListQueryFilter[] {\n const base: ListQueryFilter[] = []\n if (targetId) base.push({ path: 'target.id', op: '==', value: targetId })\n if (!clauses.some(namesResult))\n base.push({ path: 'result', op: 'in', value: [...RUN_RESULTS] })\n return base\n}\n\n/** The base's shape, for the index enumeration. */\nexport const RUN_HISTORY_BASE_PATHS = [\n { path: 'target.id' },\n { path: 'result' },\n] as const\n\n/** A run entry as stored, as far as completing it is concerned. */\nexport interface RunEntryLike {\n action?: unknown\n result?: unknown\n trigger?: unknown\n summary?: unknown\n summaryTokens?: unknown\n}\n\n/**\n * What a stored activity entry lacks to be found by the run history's query,\n * or `null` when it is not a run or already complete. The definition the\n * backfill restates (`tools/scripts/backfill-activity-run-fields.mjs`), held\n * to the same worked examples (`tools/scripts/lib/activity-run-fields.fixtures.json`).\n *\n * A run recorded before its structured fields carried only the prose line\n * `Action ran on <event>` — with ` with errors: <what failed>` when it\n * failed — so the verdict, the event and the summary are read back from it,\n * the way the history has always shown such a row. An entry that is neither\n * a stored verdict nor that prose is not a run and is left alone.\n */\nexport function runEntryCompletion(\n entry: RunEntryLike,\n): Record<string, unknown> | null {\n const stored = String(entry.result ?? '').trim()\n const action = String(entry.action ?? '').trim()\n const legacy = action.startsWith('Action ran on')\n if (!(RUN_RESULTS as readonly string[]).includes(stored) && !legacy)\n return null\n const patch: Record<string, unknown> = {}\n if (!(RUN_RESULTS as readonly string[]).includes(stored)) {\n patch['result'] = action.includes('with errors:') ? 'failed' : 'succeeded'\n }\n if (typeof entry.trigger !== 'string' || !entry.trigger) {\n const event = legacy ? /^Action ran on (\\S+)/.exec(action)?.[1] : undefined\n if (event) patch['trigger'] = event\n }\n let summary = typeof entry.summary === 'string' ? entry.summary.trim() : ''\n if (!summary) {\n const errors = /with errors:\\s*(.+)$/.exec(action)\n summary = errors ? errors[1] : legacy ? 'Ran' : action\n patch['summary'] = summary\n }\n const tokens = nameSearchTokens(summary)\n const held = Array.isArray(entry.summaryTokens) ? entry.summaryTokens : null\n if (\n !held ||\n held.length !== tokens.length ||\n held.some((token, at) => token !== tokens[at])\n ) {\n patch['summaryTokens'] = tokens\n }\n return Object.keys(patch).length ? patch : null\n}\n"],"names":["nameSearchTokens","RUN_RESULTS","RUN_SUMMARY_TOKENS","runSummaryFields","summary","summaryTokens","RUN_HISTORY_QUERY","fields","column","kind","path","operators","sorts","direction","search","tokensPath","RUN_HISTORY_HEADERS","createdAtMs","trigger","result","namesResult","clause","field","op","value","trim","runHistoryBase","targetId","clauses","base","push","some","RUN_HISTORY_BASE_PATHS","runEntryCompletion","entry","stored","String","action","legacy","startsWith","includes","patch","event","exec","undefined","errors","tokens","held","Array","isArray","length","token","at","Object","keys"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/run-history.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 { nameSearchTokens } from '@aglyn/aglyn/app-utils/name-search'\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport type {\n ListQueryDeclaration,\n ListQueryFilter,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\n\n/*\n * THE RUN HISTORY, AS A QUERY (AGL-3321).\n *\n * A run is an entry in `hosts/{hostId}/activity` that carries a verdict:\n * `result` is `succeeded`, `failed` or `skipped`, beside the `trigger` event\n * and the `summary` of what each step did. The activity feed also holds\n * publishes, media saves and member changes, so the history's query is\n * scoped to the automation (`target.id`) AND to runs (`result`) — and every\n * clause the Filters panel accepts, and the search word, is another\n * predicate on that same query. Pages come from the query, so a page of ten\n * is ten runs and a filtered page is ten matches, however far back they sit.\n *\n * Every run writer stamps `summaryTokens` beside the summary\n * ({@link runSummaryFields}), and `tools/scripts/backfill-activity-run-fields.mjs`\n * completes the entries written before: `result`, `trigger` and `summary`\n * from the prose line of a run recorded before those fields existed, and the\n * tokens on every run. A run without a `result` is invisible to the query,\n * which is why the backfill is part of shipping this.\n */\n\n/** A run's verdict, as every run writer stores it. */\nexport const RUN_RESULTS = ['succeeded', 'failed', 'skipped'] as const\nexport type RunResultValue = (typeof RUN_RESULTS)[number]\n\n/** The written word-prefix tokens of a run's summary, for the search box. */\nexport const RUN_SUMMARY_TOKENS = 'summaryTokens'\n\n/**\n * What a run writer spreads beside `summary`: the summary itself and its\n * search tokens, so the two cannot be written apart.\n */\nexport function runSummaryFields(summary: string): {\n summary: string\n summaryTokens: string[]\n} {\n return { summary, summaryTokens: nameSearchTokens(summary) }\n}\n\n/**\n * What the Filters panel offers, each a predicate the query serves beneath\n * its newest-first order: Time is that order's own field, so its ranges cost\n * no index of their own; Trigger and Result are equalities.\n *\n * `What happened` has no column filter: its words are the search box's,\n * which reads the same tokens, and a `contains` beside the search would be a\n * second array clause the query cannot hold.\n */\nexport const RUN_HISTORY_QUERY: ListQueryDeclaration = {\n fields: [\n {\n column: 'createdAtMs',\n kind: 'date',\n path: 'createdAt',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n {\n column: 'trigger',\n kind: 'exact',\n path: 'trigger',\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: 'result',\n kind: 'exact',\n path: 'result',\n operators: ['equals', 'isAnyOf'],\n },\n ] satisfies ListFilterField[],\n /*\n * The headers (AGL-3680): Time both ways, and Trigger, Result and What\n * happened — on every run, `backfill-activity-run-fields.mjs` completing\n * the old ones — `alone`, one direction each: beside the `target.id` and\n * `result` base every order is a composite per base field.\n * Who is read from the actor fields and sorts the page.\n */\n sorts: [\n { path: 'createdAt', direction: 'desc', column: 'createdAtMs', label: 'Time' },\n { path: 'createdAt', direction: 'asc', column: 'createdAtMs', label: 'Time', alone: true },\n { path: 'trigger', direction: 'asc', column: 'trigger', label: 'Trigger', alone: true },\n { path: 'result', direction: 'asc', column: 'result', label: 'Result', alone: true },\n { path: 'summary', direction: 'asc', column: 'summary', label: 'What happened', alone: true },\n ],\n search: { tokensPath: RUN_SUMMARY_TOKENS },\n}\n\nexport const RUN_HISTORY_HEADERS: Readonly<Record<string, string>> = {\n createdAtMs: 'Time',\n trigger: 'Trigger',\n result: 'Result',\n}\n\n/** A clause on Result the query serves — which is then what keeps it to runs. */\nconst namesResult = (clause: ListFilterClause): boolean =>\n clause.field === 'result' &&\n (clause.op === 'equals' || clause.op === 'isAnyOf') &&\n clause.value.trim() !== ''\n\n/**\n * The history's scope: this automation's entries, and only runs.\n *\n * \"Only runs\" is `result in [all three]` — unless the reader has asked for\n * a Result, whose own equality already names nothing but runs. The two are\n * never both on the query: a second predicate on the same field would only\n * repeat the first, and the plan serves an `equals` or `isAnyOf` on Result\n * unconditionally, so dropping the scope never lets a non-run through.\n */\nexport function runHistoryBase(\n targetId: string | undefined,\n clauses: readonly ListFilterClause[],\n): ListQueryFilter[] {\n const base: ListQueryFilter[] = []\n if (targetId) base.push({ path: 'target.id', op: '==', value: targetId })\n if (!clauses.some(namesResult))\n base.push({ path: 'result', op: 'in', value: [...RUN_RESULTS] })\n return base\n}\n\n/** The base's shape, for the index enumeration. */\nexport const RUN_HISTORY_BASE_PATHS = [\n { path: 'target.id' },\n { path: 'result' },\n] as const\n\n/** A run entry as stored, as far as completing it is concerned. */\nexport interface RunEntryLike {\n action?: unknown\n result?: unknown\n trigger?: unknown\n summary?: unknown\n summaryTokens?: unknown\n}\n\n/**\n * What a stored activity entry lacks to be found by the run history's query,\n * or `null` when it is not a run or already complete. The definition the\n * backfill restates (`tools/scripts/backfill-activity-run-fields.mjs`), held\n * to the same worked examples (`tools/scripts/lib/activity-run-fields.fixtures.json`).\n *\n * A run recorded before its structured fields carried only the prose line\n * `Action ran on <event>` — with ` with errors: <what failed>` when it\n * failed — so the verdict, the event and the summary are read back from it,\n * the way the history has always shown such a row. An entry that is neither\n * a stored verdict nor that prose is not a run and is left alone.\n */\nexport function runEntryCompletion(\n entry: RunEntryLike,\n): Record<string, unknown> | null {\n const stored = String(entry.result ?? '').trim()\n const action = String(entry.action ?? '').trim()\n const legacy = action.startsWith('Action ran on')\n if (!(RUN_RESULTS as readonly string[]).includes(stored) && !legacy)\n return null\n const patch: Record<string, unknown> = {}\n if (!(RUN_RESULTS as readonly string[]).includes(stored)) {\n patch['result'] = action.includes('with errors:') ? 'failed' : 'succeeded'\n }\n if (typeof entry.trigger !== 'string' || !entry.trigger) {\n const event = legacy ? /^Action ran on (\\S+)/.exec(action)?.[1] : undefined\n if (event) patch['trigger'] = event\n }\n let summary = typeof entry.summary === 'string' ? entry.summary.trim() : ''\n if (!summary) {\n const errors = /with errors:\\s*(.+)$/.exec(action)\n summary = errors ? errors[1] : legacy ? 'Ran' : action\n patch['summary'] = summary\n }\n const tokens = nameSearchTokens(summary)\n const held = Array.isArray(entry.summaryTokens) ? entry.summaryTokens : null\n if (\n !held ||\n held.length !== tokens.length ||\n held.some((token, at) => token !== tokens[at])\n ) {\n patch['summaryTokens'] = tokens\n }\n return Object.keys(patch).length ? patch : null\n}\n"],"names":["nameSearchTokens","RUN_RESULTS","RUN_SUMMARY_TOKENS","runSummaryFields","summary","summaryTokens","RUN_HISTORY_QUERY","fields","column","kind","path","operators","sorts","direction","label","alone","search","tokensPath","RUN_HISTORY_HEADERS","createdAtMs","trigger","result","namesResult","clause","field","op","value","trim","runHistoryBase","targetId","clauses","base","push","some","RUN_HISTORY_BASE_PATHS","runEntryCompletion","entry","stored","String","action","legacy","startsWith","includes","patch","event","exec","undefined","errors","tokens","held","Array","isArray","length","token","at","Object","keys"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
export declare function webhookSummaryText(event: string, payload: Record<string, unknown>): string;
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
*/ import { hostEventLabel } from "@aglyn/aglyn/app-utils/host-events";
|
|
17
|
+
/**
|
|
18
|
+
* The `text` an outbound webhook's body carries beside `event`, `payload`
|
|
19
|
+
* and `sentAt` (AGL-3684).
|
|
20
|
+
*
|
|
21
|
+
* A Slack incoming webhook answers `400 no_text` to a body without `text`,
|
|
22
|
+
* so the step the docs offered for "post to Slack when a form is submitted"
|
|
23
|
+
* failed every run. Slack, Google Chat and Teams incoming webhooks all post
|
|
24
|
+
* a top-level `text` as it stands; any other receiver ignores one key more.
|
|
25
|
+
*
|
|
26
|
+
* It reads as a message, not a dump: a heading naming the event (and the
|
|
27
|
+
* form, for a submission), one line per submitted value, then the page.
|
|
28
|
+
* Ids the event carries for automations — `formId`, `hostId`, the journey
|
|
29
|
+
* a submission ends — are left out; they are in `payload` for a machine.
|
|
30
|
+
*/ /** Keys a person reading the message has no use for. */ const UNREAD_KEYS = new Set([
|
|
31
|
+
'formName',
|
|
32
|
+
'formId',
|
|
33
|
+
'path',
|
|
34
|
+
'hostId',
|
|
35
|
+
'journey',
|
|
36
|
+
'campaignIds'
|
|
37
|
+
]);
|
|
38
|
+
/** Slack's own limit is 40,000 characters; a message is far shorter. */ const MAX_LINES = 30;
|
|
39
|
+
const MAX_VALUE_LENGTH = 500;
|
|
40
|
+
/** Slack and Google Chat read `&`, `<` and `>` as markup; a visitor's text must not be. */ function escapeMarkup(value) {
|
|
41
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
42
|
+
}
|
|
43
|
+
function readable(value) {
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
const items = value.filter((item)=>[
|
|
46
|
+
'string',
|
|
47
|
+
'number',
|
|
48
|
+
'boolean'
|
|
49
|
+
].includes(typeof item));
|
|
50
|
+
return items.length ? items.join(', ') : null;
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === 'string') return value.trim() || null;
|
|
53
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function clip(value) {
|
|
57
|
+
return value.length > MAX_VALUE_LENGTH ? `${value.slice(0, MAX_VALUE_LENGTH)}…` : value;
|
|
58
|
+
}
|
|
59
|
+
export function webhookSummaryText(event, payload) {
|
|
60
|
+
const formName = readable(payload['formName']);
|
|
61
|
+
const heading = event === 'formSubmission' ? `*New form submission${formName ? ` — ${escapeMarkup(clip(formName))}` : ''}*` : `*${escapeMarkup(hostEventLabel(event))}*`;
|
|
62
|
+
const lines = Object.entries(payload).filter(([key])=>!UNREAD_KEYS.has(key) && !key.startsWith('_')).map(([key, value])=>[
|
|
63
|
+
key,
|
|
64
|
+
readable(value)
|
|
65
|
+
]).filter((entry)=>entry[1] !== null).map(([key, value])=>`• *${escapeMarkup(key)}:* ${escapeMarkup(clip(value))}`);
|
|
66
|
+
const shown = lines.slice(0, MAX_LINES);
|
|
67
|
+
if (lines.length > shown.length) shown.push(`…and ${lines.length - shown.length} more`);
|
|
68
|
+
const path = readable(payload['path']);
|
|
69
|
+
return [
|
|
70
|
+
heading,
|
|
71
|
+
...shown,
|
|
72
|
+
...path ? [
|
|
73
|
+
`Page: ${escapeMarkup(clip(path))}`
|
|
74
|
+
] : []
|
|
75
|
+
].join('\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=webhook-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/webhook-text.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 { hostEventLabel } from '@aglyn/aglyn/app-utils/host-events'\n\n/**\n * The `text` an outbound webhook's body carries beside `event`, `payload`\n * and `sentAt` (AGL-3684).\n *\n * A Slack incoming webhook answers `400 no_text` to a body without `text`,\n * so the step the docs offered for \"post to Slack when a form is submitted\"\n * failed every run. Slack, Google Chat and Teams incoming webhooks all post\n * a top-level `text` as it stands; any other receiver ignores one key more.\n *\n * It reads as a message, not a dump: a heading naming the event (and the\n * form, for a submission), one line per submitted value, then the page.\n * Ids the event carries for automations — `formId`, `hostId`, the journey\n * a submission ends — are left out; they are in `payload` for a machine.\n */\n\n/** Keys a person reading the message has no use for. */\nconst UNREAD_KEYS = new Set(['formName', 'formId', 'path', 'hostId', 'journey', 'campaignIds'])\n\n/** Slack's own limit is 40,000 characters; a message is far shorter. */\nconst MAX_LINES = 30\nconst MAX_VALUE_LENGTH = 500\n\n/** Slack and Google Chat read `&`, `<` and `>` as markup; a visitor's text must not be. */\nfunction escapeMarkup(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')\n}\n\nfunction readable(value: unknown): string | null {\n if (Array.isArray(value)) {\n const items = value.filter((item) => ['string', 'number', 'boolean'].includes(typeof item))\n return items.length ? items.join(', ') : null\n }\n if (typeof value === 'string') return value.trim() || null\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n return null\n}\n\nfunction clip(value: string): string {\n return value.length > MAX_VALUE_LENGTH ? `${value.slice(0, MAX_VALUE_LENGTH)}…` : value\n}\n\nexport function webhookSummaryText(event: string, payload: Record<string, unknown>): string {\n const formName = readable(payload['formName'])\n const heading =\n event === 'formSubmission'\n ? `*New form submission${formName ? ` — ${escapeMarkup(clip(formName))}` : ''}*`\n : `*${escapeMarkup(hostEventLabel(event))}*`\n\n const lines = Object.entries(payload)\n .filter(([key]) => !UNREAD_KEYS.has(key) && !key.startsWith('_'))\n .map(([key, value]) => [key, readable(value)] as const)\n .filter((entry): entry is readonly [string, string] => entry[1] !== null)\n .map(([key, value]) => `• *${escapeMarkup(key)}:* ${escapeMarkup(clip(value))}`)\n const shown = lines.slice(0, MAX_LINES)\n if (lines.length > shown.length) shown.push(`…and ${lines.length - shown.length} more`)\n\n const path = readable(payload['path'])\n return [heading, ...shown, ...(path ? [`Page: ${escapeMarkup(clip(path))}`] : [])].join('\\n')\n}\n"],"names":["hostEventLabel","UNREAD_KEYS","Set","MAX_LINES","MAX_VALUE_LENGTH","escapeMarkup","value","replace","readable","Array","isArray","items","filter","item","includes","length","join","trim","String","clip","slice","webhookSummaryText","event","payload","formName","heading","lines","Object","entries","key","has","startsWith","map","entry","shown","push","path"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,cAAc,QAAQ,qCAAoC;AAEnE;;;;;;;;;;;;;CAaC,GAED,sDAAsD,GACtD,MAAMC,cAAc,IAAIC,IAAI;IAAC;IAAY;IAAU;IAAQ;IAAU;IAAW;CAAc;AAE9F,sEAAsE,GACtE,MAAMC,YAAY;AAClB,MAAMC,mBAAmB;AAEzB,yFAAyF,GACzF,SAASC,aAAaC,KAAa;IACjC,OAAOA,MAAMC,OAAO,CAAC,MAAM,SAASA,OAAO,CAAC,MAAM,QAAQA,OAAO,CAAC,MAAM;AAC1E;AAEA,SAASC,SAASF,KAAc;IAC9B,IAAIG,MAAMC,OAAO,CAACJ,QAAQ;QACxB,MAAMK,QAAQL,MAAMM,MAAM,CAAC,CAACC,OAAS;gBAAC;gBAAU;gBAAU;aAAU,CAACC,QAAQ,CAAC,OAAOD;QACrF,OAAOF,MAAMI,MAAM,GAAGJ,MAAMK,IAAI,CAAC,QAAQ;IAC3C;IACA,IAAI,OAAOV,UAAU,UAAU,OAAOA,MAAMW,IAAI,MAAM;IACtD,IAAI,OAAOX,UAAU,YAAY,OAAOA,UAAU,WAAW,OAAOY,OAAOZ;IAC3E,OAAO;AACT;AAEA,SAASa,KAAKb,KAAa;IACzB,OAAOA,MAAMS,MAAM,GAAGX,mBAAmB,GAAGE,MAAMc,KAAK,CAAC,GAAGhB,kBAAkB,CAAC,CAAC,GAAGE;AACpF;AAEA,OAAO,SAASe,mBAAmBC,KAAa,EAAEC,OAAgC;IAChF,MAAMC,WAAWhB,SAASe,OAAO,CAAC,WAAW;IAC7C,MAAME,UACJH,UAAU,mBACN,CAAC,oBAAoB,EAAEE,WAAW,CAAC,GAAG,EAAEnB,aAAac,KAAKK,YAAY,GAAG,GAAG,CAAC,CAAC,GAC9E,CAAC,CAAC,EAAEnB,aAAaL,eAAesB,QAAQ,CAAC,CAAC;IAEhD,MAAMI,QAAQC,OAAOC,OAAO,CAACL,SAC1BX,MAAM,CAAC,CAAC,CAACiB,IAAI,GAAK,CAAC5B,YAAY6B,GAAG,CAACD,QAAQ,CAACA,IAAIE,UAAU,CAAC,MAC3DC,GAAG,CAAC,CAAC,CAACH,KAAKvB,MAAM,GAAK;YAACuB;YAAKrB,SAASF;SAAO,EAC5CM,MAAM,CAAC,CAACqB,QAA8CA,KAAK,CAAC,EAAE,KAAK,MACnED,GAAG,CAAC,CAAC,CAACH,KAAKvB,MAAM,GAAK,CAAC,GAAG,EAAED,aAAawB,KAAK,GAAG,EAAExB,aAAac,KAAKb,SAAS;IACjF,MAAM4B,QAAQR,MAAMN,KAAK,CAAC,GAAGjB;IAC7B,IAAIuB,MAAMX,MAAM,GAAGmB,MAAMnB,MAAM,EAAEmB,MAAMC,IAAI,CAAC,CAAC,KAAK,EAAET,MAAMX,MAAM,GAAGmB,MAAMnB,MAAM,CAAC,KAAK,CAAC;IAEtF,MAAMqB,OAAO5B,SAASe,OAAO,CAAC,OAAO;IACrC,OAAO;QAACE;WAAYS;WAAWE,OAAO;YAAC,CAAC,MAAM,EAAE/B,aAAac,KAAKiB,QAAQ;SAAC,GAAG,EAAE;KAAE,CAACpB,IAAI,CAAC;AAC1F"}
|
|
@@ -40,3 +40,12 @@ export interface HostWebhook {
|
|
|
40
40
|
* which resolves, pins and refuses redirects.
|
|
41
41
|
*/
|
|
42
42
|
export declare const WEBHOOK_URL_PATTERN: RegExp;
|
|
43
|
+
/**
|
|
44
|
+
* An outbound URL as the webhooks list shows it: its host, never its path or
|
|
45
|
+
* query (AGL-3684). A Slack, Discord or Teams incoming-webhook URL carries
|
|
46
|
+
* its credential in the path — anyone holding `hooks.slack.com/services/…`
|
|
47
|
+
* can post to the channel — so the list names where it delivers and nothing
|
|
48
|
+
* that would let a screenshot or a shoulder replay it. The edit dialog still
|
|
49
|
+
* holds the whole URL for whoever changes it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function maskedWebhookUrl(url: string | undefined): string;
|
|
@@ -26,5 +26,23 @@
|
|
|
26
26
|
* Delivery goes through `fetchConfiguredPublicUrl` (`@aglyn/tenant-data-admin`),
|
|
27
27
|
* which resolves, pins and refuses redirects.
|
|
28
28
|
*/ export const WEBHOOK_URL_PATTERN = /^https:\/\/(?!localhost)(?!127\.)(?!0\.)(?!10\.)(?!172\.(1[6-9]|2\d|3[01])\.)(?!192\.168\.)(?!169\.254\.)[^\s]+$/i;
|
|
29
|
+
/**
|
|
30
|
+
* An outbound URL as the webhooks list shows it: its host, never its path or
|
|
31
|
+
* query (AGL-3684). A Slack, Discord or Teams incoming-webhook URL carries
|
|
32
|
+
* its credential in the path — anyone holding `hooks.slack.com/services/…`
|
|
33
|
+
* can post to the channel — so the list names where it delivers and nothing
|
|
34
|
+
* that would let a screenshot or a shoulder replay it. The edit dialog still
|
|
35
|
+
* holds the whole URL for whoever changes it.
|
|
36
|
+
*/ export function maskedWebhookUrl(url) {
|
|
37
|
+
const raw = String(url != null ? url : '').trim();
|
|
38
|
+
if (!raw) return '';
|
|
39
|
+
try {
|
|
40
|
+
const parsed = new URL(raw);
|
|
41
|
+
const hidden = parsed.pathname.length > 1 || parsed.search || parsed.hash;
|
|
42
|
+
return `${parsed.protocol}//${parsed.host}${hidden ? '/…' : ''}`;
|
|
43
|
+
} catch (unused) {
|
|
44
|
+
return '…';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
29
47
|
|
|
30
48
|
//# sourceMappingURL=webhooks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/webhooks.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\n/**\n * Webhooks (AGL-149) at `hosts/{hostId}/webhooks/{id}`. Outbound entries\n * are targets a `webhookPost` action step delivers to (HMAC-signed);\n * inbound entries mint `/api/hooks/{hostId}/{hookId}` endpoints that run\n * a workflow with the posted JSON in scope. Business tier (`webhooks`\n * flag); Pro can be enabled per-tenant via entitlement overrides.\n */\nexport interface HostWebhook {\n name: string\n direction: 'outbound' | 'inbound'\n /** Outbound delivery URL (https only; resolved and checked at send time). */\n url?: string\n /** Shared secret: signs outbound bodies, verifies inbound callers. */\n secret?: string\n /** Inbound: workflow (by name) enrolled with the payload in scope. */\n workflowName?: string\n enabled?: boolean\n}\n\n/**\n * A hint for the webhooks card's URL field: https, and not an obvious private\n * IPv4 literal. It is NOT the SSRF guard — it reads only the URL's text, so\n * an IPv6 literal, a decimal IPv4 or a name that resolves inward all pass it.\n * Delivery goes through `fetchConfiguredPublicUrl` (`@aglyn/tenant-data-admin`),\n * which resolves, pins and refuses redirects.\n */\nexport const WEBHOOK_URL_PATTERN =\n /^https:\\/\\/(?!localhost)(?!127\\.)(?!0\\.)(?!10\\.)(?!172\\.(1[6-9]|2\\d|3[01])\\.)(?!192\\.168\\.)(?!169\\.254\\.)[^\\s]+$/i\n"],"names":["WEBHOOK_URL_PATTERN"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GAaD;;;;;;CAMC,GACD,OAAO,MAAMA,sBACX,oHAAmH"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/model/webhooks.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\n/**\n * Webhooks (AGL-149) at `hosts/{hostId}/webhooks/{id}`. Outbound entries\n * are targets a `webhookPost` action step delivers to (HMAC-signed);\n * inbound entries mint `/api/hooks/{hostId}/{hookId}` endpoints that run\n * a workflow with the posted JSON in scope. Business tier (`webhooks`\n * flag); Pro can be enabled per-tenant via entitlement overrides.\n */\nexport interface HostWebhook {\n name: string\n direction: 'outbound' | 'inbound'\n /** Outbound delivery URL (https only; resolved and checked at send time). */\n url?: string\n /** Shared secret: signs outbound bodies, verifies inbound callers. */\n secret?: string\n /** Inbound: workflow (by name) enrolled with the payload in scope. */\n workflowName?: string\n enabled?: boolean\n}\n\n/**\n * A hint for the webhooks card's URL field: https, and not an obvious private\n * IPv4 literal. It is NOT the SSRF guard — it reads only the URL's text, so\n * an IPv6 literal, a decimal IPv4 or a name that resolves inward all pass it.\n * Delivery goes through `fetchConfiguredPublicUrl` (`@aglyn/tenant-data-admin`),\n * which resolves, pins and refuses redirects.\n */\nexport const WEBHOOK_URL_PATTERN =\n /^https:\\/\\/(?!localhost)(?!127\\.)(?!0\\.)(?!10\\.)(?!172\\.(1[6-9]|2\\d|3[01])\\.)(?!192\\.168\\.)(?!169\\.254\\.)[^\\s]+$/i\n\n/**\n * An outbound URL as the webhooks list shows it: its host, never its path or\n * query (AGL-3684). A Slack, Discord or Teams incoming-webhook URL carries\n * its credential in the path — anyone holding `hooks.slack.com/services/…`\n * can post to the channel — so the list names where it delivers and nothing\n * that would let a screenshot or a shoulder replay it. The edit dialog still\n * holds the whole URL for whoever changes it.\n */\nexport function maskedWebhookUrl(url: string | undefined): string {\n const raw = String(url ?? '').trim()\n if (!raw) return ''\n try {\n const parsed = new URL(raw)\n const hidden = parsed.pathname.length > 1 || parsed.search || parsed.hash\n return `${parsed.protocol}//${parsed.host}${hidden ? '/…' : ''}`\n } catch {\n return '…'\n }\n}\n"],"names":["WEBHOOK_URL_PATTERN","maskedWebhookUrl","url","raw","String","trim","parsed","URL","hidden","pathname","length","search","hash","protocol","host"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GAaD;;;;;;CAMC,GACD,OAAO,MAAMA,sBACX,oHAAmH;AAErH;;;;;;;CAOC,GACD,OAAO,SAASC,iBAAiBC,GAAuB;IACtD,MAAMC,MAAMC,OAAOF,cAAAA,MAAO,IAAIG,IAAI;IAClC,IAAI,CAACF,KAAK,OAAO;IACjB,IAAI;QACF,MAAMG,SAAS,IAAIC,IAAIJ;QACvB,MAAMK,SAASF,OAAOG,QAAQ,CAACC,MAAM,GAAG,KAAKJ,OAAOK,MAAM,IAAIL,OAAOM,IAAI;QACzE,OAAO,GAAGN,OAAOO,QAAQ,CAAC,EAAE,EAAEP,OAAOQ,IAAI,GAAGN,SAAS,OAAO,IAAI;IAClE,EAAE,eAAM;QACN,OAAO;IACT;AACF"}
|