@aglyn/plugins-workflows 1.0.0-beta.261 → 1.0.0-beta.263
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 +7 -4
- package/src/lib/components/host-actions-card.component.js.map +1 -1
- package/src/lib/components/host-workflows-card.component.js +7 -2
- package/src/lib/components/host-workflows-card.component.js.map +1 -1
- package/src/lib/components/org-automations-card.component.js +7 -4
- package/src/lib/components/org-automations-card.component.js.map +1 -1
- package/src/lib/components/site-org-automations-panel.component.js +7 -4
- package/src/lib/components/site-org-automations-panel.component.js.map +1 -1
- package/src/lib/server/automation-run-figures.js +19 -0
- package/src/lib/server/automation-run-figures.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/site-org-automations-panel.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 { hostEventLabel, pluginDocsHelp, scopeTokensForHost } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useFirestoreCollection,\n useOrgDataScope,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { collection, limit, query, where } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n ORG_AUTOMATIONS_COLLECTION,\n ORG_AUTOMATIONS_MAX,\n orgAutomationPausedHostIds,\n type OrgAutomationRow,\n} from '../model/org-automations'\nimport HostRunHistoryCard from './host-run-history-card.component'\nimport { useOrgAutomationsApi } from './use-org-automations-api'\nimport { HOST_ACTION_STEP_LABELS, type HostActionStepType } from '../model/host-actions'\n\n/**\n * THE ORGANIZATION'S AUTOMATIONS THAT RUN ON THIS SITE (AGL-3302), on the\n * site's own Actions section: read-only, with the one control that belongs\n * to the site — pausing an automation here, and resuming it.\n *\n * Read with the site's own scope tokens, so the listener is provable for a\n * collaborator scoped to this site as well as for an org-wide member, and\n * only the live ones placed here come back. Editing is the organization's,\n * on its own hub; a pause is the site's, through `automations/pause`, which\n * admits the site's admins and editors and refuses anyone else with its own\n * words.\n *\n * Renders nothing while there is nothing: a site the organization runs no\n * automation on has no panel to explain.\n */\nexport function SiteOrgAutomationsPanel(props: { hostId: string }) {\n const { hostId } = props\n const firestore = useFirestore()\n const api = useOrgAutomationsApi()\n const { enqueueSnackbar } = useSnackbar()\n const { scope } = useOrgDataScope({ hostId })\n const tokens = useMemo(() => scopeTokensForHost(hostId), [hostId])\n const { data: rows } = useFirestoreCollection<OrgAutomationRow>(\n () =>\n scope\n ? query(\n collection(firestore, scope[0], scope[1], ORG_AUTOMATIONS_COLLECTION),\n where('deletedAt', '==', null),\n where('visibleTo', 'array-contains-any', tokens),\n limit(ORG_AUTOMATIONS_MAX + 1),\n )\n : null,\n [firestore, scope, tokens],\n { idField: '$id' },\n )\n const automations = useMemo(\n () =>\n [...(rows ?? [])].sort((a, b) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n ),\n [rows],\n )\n const [busy, setBusy] = useState<string | null>(null)\n const [runsFor, setRunsFor] = useState<OrgAutomationRow | null>(null)\n\n const setPaused = useCallback(\n async (row: OrgAutomationRow, paused: boolean) => {\n setBusy(row.$id)\n try {\n await api('pause', { hostId, automationId: row.$id, paused })\n enqueueSnackbar(\n paused\n ? `“${row.name}” is paused on this site`\n : `“${row.name}” runs on this site again`,\n { variant: 'success', persist: false },\n )\n } catch (error) {\n enqueueSnackbar(\n (error as Error)?.message || 'The request could not be completed',\n { variant: 'warning', persist: false },\n )\n } finally {\n setBusy(null)\n }\n },\n [api, hostId, enqueueSnackbar],\n )\n\n if (!automations.length) return null\n return (\n <CardDisplay\n header={'Org automations on this site'}\n help={pluginDocsHelp('orgAutomations', { anchor: '#pause-it-on-one-site' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Your organization runs these on this site beside the site’s own ' +\n 'actions. Each run sends from this site and counts on its action ' +\n 'runs. Pausing one stops it here — and anyone waiting inside it ' +\n 'here — and leaves every other site alone.'}\n </Typography>\n {automations.map((row) => {\n const paused = orgAutomationPausedHostIds(row).includes(hostId)\n const off = row.enabled === false\n return (\n <Stack\n key={row.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\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 </Stack>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={off ? 'default' : paused ? 'warning' : 'success'}\n label={off ? 'Switched off' : paused ? 'Paused here' : 'Runs here'}\n />\n <Button\n size=\"small\"\n disabled={busy === row.$id}\n onClick={() => void setPaused(row, !paused)}\n >\n {paused ? 'Resume here' : 'Pause here'}\n </Button>\n <Button size=\"small\" onClick={() => setRunsFor(row)}>\n {'Runs'}\n </Button>\n </Stack>\n )\n })}\n </Stack>\n <Dialog\n open={Boolean(runsFor)}\n onClose={() => setRunsFor(null)}\n // md: the history's four columns fit without the table scrolling.\n maxWidth=\"md\"\n fullWidth\n >\n <DialogTitle>{`Runs — ${runsFor?.name ?? ''}`}</DialogTitle>\n <DialogContent>\n {runsFor ? (\n <HostRunHistoryCard\n hostId={hostId}\n targetId={runsFor.$id}\n targetName={runsFor.name ?? ''}\n header=\"Recent runs on this site\"\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setRunsFor(null)}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nSiteOrgAutomationsPanel.displayName = 'SiteOrgAutomationsPanel'\n\nexport default SiteOrgAutomationsPanel\n"],"names":["hostEventLabel","pluginDocsHelp","scopeTokensForHost","CardDisplay","useSnackbar","useFirestore","useFirestoreCollection","useOrgDataScope","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","collection","limit","query","where","useCallback","useMemo","useState","ORG_AUTOMATIONS_COLLECTION","ORG_AUTOMATIONS_MAX","orgAutomationPausedHostIds","HostRunHistoryCard","useOrgAutomationsApi","HOST_ACTION_STEP_LABELS","SiteOrgAutomationsPanel","props","runsFor","hostId","firestore","api","enqueueSnackbar","scope","tokens","data","rows","idField","automations","sort","a","b","String","name","localeCompare","busy","setBusy","setRunsFor","setPaused","row","paused","$id","automationId","variant","persist","error","message","length","header","help","anchor","contentGutterX","contentGutterY","spacing","color","map","includes","off","enabled","direction","sx","alignItems","flex","minWidth","noWrap","trigger","event","steps","step","type","join","size","label","disabled","onClick","open","Boolean","onClose","maxWidth","fullWidth","targetId","targetName","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/site-org-automations-panel.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 { hostEventLabel, pluginDocsHelp, scopeTokensForHost } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useFirestoreCollection,\n useOrgDataScope,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { collection, limit, query, where } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n ORG_AUTOMATIONS_COLLECTION,\n ORG_AUTOMATIONS_MAX,\n orgAutomationPausedHostIds,\n type OrgAutomationRow,\n} from '../model/org-automations'\nimport HostRunHistoryCard from './host-run-history-card.component'\nimport { useOrgAutomationsApi } from './use-org-automations-api'\nimport { HOST_ACTION_STEP_LABELS, type HostActionStepType } from '../model/host-actions'\n\n/**\n * THE ORGANIZATION'S AUTOMATIONS THAT RUN ON THIS SITE (AGL-3302), on the\n * site's own Actions section: read-only, with the one control that belongs\n * to the site — pausing an automation here, and resuming it.\n *\n * Read with the site's own scope tokens, so the listener is provable for a\n * collaborator scoped to this site as well as for an org-wide member, and\n * only the live ones placed here come back. Editing is the organization's,\n * on its own hub; a pause is the site's, through `automations/pause`, which\n * admits the site's admins and editors and refuses anyone else with its own\n * words.\n *\n * Renders nothing while there is nothing: a site the organization runs no\n * automation on has no panel to explain.\n */\nexport function SiteOrgAutomationsPanel(props: { hostId: string }) {\n const { hostId } = props\n const firestore = useFirestore()\n const api = useOrgAutomationsApi()\n const { enqueueSnackbar } = useSnackbar()\n const { scope } = useOrgDataScope({ hostId })\n const tokens = useMemo(() => scopeTokensForHost(hostId), [hostId])\n const { data: rows } = useFirestoreCollection<OrgAutomationRow>(\n () =>\n scope\n ? query(\n collection(firestore, scope[0], scope[1], ORG_AUTOMATIONS_COLLECTION),\n where('deletedAt', '==', null),\n where('visibleTo', 'array-contains-any', tokens),\n limit(ORG_AUTOMATIONS_MAX + 1),\n )\n : null,\n [firestore, scope, tokens],\n { idField: '$id' },\n )\n const automations = useMemo(\n () =>\n [...(rows ?? [])].sort((a, b) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n ),\n [rows],\n )\n const [busy, setBusy] = useState<string | null>(null)\n const [runsFor, setRunsFor] = useState<OrgAutomationRow | null>(null)\n\n const setPaused = useCallback(\n async (row: OrgAutomationRow, paused: boolean) => {\n setBusy(row.$id)\n try {\n await api('pause', { hostId, automationId: row.$id, paused })\n enqueueSnackbar(\n paused\n ? `“${row.name}” is paused on this site`\n : `“${row.name}” runs on this site again`,\n { variant: 'success', persist: false },\n )\n } catch (error) {\n enqueueSnackbar(\n (error as Error)?.message || 'The request could not be completed',\n { variant: 'warning', persist: false },\n )\n } finally {\n setBusy(null)\n }\n },\n [api, hostId, enqueueSnackbar],\n )\n\n if (!automations.length) return null\n return (\n <CardDisplay\n header={'Org automations on this site'}\n help={pluginDocsHelp('orgAutomations', { anchor: '#pause-it-on-one-site' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Your organization runs these on this site beside the site’s own ' +\n 'actions. Each run sends from this site and counts on its action ' +\n 'runs. Pausing one stops it here — and anyone waiting inside it ' +\n 'here — and leaves every other site alone.'}\n </Typography>\n {automations.map((row) => {\n const paused = orgAutomationPausedHostIds(row).includes(hostId)\n const off = row.enabled === false\n return (\n <Stack\n key={row.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.name}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {[\n `on ${hostEventLabel(row.trigger?.event)}`,\n (row.steps ?? [])\n .map(\n (step) =>\n HOST_ACTION_STEP_LABELS[step.type as HostActionStepType] ??\n step.type,\n )\n .filter(Boolean)\n .join(' → '),\n ]\n .filter(Boolean)\n .join(' · ')}\n </Typography>\n </Stack>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={off ? 'default' : paused ? 'warning' : 'success'}\n label={off ? 'Switched off' : paused ? 'Paused here' : 'Runs here'}\n />\n <Button\n size=\"small\"\n disabled={busy === row.$id}\n onClick={() => void setPaused(row, !paused)}\n >\n {paused ? 'Resume here' : 'Pause here'}\n </Button>\n <Button size=\"small\" onClick={() => setRunsFor(row)}>\n {'Runs'}\n </Button>\n </Stack>\n )\n })}\n </Stack>\n <Dialog\n open={Boolean(runsFor)}\n onClose={() => setRunsFor(null)}\n // md: the history's four columns fit without the table scrolling.\n maxWidth=\"md\"\n fullWidth\n >\n <DialogTitle>{`Runs — ${runsFor?.name ?? ''}`}</DialogTitle>\n <DialogContent>\n {runsFor ? (\n <HostRunHistoryCard\n hostId={hostId}\n targetId={runsFor.$id}\n targetName={runsFor.name ?? ''}\n header=\"Recent runs on this site\"\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setRunsFor(null)}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nSiteOrgAutomationsPanel.displayName = 'SiteOrgAutomationsPanel'\n\nexport default SiteOrgAutomationsPanel\n"],"names":["hostEventLabel","pluginDocsHelp","scopeTokensForHost","CardDisplay","useSnackbar","useFirestore","useFirestoreCollection","useOrgDataScope","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","collection","limit","query","where","useCallback","useMemo","useState","ORG_AUTOMATIONS_COLLECTION","ORG_AUTOMATIONS_MAX","orgAutomationPausedHostIds","HostRunHistoryCard","useOrgAutomationsApi","HOST_ACTION_STEP_LABELS","SiteOrgAutomationsPanel","props","runsFor","hostId","firestore","api","enqueueSnackbar","scope","tokens","data","rows","idField","automations","sort","a","b","String","name","localeCompare","busy","setBusy","setRunsFor","setPaused","row","paused","$id","automationId","variant","persist","error","message","length","header","help","anchor","contentGutterX","contentGutterY","spacing","color","map","includes","off","enabled","direction","sx","alignItems","flex","minWidth","noWrap","trigger","event","steps","step","type","filter","Boolean","join","size","label","disabled","onClick","open","onClose","maxWidth","fullWidth","targetId","targetName","displayName"],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { PLUGIN_FIGURE_MAX_ROWS, pluginFigureChange, pluginFigureWindows, registerPluginFigureReader } from "@aglyn/aglyn/plugin-manager/plugin-figures";
|
|
17
|
+
import { figureCount, figureRow, figureStat } from "@aglyn/aglyn/app-utils/weekly-summary";
|
|
17
18
|
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
18
19
|
/** Runs of one result one window reads for the per-automation split. */ export const AUTOMATION_RUN_FIGURES_WINDOW_CEILING = 500;
|
|
19
20
|
const WINDOWS = [
|
|
@@ -54,6 +55,24 @@ export function automationRunFigureReader(firestore) {
|
|
|
54
55
|
description: 'Runs of the site’s automations — actions, workflows and organization automations — over the window: how many succeeded and failed, the failure rate, and the change in runs from the window before, in total and by automation.',
|
|
55
56
|
scope: 'site',
|
|
56
57
|
windows: WINDOWS,
|
|
58
|
+
weeklySummary: {
|
|
59
|
+
order: 110,
|
|
60
|
+
summarize: (table)=>{
|
|
61
|
+
const all = figureRow(table, 'automation', 'All automations');
|
|
62
|
+
if (!all || !figureCount(all['succeeded']) && !figureCount(all['failed'])) return null;
|
|
63
|
+
return {
|
|
64
|
+
title: 'Automations',
|
|
65
|
+
stats: [
|
|
66
|
+
figureStat(table, all, 'succeeded', 'Runs finished', {
|
|
67
|
+
change: 'change'
|
|
68
|
+
}),
|
|
69
|
+
...figureCount(all['failed']) ? [
|
|
70
|
+
figureStat(table, all, 'failed', 'Runs failed')
|
|
71
|
+
] : []
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
},
|
|
57
76
|
read: async (request)=>{
|
|
58
77
|
if (!request.hostId) return {
|
|
59
78
|
ok: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/server/automation-run-figures.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PLUGIN_FIGURE_MAX_ROWS,\n pluginFigureChange,\n pluginFigureWindows,\n registerPluginFigureReader,\n type PluginFigureReader,\n} from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * A site's automation runs as a figure table another plugin reads by id\n * (AGL-3603) — the AI plugin's insights answer \"which automations failed?\"\n * from it.\n *\n * Every run an action, a workflow or an organization automation makes is a\n * row in the site's activity log carrying `result` (`succeeded` or `failed`)\n * and `target` — the same rows the run history lists. The totals are COUNT\n * aggregates over `result` in the window (the `activity` composite on\n * `result` + `createdAt`), so they are exact however busy the site is; the\n * per-automation split reads at most `AUTOMATION_RUN_FIGURES_WINDOW_CEILING`\n * runs of each result, projected to `target`, and says so when that read\n * low. A run's event payload names a person and is never read.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** Runs of one result one window reads for the per-automation split. */\nexport const AUTOMATION_RUN_FIGURES_WINDOW_CEILING = 500\n\nconst WINDOWS: readonly number[] = [7, 14, 30, 90]\n\ninterface AutomationCounts {\n succeeded: number\n failed: number\n}\n\nfunction runsOf(\n firestore: Firestore,\n hostId: string,\n result: 'succeeded' | 'failed',\n startMs: number,\n endMs: number,\n): FirebaseFirestore.Query {\n return firestore\n .collection('hosts')\n .doc(hostId)\n .collection('activity')\n .where('result', '==', result)\n .where('createdAt', '>=', new Date(startMs))\n .where('createdAt', '<', new Date(endMs))\n}\n\nasync function countOf(query: FirebaseFirestore.Query): Promise<number> {\n const snapshot = await query.count().get()\n return Number(snapshot.data().count) || 0\n}\n\n/** Runs of one result in the window, by the automation they belong to, read to the ceiling. */\nasync function byAutomation(\n query: FirebaseFirestore.Query,\n): Promise<{ names: Map<string, number>; truncated: boolean }> {\n const snapshot = await query\n .orderBy('createdAt', 'desc')\n .select('target')\n .limit(AUTOMATION_RUN_FIGURES_WINDOW_CEILING + 1)\n .get()\n const names = new Map<string, number>()\n for (const doc of snapshot.docs.slice(0, AUTOMATION_RUN_FIGURES_WINDOW_CEILING)) {\n const target = (doc.get('target') ?? {}) as { name?: unknown; id?: unknown }\n const name =\n (typeof target.name === 'string' && target.name.trim()) || 'An automation with no name'\n names.set(name, (names.get(name) ?? 0) + 1)\n }\n return { names, truncated: snapshot.docs.length > AUTOMATION_RUN_FIGURES_WINDOW_CEILING }\n}\n\nconst failureRate = (counts: AutomationCounts): number | null => {\n const total = counts.succeeded + counts.failed\n return total > 0 ? Math.round((counts.failed / total) * 1_000) / 10 : null\n}\n\nexport function automationRunFigureReader(firestore: () => Firestore): PluginFigureReader {\n return {\n id: 'automations.runs',\n label: 'Automation runs',\n description:\n 'Runs of the site’s automations — actions, workflows and organization automations — over the window: how many succeeded and failed, the failure rate, and the change in runs from the window before, in total and by automation.',\n scope: 'site',\n windows: WINDOWS,\n read: async (request) => {\n if (!request.hostId) return { ok: false, status: 400, error: 'Open a site to read its automation runs' }\n if (!WINDOWS.includes(request.days)) {\n return { ok: false, status: 400, error: 'That window is not one automation runs are read over' }\n }\n const db = firestore()\n const hostId = request.hostId\n const { current, previous } = pluginFigureWindows(request.now, request.days)\n const [succeeded, failed, before, okRuns, failedRuns] = await Promise.all([\n countOf(runsOf(db, hostId, 'succeeded', current.startMs, current.endMs)),\n countOf(runsOf(db, hostId, 'failed', current.startMs, current.endMs)),\n Promise.all([\n countOf(runsOf(db, hostId, 'succeeded', previous.startMs, previous.endMs)),\n countOf(runsOf(db, hostId, 'failed', previous.startMs, previous.endMs)),\n ]).then(([a, b]) => a + b),\n byAutomation(runsOf(db, hostId, 'succeeded', current.startMs, current.endMs)),\n byAutomation(runsOf(db, hostId, 'failed', current.startMs, current.endMs)),\n ])\n const automations = new Map<string, AutomationCounts>()\n for (const [name, count] of okRuns.names) {\n automations.set(name, { succeeded: count, failed: automations.get(name)?.failed ?? 0 })\n }\n for (const [name, count] of failedRuns.names) {\n automations.set(name, { succeeded: automations.get(name)?.succeeded ?? 0, failed: count })\n }\n // The most failures first: that is the question a person asks of runs.\n const ranked = [...automations.entries()].sort(\n (a, b) => b[1].failed - a[1].failed || b[1].succeeded - a[1].succeeded || a[0].localeCompare(b[0]),\n )\n const totals = { succeeded, failed }\n return {\n ok: true,\n table: {\n title: 'Automation runs',\n source: { label: 'Automation', path: 'automation' },\n period: { from: current.from, to: current.to, days: request.days },\n columns: [\n { key: 'automation', label: 'Automation', kind: 'text' },\n { key: 'succeeded', label: 'Succeeded', kind: 'count' },\n { key: 'failed', label: 'Failed', kind: 'count' },\n { key: 'failureRate', label: 'Failure rate', kind: 'percent' },\n { key: 'change', label: 'Change in runs', kind: 'change' },\n ],\n rows: [\n {\n automation: 'All automations',\n succeeded,\n failed,\n failureRate: failureRate(totals),\n change: pluginFigureChange(succeeded + failed, before),\n },\n ...ranked.slice(0, PLUGIN_FIGURE_MAX_ROWS - 1).map(([automation, counts]) => ({\n automation,\n succeeded: counts.succeeded,\n failed: counts.failed,\n failureRate: failureRate(counts),\n change: null,\n })),\n ],\n omitted: Math.max(0, ranked.length - (PLUGIN_FIGURE_MAX_ROWS - 1)),\n notes: [\n 'A run is counted on the day it ran. A run waiting on a later step counts as succeeded so far.',\n ...(okRuns.truncated || failedRuns.truncated\n ? [\n `The split by automation counts the most recent ${AUTOMATION_RUN_FIGURES_WINDOW_CEILING.toLocaleString('en-US')} runs of each result; the All automations row counts every run.`,\n ]\n : []),\n ],\n },\n }\n },\n }\n}\n\n/** Registers the automation runs reader from the console surface, where insight jobs run. */\nexport function registerAutomationRunFigureReader(firestore: () => Firestore): void {\n registerPluginFigureReader(automationRunFigureReader(firestore), { pluginId: BUNDLE_ID })\n}\n"],"names":["PLUGIN_FIGURE_MAX_ROWS","pluginFigureChange","pluginFigureWindows","registerPluginFigureReader","BUNDLE_ID","AUTOMATION_RUN_FIGURES_WINDOW_CEILING","WINDOWS","runsOf","firestore","hostId","result","startMs","endMs","collection","doc","where","Date","countOf","query","snapshot","count","get","Number","data","byAutomation","orderBy","select","limit","names","Map","docs","slice","target","name","trim","set","truncated","length","failureRate","counts","total","succeeded","failed","Math","round","automationRunFigureReader","id","label","description","scope","windows","read","request","ok","status","error","includes","days","db","current","previous","now","before","okRuns","failedRuns","Promise","all","then","a","b","automations","ranked","entries","sort","localeCompare","totals","table","title","source","path","period","from","to","columns","key","kind","rows","automation","change","map","omitted","max","notes","toLocaleString","registerAutomationRunFigureReader","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,sBAAsB,EACtBC,kBAAkB,EAClBC,mBAAmB,EACnBC,0BAA0B,QAErB,6CAA4C;AACnD,SAASC,SAAS,QAAQ,gCAA4B;AAmBtD,sEAAsE,GACtE,OAAO,MAAMC,wCAAwC,IAAG;AAExD,MAAMC,UAA6B;IAAC;IAAG;IAAI;IAAI;CAAG;AAOlD,SAASC,OACPC,SAAoB,EACpBC,MAAc,EACdC,MAA8B,EAC9BC,OAAe,EACfC,KAAa;IAEb,OAAOJ,UACJK,UAAU,CAAC,SACXC,GAAG,CAACL,QACJI,UAAU,CAAC,YACXE,KAAK,CAAC,UAAU,MAAML,QACtBK,KAAK,CAAC,aAAa,MAAM,IAAIC,KAAKL,UAClCI,KAAK,CAAC,aAAa,KAAK,IAAIC,KAAKJ;AACtC;AAEA,eAAeK,QAAQC,KAA8B;IACnD,MAAMC,WAAW,MAAMD,MAAME,KAAK,GAAGC,GAAG;IACxC,OAAOC,OAAOH,SAASI,IAAI,GAAGH,KAAK,KAAK;AAC1C;AAEA,6FAA6F,GAC7F,eAAeI,aACbN,KAA8B;IAE9B,MAAMC,WAAW,MAAMD,MACpBO,OAAO,CAAC,aAAa,QACrBC,MAAM,CAAC,UACPC,KAAK,CAACtB,wCAAwC,GAC9CgB,GAAG;IACN,MAAMO,QAAQ,IAAIC;IAClB,KAAK,MAAMf,OAAOK,SAASW,IAAI,CAACC,KAAK,CAAC,GAAG1B,uCAAwC;YAC/DS,UAGCc;QAHjB,MAAMI,UAAUlB,WAAAA,IAAIO,GAAG,CAAC,qBAARP,WAAqB,CAAC;QACtC,MAAMmB,OACJ,AAAC,OAAOD,OAAOC,IAAI,KAAK,YAAYD,OAAOC,IAAI,CAACC,IAAI,MAAO;QAC7DN,MAAMO,GAAG,CAACF,MAAM,EAACL,aAAAA,MAAMP,GAAG,CAACY,iBAAVL,aAAmB,KAAK;IAC3C;IACA,OAAO;QAAEA;QAAOQ,WAAWjB,SAASW,IAAI,CAACO,MAAM,GAAGhC;IAAsC;AAC1F;AAEA,MAAMiC,cAAc,CAACC;IACnB,MAAMC,QAAQD,OAAOE,SAAS,GAAGF,OAAOG,MAAM;IAC9C,OAAOF,QAAQ,IAAIG,KAAKC,KAAK,CAAC,AAACL,OAAOG,MAAM,GAAGF,QAAS,QAAS,KAAK;AACxE;AAEA,OAAO,SAASK,0BAA0BrC,SAA0B;IAClE,OAAO;QACLsC,IAAI;QACJC,OAAO;QACPC,aACE;QACFC,OAAO;QACPC,SAAS5C;QACT6C,MAAM,OAAOC;YACX,IAAI,CAACA,QAAQ3C,MAAM,EAAE,OAAO;gBAAE4C,IAAI;gBAAOC,QAAQ;gBAAKC,OAAO;YAA0C;YACvG,IAAI,CAACjD,QAAQkD,QAAQ,CAACJ,QAAQK,IAAI,GAAG;gBACnC,OAAO;oBAAEJ,IAAI;oBAAOC,QAAQ;oBAAKC,OAAO;gBAAuD;YACjG;YACA,MAAMG,KAAKlD;YACX,MAAMC,SAAS2C,QAAQ3C,MAAM;YAC7B,MAAM,EAAEkD,OAAO,EAAEC,QAAQ,EAAE,GAAG1D,oBAAoBkD,QAAQS,GAAG,EAAET,QAAQK,IAAI;YAC3E,MAAM,CAAChB,WAAWC,QAAQoB,QAAQC,QAAQC,WAAW,GAAG,MAAMC,QAAQC,GAAG,CAAC;gBACxEjD,QAAQV,OAAOmD,IAAIjD,QAAQ,aAAakD,QAAQhD,OAAO,EAAEgD,QAAQ/C,KAAK;gBACtEK,QAAQV,OAAOmD,IAAIjD,QAAQ,UAAUkD,QAAQhD,OAAO,EAAEgD,QAAQ/C,KAAK;gBACnEqD,QAAQC,GAAG,CAAC;oBACVjD,QAAQV,OAAOmD,IAAIjD,QAAQ,aAAamD,SAASjD,OAAO,EAAEiD,SAAShD,KAAK;oBACxEK,QAAQV,OAAOmD,IAAIjD,QAAQ,UAAUmD,SAASjD,OAAO,EAAEiD,SAAShD,KAAK;iBACtE,EAAEuD,IAAI,CAAC,CAAC,CAACC,GAAGC,EAAE,GAAKD,IAAIC;gBACxB7C,aAAajB,OAAOmD,IAAIjD,QAAQ,aAAakD,QAAQhD,OAAO,EAAEgD,QAAQ/C,KAAK;gBAC3EY,aAAajB,OAAOmD,IAAIjD,QAAQ,UAAUkD,QAAQhD,OAAO,EAAEgD,QAAQ/C,KAAK;aACzE;YACD,MAAM0D,cAAc,IAAIzC;YACxB,KAAK,MAAM,CAACI,MAAMb,MAAM,IAAI2C,OAAOnC,KAAK,CAAE;;oBACU0C;gBAAlDA,YAAYnC,GAAG,CAACF,MAAM;oBAAEQ,WAAWrB;oBAAOsB,MAAM,WAAE4B,mBAAAA,YAAYjD,GAAG,CAACY,0BAAhBqC,iBAAuB5B,MAAM,mBAAI;gBAAE;YACvF;YACA,KAAK,MAAM,CAACT,MAAMb,MAAM,IAAI4C,WAAWpC,KAAK,CAAE;;oBACT0C;gBAAnCA,YAAYnC,GAAG,CAACF,MAAM;oBAAEQ,SAAS,YAAE6B,oBAAAA,YAAYjD,GAAG,CAACY,0BAAhBqC,kBAAuB7B,SAAS,oBAAI;oBAAGC,QAAQtB;gBAAM;YAC1F;YACA,uEAAuE;YACvE,MAAMmD,SAAS;mBAAID,YAAYE,OAAO;aAAG,CAACC,IAAI,CAC5C,CAACL,GAAGC,IAAMA,CAAC,CAAC,EAAE,CAAC3B,MAAM,GAAG0B,CAAC,CAAC,EAAE,CAAC1B,MAAM,IAAI2B,CAAC,CAAC,EAAE,CAAC5B,SAAS,GAAG2B,CAAC,CAAC,EAAE,CAAC3B,SAAS,IAAI2B,CAAC,CAAC,EAAE,CAACM,aAAa,CAACL,CAAC,CAAC,EAAE;YAEnG,MAAMM,SAAS;gBAAElC;gBAAWC;YAAO;YACnC,OAAO;gBACLW,IAAI;gBACJuB,OAAO;oBACLC,OAAO;oBACPC,QAAQ;wBAAE/B,OAAO;wBAAcgC,MAAM;oBAAa;oBAClDC,QAAQ;wBAAEC,MAAMtB,QAAQsB,IAAI;wBAAEC,IAAIvB,QAAQuB,EAAE;wBAAEzB,MAAML,QAAQK,IAAI;oBAAC;oBACjE0B,SAAS;wBACP;4BAAEC,KAAK;4BAAcrC,OAAO;4BAAcsC,MAAM;wBAAO;wBACvD;4BAAED,KAAK;4BAAarC,OAAO;4BAAasC,MAAM;wBAAQ;wBACtD;4BAAED,KAAK;4BAAUrC,OAAO;4BAAUsC,MAAM;wBAAQ;wBAChD;4BAAED,KAAK;4BAAerC,OAAO;4BAAgBsC,MAAM;wBAAU;wBAC7D;4BAAED,KAAK;4BAAUrC,OAAO;4BAAkBsC,MAAM;wBAAS;qBAC1D;oBACDC,MAAM;wBACJ;4BACEC,YAAY;4BACZ9C;4BACAC;4BACAJ,aAAaA,YAAYqC;4BACzBa,QAAQvF,mBAAmBwC,YAAYC,QAAQoB;wBACjD;2BACGS,OAAOxC,KAAK,CAAC,GAAG/B,yBAAyB,GAAGyF,GAAG,CAAC,CAAC,CAACF,YAAYhD,OAAO,GAAM,CAAA;gCAC5EgD;gCACA9C,WAAWF,OAAOE,SAAS;gCAC3BC,QAAQH,OAAOG,MAAM;gCACrBJ,aAAaA,YAAYC;gCACzBiD,QAAQ;4BACV,CAAA;qBACD;oBACDE,SAAS/C,KAAKgD,GAAG,CAAC,GAAGpB,OAAOlC,MAAM,GAAIrC,CAAAA,yBAAyB,CAAA;oBAC/D4F,OAAO;wBACL;2BACI7B,OAAO3B,SAAS,IAAI4B,WAAW5B,SAAS,GACxC;4BACE,CAAC,+CAA+C,EAAE/B,sCAAsCwF,cAAc,CAAC,SAAS,+DAA+D,CAAC;yBACjL,GACD,EAAE;qBACP;gBACH;YACF;QACF;IACF;AACF;AAEA,2FAA2F,GAC3F,OAAO,SAASC,kCAAkCtF,SAA0B;IAC1EL,2BAA2B0C,0BAA0BrC,YAAY;QAAEuF,UAAU3F;IAAU;AACzF"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/server/automation-run-figures.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PLUGIN_FIGURE_MAX_ROWS,\n pluginFigureChange,\n pluginFigureWindows,\n registerPluginFigureReader,\n type PluginFigureReader,\n} from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport { figureCount, figureRow, figureStat } from '@aglyn/aglyn/app-utils/weekly-summary'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * A site's automation runs as a figure table another plugin reads by id\n * (AGL-3603) — the AI plugin's insights answer \"which automations failed?\"\n * from it.\n *\n * Every run an action, a workflow or an organization automation makes is a\n * row in the site's activity log carrying `result` (`succeeded` or `failed`)\n * and `target` — the same rows the run history lists. The totals are COUNT\n * aggregates over `result` in the window (the `activity` composite on\n * `result` + `createdAt`), so they are exact however busy the site is; the\n * per-automation split reads at most `AUTOMATION_RUN_FIGURES_WINDOW_CEILING`\n * runs of each result, projected to `target`, and says so when that read\n * low. A run's event payload names a person and is never read.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** Runs of one result one window reads for the per-automation split. */\nexport const AUTOMATION_RUN_FIGURES_WINDOW_CEILING = 500\n\nconst WINDOWS: readonly number[] = [7, 14, 30, 90]\n\ninterface AutomationCounts {\n succeeded: number\n failed: number\n}\n\nfunction runsOf(\n firestore: Firestore,\n hostId: string,\n result: 'succeeded' | 'failed',\n startMs: number,\n endMs: number,\n): FirebaseFirestore.Query {\n return firestore\n .collection('hosts')\n .doc(hostId)\n .collection('activity')\n .where('result', '==', result)\n .where('createdAt', '>=', new Date(startMs))\n .where('createdAt', '<', new Date(endMs))\n}\n\nasync function countOf(query: FirebaseFirestore.Query): Promise<number> {\n const snapshot = await query.count().get()\n return Number(snapshot.data().count) || 0\n}\n\n/** Runs of one result in the window, by the automation they belong to, read to the ceiling. */\nasync function byAutomation(\n query: FirebaseFirestore.Query,\n): Promise<{ names: Map<string, number>; truncated: boolean }> {\n const snapshot = await query\n .orderBy('createdAt', 'desc')\n .select('target')\n .limit(AUTOMATION_RUN_FIGURES_WINDOW_CEILING + 1)\n .get()\n const names = new Map<string, number>()\n for (const doc of snapshot.docs.slice(0, AUTOMATION_RUN_FIGURES_WINDOW_CEILING)) {\n const target = (doc.get('target') ?? {}) as { name?: unknown; id?: unknown }\n const name =\n (typeof target.name === 'string' && target.name.trim()) || 'An automation with no name'\n names.set(name, (names.get(name) ?? 0) + 1)\n }\n return { names, truncated: snapshot.docs.length > AUTOMATION_RUN_FIGURES_WINDOW_CEILING }\n}\n\nconst failureRate = (counts: AutomationCounts): number | null => {\n const total = counts.succeeded + counts.failed\n return total > 0 ? Math.round((counts.failed / total) * 1_000) / 10 : null\n}\n\nexport function automationRunFigureReader(firestore: () => Firestore): PluginFigureReader {\n return {\n id: 'automations.runs',\n label: 'Automation runs',\n description:\n 'Runs of the site’s automations — actions, workflows and organization automations — over the window: how many succeeded and failed, the failure rate, and the change in runs from the window before, in total and by automation.',\n scope: 'site',\n windows: WINDOWS,\n weeklySummary: {\n order: 110,\n summarize: (table) => {\n const all = figureRow(table, 'automation', 'All automations')\n if (!all || (!figureCount(all['succeeded']) && !figureCount(all['failed']))) return null\n return {\n title: 'Automations',\n stats: [\n figureStat(table, all, 'succeeded', 'Runs finished', { change: 'change' }),\n ...(figureCount(all['failed']) ? [figureStat(table, all, 'failed', 'Runs failed')] : []),\n ],\n }\n },\n },\n read: async (request) => {\n if (!request.hostId) return { ok: false, status: 400, error: 'Open a site to read its automation runs' }\n if (!WINDOWS.includes(request.days)) {\n return { ok: false, status: 400, error: 'That window is not one automation runs are read over' }\n }\n const db = firestore()\n const hostId = request.hostId\n const { current, previous } = pluginFigureWindows(request.now, request.days)\n const [succeeded, failed, before, okRuns, failedRuns] = await Promise.all([\n countOf(runsOf(db, hostId, 'succeeded', current.startMs, current.endMs)),\n countOf(runsOf(db, hostId, 'failed', current.startMs, current.endMs)),\n Promise.all([\n countOf(runsOf(db, hostId, 'succeeded', previous.startMs, previous.endMs)),\n countOf(runsOf(db, hostId, 'failed', previous.startMs, previous.endMs)),\n ]).then(([a, b]) => a + b),\n byAutomation(runsOf(db, hostId, 'succeeded', current.startMs, current.endMs)),\n byAutomation(runsOf(db, hostId, 'failed', current.startMs, current.endMs)),\n ])\n const automations = new Map<string, AutomationCounts>()\n for (const [name, count] of okRuns.names) {\n automations.set(name, { succeeded: count, failed: automations.get(name)?.failed ?? 0 })\n }\n for (const [name, count] of failedRuns.names) {\n automations.set(name, { succeeded: automations.get(name)?.succeeded ?? 0, failed: count })\n }\n // The most failures first: that is the question a person asks of runs.\n const ranked = [...automations.entries()].sort(\n (a, b) => b[1].failed - a[1].failed || b[1].succeeded - a[1].succeeded || a[0].localeCompare(b[0]),\n )\n const totals = { succeeded, failed }\n return {\n ok: true,\n table: {\n title: 'Automation runs',\n source: { label: 'Automation', path: 'automation' },\n period: { from: current.from, to: current.to, days: request.days },\n columns: [\n { key: 'automation', label: 'Automation', kind: 'text' },\n { key: 'succeeded', label: 'Succeeded', kind: 'count' },\n { key: 'failed', label: 'Failed', kind: 'count' },\n { key: 'failureRate', label: 'Failure rate', kind: 'percent' },\n { key: 'change', label: 'Change in runs', kind: 'change' },\n ],\n rows: [\n {\n automation: 'All automations',\n succeeded,\n failed,\n failureRate: failureRate(totals),\n change: pluginFigureChange(succeeded + failed, before),\n },\n ...ranked.slice(0, PLUGIN_FIGURE_MAX_ROWS - 1).map(([automation, counts]) => ({\n automation,\n succeeded: counts.succeeded,\n failed: counts.failed,\n failureRate: failureRate(counts),\n change: null,\n })),\n ],\n omitted: Math.max(0, ranked.length - (PLUGIN_FIGURE_MAX_ROWS - 1)),\n notes: [\n 'A run is counted on the day it ran. A run waiting on a later step counts as succeeded so far.',\n ...(okRuns.truncated || failedRuns.truncated\n ? [\n `The split by automation counts the most recent ${AUTOMATION_RUN_FIGURES_WINDOW_CEILING.toLocaleString('en-US')} runs of each result; the All automations row counts every run.`,\n ]\n : []),\n ],\n },\n }\n },\n }\n}\n\n/** Registers the automation runs reader from the console surface, where insight jobs run. */\nexport function registerAutomationRunFigureReader(firestore: () => Firestore): void {\n registerPluginFigureReader(automationRunFigureReader(firestore), { pluginId: BUNDLE_ID })\n}\n"],"names":["PLUGIN_FIGURE_MAX_ROWS","pluginFigureChange","pluginFigureWindows","registerPluginFigureReader","figureCount","figureRow","figureStat","BUNDLE_ID","AUTOMATION_RUN_FIGURES_WINDOW_CEILING","WINDOWS","runsOf","firestore","hostId","result","startMs","endMs","collection","doc","where","Date","countOf","query","snapshot","count","get","Number","data","byAutomation","orderBy","select","limit","names","Map","docs","slice","target","name","trim","set","truncated","length","failureRate","counts","total","succeeded","failed","Math","round","automationRunFigureReader","id","label","description","scope","windows","weeklySummary","order","summarize","table","all","title","stats","change","read","request","ok","status","error","includes","days","db","current","previous","now","before","okRuns","failedRuns","Promise","then","a","b","automations","ranked","entries","sort","localeCompare","totals","source","path","period","from","to","columns","key","kind","rows","automation","map","omitted","max","notes","toLocaleString","registerAutomationRunFigureReader","pluginId"],"mappings":"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"}
|