@aglyn/plugins-workflows 1.0.0-beta.228 → 1.0.0-beta.230
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-actions-card.component.js +20 -3
- package/src/lib/components/host-actions-card.component.js.map +1 -1
- package/src/lib/components/host-workflows-card.component.d.ts +7 -0
- package/src/lib/components/host-workflows-card.component.js +35 -5
- package/src/lib/components/host-workflows-card.component.js.map +1 -1
- package/src/lib/components/org-automation-editor.component.d.ts +9 -0
- package/src/lib/components/org-automation-editor.component.js +32 -1
- package/src/lib/components/org-automation-editor.component.js.map +1 -1
- package/src/lib/components/org-automations-card.component.js +41 -9
- package/src/lib/components/org-automations-card.component.js.map +1 -1
- package/src/lib/components/workflow-zones.d.ts +53 -1
- package/src/lib/components/workflow-zones.js +14 -0
- package/src/lib/components/workflow-zones.js.map +1 -1
- package/src/lib/components/workflows-console-page.js +5 -3
- package/src/lib/components/workflows-console-page.js.map +1 -1
- package/src/lib/engine/run-event-actions.js +21 -5
- package/src/lib/engine/run-event-actions.js.map +1 -1
- package/src/lib/engine/webhook-delivery.d.ts +24 -0
- package/src/lib/engine/webhook-delivery.js +24 -0
- package/src/lib/engine/webhook-delivery.js.map +1 -0
- package/src/lib/model/webhooks.d.ts +8 -2
- package/src/lib/model/webhooks.js +7 -1
- package/src/lib/model/webhooks.js.map +1 -1
- package/src/lib/plugin.js +12 -3
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/automation-run-figures.d.ts +38 -0
- package/src/lib/server/automation-run-figures.js +182 -0
- package/src/lib/server/automation-run-figures.js.map +1 -0
- package/src/lib/server-console.js +5 -0
- package/src/lib/server-console.js.map +1 -1
|
@@ -18,6 +18,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
19
|
import { checkEntitlement, hostEventLabel, pluginDocsHelp } from "@aglyn/aglyn";
|
|
20
20
|
import { hostIdsFromScope, isOrgWideScope } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
21
|
+
import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
|
|
21
22
|
import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
22
23
|
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
23
24
|
import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
@@ -26,8 +27,9 @@ import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-inst
|
|
|
26
27
|
import { Alert, Button, Chip, Menu, MenuItem, Stack, Switch, Typography } from "@mui/material";
|
|
27
28
|
import { collection, documentId, limit, orderBy, query, where } from "firebase/firestore";
|
|
28
29
|
import { useCallback, useMemo, useState } from "react";
|
|
29
|
-
import { ORG_AUTOMATIONS_COLLECTION, ORG_AUTOMATIONS_MAX, orgAutomationPausedHostIds, readOrgAutomation } from "../model/org-automations.js";
|
|
30
|
-
import { newOrgAutomationDraft, OrgAutomationEditor, orgAutomationBody, orgAutomationDraft } from "./org-automation-editor.component.js";
|
|
30
|
+
import { ORG_AUTOMATION_STEP_TYPES, ORG_AUTOMATION_TRIGGER_EVENTS, ORG_AUTOMATIONS_COLLECTION, ORG_AUTOMATIONS_MAX, orgAutomationPausedHostIds, readOrgAutomation } from "../model/org-automations.js";
|
|
31
|
+
import { newOrgAutomationDraft, OrgAutomationEditor, orgAutomationBody, orgAutomationDraft, orgAutomationDraftFromProposal } from "./org-automation-editor.component.js";
|
|
32
|
+
import { ORG_AUTOMATIONS_ZONE } from "./workflow-zones.js";
|
|
31
33
|
import { useOrgAutomationsApi } from "./use-org-automations-api.js";
|
|
32
34
|
import { orgSiteName } from "./workflows-org-mount.js";
|
|
33
35
|
import { HOST_ACTION_STEP_LABELS } from "../model/host-actions.js";
|
|
@@ -99,19 +101,47 @@ import { HOST_ACTION_STEP_LABELS } from "../model/host-actions.js";
|
|
|
99
101
|
}), [
|
|
100
102
|
enqueueSnackbar
|
|
101
103
|
]);
|
|
104
|
+
const refuseUnentitled = useCallback(()=>{
|
|
105
|
+
enqueueSnackbar('Org automations are built from the actions builder, which requires a Pro plan — see Billing to upgrade', {
|
|
106
|
+
variant: 'warning',
|
|
107
|
+
persist: false
|
|
108
|
+
});
|
|
109
|
+
}, [
|
|
110
|
+
enqueueSnackbar
|
|
111
|
+
]);
|
|
102
112
|
const handleAdd = useCallback(()=>{
|
|
113
|
+
if (!entitled) return refuseUnentitled();
|
|
114
|
+
setDraft(newOrgAutomationDraft());
|
|
115
|
+
}, [
|
|
116
|
+
entitled,
|
|
117
|
+
refuseUnentitled
|
|
118
|
+
]);
|
|
119
|
+
/*
|
|
120
|
+
* For the `orgAutomations` zone (AGL-3603): an automation another plugin
|
|
121
|
+
* proposed opens in the editor as a new one, unsaved and switched off —
|
|
122
|
+
* the same door Add org automation opens, so the plan is asked first and
|
|
123
|
+
* Save is still the only write.
|
|
124
|
+
*/ const propose = useCallback((proposal)=>{
|
|
103
125
|
if (!entitled) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
persist: false
|
|
107
|
-
});
|
|
108
|
-
return;
|
|
126
|
+
refuseUnentitled();
|
|
127
|
+
return false;
|
|
109
128
|
}
|
|
110
|
-
|
|
129
|
+
const opened = orgAutomationDraftFromProposal(proposal);
|
|
130
|
+
if (!opened) return false;
|
|
131
|
+
setDraft(opened);
|
|
132
|
+
return true;
|
|
111
133
|
}, [
|
|
112
134
|
entitled,
|
|
113
|
-
|
|
135
|
+
refuseUnentitled
|
|
114
136
|
]);
|
|
137
|
+
const ExtensionZone = useConsoleWidgetSlot();
|
|
138
|
+
const automationsZone = ExtensionZone && canEdit ? /*#__PURE__*/ _jsx(ExtensionZone, {
|
|
139
|
+
slot: ORG_AUTOMATIONS_ZONE.id,
|
|
140
|
+
orgId: mount.orgId,
|
|
141
|
+
triggers: ORG_AUTOMATION_TRIGGER_EVENTS,
|
|
142
|
+
steps: ORG_AUTOMATION_STEP_TYPES,
|
|
143
|
+
propose: propose
|
|
144
|
+
}) : null;
|
|
115
145
|
const handleSave = useCallback(async ()=>{
|
|
116
146
|
if (!draft) return;
|
|
117
147
|
const body = orgAutomationBody(draft);
|
|
@@ -212,6 +242,8 @@ import { HOST_ACTION_STEP_LABELS } from "../model/host-actions.js";
|
|
|
212
242
|
help: pluginDocsHelp('orgAutomations', {
|
|
213
243
|
anchor: '#what-an-org-automation-is'
|
|
214
244
|
}),
|
|
245
|
+
// Other ways to start an org automation (AGL-3603), in the header.
|
|
246
|
+
actions: automationsZone != null ? automationsZone : undefined,
|
|
215
247
|
contentGutterX: true,
|
|
216
248
|
contentGutterY: true,
|
|
217
249
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/org-automations-card.component.tsx"],"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'use client'\n\nimport {\n type AglynOrgBilling,\n checkEntitlement,\n hostEventLabel,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport {\n hostIdsFromScope,\n isOrgWideScope,\n} from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Chip,\n Menu,\n MenuItem,\n Stack,\n Switch,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n documentId,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n ORG_AUTOMATIONS_COLLECTION,\n ORG_AUTOMATIONS_MAX,\n orgAutomationPausedHostIds,\n type OrgAutomationRow,\n readOrgAutomation,\n} from '../model/org-automations'\nimport {\n newOrgAutomationDraft,\n OrgAutomationEditor,\n orgAutomationBody,\n type OrgAutomationDraft,\n orgAutomationDraft,\n} from './org-automation-editor.component'\nimport { useOrgAutomationsApi } from './use-org-automations-api'\nimport { orgSiteName, type WorkflowsOrgMount } from './workflows-org-mount'\nimport { HOST_ACTION_STEP_LABELS, type HostActionStepType } from '../model/host-actions'\n\nexport interface OrgAutomationsCardProps {\n mount: WorkflowsOrgMount\n org?: Partial<AglynOrgBilling>\n /**\n * Whether the reader may write the organization's automations — an owner,\n * admin or editor. The route decides; this keeps a viewer from being\n * offered controls that are about to refuse them.\n */\n canEdit: boolean\n}\n\n/** The sites an automation runs on, by id: every org site for `['org']`. */\nfunction placedSiteIds(\n row: Pick<OrgAutomationRow, 'visibleTo'>,\n mount: WorkflowsOrgMount,\n): string[] {\n return isOrgWideScope(row.visibleTo)\n ? mount.hosts.map((host) => host.id)\n : hostIdsFromScope(row.visibleTo)\n}\n\n/** \"Runs on every site\" or \"Runs on Shop, Blog\". */\nfunction placementLine(\n row: Pick<OrgAutomationRow, 'visibleTo'>,\n mount: WorkflowsOrgMount,\n): string {\n if (isOrgWideScope(row.visibleTo)) return 'Runs on every site'\n const names = hostIdsFromScope(row.visibleTo).map((id) => orgSiteName(mount, id))\n return names.length ? `Runs on ${names.join(', ')}` : 'Runs on no site'\n}\n\n/**\n * THE ORGANIZATION'S AUTOMATIONS (AGL-3302): what the organization runs on\n * the sites it chooses.\n *\n * One ceilinged read of the live automations — the org hub admits only\n * org-wide members, who read every one — and every write through\n * `automations/manage` or `automations/pause`, because the collection is\n * closed to client writes. Each row says where it runs and where it is\n * paused; a paused site is resumed from its chip, and any placed site can be\n * paused from the row, which is the same control the site's own hub offers.\n */\nexport function OrgAutomationsCard(props: OrgAutomationsCardProps) {\n const { mount, org, canEdit } = props\n const firestore = useFirestore()\n const api = useOrgAutomationsApi()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n\n /*\n * Every LIVE automation, ceilinged at the organization's cap plus the probe\n * row. `deletedAt` is always written — `null` while live — so equality asks\n * for exactly the live ones and deleted documents never take a place in the\n * window. Ordered by document id, which the automatic single-field index\n * carries behind the equality, so the window is named rather than whatever\n * order Firestore returns; the list is sorted by name once it is in hand.\n */\n const { data: rows } = useFirestoreCollection<OrgAutomationRow>(\n () =>\n query(\n collection(firestore, 'orgs', mount.orgId, ORG_AUTOMATIONS_COLLECTION),\n where('deletedAt', '==', null),\n orderBy(documentId()),\n limit(ORG_AUTOMATIONS_MAX + 1),\n ),\n [firestore, mount.orgId],\n { idField: '$id' },\n )\n const automations = useMemo(\n () =>\n [...(rows ?? [])]\n .slice(0, ORG_AUTOMATIONS_MAX)\n .sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''))),\n [rows],\n )\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const shown = useMemo(\n () => automations.slice(page * pageSize, page * pageSize + pageSize),\n [automations, page, pageSize],\n )\n\n const [draft, setDraft] = useState<OrgAutomationDraft | null>(null)\n /**\n * The editor has been opened at least once — the latch the pickers key on,\n * so Cancel and the next Edit do not buy the option lists twice.\n */\n const [editorOpened, setEditorOpened] = useState(false)\n if (draft && !editorOpened) setEditorOpened(true)\n const [saving, setSaving] = useState(false)\n const [pauseMenu, setPauseMenu] = useState<{\n anchor: HTMLElement\n row: OrgAutomationRow\n } | null>(null)\n\n const entitled = checkEntitlement(org, 'actions')\n const fail = useCallback(\n (error: unknown) =>\n enqueueSnackbar((error as Error)?.message || 'The request could not be completed', {\n variant: 'warning',\n persist: false,\n }),\n [enqueueSnackbar],\n )\n\n const handleAdd = useCallback(() => {\n if (!entitled) {\n enqueueSnackbar(\n 'Org automations are built from the actions builder, which requires a Pro plan — see Billing to upgrade',\n { variant: 'warning', persist: false },\n )\n return\n }\n setDraft(newOrgAutomationDraft())\n }, [entitled, enqueueSnackbar])\n\n const handleSave = useCallback(async () => {\n if (!draft) return\n const body = orgAutomationBody(draft)\n // The route's own reader, asked first so a mistake reads the same here as\n // it would coming back from the server — and costs no round trip.\n const read = readOrgAutomation(body)\n if (read.ok === false) {\n return void enqueueSnackbar(read.problem, { variant: 'warning', persist: false })\n }\n setSaving(true)\n try {\n await api('manage', {\n orgId: mount.orgId,\n action: draft.id ? 'update' : 'create',\n ...(draft.id ? { automationId: draft.id } : {}),\n automation: body,\n })\n setDraft(null)\n enqueueSnackbar('Org automation saved', { variant: 'success', persist: false })\n } catch (error) {\n fail(error)\n } finally {\n setSaving(false)\n }\n }, [draft, api, mount.orgId, enqueueSnackbar, fail])\n\n const handleToggle = useCallback(\n (row: OrgAutomationRow) => async (event: { target: { checked: boolean } }) => {\n try {\n await api('manage', {\n orgId: mount.orgId,\n action: 'setEnabled',\n automationId: row.$id,\n enabled: event.target.checked,\n })\n } catch (error) {\n fail(error)\n }\n },\n [api, mount.orgId, fail],\n )\n\n const handleDelete = useCallback(\n (row: OrgAutomationRow) => async () => {\n const confirmed = await confirm({\n title: 'Delete this org automation?',\n description:\n `\"${row.name}\" stops running on every site it is placed on, and ` +\n 'anyone waiting inside it stops too.',\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n try {\n await api('manage', {\n orgId: mount.orgId,\n action: 'delete',\n automationId: row.$id,\n })\n } catch (error) {\n fail(error)\n }\n },\n [confirm, api, mount.orgId, fail],\n )\n\n const setPaused = useCallback(\n async (row: OrgAutomationRow, hostId: string, paused: boolean) => {\n setPauseMenu(null)\n try {\n await api('pause', { hostId, automationId: row.$id, paused })\n } catch (error) {\n fail(error)\n }\n },\n [api, fail],\n )\n\n return (\n <CardDisplay\n header={'Org automations'}\n help={pluginDocsHelp('orgAutomations', { anchor: '#what-an-org-automation-is' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Write an automation once and run it on every site you choose. ' +\n 'Each run is that site’s own: its email goes from that site, it ' +\n 'counts on that site’s action runs, and the site can pause it ' +\n 'for itself. Pro plans and up.'}\n </Typography>\n {shown.map((row) => {\n const paused = orgAutomationPausedHostIds(row)\n const placed = placedSiteIds(row, mount)\n const pausable = placed.filter((hostId) => !paused.includes(hostId))\n return (\n <Stack\n key={row.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Switch\n size=\"small\"\n checked={row.enabled !== false}\n disabled={!canEdit}\n onChange={handleToggle(row)}\n slotProps={{ input: { 'aria-label': `Switch ${row.name} on or off` } }}\n />\n <Stack sx={{ flex: 1, minWidth: 0 }} spacing={0.25}>\n <Typography variant=\"body2\" noWrap>\n {row.name}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`on ${hostEventLabel(row.trigger?.event)} · ${(row.steps ?? [])\n .map(\n (step) =>\n HOST_ACTION_STEP_LABELS[step.type as HostActionStepType] ??\n step.type,\n )\n .join(' → ')}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {placementLine(row, mount)}\n </Typography>\n {paused.length ? (\n <Stack direction=\"row\" spacing={0.5} sx={{ flexWrap: 'wrap', gap: 0.5 }}>\n {paused.map((hostId) => (\n <Chip\n key={hostId}\n size=\"small\"\n variant=\"outlined\"\n color=\"warning\"\n label={`Paused on ${orgSiteName(mount, hostId)}`}\n {...(canEdit\n ? { onDelete: () => void setPaused(row, hostId, false) }\n : {})}\n />\n ))}\n </Stack>\n ) : null}\n </Stack>\n {canEdit ? (\n <>\n <Button size=\"small\" onClick={() => setDraft(orgAutomationDraft(row))}>\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n disabled={!pausable.length}\n aria-haspopup=\"menu\"\n onClick={(event) =>\n setPauseMenu({ anchor: event.currentTarget, row })\n }\n >\n {'Pause on…'}\n </Button>\n <Button size=\"small\" color=\"error\" onClick={handleDelete(row)}>\n {'Delete'}\n </Button>\n </>\n ) : null}\n </Stack>\n )\n })}\n {automations.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No org automations yet.'}\n </Typography>\n ) : (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={shown.length}\n count={automations.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n {(rows?.length ?? 0) > ORG_AUTOMATIONS_MAX ? (\n <Alert severity=\"info\">\n {`Showing ${ORG_AUTOMATIONS_MAX} org automations, the most an ` +\n 'organization holds.'}\n </Alert>\n ) : null}\n {canEdit ? (\n <Button\n size=\"small\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={handleAdd}\n >\n {'Add org automation'}\n </Button>\n ) : null}\n </Stack>\n <Menu\n anchorEl={pauseMenu?.anchor ?? null}\n open={Boolean(pauseMenu)}\n onClose={() => setPauseMenu(null)}\n >\n {pauseMenu\n ? placedSiteIds(pauseMenu.row, mount)\n .filter(\n (hostId) =>\n !orgAutomationPausedHostIds(pauseMenu.row).includes(hostId),\n )\n .map((hostId) => (\n <MenuItem\n key={hostId}\n onClick={() => void setPaused(pauseMenu.row, hostId, true)}\n >\n {`Pause on ${orgSiteName(mount, hostId)}`}\n </MenuItem>\n ))\n : null}\n </Menu>\n <OrgAutomationEditor\n mount={mount}\n draft={draft}\n onDraft={(update) =>\n setDraft((previous) => (previous ? update(previous) : previous))\n }\n opened={editorOpened}\n saving={saving}\n onSave={() => void handleSave()}\n onClose={() => setDraft(null)}\n />\n </CardDisplay>\n )\n}\nOrgAutomationsCard.displayName = 'OrgAutomationsCard'\n\nexport default OrgAutomationsCard\n"],"names":["checkEntitlement","hostEventLabel","pluginDocsHelp","hostIdsFromScope","isOrgWideScope","CardDisplay","useConfirmationContext","ListPagination","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","useFirestore","useFirestoreCollection","Alert","Button","Chip","Menu","MenuItem","Stack","Switch","Typography","collection","documentId","limit","orderBy","query","where","useCallback","useMemo","useState","ORG_AUTOMATIONS_COLLECTION","ORG_AUTOMATIONS_MAX","orgAutomationPausedHostIds","readOrgAutomation","newOrgAutomationDraft","OrgAutomationEditor","orgAutomationBody","orgAutomationDraft","useOrgAutomationsApi","orgSiteName","HOST_ACTION_STEP_LABELS","placedSiteIds","row","mount","visibleTo","hosts","map","host","id","placementLine","names","length","join","OrgAutomationsCard","props","org","canEdit","firestore","api","enqueueSnackbar","confirm","data","rows","orgId","idField","automations","slice","sort","a","b","String","name","localeCompare","page","setPage","pageSize","setPageSize","shown","draft","setDraft","editorOpened","setEditorOpened","saving","setSaving","pauseMenu","setPauseMenu","entitled","fail","error","message","variant","persist","handleAdd","handleSave","body","read","ok","problem","action","automationId","automation","handleToggle","event","$id","enabled","target","checked","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","setPaused","hostId","paused","header","help","anchor","contentGutterX","contentGutterY","spacing","placed","pausable","filter","includes","direction","sx","alignItems","size","disabled","onChange","slotProps","input","flex","minWidth","noWrap","trigger","steps","step","type","flexWrap","gap","label","onDelete","onClick","aria-haspopup","currentTarget","rowCount","count","onPageChange","onPageSizeChange","severity","alignSelf","anchorEl","open","Boolean","onClose","onDraft","update","previous","opened","onSave","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAEEA,gBAAgB,EAChBC,cAAc,EACdC,cAAc,QACT,eAAc;AACrB,SACEC,gBAAgB,EAChBC,cAAc,QACT,sCAAqC;AAC5C,SAASC,WAAW,EAAEC,sBAAsB,QAAQ,uBAAsB;AAC1E,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,uBAAuB,QAAQ,8CAA6C;AACrF,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,YAAY,EACZC,sBAAsB,QACjB,iCAAgC;AACvC,SACEC,KAAK,EACLC,MAAM,EACNC,IAAI,EACJC,IAAI,EACJC,QAAQ,EACRC,KAAK,EACLC,MAAM,EACNC,UAAU,QACL,gBAAe;AACtB,SACEC,UAAU,EACVC,UAAU,EACVC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,KAAK,QACA,qBAAoB;AAC3B,SAASC,WAAW,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACtD,SACEC,0BAA0B,EAC1BC,mBAAmB,EACnBC,0BAA0B,EAE1BC,iBAAiB,QACZ,8BAA0B;AACjC,SACEC,qBAAqB,EACrBC,mBAAmB,EACnBC,iBAAiB,EAEjBC,kBAAkB,QACb,uCAAmC;AAC1C,SAASC,oBAAoB,QAAQ,+BAA2B;AAChE,SAASC,WAAW,QAAgC,2BAAuB;AAC3E,SAASC,uBAAuB,QAAiC,2BAAuB;AAaxF,0EAA0E,GAC1E,SAASC,cACPC,GAAwC,EACxCC,KAAwB;IAExB,OAAOtC,eAAeqC,IAAIE,SAAS,IAC/BD,MAAME,KAAK,CAACC,GAAG,CAAC,CAACC,OAASA,KAAKC,EAAE,IACjC5C,iBAAiBsC,IAAIE,SAAS;AACpC;AAEA,kDAAkD,GAClD,SAASK,cACPP,GAAwC,EACxCC,KAAwB;IAExB,IAAItC,eAAeqC,IAAIE,SAAS,GAAG,OAAO;IAC1C,MAAMM,QAAQ9C,iBAAiBsC,IAAIE,SAAS,EAAEE,GAAG,CAAC,CAACE,KAAOT,YAAYI,OAAOK;IAC7E,OAAOE,MAAMC,MAAM,GAAG,CAAC,QAAQ,EAAED,MAAME,IAAI,CAAC,OAAO,GAAG;AACxD;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC,mBAAmBC,KAA8B;;IAC/D,MAAM,EAAEX,KAAK,EAAEY,GAAG,EAAEC,OAAO,EAAE,GAAGF;IAChC,MAAMG,YAAY9C;IAClB,MAAM+C,MAAMpB;IACZ,MAAM,EAAEqB,eAAe,EAAE,GAAGjD;IAC5B,MAAM,EAAEkD,OAAO,EAAE,GAAGrD;IAEpB;;;;;;;GAOC,GACD,MAAM,EAAEsD,MAAMC,IAAI,EAAE,GAAGlD,uBACrB,IACEa,MACEJ,WAAWoC,WAAW,QAAQd,MAAMoB,KAAK,EAAEjC,6BAC3CJ,MAAM,aAAa,MAAM,OACzBF,QAAQF,eACRC,MAAMQ,sBAAsB,KAEhC;QAAC0B;QAAWd,MAAMoB,KAAK;KAAC,EACxB;QAAEC,SAAS;IAAM;IAEnB,MAAMC,cAAcrC,QAClB,IACE;eAAKkC,eAAAA,OAAQ,EAAE;SAAE,CACdI,KAAK,CAAC,GAAGnC,qBACToC,IAAI,CAAC,CAACC,GAAGC;gBAAaD,SAAmCC;mBAA1CC,QAAOF,UAAAA,EAAEG,IAAI,YAANH,UAAU,IAAII,aAAa,CAACF,QAAOD,UAAAA,EAAEE,IAAI,YAANF,UAAU;YACxE;QAACP;KAAK;IAER,MAAM,CAACW,MAAMC,QAAQ,GAAG7C,SAAS;IACjC,MAAM,CAAC8C,UAAUC,YAAY,GAAG/C,SAASpB;IACzC,MAAMoE,QAAQjD,QACZ,IAAMqC,YAAYC,KAAK,CAACO,OAAOE,UAAUF,OAAOE,WAAWA,WAC3D;QAACV;QAAaQ;QAAME;KAAS;IAG/B,MAAM,CAACG,OAAOC,SAAS,GAAGlD,SAAoC;IAC9D;;;GAGC,GACD,MAAM,CAACmD,cAAcC,gBAAgB,GAAGpD,SAAS;IACjD,IAAIiD,SAAS,CAACE,cAAcC,gBAAgB;IAC5C,MAAM,CAACC,QAAQC,UAAU,GAAGtD,SAAS;IACrC,MAAM,CAACuD,WAAWC,aAAa,GAAGxD,SAGxB;IAEV,MAAMyD,WAAWrF,iBAAiBsD,KAAK;IACvC,MAAMgC,OAAO5D,YACX,CAAC6D,QACC7B,gBAAgB,CAAC6B,yBAAD,AAACA,MAAiBC,OAAO,KAAI,sCAAsC;YACjFC,SAAS;YACTC,SAAS;QACX,IACF;QAAChC;KAAgB;IAGnB,MAAMiC,YAAYjE,YAAY;QAC5B,IAAI,CAAC2D,UAAU;YACb3B,gBACE,0GACA;gBAAE+B,SAAS;gBAAWC,SAAS;YAAM;YAEvC;QACF;QACAZ,SAAS7C;IACX,GAAG;QAACoD;QAAU3B;KAAgB;IAE9B,MAAMkC,aAAalE,YAAY;QAC7B,IAAI,CAACmD,OAAO;QACZ,MAAMgB,OAAO1D,kBAAkB0C;QAC/B,0EAA0E;QAC1E,kEAAkE;QAClE,MAAMiB,OAAO9D,kBAAkB6D;QAC/B,IAAIC,KAAKC,EAAE,KAAK,OAAO;YACrB,OAAO,KAAKrC,gBAAgBoC,KAAKE,OAAO,EAAE;gBAAEP,SAAS;gBAAWC,SAAS;YAAM;QACjF;QACAR,UAAU;QACV,IAAI;YACF,MAAMzB,IAAI,UAAU;gBAClBK,OAAOpB,MAAMoB,KAAK;gBAClBmC,QAAQpB,MAAM9B,EAAE,GAAG,WAAW;eAC1B8B,MAAM9B,EAAE,GAAG;gBAAEmD,cAAcrB,MAAM9B,EAAE;YAAC,IAAI,CAAC;gBAC7CoD,YAAYN;;YAEdf,SAAS;YACTpB,gBAAgB,wBAAwB;gBAAE+B,SAAS;gBAAWC,SAAS;YAAM;QAC/E,EAAE,OAAOH,OAAO;YACdD,KAAKC;QACP,SAAU;YACRL,UAAU;QACZ;IACF,GAAG;QAACL;QAAOpB;QAAKf,MAAMoB,KAAK;QAAEJ;QAAiB4B;KAAK;IAEnD,MAAMc,eAAe1E,YACnB,CAACe,MAA0B,OAAO4D;YAChC,IAAI;gBACF,MAAM5C,IAAI,UAAU;oBAClBK,OAAOpB,MAAMoB,KAAK;oBAClBmC,QAAQ;oBACRC,cAAczD,IAAI6D,GAAG;oBACrBC,SAASF,MAAMG,MAAM,CAACC,OAAO;gBAC/B;YACF,EAAE,OAAOlB,OAAO;gBACdD,KAAKC;YACP;QACF,GACA;QAAC9B;QAAKf,MAAMoB,KAAK;QAAEwB;KAAK;IAG1B,MAAMoB,eAAehF,YACnB,CAACe,MAA0B;YACzB,MAAMkE,YAAY,MAAMhD,QAAQ;gBAC9BiD,OAAO;gBACPC,aACE,CAAC,CAAC,EAAEpE,IAAI6B,IAAI,CAAC,mDAAmD,CAAC,GACjE;gBACFwC,kBAAkB;gBAClBC,yBAAyB;oBAAEC,OAAO;gBAAQ;YAC5C,GACGC,IAAI,CAAC,IAAM,MACXC,KAAK,CAAC,IAAM;YACf,IAAI,CAACP,WAAW;YAChB,IAAI;gBACF,MAAMlD,IAAI,UAAU;oBAClBK,OAAOpB,MAAMoB,KAAK;oBAClBmC,QAAQ;oBACRC,cAAczD,IAAI6D,GAAG;gBACvB;YACF,EAAE,OAAOf,OAAO;gBACdD,KAAKC;YACP;QACF,GACA;QAAC5B;QAASF;QAAKf,MAAMoB,KAAK;QAAEwB;KAAK;IAGnC,MAAM6B,YAAYzF,YAChB,OAAOe,KAAuB2E,QAAgBC;QAC5CjC,aAAa;QACb,IAAI;YACF,MAAM3B,IAAI,SAAS;gBAAE2D;gBAAQlB,cAAczD,IAAI6D,GAAG;gBAAEe;YAAO;QAC7D,EAAE,OAAO9B,OAAO;YACdD,KAAKC;QACP;IACF,GACA;QAAC9B;QAAK6B;KAAK;IAGb,qBACE,MAACjF;QACCiH,QAAQ;QACRC,MAAMrH,eAAe,kBAAkB;YAAEsH,QAAQ;QAA6B;QAC9EC,cAAc;QACdC,cAAc;;0BAEd,MAACzG;gBAAM0G,SAAS;;kCACd,KAACxG;wBAAWsE,SAAQ;wBAAQuB,OAAM;kCAC/B,mEACC,oEACA,kEACA;;oBAEHpC,MAAM/B,GAAG,CAAC,CAACJ;4BAuB8CA;4BAA1BA;wBAtB9B,MAAM4E,SAAStF,2BAA2BU;wBAC1C,MAAMmF,SAASpF,cAAcC,KAAKC;wBAClC,MAAMmF,WAAWD,OAAOE,MAAM,CAAC,CAACV,SAAW,CAACC,OAAOU,QAAQ,CAACX;wBAC5D,qBACE,MAACnG;4BAEC+G,WAAU;4BACVL,SAAS;4BACTM,IAAI;gCAAEC,YAAY;4BAAS;;8CAE3B,KAAChH;oCACCiH,MAAK;oCACL1B,SAAShE,IAAI8D,OAAO,KAAK;oCACzB6B,UAAU,CAAC7E;oCACX8E,UAAUjC,aAAa3D;oCACvB6F,WAAW;wCAAEC,OAAO;4CAAE,cAAc,CAAC,OAAO,EAAE9F,IAAI6B,IAAI,CAAC,UAAU,CAAC;wCAAC;oCAAE;;8CAEvE,MAACrD;oCAAMgH,IAAI;wCAAEO,MAAM;wCAAGC,UAAU;oCAAE;oCAAGd,SAAS;;sDAC5C,KAACxG;4CAAWsE,SAAQ;4CAAQiD,MAAM;sDAC/BjG,IAAI6B,IAAI;;sDAEX,KAACnD;4CAAWsE,SAAQ;4CAAUuB,OAAM;4CAAiB0B,MAAM;sDACxD,CAAC,GAAG,EAAEzI,gBAAewC,eAAAA,IAAIkG,OAAO,qBAAXlG,aAAa4D,KAAK,EAAE,GAAG,EAAE,EAAC5D,aAAAA,IAAImG,KAAK,YAATnG,aAAa,EAAE,EAC5DI,GAAG,CACF,CAACgG;oDACCtG;wDAAAA,qCAAAA,uBAAuB,CAACsG,KAAKC,IAAI,CAAuB,YAAxDvG,qCACAsG,KAAKC,IAAI;+CAEZ3F,IAAI,CAAC,QAAQ;;sDAElB,KAAChC;4CAAWsE,SAAQ;4CAAUuB,OAAM;4CAAiB0B,MAAM;sDACxD1F,cAAcP,KAAKC;;wCAErB2E,OAAOnE,MAAM,iBACZ,KAACjC;4CAAM+G,WAAU;4CAAML,SAAS;4CAAKM,IAAI;gDAAEc,UAAU;gDAAQC,KAAK;4CAAI;sDACnE3B,OAAOxE,GAAG,CAAC,CAACuE,uBACX,KAACtG;oDAECqH,MAAK;oDACL1C,SAAQ;oDACRuB,OAAM;oDACNiC,OAAO,CAAC,UAAU,EAAE3G,YAAYI,OAAO0E,SAAS;mDAC3C7D,UACD;oDAAE2F,UAAU,IAAM,KAAK/B,UAAU1E,KAAK2E,QAAQ;gDAAO,IACrD,CAAC,IAPAA;6CAWT;;;gCAEL7D,wBACC;;sDACE,KAAC1C;4CAAOsH,MAAK;4CAAQgB,SAAS,IAAMrE,SAAS1C,mBAAmBK;sDAC7D;;sDAEH,KAAC5B;4CACCsH,MAAK;4CACLC,UAAU,CAACP,SAAS3E,MAAM;4CAC1BkG,iBAAc;4CACdD,SAAS,CAAC9C,QACRjB,aAAa;oDAAEoC,QAAQnB,MAAMgD,aAAa;oDAAE5G;gDAAI;sDAGjD;;sDAEH,KAAC5B;4CAAOsH,MAAK;4CAAQnB,OAAM;4CAAQmC,SAASzC,aAAajE;sDACtD;;;qCAGH;;2BAhECA,IAAI6D,GAAG;oBAmElB;oBACCtC,YAAYd,MAAM,KAAK,kBACtB,KAAC/B;wBAAWsE,SAAQ;wBAAQuB,OAAM;kCAC/B;uCAGH,KAACzG;wBACCiE,MAAMA;wBACNE,UAAUA;wBACV4E,UAAU1E,MAAM1B,MAAM;wBACtBqG,OAAOvF,YAAYd,MAAM;wBACzBsG,cAAc/E;wBACdgF,kBAAkB9E;;oBAGrB,SAACd,wBAAAA,KAAMX,MAAM,mBAAI,KAAKpB,oCACrB,KAAClB;wBAAM8I,UAAS;kCACb,CAAC,QAAQ,EAAE5H,oBAAoB,8BAA8B,CAAC,GAC7D;yBAEF;oBACHyB,wBACC,KAAC1C;wBACCsH,MAAK;wBACLnB,OAAM;wBACNiB,IAAI;4BAAE0B,WAAW;wBAAa;wBAC9BR,SAASxD;kCAER;yBAED;;;0BAEN,KAAC5E;gBACC6I,QAAQ,WAAEzE,6BAAAA,UAAWqC,MAAM,oBAAI;gBAC/BqC,MAAMC,QAAQ3E;gBACd4E,SAAS,IAAM3E,aAAa;0BAE3BD,YACG3C,cAAc2C,UAAU1C,GAAG,EAAEC,OAC1BoF,MAAM,CACL,CAACV,SACC,CAACrF,2BAA2BoD,UAAU1C,GAAG,EAAEsF,QAAQ,CAACX,SAEvDvE,GAAG,CAAC,CAACuE,uBACJ,KAACpG;wBAECmI,SAAS,IAAM,KAAKhC,UAAUhC,UAAU1C,GAAG,EAAE2E,QAAQ;kCAEpD,CAAC,SAAS,EAAE9E,YAAYI,OAAO0E,SAAS;uBAHpCA,WAMX;;0BAEN,KAAClF;gBACCQ,OAAOA;gBACPmC,OAAOA;gBACPmF,SAAS,CAACC,SACRnF,SAAS,CAACoF,WAAcA,WAAWD,OAAOC,YAAYA;gBAExDC,QAAQpF;gBACRE,QAAQA;gBACRmF,QAAQ,IAAM,KAAKxE;gBACnBmE,SAAS,IAAMjF,SAAS;;;;AAIhC;AACA1B,mBAAmBiH,WAAW,GAAG;AAEjC,eAAejH,mBAAkB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/org-automations-card.component.tsx"],"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'use client'\n\nimport {\n type AglynOrgBilling,\n checkEntitlement,\n hostEventLabel,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport {\n hostIdsFromScope,\n isOrgWideScope,\n} from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Chip,\n Menu,\n MenuItem,\n Stack,\n Switch,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n documentId,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n ORG_AUTOMATION_STEP_TYPES,\n ORG_AUTOMATION_TRIGGER_EVENTS,\n ORG_AUTOMATIONS_COLLECTION,\n ORG_AUTOMATIONS_MAX,\n orgAutomationPausedHostIds,\n type OrgAutomationRow,\n readOrgAutomation,\n} from '../model/org-automations'\nimport {\n newOrgAutomationDraft,\n OrgAutomationEditor,\n orgAutomationBody,\n type OrgAutomationDraft,\n orgAutomationDraft,\n orgAutomationDraftFromProposal,\n} from './org-automation-editor.component'\nimport { ORG_AUTOMATIONS_ZONE, type OrgAutomationProposal } from './workflow-zones'\nimport { useOrgAutomationsApi } from './use-org-automations-api'\nimport { orgSiteName, type WorkflowsOrgMount } from './workflows-org-mount'\nimport { HOST_ACTION_STEP_LABELS, type HostActionStepType } from '../model/host-actions'\n\nexport interface OrgAutomationsCardProps {\n mount: WorkflowsOrgMount\n org?: Partial<AglynOrgBilling>\n /**\n * Whether the reader may write the organization's automations — an owner,\n * admin or editor. The route decides; this keeps a viewer from being\n * offered controls that are about to refuse them.\n */\n canEdit: boolean\n}\n\n/** The sites an automation runs on, by id: every org site for `['org']`. */\nfunction placedSiteIds(\n row: Pick<OrgAutomationRow, 'visibleTo'>,\n mount: WorkflowsOrgMount,\n): string[] {\n return isOrgWideScope(row.visibleTo)\n ? mount.hosts.map((host) => host.id)\n : hostIdsFromScope(row.visibleTo)\n}\n\n/** \"Runs on every site\" or \"Runs on Shop, Blog\". */\nfunction placementLine(\n row: Pick<OrgAutomationRow, 'visibleTo'>,\n mount: WorkflowsOrgMount,\n): string {\n if (isOrgWideScope(row.visibleTo)) return 'Runs on every site'\n const names = hostIdsFromScope(row.visibleTo).map((id) => orgSiteName(mount, id))\n return names.length ? `Runs on ${names.join(', ')}` : 'Runs on no site'\n}\n\n/**\n * THE ORGANIZATION'S AUTOMATIONS (AGL-3302): what the organization runs on\n * the sites it chooses.\n *\n * One ceilinged read of the live automations — the org hub admits only\n * org-wide members, who read every one — and every write through\n * `automations/manage` or `automations/pause`, because the collection is\n * closed to client writes. Each row says where it runs and where it is\n * paused; a paused site is resumed from its chip, and any placed site can be\n * paused from the row, which is the same control the site's own hub offers.\n */\nexport function OrgAutomationsCard(props: OrgAutomationsCardProps) {\n const { mount, org, canEdit } = props\n const firestore = useFirestore()\n const api = useOrgAutomationsApi()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n\n /*\n * Every LIVE automation, ceilinged at the organization's cap plus the probe\n * row. `deletedAt` is always written — `null` while live — so equality asks\n * for exactly the live ones and deleted documents never take a place in the\n * window. Ordered by document id, which the automatic single-field index\n * carries behind the equality, so the window is named rather than whatever\n * order Firestore returns; the list is sorted by name once it is in hand.\n */\n const { data: rows } = useFirestoreCollection<OrgAutomationRow>(\n () =>\n query(\n collection(firestore, 'orgs', mount.orgId, ORG_AUTOMATIONS_COLLECTION),\n where('deletedAt', '==', null),\n orderBy(documentId()),\n limit(ORG_AUTOMATIONS_MAX + 1),\n ),\n [firestore, mount.orgId],\n { idField: '$id' },\n )\n const automations = useMemo(\n () =>\n [...(rows ?? [])]\n .slice(0, ORG_AUTOMATIONS_MAX)\n .sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''))),\n [rows],\n )\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const shown = useMemo(\n () => automations.slice(page * pageSize, page * pageSize + pageSize),\n [automations, page, pageSize],\n )\n\n const [draft, setDraft] = useState<OrgAutomationDraft | null>(null)\n /**\n * The editor has been opened at least once — the latch the pickers key on,\n * so Cancel and the next Edit do not buy the option lists twice.\n */\n const [editorOpened, setEditorOpened] = useState(false)\n if (draft && !editorOpened) setEditorOpened(true)\n const [saving, setSaving] = useState(false)\n const [pauseMenu, setPauseMenu] = useState<{\n anchor: HTMLElement\n row: OrgAutomationRow\n } | null>(null)\n\n const entitled = checkEntitlement(org, 'actions')\n const fail = useCallback(\n (error: unknown) =>\n enqueueSnackbar((error as Error)?.message || 'The request could not be completed', {\n variant: 'warning',\n persist: false,\n }),\n [enqueueSnackbar],\n )\n\n const refuseUnentitled = useCallback(() => {\n enqueueSnackbar(\n 'Org automations are built from the actions builder, which requires a Pro plan — see Billing to upgrade',\n { variant: 'warning', persist: false },\n )\n }, [enqueueSnackbar])\n\n const handleAdd = useCallback(() => {\n if (!entitled) return refuseUnentitled()\n setDraft(newOrgAutomationDraft())\n }, [entitled, refuseUnentitled])\n\n /*\n * For the `orgAutomations` zone (AGL-3603): an automation another plugin\n * proposed opens in the editor as a new one, unsaved and switched off —\n * the same door Add org automation opens, so the plan is asked first and\n * Save is still the only write.\n */\n const propose = useCallback(\n (proposal: OrgAutomationProposal) => {\n if (!entitled) {\n refuseUnentitled()\n return false\n }\n const opened = orgAutomationDraftFromProposal(proposal)\n if (!opened) return false\n setDraft(opened)\n return true\n },\n [entitled, refuseUnentitled],\n )\n const ExtensionZone = useConsoleWidgetSlot()\n const automationsZone =\n ExtensionZone && canEdit ? (\n <ExtensionZone\n slot={ORG_AUTOMATIONS_ZONE.id}\n orgId={mount.orgId}\n triggers={ORG_AUTOMATION_TRIGGER_EVENTS}\n steps={ORG_AUTOMATION_STEP_TYPES}\n propose={propose}\n />\n ) : null\n\n const handleSave = useCallback(async () => {\n if (!draft) return\n const body = orgAutomationBody(draft)\n // The route's own reader, asked first so a mistake reads the same here as\n // it would coming back from the server — and costs no round trip.\n const read = readOrgAutomation(body)\n if (read.ok === false) {\n return void enqueueSnackbar(read.problem, { variant: 'warning', persist: false })\n }\n setSaving(true)\n try {\n await api('manage', {\n orgId: mount.orgId,\n action: draft.id ? 'update' : 'create',\n ...(draft.id ? { automationId: draft.id } : {}),\n automation: body,\n })\n setDraft(null)\n enqueueSnackbar('Org automation saved', { variant: 'success', persist: false })\n } catch (error) {\n fail(error)\n } finally {\n setSaving(false)\n }\n }, [draft, api, mount.orgId, enqueueSnackbar, fail])\n\n const handleToggle = useCallback(\n (row: OrgAutomationRow) => async (event: { target: { checked: boolean } }) => {\n try {\n await api('manage', {\n orgId: mount.orgId,\n action: 'setEnabled',\n automationId: row.$id,\n enabled: event.target.checked,\n })\n } catch (error) {\n fail(error)\n }\n },\n [api, mount.orgId, fail],\n )\n\n const handleDelete = useCallback(\n (row: OrgAutomationRow) => async () => {\n const confirmed = await confirm({\n title: 'Delete this org automation?',\n description:\n `\"${row.name}\" stops running on every site it is placed on, and ` +\n 'anyone waiting inside it stops too.',\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n try {\n await api('manage', {\n orgId: mount.orgId,\n action: 'delete',\n automationId: row.$id,\n })\n } catch (error) {\n fail(error)\n }\n },\n [confirm, api, mount.orgId, fail],\n )\n\n const setPaused = useCallback(\n async (row: OrgAutomationRow, hostId: string, paused: boolean) => {\n setPauseMenu(null)\n try {\n await api('pause', { hostId, automationId: row.$id, paused })\n } catch (error) {\n fail(error)\n }\n },\n [api, fail],\n )\n\n return (\n <CardDisplay\n header={'Org automations'}\n help={pluginDocsHelp('orgAutomations', { anchor: '#what-an-org-automation-is' })}\n // Other ways to start an org automation (AGL-3603), in the header.\n actions={automationsZone ?? undefined}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Write an automation once and run it on every site you choose. ' +\n 'Each run is that site’s own: its email goes from that site, it ' +\n 'counts on that site’s action runs, and the site can pause it ' +\n 'for itself. Pro plans and up.'}\n </Typography>\n {shown.map((row) => {\n const paused = orgAutomationPausedHostIds(row)\n const placed = placedSiteIds(row, mount)\n const pausable = placed.filter((hostId) => !paused.includes(hostId))\n return (\n <Stack\n key={row.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Switch\n size=\"small\"\n checked={row.enabled !== false}\n disabled={!canEdit}\n onChange={handleToggle(row)}\n slotProps={{ input: { 'aria-label': `Switch ${row.name} on or off` } }}\n />\n <Stack sx={{ flex: 1, minWidth: 0 }} spacing={0.25}>\n <Typography variant=\"body2\" noWrap>\n {row.name}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`on ${hostEventLabel(row.trigger?.event)} · ${(row.steps ?? [])\n .map(\n (step) =>\n HOST_ACTION_STEP_LABELS[step.type as HostActionStepType] ??\n step.type,\n )\n .join(' → ')}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {placementLine(row, mount)}\n </Typography>\n {paused.length ? (\n <Stack direction=\"row\" spacing={0.5} sx={{ flexWrap: 'wrap', gap: 0.5 }}>\n {paused.map((hostId) => (\n <Chip\n key={hostId}\n size=\"small\"\n variant=\"outlined\"\n color=\"warning\"\n label={`Paused on ${orgSiteName(mount, hostId)}`}\n {...(canEdit\n ? { onDelete: () => void setPaused(row, hostId, false) }\n : {})}\n />\n ))}\n </Stack>\n ) : null}\n </Stack>\n {canEdit ? (\n <>\n <Button size=\"small\" onClick={() => setDraft(orgAutomationDraft(row))}>\n {'Edit'}\n </Button>\n <Button\n size=\"small\"\n disabled={!pausable.length}\n aria-haspopup=\"menu\"\n onClick={(event) =>\n setPauseMenu({ anchor: event.currentTarget, row })\n }\n >\n {'Pause on…'}\n </Button>\n <Button size=\"small\" color=\"error\" onClick={handleDelete(row)}>\n {'Delete'}\n </Button>\n </>\n ) : null}\n </Stack>\n )\n })}\n {automations.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No org automations yet.'}\n </Typography>\n ) : (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={shown.length}\n count={automations.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n {(rows?.length ?? 0) > ORG_AUTOMATIONS_MAX ? (\n <Alert severity=\"info\">\n {`Showing ${ORG_AUTOMATIONS_MAX} org automations, the most an ` +\n 'organization holds.'}\n </Alert>\n ) : null}\n {canEdit ? (\n <Button\n size=\"small\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={handleAdd}\n >\n {'Add org automation'}\n </Button>\n ) : null}\n </Stack>\n <Menu\n anchorEl={pauseMenu?.anchor ?? null}\n open={Boolean(pauseMenu)}\n onClose={() => setPauseMenu(null)}\n >\n {pauseMenu\n ? placedSiteIds(pauseMenu.row, mount)\n .filter(\n (hostId) =>\n !orgAutomationPausedHostIds(pauseMenu.row).includes(hostId),\n )\n .map((hostId) => (\n <MenuItem\n key={hostId}\n onClick={() => void setPaused(pauseMenu.row, hostId, true)}\n >\n {`Pause on ${orgSiteName(mount, hostId)}`}\n </MenuItem>\n ))\n : null}\n </Menu>\n <OrgAutomationEditor\n mount={mount}\n draft={draft}\n onDraft={(update) =>\n setDraft((previous) => (previous ? update(previous) : previous))\n }\n opened={editorOpened}\n saving={saving}\n onSave={() => void handleSave()}\n onClose={() => setDraft(null)}\n />\n </CardDisplay>\n )\n}\nOrgAutomationsCard.displayName = 'OrgAutomationsCard'\n\nexport default OrgAutomationsCard\n"],"names":["checkEntitlement","hostEventLabel","pluginDocsHelp","hostIdsFromScope","isOrgWideScope","useConsoleWidgetSlot","CardDisplay","useConfirmationContext","ListPagination","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","useFirestore","useFirestoreCollection","Alert","Button","Chip","Menu","MenuItem","Stack","Switch","Typography","collection","documentId","limit","orderBy","query","where","useCallback","useMemo","useState","ORG_AUTOMATION_STEP_TYPES","ORG_AUTOMATION_TRIGGER_EVENTS","ORG_AUTOMATIONS_COLLECTION","ORG_AUTOMATIONS_MAX","orgAutomationPausedHostIds","readOrgAutomation","newOrgAutomationDraft","OrgAutomationEditor","orgAutomationBody","orgAutomationDraft","orgAutomationDraftFromProposal","ORG_AUTOMATIONS_ZONE","useOrgAutomationsApi","orgSiteName","HOST_ACTION_STEP_LABELS","placedSiteIds","row","mount","visibleTo","hosts","map","host","id","placementLine","names","length","join","OrgAutomationsCard","props","org","canEdit","firestore","api","enqueueSnackbar","confirm","data","rows","orgId","idField","automations","slice","sort","a","b","String","name","localeCompare","page","setPage","pageSize","setPageSize","shown","draft","setDraft","editorOpened","setEditorOpened","saving","setSaving","pauseMenu","setPauseMenu","entitled","fail","error","message","variant","persist","refuseUnentitled","handleAdd","propose","proposal","opened","ExtensionZone","automationsZone","slot","triggers","steps","handleSave","body","read","ok","problem","action","automationId","automation","handleToggle","event","$id","enabled","target","checked","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","setPaused","hostId","paused","header","help","anchor","actions","undefined","contentGutterX","contentGutterY","spacing","placed","pausable","filter","includes","direction","sx","alignItems","size","disabled","onChange","slotProps","input","flex","minWidth","noWrap","trigger","step","type","flexWrap","gap","label","onDelete","onClick","aria-haspopup","currentTarget","rowCount","count","onPageChange","onPageSizeChange","severity","alignSelf","anchorEl","open","Boolean","onClose","onDraft","update","previous","onSave","displayName"],"mappings":"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"}
|
|
@@ -57,7 +57,8 @@ export interface AutomationTarget {
|
|
|
57
57
|
/**
|
|
58
58
|
* The editor of one SAVED automation — an action or a workflow — on the
|
|
59
59
|
* Automation page (AGL-2919). A widget here reads the automation as it is
|
|
60
|
-
* stored and changes nothing in the editor
|
|
60
|
+
* stored and changes nothing in the editor; what it drafts is a new
|
|
61
|
+
* automation, which `openAction` opens.
|
|
61
62
|
*/
|
|
62
63
|
export interface AutomationEditorZoneProps {
|
|
63
64
|
hostId: string;
|
|
@@ -65,6 +66,13 @@ export interface AutomationEditorZoneProps {
|
|
|
65
66
|
orgId: string | undefined;
|
|
66
67
|
/** The automation the editor has open, as it is stored. */
|
|
67
68
|
target: AutomationTarget;
|
|
69
|
+
/**
|
|
70
|
+
* Opens a listed action in the Actions editor in place of the one open —
|
|
71
|
+
* a changed copy a widget drafted, say — and answers `false` when the id
|
|
72
|
+
* names no action the list has read yet (AGL-3603). The Actions editor
|
|
73
|
+
* hands it; a workflow's editor does not.
|
|
74
|
+
*/
|
|
75
|
+
openAction?: (actionId: string) => boolean;
|
|
68
76
|
}
|
|
69
77
|
/**
|
|
70
78
|
* One FAILED run in an automation's run history (AGL-2919), drawn once per
|
|
@@ -81,5 +89,49 @@ export interface AutomationRunZoneProps {
|
|
|
81
89
|
runId: string;
|
|
82
90
|
}
|
|
83
91
|
export declare const HOST_AUTOMATIONS_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<HostAutomationsZoneProps>;
|
|
92
|
+
/**
|
|
93
|
+
* The workspace's Org automations section, in its card's header (AGL-3603):
|
|
94
|
+
* another way to start an org automation, drawn for a member who may write
|
|
95
|
+
* them. `triggers` and `steps` are what an org automation may start on and
|
|
96
|
+
* hold — this plugin's own lists, handed over so a widget never restates
|
|
97
|
+
* them. `propose` opens an automation in the section's editor as a NEW one,
|
|
98
|
+
* unsaved and switched off, and answers `false` when the editor cannot take
|
|
99
|
+
* it. A widget here writes nothing: the editor's Save, through the section's
|
|
100
|
+
* route, is the write.
|
|
101
|
+
*/
|
|
102
|
+
export interface OrgAutomationsZoneProps {
|
|
103
|
+
orgId: string;
|
|
104
|
+
triggers: readonly string[];
|
|
105
|
+
steps: readonly string[];
|
|
106
|
+
propose: (automation: OrgAutomationProposal) => boolean;
|
|
107
|
+
}
|
|
108
|
+
/** An automation a widget proposes to the Org automations editor. */
|
|
109
|
+
export interface OrgAutomationProposal {
|
|
110
|
+
name: string;
|
|
111
|
+
trigger: {
|
|
112
|
+
event: string;
|
|
113
|
+
conditions?: ReadonlyArray<{
|
|
114
|
+
field: string;
|
|
115
|
+
op: string;
|
|
116
|
+
value?: string;
|
|
117
|
+
}>;
|
|
118
|
+
combinator?: 'and' | 'or';
|
|
119
|
+
};
|
|
120
|
+
steps: ReadonlyArray<Record<string, unknown> & {
|
|
121
|
+
type: string;
|
|
122
|
+
}>;
|
|
123
|
+
}
|
|
124
|
+
export declare const ORG_AUTOMATIONS_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<OrgAutomationsZoneProps>;
|
|
84
125
|
export declare const AUTOMATION_EDITOR_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<AutomationEditorZoneProps>;
|
|
85
126
|
export declare const AUTOMATION_RUN_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<AutomationRunZoneProps>;
|
|
127
|
+
/**
|
|
128
|
+
* The query parameter that asks the Actions section to open one of its
|
|
129
|
+
* actions in the editor once its list has read it (AGL-3603) — how the
|
|
130
|
+
* Workflows section's `openAction` hands over an action a widget drafted
|
|
131
|
+
* there, since the action lives in the other section.
|
|
132
|
+
*/
|
|
133
|
+
export declare const OPEN_ACTION_PARAM = "action";
|
|
134
|
+
/** The Actions section's address, opening `actionId` in its editor. */
|
|
135
|
+
export declare function openActionHref(actionsHref: string, actionId: string): string;
|
|
136
|
+
/** The action the address asks the Actions section to open, or `null`. */
|
|
137
|
+
export declare function requestedActionId(search: string): string | null;
|
|
@@ -16,7 +16,21 @@
|
|
|
16
16
|
*/ import { definePluginZone } from "@aglyn/aglyn/plugin-manager/plugin-zones";
|
|
17
17
|
export const WORKFLOW_USAGE_ZONE = definePluginZone('workflowUsage');
|
|
18
18
|
export const HOST_AUTOMATIONS_ZONE = definePluginZone('hostAutomations');
|
|
19
|
+
export const ORG_AUTOMATIONS_ZONE = definePluginZone('orgAutomations');
|
|
19
20
|
export const AUTOMATION_EDITOR_ZONE = definePluginZone('automationEditor');
|
|
20
21
|
export const AUTOMATION_RUN_ZONE = definePluginZone('automationRun');
|
|
22
|
+
/**
|
|
23
|
+
* The query parameter that asks the Actions section to open one of its
|
|
24
|
+
* actions in the editor once its list has read it (AGL-3603) — how the
|
|
25
|
+
* Workflows section's `openAction` hands over an action a widget drafted
|
|
26
|
+
* there, since the action lives in the other section.
|
|
27
|
+
*/ export const OPEN_ACTION_PARAM = 'action';
|
|
28
|
+
/** The Actions section's address, opening `actionId` in its editor. */ export function openActionHref(actionsHref, actionId) {
|
|
29
|
+
return `${actionsHref}?${OPEN_ACTION_PARAM}=${encodeURIComponent(actionId)}`;
|
|
30
|
+
}
|
|
31
|
+
/** The action the address asks the Actions section to open, or `null`. */ export function requestedActionId(search) {
|
|
32
|
+
const id = new URLSearchParams(search).get(OPEN_ACTION_PARAM);
|
|
33
|
+
return id && /^[A-Za-z0-9_-]{1,128}$/.test(id) ? id : null;
|
|
34
|
+
}
|
|
21
35
|
|
|
22
36
|
//# sourceMappingURL=workflow-zones.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/workflow-zones.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 type { WhereUsedResult } from '@aglyn/aglyn/app-utils/where-used'\nimport { definePluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\n\n/**\n * Where a workflow is used, shown by whichever plugin draws references.\n *\n * The Automation page asks the platform's where-used scan what a workflow\n * computes and holds the answer. Laying that answer out — the rows, the deep\n * links into a published version, the flag on a legacy name token — is the\n * same dialog a variable and a function open, and it belongs to the plugin\n * that owns those. This page hosts the zone and hands it the answer.\n */\nexport interface WorkflowUsageZoneProps {\n hostId: string\n /** The scan this page already ran; `null` closes whatever is drawn. */\n usage: { name: string; result: WhereUsedResult } | null\n onClose: () => void\n}\n\nexport const WORKFLOW_USAGE_ZONE =\n definePluginZone<WorkflowUsageZoneProps>('workflowUsage')\n\n/**\n * The Automation page's Actions, beside Add action and Recipes (AGL-2919):\n * another way to start an automation. `openAction` opens a listed action in\n * the Actions editor, and answers `false` when the id names no action the\n * list has read yet — which is how a widget whose draft was just written\n * tells a list that has not caught up from one that opened it. A widget here\n * writes nothing through the page.\n */\nexport interface HostAutomationsZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n openAction: (actionId: string) => boolean\n}\n\n/** One automation on the Automation page: an action, or a workflow (AGL-2919). */\nexport interface AutomationTarget {\n type: 'action' | 'workflow'\n id: string\n name: string\n}\n\n/**\n * The editor of one SAVED automation — an action or a workflow — on the\n * Automation page (AGL-2919). A widget here reads the automation as it is\n * stored and changes nothing in the editor.\n */\nexport interface AutomationEditorZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n /** The automation the editor has open, as it is stored. */\n target: AutomationTarget\n}\n\n/**\n * One FAILED run in an automation's run history (AGL-2919), drawn once per\n * failed row. A widget here reads the run as it was recorded and changes\n * nothing.\n */\nexport interface AutomationRunZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n /** The automation the run belongs to. */\n target: AutomationTarget\n /** The run's entry in the site's activity log. */\n runId: string\n}\n\nexport const HOST_AUTOMATIONS_ZONE =\n definePluginZone<HostAutomationsZoneProps>('hostAutomations')\n\nexport const AUTOMATION_EDITOR_ZONE =\n definePluginZone<AutomationEditorZoneProps>('automationEditor')\n\nexport const AUTOMATION_RUN_ZONE =\n definePluginZone<AutomationRunZoneProps>('automationRun')\n"],"names":["definePluginZone","WORKFLOW_USAGE_ZONE","HOST_AUTOMATIONS_ZONE","AUTOMATION_EDITOR_ZONE","AUTOMATION_RUN_ZONE"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,gBAAgB,QAAQ,2CAA0C;AAkB3E,OAAO,MAAMC,sBACXD,iBAAyC,iBAAgB;
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/workflow-zones.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 type { WhereUsedResult } from '@aglyn/aglyn/app-utils/where-used'\nimport { definePluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\n\n/**\n * Where a workflow is used, shown by whichever plugin draws references.\n *\n * The Automation page asks the platform's where-used scan what a workflow\n * computes and holds the answer. Laying that answer out — the rows, the deep\n * links into a published version, the flag on a legacy name token — is the\n * same dialog a variable and a function open, and it belongs to the plugin\n * that owns those. This page hosts the zone and hands it the answer.\n */\nexport interface WorkflowUsageZoneProps {\n hostId: string\n /** The scan this page already ran; `null` closes whatever is drawn. */\n usage: { name: string; result: WhereUsedResult } | null\n onClose: () => void\n}\n\nexport const WORKFLOW_USAGE_ZONE =\n definePluginZone<WorkflowUsageZoneProps>('workflowUsage')\n\n/**\n * The Automation page's Actions, beside Add action and Recipes (AGL-2919):\n * another way to start an automation. `openAction` opens a listed action in\n * the Actions editor, and answers `false` when the id names no action the\n * list has read yet — which is how a widget whose draft was just written\n * tells a list that has not caught up from one that opened it. A widget here\n * writes nothing through the page.\n */\nexport interface HostAutomationsZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n openAction: (actionId: string) => boolean\n}\n\n/** One automation on the Automation page: an action, or a workflow (AGL-2919). */\nexport interface AutomationTarget {\n type: 'action' | 'workflow'\n id: string\n name: string\n}\n\n/**\n * The editor of one SAVED automation — an action or a workflow — on the\n * Automation page (AGL-2919). A widget here reads the automation as it is\n * stored and changes nothing in the editor; what it drafts is a new\n * automation, which `openAction` opens.\n */\nexport interface AutomationEditorZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n /** The automation the editor has open, as it is stored. */\n target: AutomationTarget\n /**\n * Opens a listed action in the Actions editor in place of the one open —\n * a changed copy a widget drafted, say — and answers `false` when the id\n * names no action the list has read yet (AGL-3603). The Actions editor\n * hands it; a workflow's editor does not.\n */\n openAction?: (actionId: string) => boolean\n}\n\n/**\n * One FAILED run in an automation's run history (AGL-2919), drawn once per\n * failed row. A widget here reads the run as it was recorded and changes\n * nothing.\n */\nexport interface AutomationRunZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n /** The automation the run belongs to. */\n target: AutomationTarget\n /** The run's entry in the site's activity log. */\n runId: string\n}\n\nexport const HOST_AUTOMATIONS_ZONE =\n definePluginZone<HostAutomationsZoneProps>('hostAutomations')\n\n/**\n * The workspace's Org automations section, in its card's header (AGL-3603):\n * another way to start an org automation, drawn for a member who may write\n * them. `triggers` and `steps` are what an org automation may start on and\n * hold — this plugin's own lists, handed over so a widget never restates\n * them. `propose` opens an automation in the section's editor as a NEW one,\n * unsaved and switched off, and answers `false` when the editor cannot take\n * it. A widget here writes nothing: the editor's Save, through the section's\n * route, is the write.\n */\nexport interface OrgAutomationsZoneProps {\n orgId: string\n triggers: readonly string[]\n steps: readonly string[]\n propose: (automation: OrgAutomationProposal) => boolean\n}\n\n/** An automation a widget proposes to the Org automations editor. */\nexport interface OrgAutomationProposal {\n name: string\n trigger: {\n event: string\n conditions?: ReadonlyArray<{ field: string; op: string; value?: string }>\n combinator?: 'and' | 'or'\n }\n steps: ReadonlyArray<Record<string, unknown> & { type: string }>\n}\n\nexport const ORG_AUTOMATIONS_ZONE =\n definePluginZone<OrgAutomationsZoneProps>('orgAutomations')\n\nexport const AUTOMATION_EDITOR_ZONE =\n definePluginZone<AutomationEditorZoneProps>('automationEditor')\n\nexport const AUTOMATION_RUN_ZONE =\n definePluginZone<AutomationRunZoneProps>('automationRun')\n\n/**\n * The query parameter that asks the Actions section to open one of its\n * actions in the editor once its list has read it (AGL-3603) — how the\n * Workflows section's `openAction` hands over an action a widget drafted\n * there, since the action lives in the other section.\n */\nexport const OPEN_ACTION_PARAM = 'action'\n\n/** The Actions section's address, opening `actionId` in its editor. */\nexport function openActionHref(actionsHref: string, actionId: string): string {\n return `${actionsHref}?${OPEN_ACTION_PARAM}=${encodeURIComponent(actionId)}`\n}\n\n/** The action the address asks the Actions section to open, or `null`. */\nexport function requestedActionId(search: string): string | null {\n const id = new URLSearchParams(search).get(OPEN_ACTION_PARAM)\n return id && /^[A-Za-z0-9_-]{1,128}$/.test(id) ? id : null\n}\n"],"names":["definePluginZone","WORKFLOW_USAGE_ZONE","HOST_AUTOMATIONS_ZONE","ORG_AUTOMATIONS_ZONE","AUTOMATION_EDITOR_ZONE","AUTOMATION_RUN_ZONE","OPEN_ACTION_PARAM","openActionHref","actionsHref","actionId","encodeURIComponent","requestedActionId","search","id","URLSearchParams","get","test"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,gBAAgB,QAAQ,2CAA0C;AAkB3E,OAAO,MAAMC,sBACXD,iBAAyC,iBAAgB;AA4D3D,OAAO,MAAME,wBACXF,iBAA2C,mBAAkB;AA8B/D,OAAO,MAAMG,uBACXH,iBAA0C,kBAAiB;AAE7D,OAAO,MAAMI,yBACXJ,iBAA4C,oBAAmB;AAEjE,OAAO,MAAMK,sBACXL,iBAAyC,iBAAgB;AAE3D;;;;;CAKC,GACD,OAAO,MAAMM,oBAAoB,SAAQ;AAEzC,qEAAqE,GACrE,OAAO,SAASC,eAAeC,WAAmB,EAAEC,QAAgB;IAClE,OAAO,GAAGD,YAAY,CAAC,EAAEF,kBAAkB,CAAC,EAAEI,mBAAmBD,WAAW;AAC9E;AAEA,wEAAwE,GACxE,OAAO,SAASE,kBAAkBC,MAAc;IAC9C,MAAMC,KAAK,IAAIC,gBAAgBF,QAAQG,GAAG,CAACT;IAC3C,OAAOO,MAAM,yBAAyBG,IAAI,CAACH,MAAMA,KAAK;AACxD"}
|
|
@@ -34,7 +34,7 @@ import { workflowsOrgMount } from "./workflows-org-mount.js";
|
|
|
34
34
|
* would CONSTRUCT all three every render, and each card opens its Firestore
|
|
35
35
|
* listens on mount — which is the entire cost this page exists to stop paying.
|
|
36
36
|
* Only the returned branch is ever built.
|
|
37
|
-
*/ function sectionBody(section, hostId, org) {
|
|
37
|
+
*/ function sectionBody(section, hostId, org, /** The Actions section's address, where a drafted action is opened. */ actionsHref) {
|
|
38
38
|
switch(section){
|
|
39
39
|
case 'workflows':
|
|
40
40
|
return(/*
|
|
@@ -52,7 +52,8 @@ import { workflowsOrgMount } from "./workflows-org-mount.js";
|
|
|
52
52
|
}),
|
|
53
53
|
/*#__PURE__*/ _jsx(HostWorkflowsCard, {
|
|
54
54
|
hostId: hostId,
|
|
55
|
-
org: org
|
|
55
|
+
org: org,
|
|
56
|
+
actionsHref: actionsHref
|
|
56
57
|
})
|
|
57
58
|
]
|
|
58
59
|
}));
|
|
@@ -148,6 +149,7 @@ import { workflowsOrgMount } from "./workflows-org-mount.js";
|
|
|
148
149
|
* site and an ACTION belongs to the site too; an interaction belongs to the
|
|
149
150
|
* document that holds it and is edited on the element, not here.
|
|
150
151
|
*/ export function WorkflowsConsolePage(props) {
|
|
152
|
+
var _sections_find;
|
|
151
153
|
const { hostId, orgMount, org, permissions, section, sections, basePath } = props;
|
|
152
154
|
/*
|
|
153
155
|
* Mounted with no site: the organization's hub (AGL-3302). Memoised so the
|
|
@@ -176,7 +178,7 @@ import { workflowsOrgMount } from "./workflows-org-mount.js";
|
|
|
176
178
|
}
|
|
177
179
|
return /*#__PURE__*/ _jsx(HubSections, {
|
|
178
180
|
sections: sections,
|
|
179
|
-
children: sectionBody(section, hostId, org)
|
|
181
|
+
children: sectionBody(section, hostId, org, (_sections_find = sections.find((one)=>one.id === 'actions')) == null ? void 0 : _sections_find.href)
|
|
180
182
|
});
|
|
181
183
|
}
|
|
182
184
|
WorkflowsConsolePage.displayName = 'WorkflowsConsolePage';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/workflows-console-page.tsx"],"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'use client'\n\nimport type { AglynOrgBilling, ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { HubSections } from '@aglyn/shared-ui-next'\nimport { Stack } from '@mui/material'\nimport { useMemo, type ReactNode } from 'react'\nimport HostActionsCard from './host-actions-card.component'\nimport HostWebhooksCard from './host-webhooks-card.component'\nimport HostWorkflowsCard from './host-workflows-card.component'\nimport OrgAutomationsCard from './org-automations-card.component'\nimport OrgSiteAutomationList from './org-site-automation-list.component'\nimport RunQuotaLine from './run-quota-line.component'\nimport SiteOrgAutomationsPanel from './site-org-automations-panel.component'\nimport type { WorkflowsConsoleSectionId } from './workflows-console-sections'\nimport {\n workflowsOrgMount,\n type WorkflowsOrgMount,\n} from './workflows-org-mount'\n\n/**\n * The body of one workflows section, built only when that section is the one\n * being read (AGL-2501).\n *\n * A function rather than a map of nodes on purpose: a `Record<id, ReactNode>`\n * would CONSTRUCT all three every render, and each card opens its Firestore\n * listens on mount — which is the entire cost this page exists to stop paying.\n * Only the returned branch is ever built.\n */\nfunction sectionBody(\n section: WorkflowsConsoleSectionId,\n hostId: string,\n org: Partial<AglynOrgBilling> | undefined,\n): ReactNode {\n switch (section) {\n case 'workflows':\n return (\n /*\n * `N runs this month · M included` (AGL-2171), which the run-history\n * mockup puts opposite the heading. The quota silently stops\n * automations running once it is reached, and the only place it was\n * reported was the Billing page.\n */\n <Stack spacing={1}>\n <RunQuotaLine hostId={hostId} org={org} counter=\"workflowRuns\" />\n <HostWorkflowsCard hostId={hostId} org={org} />\n </Stack>\n )\n case 'actions':\n return (\n <Stack spacing={1}>\n {/* `actionRunsPerMonth` had NO customer-facing surface at all\n before this — staff panel and usage-alerts only. */}\n <RunQuotaLine hostId={hostId} org={org} counter=\"actionRuns\" />\n <HostActionsCard hostId={hostId} org={org} />\n {/* What the organization runs on this site beside the site's own\n actions, and the site's pause for each (AGL-3302). */}\n <SiteOrgAutomationsPanel hostId={hostId} />\n </Stack>\n )\n case 'webhooks':\n return <HostWebhooksCard hostId={hostId} org={org} />\n default:\n return null\n }\n}\n\n/**\n * The body of one ORGANIZATION-level section (AGL-3302): the org automations,\n * and every site's own workflows, actions and webhooks listed with the site\n * each belongs to.\n *\n * The run allowance line heads the workflows and actions lists here too: the\n * allowance is the workspace's (AGL-3472), every site's runs spend it, and\n * this is the page that lists them all.\n */\nfunction orgSectionBody(\n section: WorkflowsConsoleSectionId,\n mount: WorkflowsOrgMount,\n org: Partial<AglynOrgBilling> | undefined,\n canEdit: boolean,\n): ReactNode {\n switch (section) {\n case 'automations':\n return <OrgAutomationsCard mount={mount} org={org} canEdit={canEdit} />\n case 'workflows':\n case 'actions':\n return (\n <Stack spacing={1}>\n <RunQuotaLine\n hostId={null}\n orgId={mount.orgId}\n org={org}\n counter={section === 'workflows' ? 'workflowRuns' : 'actionRuns'}\n />\n <OrgSiteAutomationList mount={mount} kind={section} canRead />\n </Stack>\n )\n case 'webhooks':\n // A webhook holds its site's secret: the rules admit a site's admins\n // and editors, which every org-wide member but a viewer is.\n return (\n <OrgSiteAutomationList mount={mount} kind=\"webhooks\" canRead={canEdit} />\n )\n default:\n return null\n }\n}\n\n/**\n * Automation page (AGL-101/148/149 → AGL-395): the automation surface —\n * workflow builder, event-triggered actions, and webhooks — owned by the\n * workflows plugin and rendered by the shell's generic plugin route. Each card\n * runs its own entitlement check (workflows / actions / webhooks are distinct\n * plan flags), so the shell's resolved `org` doc flows into all three.\n *\n * Sections are ROUTES (AGL-2501). `HubTabs lazy` already mounted one panel, so\n * this is not a read saving — `workflows-console-read-cost.spec.tsx` was\n * written BEFORE the conversion precisely to hold that line, and reports the\n * same counts after. What routing adds is that the URL names the section: it is\n * linkable, the back button walks sections, the breadcrumb says where you are,\n * and \"mount only what is open\" is structural rather than a `lazy` flag.\n *\n * The section ids are unchanged by the Automation rename: they are the\n * addresses people bookmark and the docs link to. A workflow belongs to the\n * site and an ACTION belongs to the site too; an interaction belongs to the\n * document that holds it and is edited on the element, not here.\n */\nexport function WorkflowsConsolePage(props: ConsolePluginPageProps) {\n const { hostId, orgMount, org, permissions, section, sections, basePath } =\n props\n /*\n * Mounted with no site: the organization's hub (AGL-3302). Memoised so the\n * cards below, which key their listeners on the mount's org, see one object.\n */\n const mount = useMemo(\n () => (hostId == null ? workflowsOrgMount(orgMount, basePath) : null),\n [hostId, orgMount, basePath],\n )\n\n /*\n * Nothing until the URL names a section. The shell redirects a bare hub URL\n * to the landing section and holds a spinner while it does, so this state is\n * transient — and rendering a default section here instead would pay for its\n * listens on a URL that is already being replaced.\n */\n if (!section || !sections?.length || !basePath) return null\n\n if (hostId == null) {\n // No site and no org to stand in for it: nothing this page can scope.\n if (!mount) return null\n return (\n <HubSections sections={sections}>\n {orgSectionBody(\n section as WorkflowsConsoleSectionId,\n mount,\n org,\n // An org viewer's `editHosts` is false; every other org role's is\n // true. The server routes decide — this keeps a viewer from being\n // offered controls that are about to refuse them.\n permissions?.editHosts !== false,\n )}\n </HubSections>\n )\n }\n\n return (\n <HubSections sections={sections}>\n {sectionBody(section as WorkflowsConsoleSectionId, hostId, org)}\n </HubSections>\n )\n}\nWorkflowsConsolePage.displayName = 'WorkflowsConsolePage'\n\nexport default WorkflowsConsolePage\n"],"names":["HubSections","Stack","useMemo","HostActionsCard","HostWebhooksCard","HostWorkflowsCard","OrgAutomationsCard","OrgSiteAutomationList","RunQuotaLine","SiteOrgAutomationsPanel","workflowsOrgMount","sectionBody","section","hostId","org","spacing","counter","orgSectionBody","mount","canEdit","orgId","kind","canRead","WorkflowsConsolePage","props","orgMount","permissions","sections","basePath","length","editHosts","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAGA,SAASA,WAAW,QAAQ,wBAAuB;AACnD,SAASC,KAAK,QAAQ,gBAAe;AACrC,SAASC,OAAO,QAAwB,QAAO;AAC/C,OAAOC,qBAAqB,mCAA+B;AAC3D,OAAOC,sBAAsB,oCAAgC;AAC7D,OAAOC,uBAAuB,qCAAiC;AAC/D,OAAOC,wBAAwB,sCAAkC;AACjE,OAAOC,2BAA2B,0CAAsC;AACxE,OAAOC,kBAAkB,gCAA4B;AACrD,OAAOC,6BAA6B,4CAAwC;AAE5E,SACEC,iBAAiB,QAEZ,2BAAuB;AAE9B;;;;;;;;CAQC,GACD,SAASC,YACPC,OAAkC,EAClCC,MAAc,EACdC,GAAyC;IAEzC,OAAQF;QACN,KAAK;YACH,OACE;;;;;SAKC,iBACD,MAACX;gBAAMc,SAAS;;kCACd,KAACP;wBAAaK,QAAQA;wBAAQC,KAAKA;wBAAKE,SAAQ;;kCAChD,KAACX;wBAAkBQ,QAAQA;wBAAQC,KAAKA;;;;QAG9C,KAAK;YACH,qBACE,MAACb;gBAAMc,SAAS;;kCAGd,KAACP;wBAAaK,QAAQA;wBAAQC,KAAKA;wBAAKE,SAAQ;;kCAChD,KAACb;wBAAgBU,QAAQA;wBAAQC,KAAKA;;kCAGtC,KAACL;wBAAwBI,QAAQA;;;;QAGvC,KAAK;YACH,qBAAO,KAACT;gBAAiBS,QAAQA;gBAAQC,KAAKA;;QAChD;YACE,OAAO;IACX;AACF;AAEA;;;;;;;;CAQC,GACD,SAASG,eACPL,OAAkC,EAClCM,KAAwB,EACxBJ,GAAyC,EACzCK,OAAgB;IAEhB,OAAQP;QACN,KAAK;YACH,qBAAO,KAACN;gBAAmBY,OAAOA;gBAAOJ,KAAKA;gBAAKK,SAASA;;QAC9D,KAAK;QACL,KAAK;YACH,qBACE,MAAClB;gBAAMc,SAAS;;kCACd,KAACP;wBACCK,QAAQ;wBACRO,OAAOF,MAAME,KAAK;wBAClBN,KAAKA;wBACLE,SAASJ,YAAY,cAAc,iBAAiB;;kCAEtD,KAACL;wBAAsBW,OAAOA;wBAAOG,MAAMT;wBAASU,OAAO;;;;QAGjE,KAAK;YACH,qEAAqE;YACrE,4DAA4D;YAC5D,qBACE,KAACf;gBAAsBW,OAAOA;gBAAOG,MAAK;gBAAWC,SAASH;;QAElE;YACE,OAAO;IACX;AACF;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASI,qBAAqBC,KAA6B;IAChE,MAAM,EAAEX,MAAM,EAAEY,QAAQ,EAAEX,GAAG,EAAEY,WAAW,EAAEd,OAAO,EAAEe,QAAQ,EAAEC,QAAQ,EAAE,GACvEJ;IACF;;;GAGC,GACD,MAAMN,QAAQhB,QACZ,IAAOW,UAAU,OAAOH,kBAAkBe,UAAUG,YAAY,MAChE;QAACf;QAAQY;QAAUG;KAAS;IAG9B;;;;;GAKC,GACD,IAAI,CAAChB,WAAW,EAACe,4BAAAA,SAAUE,MAAM,KAAI,CAACD,UAAU,OAAO;IAEvD,IAAIf,UAAU,MAAM;QAClB,sEAAsE;QACtE,IAAI,CAACK,OAAO,OAAO;QACnB,qBACE,KAAClB;YAAY2B,UAAUA;sBACpBV,eACCL,SACAM,OACAJ,KACA,kEAAkE;YAClE,kEAAkE;YAClE,kDAAkD;YAClDY,CAAAA,+BAAAA,YAAaI,SAAS,MAAK;;IAInC;IAEA,qBACE,KAAC9B;QAAY2B,UAAUA;kBACpBhB,YAAYC,SAAsCC,QAAQC;;AAGjE;AACAS,qBAAqBQ,WAAW,GAAG;AAEnC,eAAeR,qBAAoB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/workflows-console-page.tsx"],"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'use client'\n\nimport type { AglynOrgBilling, ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { HubSections } from '@aglyn/shared-ui-next'\nimport { Stack } from '@mui/material'\nimport { useMemo, type ReactNode } from 'react'\nimport HostActionsCard from './host-actions-card.component'\nimport HostWebhooksCard from './host-webhooks-card.component'\nimport HostWorkflowsCard from './host-workflows-card.component'\nimport OrgAutomationsCard from './org-automations-card.component'\nimport OrgSiteAutomationList from './org-site-automation-list.component'\nimport RunQuotaLine from './run-quota-line.component'\nimport SiteOrgAutomationsPanel from './site-org-automations-panel.component'\nimport type { WorkflowsConsoleSectionId } from './workflows-console-sections'\nimport {\n workflowsOrgMount,\n type WorkflowsOrgMount,\n} from './workflows-org-mount'\n\n/**\n * The body of one workflows section, built only when that section is the one\n * being read (AGL-2501).\n *\n * A function rather than a map of nodes on purpose: a `Record<id, ReactNode>`\n * would CONSTRUCT all three every render, and each card opens its Firestore\n * listens on mount — which is the entire cost this page exists to stop paying.\n * Only the returned branch is ever built.\n */\nfunction sectionBody(\n section: WorkflowsConsoleSectionId,\n hostId: string,\n org: Partial<AglynOrgBilling> | undefined,\n /** The Actions section's address, where a drafted action is opened. */\n actionsHref?: string,\n): ReactNode {\n switch (section) {\n case 'workflows':\n return (\n /*\n * `N runs this month · M included` (AGL-2171), which the run-history\n * mockup puts opposite the heading. The quota silently stops\n * automations running once it is reached, and the only place it was\n * reported was the Billing page.\n */\n <Stack spacing={1}>\n <RunQuotaLine hostId={hostId} org={org} counter=\"workflowRuns\" />\n <HostWorkflowsCard\n hostId={hostId}\n org={org}\n actionsHref={actionsHref}\n />\n </Stack>\n )\n case 'actions':\n return (\n <Stack spacing={1}>\n {/* `actionRunsPerMonth` had NO customer-facing surface at all\n before this — staff panel and usage-alerts only. */}\n <RunQuotaLine hostId={hostId} org={org} counter=\"actionRuns\" />\n <HostActionsCard hostId={hostId} org={org} />\n {/* What the organization runs on this site beside the site's own\n actions, and the site's pause for each (AGL-3302). */}\n <SiteOrgAutomationsPanel hostId={hostId} />\n </Stack>\n )\n case 'webhooks':\n return <HostWebhooksCard hostId={hostId} org={org} />\n default:\n return null\n }\n}\n\n/**\n * The body of one ORGANIZATION-level section (AGL-3302): the org automations,\n * and every site's own workflows, actions and webhooks listed with the site\n * each belongs to.\n *\n * The run allowance line heads the workflows and actions lists here too: the\n * allowance is the workspace's (AGL-3472), every site's runs spend it, and\n * this is the page that lists them all.\n */\nfunction orgSectionBody(\n section: WorkflowsConsoleSectionId,\n mount: WorkflowsOrgMount,\n org: Partial<AglynOrgBilling> | undefined,\n canEdit: boolean,\n): ReactNode {\n switch (section) {\n case 'automations':\n return <OrgAutomationsCard mount={mount} org={org} canEdit={canEdit} />\n case 'workflows':\n case 'actions':\n return (\n <Stack spacing={1}>\n <RunQuotaLine\n hostId={null}\n orgId={mount.orgId}\n org={org}\n counter={section === 'workflows' ? 'workflowRuns' : 'actionRuns'}\n />\n <OrgSiteAutomationList mount={mount} kind={section} canRead />\n </Stack>\n )\n case 'webhooks':\n // A webhook holds its site's secret: the rules admit a site's admins\n // and editors, which every org-wide member but a viewer is.\n return (\n <OrgSiteAutomationList mount={mount} kind=\"webhooks\" canRead={canEdit} />\n )\n default:\n return null\n }\n}\n\n/**\n * Automation page (AGL-101/148/149 → AGL-395): the automation surface —\n * workflow builder, event-triggered actions, and webhooks — owned by the\n * workflows plugin and rendered by the shell's generic plugin route. Each card\n * runs its own entitlement check (workflows / actions / webhooks are distinct\n * plan flags), so the shell's resolved `org` doc flows into all three.\n *\n * Sections are ROUTES (AGL-2501). `HubTabs lazy` already mounted one panel, so\n * this is not a read saving — `workflows-console-read-cost.spec.tsx` was\n * written BEFORE the conversion precisely to hold that line, and reports the\n * same counts after. What routing adds is that the URL names the section: it is\n * linkable, the back button walks sections, the breadcrumb says where you are,\n * and \"mount only what is open\" is structural rather than a `lazy` flag.\n *\n * The section ids are unchanged by the Automation rename: they are the\n * addresses people bookmark and the docs link to. A workflow belongs to the\n * site and an ACTION belongs to the site too; an interaction belongs to the\n * document that holds it and is edited on the element, not here.\n */\nexport function WorkflowsConsolePage(props: ConsolePluginPageProps) {\n const { hostId, orgMount, org, permissions, section, sections, basePath } =\n props\n /*\n * Mounted with no site: the organization's hub (AGL-3302). Memoised so the\n * cards below, which key their listeners on the mount's org, see one object.\n */\n const mount = useMemo(\n () => (hostId == null ? workflowsOrgMount(orgMount, basePath) : null),\n [hostId, orgMount, basePath],\n )\n\n /*\n * Nothing until the URL names a section. The shell redirects a bare hub URL\n * to the landing section and holds a spinner while it does, so this state is\n * transient — and rendering a default section here instead would pay for its\n * listens on a URL that is already being replaced.\n */\n if (!section || !sections?.length || !basePath) return null\n\n if (hostId == null) {\n // No site and no org to stand in for it: nothing this page can scope.\n if (!mount) return null\n return (\n <HubSections sections={sections}>\n {orgSectionBody(\n section as WorkflowsConsoleSectionId,\n mount,\n org,\n // An org viewer's `editHosts` is false; every other org role's is\n // true. The server routes decide — this keeps a viewer from being\n // offered controls that are about to refuse them.\n permissions?.editHosts !== false,\n )}\n </HubSections>\n )\n }\n\n return (\n <HubSections sections={sections}>\n {sectionBody(\n section as WorkflowsConsoleSectionId,\n hostId,\n org,\n sections.find((one) => one.id === 'actions')?.href,\n )}\n </HubSections>\n )\n}\nWorkflowsConsolePage.displayName = 'WorkflowsConsolePage'\n\nexport default WorkflowsConsolePage\n"],"names":["HubSections","Stack","useMemo","HostActionsCard","HostWebhooksCard","HostWorkflowsCard","OrgAutomationsCard","OrgSiteAutomationList","RunQuotaLine","SiteOrgAutomationsPanel","workflowsOrgMount","sectionBody","section","hostId","org","actionsHref","spacing","counter","orgSectionBody","mount","canEdit","orgId","kind","canRead","WorkflowsConsolePage","props","sections","orgMount","permissions","basePath","length","editHosts","find","one","id","href","displayName"],"mappings":"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"}
|
|
@@ -34,7 +34,6 @@ import { FieldValue } from "firebase-admin/firestore";
|
|
|
34
34
|
import { runSummaryFields } from "../model/run-history.js";
|
|
35
35
|
import { ACTION_MAX_EVENT_DEPTH, runWorkflow, WORKFLOW_MAX_STEPS } from "../model/workflows.js";
|
|
36
36
|
import { describeStepOutcome } from "../model/step-outcomes.js";
|
|
37
|
-
import { WEBHOOK_URL_PATTERN } from "../model/webhooks.js";
|
|
38
37
|
import { eventRunSuspension } from "./site-suspension.js";
|
|
39
38
|
import { triggeredDocsForEvent } from "./triggered-docs.js";
|
|
40
39
|
import { resolveStepEmailMerge, stepEmailMergeContext } from "./email-merge.js";
|
|
@@ -297,7 +296,7 @@ export function makeWorkflowContextLoader(hostRef) {
|
|
|
297
296
|
// Id-first lookup (AGL-261); the name query is the legacy path.
|
|
298
297
|
const hookDoc = ((_step_webhookId = step.webhookId) == null ? void 0 : _step_webhookId.trim()) ? await hostRef.collection('webhooks').doc(step.webhookId.trim()).get() : (await hostRef.collection('webhooks').where('name', '==', (_ref3 = (_step_webhookName = step.webhookName) == null ? void 0 : _step_webhookName.trim()) != null ? _ref3 : '').limit(1).get()).docs[0];
|
|
299
298
|
const hook = (hookDoc == null ? void 0 : hookDoc.exists) ? hookDoc.data() : undefined;
|
|
300
|
-
if (!hook || hookDoc.get('deletedAt') || hook.enabled === false || hook.direction !== 'outbound' || !hook.url
|
|
299
|
+
if (!hook || hookDoc.get('deletedAt') || hook.enabled === false || hook.direction !== 'outbound' || !hook.url) {
|
|
301
300
|
return failed(`unknown webhook "${step.webhookName || step.webhookId}"`);
|
|
302
301
|
}
|
|
303
302
|
const body = JSON.stringify({
|
|
@@ -308,11 +307,23 @@ export function makeWorkflowContextLoader(hostRef) {
|
|
|
308
307
|
const signature = hook.secret ? createHmac('sha256', hook.secret).update(body).digest('hex') : '';
|
|
309
308
|
// Two quick retries — serverless-friendly; longer retry queues
|
|
310
309
|
// are a follow-up.
|
|
310
|
+
//
|
|
311
|
+
// The hook's URL is tenant-typed and the step runs on any visitor's
|
|
312
|
+
// form submission, so it goes out through the configured-URL fetch
|
|
313
|
+
// (AGL-3363), never a bare `fetch`: https only, the name resolved to
|
|
314
|
+
// PUBLIC addresses with the socket pinned to the one checked, and no
|
|
315
|
+
// redirect followed — a 3xx is a failed delivery. That is what refuses
|
|
316
|
+
// `[::1]`, `[::ffff:127.0.0.1]`, a decimal `2130706433`, a name that
|
|
317
|
+
// resolves inward, and a public host answering `302` to the metadata
|
|
318
|
+
// endpoint, none of which a pattern over the URL text can see.
|
|
319
|
+
//
|
|
320
|
+
// Loaded here, not at the top: see `webhook-delivery.ts`.
|
|
321
|
+
const { describeConfiguredUrlRefusal, fetchConfiguredPublicUrl } = await import("./webhook-delivery.js");
|
|
311
322
|
let delivered = false;
|
|
312
323
|
let lastStatus;
|
|
313
324
|
for(let attempt = 0; attempt < 3 && !delivered; attempt += 1){
|
|
314
325
|
try {
|
|
315
|
-
const
|
|
326
|
+
const result = await fetchConfiguredPublicUrl(hook.url, {
|
|
316
327
|
method: 'POST',
|
|
317
328
|
headers: _extends({
|
|
318
329
|
'Content-Type': 'application/json'
|
|
@@ -322,8 +333,13 @@ export function makeWorkflowContextLoader(hostRef) {
|
|
|
322
333
|
body,
|
|
323
334
|
signal: AbortSignal.timeout(5000)
|
|
324
335
|
});
|
|
325
|
-
|
|
326
|
-
|
|
336
|
+
if ('refusal' in result) {
|
|
337
|
+
// A refusal is the URL's, not the network's: retrying it would
|
|
338
|
+
// only re-resolve the same answer.
|
|
339
|
+
return failed(`webhook "${step.webhookName || step.webhookId}" refused: ${describeConfiguredUrlRefusal(result.refusal)}`);
|
|
340
|
+
}
|
|
341
|
+
lastStatus = result.status;
|
|
342
|
+
delivered = result.status >= 200 && result.status < 300;
|
|
327
343
|
} catch (unused) {
|
|
328
344
|
// Retry below.
|
|
329
345
|
}
|