@aglyn/plugins-workflows 1.0.0-beta.143

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.
Files changed (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +52 -0
  3. package/package.json +53 -0
  4. package/src/index.d.ts +18 -0
  5. package/src/index.js +19 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/automation-step-fields.component.d.ts +74 -0
  8. package/src/lib/components/automation-step-fields.component.js +1210 -0
  9. package/src/lib/components/automation-step-fields.component.js.map +1 -0
  10. package/src/lib/components/host-actions-card.component.d.ts +15 -0
  11. package/src/lib/components/host-actions-card.component.js +1229 -0
  12. package/src/lib/components/host-actions-card.component.js.map +1 -0
  13. package/src/lib/components/host-activity-card.component.d.ts +84 -0
  14. package/src/lib/components/host-activity-card.component.js +280 -0
  15. package/src/lib/components/host-activity-card.component.js.map +1 -0
  16. package/src/lib/components/host-run-history-card.component.d.ts +40 -0
  17. package/src/lib/components/host-run-history-card.component.js +284 -0
  18. package/src/lib/components/host-run-history-card.component.js.map +1 -0
  19. package/src/lib/components/host-webhooks-card.component.d.ts +15 -0
  20. package/src/lib/components/host-webhooks-card.component.js +421 -0
  21. package/src/lib/components/host-webhooks-card.component.js.map +1 -0
  22. package/src/lib/components/host-workflows-card.component.d.ts +18 -0
  23. package/src/lib/components/host-workflows-card.component.js +952 -0
  24. package/src/lib/components/host-workflows-card.component.js.map +1 -0
  25. package/src/lib/components/run-quota-line.component.d.ts +27 -0
  26. package/src/lib/components/run-quota-line.component.js +72 -0
  27. package/src/lib/components/run-quota-line.component.js.map +1 -0
  28. package/src/lib/components/use-automation-step-pickers.d.ts +27 -0
  29. package/src/lib/components/use-automation-step-pickers.js +205 -0
  30. package/src/lib/components/use-automation-step-pickers.js.map +1 -0
  31. package/src/lib/components/workflow-zones.d.ts +36 -0
  32. package/src/lib/components/workflow-zones.js +19 -0
  33. package/src/lib/components/workflow-zones.js.map +1 -0
  34. package/src/lib/components/workflows-console-page.d.ts +25 -0
  35. package/src/lib/components/workflows-console-page.js +112 -0
  36. package/src/lib/components/workflows-console-page.js.map +1 -0
  37. package/src/lib/components/workflows-console-sections.d.ts +34 -0
  38. package/src/lib/components/workflows-console-sections.js +50 -0
  39. package/src/lib/components/workflows-console-sections.js.map +1 -0
  40. package/src/lib/constants/bundle-common.d.ts +8 -0
  41. package/src/lib/constants/bundle-common.js +9 -0
  42. package/src/lib/constants/bundle-common.js.map +1 -0
  43. package/src/lib/declarations.server.d.ts +41 -0
  44. package/src/lib/declarations.server.js +54 -0
  45. package/src/lib/declarations.server.js.map +1 -0
  46. package/src/lib/engine/crm-action-steps.d.ts +129 -0
  47. package/src/lib/engine/crm-action-steps.js +432 -0
  48. package/src/lib/engine/crm-action-steps.js.map +1 -0
  49. package/src/lib/engine/flow-enrollments.d.ts +264 -0
  50. package/src/lib/engine/flow-enrollments.js +386 -0
  51. package/src/lib/engine/flow-enrollments.js.map +1 -0
  52. package/src/lib/engine/run-event-actions.d.ts +232 -0
  53. package/src/lib/engine/run-event-actions.js +1392 -0
  54. package/src/lib/engine/run-event-actions.js.map +1 -0
  55. package/src/lib/engine/run-event-automations.d.ts +28 -0
  56. package/src/lib/engine/run-event-automations.js +37 -0
  57. package/src/lib/engine/run-event-automations.js.map +1 -0
  58. package/src/lib/engine/run-event-workflows.d.ts +75 -0
  59. package/src/lib/engine/run-event-workflows.js +311 -0
  60. package/src/lib/engine/run-event-workflows.js.map +1 -0
  61. package/src/lib/engine/workflow-steps.d.ts +107 -0
  62. package/src/lib/engine/workflow-steps.js +124 -0
  63. package/src/lib/engine/workflow-steps.js.map +1 -0
  64. package/src/lib/plugin.d.ts +33 -0
  65. package/src/lib/plugin.js +94 -0
  66. package/src/lib/plugin.js.map +1 -0
  67. package/src/lib/server-automation-drafts.d.ts +90 -0
  68. package/src/lib/server-automation-drafts.js +429 -0
  69. package/src/lib/server-automation-drafts.js.map +1 -0
  70. package/src/lib/server-console.d.ts +27 -0
  71. package/src/lib/server-console.js +35 -0
  72. package/src/lib/server-console.js.map +1 -0
  73. package/src/lib/server.d.ts +29 -0
  74. package/src/lib/server.js +298 -0
  75. package/src/lib/server.js.map +1 -0
@@ -0,0 +1,952 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
19
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
20
+ import { checkEntitlement, checkQuota, HOST_EVENT_TYPES, hostEventLabel, hostEventPayloadHint, pluginDocsHelp, runWorkflow, WORKFLOW_MAX_STEPS } from "@aglyn/aglyn";
21
+ /*
22
+ * The MODULE, not the barrel, for the two PURE helpers — every spec that
23
+ * renders this card mocks `@aglyn/tenant-feature-instance` wholesale to stage
24
+ * its Firestore hooks, and a query builder imported through that barrel
25
+ * disappears under the mock. Neither of these is a hook.
26
+ */ import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
27
+ import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
28
+ import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
29
+ import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
30
+ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
31
+ import QuotaReadoutComponent from "@aglyn/shared-ui-jsx/components/quota-readout.component";
32
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
33
+ import { Timestamp } from "@aglyn/shared-util-timestamp";
34
+ import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, TextField, Typography } from "@mui/material";
35
+ import { collection, doc, getCountFromServer, setDoc, updateDoc } from "firebase/firestore";
36
+ import { useCallback, useEffect, useMemo, useState } from "react";
37
+ import { useDuplicateResource, useFirestore, useFirestoreCollection, useHostResourceApi, useUser, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
38
+ import { AutomationStepFields, defaultStep } from "./automation-step-fields.component.js";
39
+ import { isWorkflowActionStep, validateWorkflowSteps, WORKFLOW_ACTION_STEP_TYPES, workflowFunctionCalls, workflowStepTypeLabel } from "../engine/workflow-steps.js";
40
+ import HostRunHistoryCard from "./host-run-history-card.component.js";
41
+ import { EDITOR_OPTION_CEILING, useAutomationStepPickers } from "./use-automation-step-pickers.js";
42
+ import { listConsoleWidgets } from "@aglyn/aglyn";
43
+ import { fetchWhereUsed, summarizeDependents } from "@aglyn/aglyn/app-utils/where-used";
44
+ import { WORKFLOW_USAGE_ZONE } from "./workflow-zones.js";
45
+ /**
46
+ * How many workflows the card reads.
47
+ *
48
+ * A CEILING, not a page size — see the query, which explains why this list
49
+ * cannot be paged by the server without making the duplicate-name check lie.
50
+ */ const WORKFLOW_CEILING = 100;
51
+ /** The "Do" entry for the step kind a workflow has always had: a call. */ const FUNCTION_CALL_KIND = 'functionCall';
52
+ /**
53
+ * What a step may be (AGL-3105).
54
+ *
55
+ * The function call comes first because it is what a workflow was before the
56
+ * two engines became one, and is still what a new step is. After it comes
57
+ * every Actions step a SERVER event can run — the client-only interactions
58
+ * are absent, because a workflow runs where there is no page to run them on.
59
+ */ const STEP_KINDS = [
60
+ {
61
+ value: FUNCTION_CALL_KIND,
62
+ label: 'Call a function'
63
+ },
64
+ ...WORKFLOW_ACTION_STEP_TYPES.map((value)=>({
65
+ value,
66
+ label: workflowStepTypeLabel(value)
67
+ }))
68
+ ];
69
+ /**
70
+ * Workflow builder v1 (AGL-101): pipelines of host-function calls — each
71
+ * step's argument expressions see host variables and previous step results
72
+ * (`step1`, or a custom result name). Runs through the pure `runWorkflow`
73
+ * evaluator; test-run panel included. Plan-gated (`workflows` flag +
74
+ * `workflowsPerHost` cap, AGL-99); site-event triggers are v2.
75
+ */ export function HostWorkflowsCard(props) {
76
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _runsFor_name;
77
+ var _draft_trigger, _draft_trigger1, _draft_trigger2;
78
+ const { hostId } = props;
79
+ const firestore = useFirestore();
80
+ const { data: user } = useUser();
81
+ const createHostResource = useHostResourceApi();
82
+ const { enqueueSnackbar } = useSnackbar();
83
+ // Duplicate (AGL-2936): the copy keeps every step and arrives disarmed —
84
+ // its trigger is cleared — so nothing runs twice until somebody arms it.
85
+ const duplicate = useDuplicateResource({
86
+ hostId,
87
+ onDuplicated: (_kind, copy)=>enqueueSnackbar(`Duplicated as “${copy.name}” — arm its trigger to run it`, {
88
+ variant: 'success'
89
+ })
90
+ });
91
+ const { confirm } = useConfirmationContext();
92
+ const { org } = props;
93
+ /**
94
+ * The shell's zone renderer, for what other plugins add to a saved workflow's
95
+ * editor (AGL-2919); `null` outside the console shell.
96
+ */ const ExtensionZone = useConsoleWidgetSlot();
97
+ const { data: workflowDocs, status: workflowsStatus, /**
98
+ * The rows this editor is seeded from are unconfirmed by the server
99
+ * (AGL-1358). Editing destructures a whole stored workflow into `draft`
100
+ * and writes `{...definition}` back, so `merge: true` protects nothing.
101
+ * `trigger` is the field that makes this more than a lost edit: it is
102
+ * `{event, filter}` or `null` for manual-only, and nothing else on this
103
+ * card writes it, so a cached seed can re-arm an automation the author
104
+ * disarmed — or disarm one running on a live site — and revert every
105
+ * step of the pipeline with it.
106
+ */ fromCache: workflowsFromCache } = useFirestoreCollection(/*
107
+ * ORDERED AND CEILINGED, deliberately not paged by the query (AGL-2501) —
108
+ * the same decision as the logic plugin's two cards, for the same reasons.
109
+ *
110
+ * `limit(100)` alone is answered in DOCUMENT-ID order, so the window is a
111
+ * pseudo-random hundred that the `localeCompare` below arranges
112
+ * alphabetically, and nothing said the list was bounded.
113
+ *
114
+ * `collectionCeiling` does not change WHICH hundred — document-id order is
115
+ * what the bare cap already returned. What it changes is that the order is
116
+ * NAMED, so the obvious next edit is caught: ordering on `name` would HIDE
117
+ * every workflow written without one rather than mis-sorting the list, and
118
+ * `/api/hosts/resources` validates no field for presence while
119
+ * `IMPORTABLE_FIELDS.workflows` copies one only if the export carried it.
120
+ *
121
+ * The QUERY is not paged because `nameTaken` below tests the draft against
122
+ * these rows: computed variables look their workflow up BY NAME
123
+ * (AGL-185/AGL-261), so a duplicate silently rebinds a live binding, and a
124
+ * uniqueness check over a ten-row page would let one through.
125
+ */ ()=>collectionCeiling(collection(firestore, 'hosts', hostId, 'workflows'), WORKFLOW_CEILING), [
126
+ firestore,
127
+ hostId
128
+ ], {
129
+ idField: '$id'
130
+ });
131
+ const { rows: readWorkflows, truncated: workflowsTruncated } = ceilingedWindow(workflowDocs, WORKFLOW_CEILING);
132
+ const [draft, setDraft] = useState(null);
133
+ /**
134
+ * The editor has been opened at least once in this session.
135
+ *
136
+ * A LATCH rather than `draft` itself, because the two reads below key their
137
+ * listeners on it. Tracking the dialog would tear those subscriptions down
138
+ * on Cancel and pay for them again on the next Edit, so a merchant working
139
+ * through ten actions would buy the same two windows ten times — worse than
140
+ * the mount-time read this replaces. Latched, a reader who never edits pays
141
+ * nothing and a reader who edits pays once.
142
+ */ const [editorOpened, setEditorOpened] = useState(false);
143
+ if (draft && !editorOpened) setEditorOpened(true);
144
+ /*
145
+ * THE EDITOR'S WORKING SET, read when the editor opens.
146
+ *
147
+ * Nothing outside the dialog reads either of these: the table renders a
148
+ * workflow's stored step names, and `runWorkflow` is only reachable from the
149
+ * test-run button inside the dialog. Mounting them unconditionally charges
150
+ * every visitor to this page two windows to fill a select and seed a
151
+ * scratchpad most of them never open.
152
+ *
153
+ * Ceilinged and ordered for the reason the workflows query above gives: a
154
+ * bare `limit` is answered in document-id order, so the `localeCompare` on
155
+ * the options below would arrange a pseudo-random hundred into a convincing
156
+ * alphabet, and a function past the window could not be picked with nothing
157
+ * saying why.
158
+ */ const { data: functionRead } = useFirestoreCollection(()=>editorOpened ? collectionCeiling(collection(firestore, 'hosts', hostId, 'functions'), EDITOR_OPTION_CEILING) : null, [
159
+ firestore,
160
+ hostId,
161
+ editorOpened
162
+ ], {
163
+ idField: '$id'
164
+ });
165
+ // Memoised so the window keeps one identity while the rows do — the option
166
+ // list and the two lookup maps below are all built from it.
167
+ const { rows: functionDocs, truncated: functionsTruncated } = useMemo(()=>ceilingedWindow(functionRead, EDITOR_OPTION_CEILING), [
168
+ functionRead
169
+ ]);
170
+ const { data: variableRead } = useFirestoreCollection(()=>editorOpened ? collectionCeiling(collection(firestore, 'hosts', hostId, 'variables'), EDITOR_OPTION_CEILING) : null, [
171
+ firestore,
172
+ hostId,
173
+ editorOpened
174
+ ], {
175
+ idField: '$id'
176
+ });
177
+ const { rows: variableDocs, truncated: variablesTruncated } = useMemo(()=>ceilingedWindow(variableRead, EDITOR_OPTION_CEILING), [
178
+ variableRead
179
+ ]);
180
+ /*
181
+ * What an ACTIONS step can be pointed at, on the same latch (AGL-3105).
182
+ *
183
+ * The hook the Actions builder uses, because a workflow step may be any
184
+ * server-side Actions step and the two editors therefore offer one set of
185
+ * records. Read on the editor's first open, like the two windows above, so
186
+ * a reader who never edits a workflow still pays nothing for them.
187
+ */ const { pickers: stepPickers, truncated: truncatedPickers } = useAutomationStepPickers(hostId, editorOpened);
188
+ // Sorting is safe here in a way it is not on a paged list: these rows are
189
+ // the whole collection below the ceiling, not a slice of one.
190
+ const workflows = useMemo(()=>readWorkflows.filter((workflow)=>!workflow.deletedAt).sort((a, b)=>{
191
+ var _a_name, _b_name;
192
+ return String((_a_name = a.name) != null ? _a_name : '').localeCompare(String((_b_name = b.name) != null ? _b_name : ''));
193
+ }), [
194
+ readWorkflows
195
+ ]);
196
+ // The page is a SLICE: the rows are already in hand and `nameTaken` needs
197
+ // all of them.
198
+ const [page, setPage] = useState(0);
199
+ const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
200
+ const visibleWorkflows = useMemo(()=>workflows.slice(page * pageSize, page * pageSize + pageSize), [
201
+ workflows,
202
+ page,
203
+ pageSize
204
+ ]);
205
+ /**
206
+ * The workflow HEAD-COUNT is a server aggregate, not the length of the
207
+ * capped listener (AGL-1716, the AGL-1706 shape).
208
+ *
209
+ * The listener is `limit(100)` and `workflows.length` fed
210
+ * `checkQuota(org, 'workflowsPerHost', …)`, whose bands are 0 / 3 / 25 /
211
+ * 100 / 250 / 500. The window TIES Business's 100 and hides Scale's 250
212
+ * and Advanced's 500, so on those plans the gate compared 100 against
213
+ * hundreds and could never refuse — while `api/hosts/resources` counted
214
+ * the collection and did. The card offered headroom that did not exist and
215
+ * then failed the create.
216
+ *
217
+ * UNFILTERED, matching that route's plain `collection('workflows').count()`
218
+ * — its `softDeletes` branch governs only the flat per-host webhook cap, so
219
+ * soft-deleted workflows have always counted server-side while this card
220
+ * excluded them. Only a create moves the number, so that is where it is
221
+ * re-read.
222
+ */ const [workflowCountEpoch, setWorkflowCountEpoch] = useState(0);
223
+ const [serverWorkflowCount, setServerWorkflowCount] = useState(null);
224
+ useEffect(()=>{
225
+ let active = true;
226
+ void getCountFromServer(collection(firestore, 'hosts', hostId, 'workflows')).then((snapshot)=>{
227
+ if (active) setServerWorkflowCount(snapshot.data().count);
228
+ }).catch(()=>{
229
+ // Falls back to the loaded rows — a LOWER bound and the prior
230
+ // behaviour, never 0, which `checkQuota` would answer as "no usage"
231
+ // on a site that is over its band.
232
+ });
233
+ return ()=>{
234
+ active = false;
235
+ };
236
+ }, [
237
+ firestore,
238
+ hostId,
239
+ workflowCountEpoch
240
+ ]);
241
+ const workflowCount = serverWorkflowCount != null ? serverWorkflowCount : workflows.length;
242
+ // Double-keyed by doc id and name (AGL-261): id references are
243
+ // rename-safe; legacy name references keep resolving.
244
+ const functions = useMemo(()=>{
245
+ const map = {};
246
+ for (const definition of functionDocs != null ? functionDocs : []){
247
+ if (definition.deletedAt) continue;
248
+ if (definition.$id) map[definition.$id] = definition;
249
+ if (definition.name) map[definition.name] = definition;
250
+ }
251
+ return map;
252
+ }, [
253
+ functionDocs
254
+ ]);
255
+ const functionOptions = useMemo(()=>(functionDocs != null ? functionDocs : []).filter((definition)=>!definition.deletedAt && definition.name).map((definition)=>({
256
+ id: definition.$id,
257
+ name: definition.name
258
+ })).sort((a, b)=>a.name.localeCompare(b.name)), [
259
+ functionDocs
260
+ ]);
261
+ const variables = useMemo(()=>{
262
+ const map = {};
263
+ for (const variable of variableDocs != null ? variableDocs : []){
264
+ if (!variable.deletedAt && variable.name) map[variable.name] = variable;
265
+ }
266
+ return map;
267
+ }, [
268
+ variableDocs
269
+ ]);
270
+ // Where-used dialog (AGL-193).
271
+ const [usage, setUsage] = useState(null);
272
+ const [usageLoading, setUsageLoading] = useState(null);
273
+ const [testResult, setTestResult] = useState(null);
274
+ const [runsFor, setRunsFor] = useState(null);
275
+ // Case-insensitive uniqueness (AGL-185): workflow names must stay
276
+ // unambiguous for computed-variable lookups keyed by name.
277
+ const nameTaken = Boolean(draft && workflows.some((definition)=>{
278
+ var _definition_name;
279
+ return String((_definition_name = definition.name) != null ? _definition_name : '').toLowerCase() === draft.name.trim().toLowerCase() && definition.$id !== draft.id;
280
+ }));
281
+ const patch = useCallback((updater)=>setDraft((previous)=>previous ? updater(previous) : previous), []);
282
+ /** The saved workflow the editor has open, as the `automationEditor` zone names it. */ const draftId = (_ref = draft == null ? void 0 : draft.id) != null ? _ref : null;
283
+ const draftName = (_ref1 = draft == null ? void 0 : draft.name) != null ? _ref1 : '';
284
+ const editorTarget = useMemo(()=>draftId ? {
285
+ type: 'workflow',
286
+ id: draftId,
287
+ name: draftName
288
+ } : null, // The name the editor opened with: the zone reads the workflow as it is
289
+ // stored, so a rename being typed does not make it a different one.
290
+ // eslint-disable-next-line react-hooks/exhaustive-deps
291
+ [
292
+ draftId
293
+ ]);
294
+ const handleAdd = useCallback(()=>{
295
+ // Feature + cap gate (AGL-99); dark-launch for plan-less workspaces.
296
+ if (!checkEntitlement(org, 'workflows')) {
297
+ return void enqueueSnackbar('Workflows require a Starter plan — see Billing to upgrade', {
298
+ variant: 'warning',
299
+ persist: false
300
+ });
301
+ }
302
+ // The site's workflows, not this page of them (AGL-1716).
303
+ const quota = checkQuota(org, 'workflowsPerHost', workflowCount);
304
+ if (!quota.allowed) {
305
+ return void enqueueSnackbar(`Workflow limit reached (${quota.limit}) — upgrade in Billing`, {
306
+ variant: 'warning',
307
+ persist: false
308
+ });
309
+ }
310
+ setTestResult(null);
311
+ setDraft({
312
+ id: null,
313
+ name: '',
314
+ steps: [
315
+ {
316
+ functionName: '',
317
+ args: [],
318
+ resultName: ''
319
+ }
320
+ ],
321
+ returnValue: '',
322
+ trigger: null
323
+ });
324
+ }, [
325
+ org,
326
+ workflowCount,
327
+ enqueueSnackbar
328
+ ]);
329
+ /**
330
+ * Every list the ceiling cut, named — the editor's two and the step
331
+ * pickers' six, in one notice for the reason the Actions builder gives:
332
+ * they are one read, and an author told the function list is short learns
333
+ * nothing from being told again about datasets.
334
+ */ const shortLists = useMemo(()=>[
335
+ functionsTruncated ? 'functions' : null,
336
+ variablesTruncated ? 'variables' : null,
337
+ ...truncatedPickers
338
+ ].filter((name)=>name !== null), [
339
+ functionsTruncated,
340
+ variablesTruncated,
341
+ truncatedPickers
342
+ ]);
343
+ /**
344
+ * The first thing wrong with the steps, said the way the editors say it.
345
+ *
346
+ * The same check the engine makes of what it is handed: an Actions step is
347
+ * held to the Actions validator, a call has to name a function, and a step
348
+ * only a visitor's browser can run is refused. Said while the author is
349
+ * still in the dialog rather than after a save that stores a step nothing
350
+ * can run.
351
+ */ const stepProblem = useMemo(()=>draft ? validateWorkflowSteps(draft.steps) : null, [
352
+ draft
353
+ ]);
354
+ const handleTestRun = useCallback(()=>{
355
+ if (!draft) return;
356
+ /*
357
+ * The FUNCTION CALLS, evaluated. A test run is the pure evaluator, and
358
+ * an Actions step is not something to evaluate — it sends, writes and
359
+ * charges. Running one here would perform it against the live site from
360
+ * a button labelled "Test run", so the calls are evaluated, each keeping
361
+ * the result name it holds among all the steps, and the rest is left to
362
+ * the run itself.
363
+ */ const run = runWorkflow(workflowFunctionCalls(draft), functions, variables);
364
+ setTestResult(run.ok === false ? `Error: ${run.error}` : `Result: ${String(run.value)} (${Object.entries(run.results).map(([key, value])=>`${key}=${value}`).join(', ')})`);
365
+ }, [
366
+ draft,
367
+ functions,
368
+ variables
369
+ ]);
370
+ const handleSave = useCallback(async ()=>{
371
+ if (!draft || !draft.name.trim() || nameTaken) return;
372
+ // The steps are checked at the door, not after the write: a stored step
373
+ // nothing can run is a run that fails on the site rather than here.
374
+ if (stepProblem) {
375
+ return void enqueueSnackbar(stepProblem, {
376
+ variant: 'warning',
377
+ persist: false
378
+ });
379
+ }
380
+ const { id: draftId } = draft, definition = _object_without_properties_loose(draft, [
381
+ "id"
382
+ ]);
383
+ const fields = _extends({}, definition, {
384
+ name: draft.name.trim().slice(0, 60)
385
+ });
386
+ try {
387
+ if (draftId) {
388
+ /**
389
+ * Edit stays client-direct (no quota consumed) — and is refused when
390
+ * its seed was never confirmed by the server (AGL-1358). `fields` is
391
+ * the whole definition spread out of the listener row, `trigger` and
392
+ * `steps` included, so `merge: true` has nothing left to protect.
393
+ *
394
+ * The guard WRAPS the write. An early return is a shape you can keep
395
+ * while losing the protection; here the write is only reachable
396
+ * through the verdict.
397
+ */ const verdict = await writeGuardedBySeed({
398
+ subject: 'workflow',
399
+ unreadable: workflowsStatus === 'error',
400
+ fromCache: workflowsFromCache
401
+ }, async ()=>{
402
+ await setDoc(doc(firestore, 'hosts', hostId, 'workflows', draftId), _extends({}, fields, {
403
+ updatedAt: Timestamp.now()
404
+ }), {
405
+ merge: true
406
+ });
407
+ });
408
+ // Before `setDraft(null)`, so a refusal keeps the dialog open with
409
+ // every step that was built and its test run still on screen.
410
+ if (!verdict.ok) {
411
+ return void enqueueSnackbar(verdict.message, {
412
+ variant: 'warning',
413
+ persist: false
414
+ });
415
+ }
416
+ } else {
417
+ // New workflow rides the quota-enforcing resources API (AGL-473) —
418
+ // it also re-checks the `workflows` entitlement server-side.
419
+ await createHostResource({
420
+ hostId,
421
+ resource: 'workflow',
422
+ data: fields
423
+ });
424
+ // A create moves the count; the one-shot aggregate below does not
425
+ // refresh itself the way the listener refreshes the rows.
426
+ setWorkflowCountEpoch((epoch)=>epoch + 1);
427
+ }
428
+ setDraft(null);
429
+ enqueueSnackbar('Workflow saved', {
430
+ variant: 'success',
431
+ persist: false
432
+ });
433
+ } catch (error) {
434
+ var _ref;
435
+ console.error(error);
436
+ enqueueSnackbar((_ref = error == null ? void 0 : error.message) != null ? _ref : 'An error has occurred', {
437
+ variant: 'error',
438
+ allowDuplicate: true
439
+ });
440
+ }
441
+ }, [
442
+ draft,
443
+ nameTaken,
444
+ stepProblem,
445
+ firestore,
446
+ hostId,
447
+ createHostResource,
448
+ enqueueSnackbar,
449
+ workflowsStatus,
450
+ workflowsFromCache
451
+ ]);
452
+ const handleShowUsage = useCallback((workflow)=>async ()=>{
453
+ setUsageLoading(workflow.$id);
454
+ const result = await fetchWhereUsed(user, {
455
+ hostId,
456
+ kind: 'workflow',
457
+ id: workflow.$id,
458
+ name: workflow.name
459
+ });
460
+ setUsageLoading(null);
461
+ // The rows and their deep links are drawn in a zone; with nothing
462
+ // registered there the answer is still given, in words.
463
+ if (listConsoleWidgets(WORKFLOW_USAGE_ZONE.id).length === 0) {
464
+ enqueueSnackbar(result.total ? `"${workflow.name}" computes ${summarizeDependents(result)}` : `"${workflow.name}" is not referenced by anything published`, {
465
+ variant: 'info',
466
+ persist: false
467
+ });
468
+ return;
469
+ }
470
+ setUsage({
471
+ name: workflow.name,
472
+ result
473
+ });
474
+ }, [
475
+ user,
476
+ hostId,
477
+ enqueueSnackbar
478
+ ]);
479
+ const handleDelete = useCallback((workflow)=>async ()=>{
480
+ // Dependents warning (AGL-187): computed variables backed by this
481
+ // workflow keep their stored fallback value once it is gone.
482
+ const scan = await fetchWhereUsed(user, {
483
+ hostId,
484
+ kind: 'workflow',
485
+ id: workflow.$id,
486
+ name: workflow.name
487
+ });
488
+ const confirmed = await confirm({
489
+ title: 'Delete this workflow?',
490
+ description: scan.total ? `"${workflow.name}" computes ${summarizeDependents(scan)} — ` + 'those variables will fall back to their stored values.' : `"${workflow.name}" will no longer be runnable.`,
491
+ confirmationText: 'Delete',
492
+ confirmationButtonProps: {
493
+ color: 'error'
494
+ }
495
+ }).then(()=>true).catch(()=>false);
496
+ if (!confirmed) return;
497
+ await updateDoc(doc(firestore, 'hosts', hostId, 'workflows', workflow.$id), {
498
+ deletedAt: Timestamp.now()
499
+ });
500
+ enqueueSnackbar('Workflow deleted', {
501
+ variant: 'success',
502
+ persist: false
503
+ });
504
+ }, [
505
+ user,
506
+ confirm,
507
+ firestore,
508
+ hostId,
509
+ enqueueSnackbar
510
+ ]);
511
+ return /*#__PURE__*/ _jsxs(CardDisplay, {
512
+ header: 'Workflows',
513
+ help: pluginDocsHelp('buildAWorkflow', {
514
+ anchor: '#1-open-the-workflows-page'
515
+ }),
516
+ contentGutterX: true,
517
+ contentGutterY: true,
518
+ children: [
519
+ duplicate.dialog,
520
+ /*#__PURE__*/ _jsxs(Stack, {
521
+ spacing: 1,
522
+ children: [
523
+ workflows.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
524
+ variant: "body2",
525
+ color: "text.secondary",
526
+ children: 'Chain your functions into multi-step pipelines — each step ' + 'feeds the next. Site-event triggers are coming next.'
527
+ }) : visibleWorkflows.map((workflow)=>{
528
+ var _workflow_steps, _workflow_steps1, _workflow_steps2;
529
+ var _workflow_trigger;
530
+ return /*#__PURE__*/ _jsxs(Stack, {
531
+ direction: "row",
532
+ spacing: 1,
533
+ sx: {
534
+ alignItems: 'center'
535
+ },
536
+ children: [
537
+ /*#__PURE__*/ _jsxs(Stack, {
538
+ sx: {
539
+ flex: 1,
540
+ minWidth: 0
541
+ },
542
+ children: [
543
+ /*#__PURE__*/ _jsx(Typography, {
544
+ variant: "body2",
545
+ noWrap: true,
546
+ children: workflow.name
547
+ }),
548
+ /*#__PURE__*/ _jsx(Typography, {
549
+ variant: "caption",
550
+ color: "text.secondary",
551
+ noWrap: true,
552
+ children: `${((_workflow_steps = workflow.steps) != null ? _workflow_steps : []).length} step${((_workflow_steps1 = workflow.steps) != null ? _workflow_steps1 : []).length === 1 ? '' : 's'}${((_workflow_trigger = workflow.trigger) == null ? void 0 : _workflow_trigger.event) ? ` · on ${hostEventLabel(workflow.trigger.event)}` : ''} · ${((_workflow_steps2 = workflow.steps) != null ? _workflow_steps2 : []).map((step)=>step.functionName).join(' → ')}`
553
+ })
554
+ ]
555
+ }),
556
+ /*#__PURE__*/ _jsx(Button, {
557
+ size: "small",
558
+ disabled: usageLoading === workflow.$id,
559
+ onClick: handleShowUsage(workflow),
560
+ children: usageLoading === workflow.$id ? 'Scanning…' : 'Usage'
561
+ }),
562
+ /*#__PURE__*/ _jsx(Button, {
563
+ size: "small",
564
+ onClick: ()=>setRunsFor(workflow),
565
+ children: 'Runs'
566
+ }),
567
+ /*#__PURE__*/ _jsx(Button, {
568
+ size: "small",
569
+ onClick: ()=>{
570
+ var _workflow_name, _workflow_steps, _workflow_returnValue, _workflow_trigger;
571
+ setTestResult(null);
572
+ setDraft({
573
+ id: workflow.$id,
574
+ name: (_workflow_name = workflow.name) != null ? _workflow_name : '',
575
+ steps: (_workflow_steps = workflow.steps) != null ? _workflow_steps : [],
576
+ returnValue: (_workflow_returnValue = workflow.returnValue) != null ? _workflow_returnValue : '',
577
+ trigger: (_workflow_trigger = workflow.trigger) != null ? _workflow_trigger : null
578
+ });
579
+ },
580
+ children: 'Edit'
581
+ }),
582
+ /*#__PURE__*/ _jsx(Button, {
583
+ size: "small",
584
+ onClick: ()=>{
585
+ var _workflow_name;
586
+ return duplicate.request('workflow', {
587
+ id: workflow.$id,
588
+ name: (_workflow_name = workflow.name) != null ? _workflow_name : ''
589
+ });
590
+ },
591
+ children: 'Duplicate…'
592
+ }),
593
+ /*#__PURE__*/ _jsx(Button, {
594
+ size: "small",
595
+ color: "error",
596
+ onClick: handleDelete(workflow),
597
+ children: 'Delete'
598
+ })
599
+ ]
600
+ }, workflow.$id);
601
+ }),
602
+ workflows.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
603
+ page: page,
604
+ pageSize: pageSize,
605
+ rowCount: visibleWorkflows.length,
606
+ // The workflows the card HOLDS. Not `workflowCount`, which is the
607
+ // site's total including soft-deleted rows and answers the quota's
608
+ // question rather than the reader's.
609
+ count: workflows.length,
610
+ onPageChange: setPage,
611
+ onPageSizeChange: setPageSize
612
+ }),
613
+ workflowsTruncated ? /*#__PURE__*/ _jsx(Alert, {
614
+ severity: "info",
615
+ children: `Showing ${WORKFLOW_CEILING} workflows, ordered by id. This site ` + 'has more — the duplicate-name check below only covers the ' + 'ones listed here, so a name may already be taken by one that ' + 'is not.'
616
+ }) : null,
617
+ /*#__PURE__*/ _jsx(Button, {
618
+ size: "small",
619
+ color: "primary",
620
+ sx: {
621
+ alignSelf: 'flex-start'
622
+ },
623
+ onClick: handleAdd,
624
+ children: 'Add workflow'
625
+ }),
626
+ /*#__PURE__*/ _jsx(QuotaReadoutComponent, {
627
+ ready: org != null,
628
+ used: workflowCount,
629
+ limit: checkQuota(org, 'workflowsPerHost', workflowCount).limit,
630
+ noun: "workflow"
631
+ })
632
+ ]
633
+ }),
634
+ /*#__PURE__*/ _jsxs(Dialog, {
635
+ open: Boolean(draft),
636
+ onClose: ()=>setDraft(null),
637
+ maxWidth: "sm",
638
+ fullWidth: true,
639
+ children: [
640
+ /*#__PURE__*/ _jsx(DialogTitle, {
641
+ children: (draft == null ? void 0 : draft.id) ? 'Edit Workflow' : 'Add Workflow'
642
+ }),
643
+ /*#__PURE__*/ _jsxs(DialogContent, {
644
+ sx: {
645
+ display: 'flex',
646
+ flexDirection: 'column',
647
+ gap: 1.5,
648
+ pt: 1
649
+ },
650
+ children: [
651
+ ExtensionZone && editorTarget ? /*#__PURE__*/ _jsx(ExtensionZone, {
652
+ slot: "automationEditor",
653
+ hostId: hostId,
654
+ orgId: org == null ? void 0 : org.$id,
655
+ target: editorTarget
656
+ }) : null,
657
+ /*#__PURE__*/ _jsx(TextField, {
658
+ label: "Name",
659
+ helperText: nameTaken ? 'A workflow with this name already exists' : 'Used to identify the workflow',
660
+ error: nameTaken,
661
+ value: (_ref2 = draft == null ? void 0 : draft.name) != null ? _ref2 : '',
662
+ onChange: (event)=>patch((previous)=>_extends({}, previous, {
663
+ name: event.target.value
664
+ })),
665
+ size: "small",
666
+ autoFocus: true,
667
+ sx: {
668
+ mt: 1
669
+ }
670
+ }),
671
+ /*#__PURE__*/ _jsx(Typography, {
672
+ variant: "overline",
673
+ color: "text.secondary",
674
+ children: 'Steps'
675
+ }),
676
+ shortLists.length > 0 ? /*#__PURE__*/ _jsx(Alert, {
677
+ severity: "info",
678
+ children: `Offering the first ${EDITOR_OPTION_CEILING} ` + `${shortLists.join(', ')} on this site, ordered by id. ` + 'There are more, so the pickers below are short and a test ' + 'run may not resolve every expression.'
679
+ }) : null,
680
+ ((_ref3 = draft == null ? void 0 : draft.steps) != null ? _ref3 : []).map((step, index)=>{
681
+ var _functions_, _call_functionId, _ref, _call_functionId1, _call_resultName, _ref1;
682
+ var _functions_call_functionName;
683
+ const call = isWorkflowActionStep(step) ? null : step;
684
+ const definition = call ? (_functions_ = functions[(_call_functionId = call.functionId) != null ? _call_functionId : '']) != null ? _functions_ : functions[call.functionName] : null;
685
+ return /*#__PURE__*/ _jsx(AutomationStepFields, {
686
+ /*
687
+ * The editor speaks the Actions vocabulary. A function call
688
+ * has no Actions type, so it takes none of those fields and
689
+ * renders its own through `fields` instead — which is what
690
+ * keeps this one editor rather than two.
691
+ */ step: step,
692
+ index: index,
693
+ kind: call ? FUNCTION_CALL_KIND : step.type,
694
+ kinds: STEP_KINDS,
695
+ stepForKind: (value)=>value === FUNCTION_CALL_KIND ? {
696
+ functionName: '',
697
+ args: [],
698
+ resultName: ''
699
+ } : defaultStep(value),
700
+ pickers: stepPickers,
701
+ onSteps: (update)=>patch((previous)=>_extends({}, previous, {
702
+ steps: update(previous.steps)
703
+ })),
704
+ fields: call ? /*#__PURE__*/ _jsxs(Stack, {
705
+ direction: "row",
706
+ spacing: 1,
707
+ sx: {
708
+ alignItems: 'center',
709
+ flex: 1,
710
+ flexWrap: 'wrap'
711
+ },
712
+ children: [
713
+ /*#__PURE__*/ _jsx(TextField, {
714
+ label: "Function",
715
+ // Stored by id (AGL-261); legacy steps carry only the
716
+ // name and resolve through the double-keyed map.
717
+ value: (_ref = (_call_functionId1 = call.functionId) != null ? _call_functionId1 : (_functions_call_functionName = functions[call.functionName]) == null ? void 0 : _functions_call_functionName.$id) != null ? _ref : '',
718
+ onChange: (event)=>patch((previous)=>_extends({}, previous, {
719
+ steps: previous.steps.map((s, index2)=>{
720
+ var _ref;
721
+ var _functionOptions_find;
722
+ return index2 === index ? _extends({}, s, {
723
+ functionId: event.target.value,
724
+ functionName: (_ref = (_functionOptions_find = functionOptions.find((option)=>option.id === event.target.value)) == null ? void 0 : _functionOptions_find.name) != null ? _ref : s.functionName
725
+ }) : s;
726
+ })
727
+ })),
728
+ size: "small",
729
+ select: true,
730
+ sx: {
731
+ minWidth: 160,
732
+ flex: 1
733
+ },
734
+ children: functionOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
735
+ value: option.id,
736
+ children: option.name
737
+ }, option.id))
738
+ }),
739
+ /*#__PURE__*/ _jsx(TextField, {
740
+ label: "Result name",
741
+ placeholder: `step${index + 1}`,
742
+ value: (_call_resultName = call.resultName) != null ? _call_resultName : '',
743
+ onChange: (event)=>patch((previous)=>_extends({}, previous, {
744
+ steps: previous.steps.map((s, index2)=>index2 === index ? _extends({}, s, {
745
+ resultName: event.target.value.replace(/[^a-zA-Z0-9_]/g, '')
746
+ }) : s)
747
+ })),
748
+ size: "small",
749
+ sx: {
750
+ width: 140
751
+ }
752
+ }),
753
+ ((_ref1 = definition == null ? void 0 : definition.parameters) != null ? _ref1 : []).map((parameter, parameterIndex)=>{
754
+ var _ref;
755
+ var _call_args;
756
+ return /*#__PURE__*/ _jsx(TextField, {
757
+ label: `${parameter.name} expression`,
758
+ placeholder: parameterIndex === 0 && index > 0 ? `step${index}` : 'a variable, number, or expression',
759
+ value: (_ref = (_call_args = call.args) == null ? void 0 : _call_args[parameterIndex]) != null ? _ref : '',
760
+ onChange: (event)=>patch((previous)=>_extends({}, previous, {
761
+ steps: previous.steps.map((s, index2)=>{
762
+ var _s_args;
763
+ if (index2 !== index) return s;
764
+ const args = [
765
+ ...(_s_args = s.args) != null ? _s_args : []
766
+ ];
767
+ args[parameterIndex] = event.target.value;
768
+ return _extends({}, s, {
769
+ args
770
+ });
771
+ })
772
+ })),
773
+ size: "small",
774
+ sx: {
775
+ minWidth: 180,
776
+ flex: 1
777
+ }
778
+ }, parameter.name);
779
+ })
780
+ ]
781
+ }) : undefined
782
+ }, index);
783
+ }),
784
+ /*#__PURE__*/ _jsx(Button, {
785
+ size: "small",
786
+ sx: {
787
+ alignSelf: 'flex-start'
788
+ },
789
+ disabled: ((_ref4 = draft == null ? void 0 : draft.steps.length) != null ? _ref4 : 0) >= WORKFLOW_MAX_STEPS,
790
+ onClick: ()=>patch((previous)=>_extends({}, previous, {
791
+ steps: [
792
+ ...previous.steps,
793
+ {
794
+ functionName: '',
795
+ args: [],
796
+ resultName: ''
797
+ }
798
+ ]
799
+ })),
800
+ children: 'Add step'
801
+ }),
802
+ /*#__PURE__*/ _jsx(Typography, {
803
+ variant: "overline",
804
+ color: "text.secondary",
805
+ children: 'Trigger'
806
+ }),
807
+ /*#__PURE__*/ _jsxs(Stack, {
808
+ direction: "row",
809
+ spacing: 1,
810
+ children: [
811
+ /*#__PURE__*/ _jsxs(TextField, {
812
+ label: "Run on event",
813
+ value: (_ref5 = draft == null ? void 0 : (_draft_trigger = draft.trigger) == null ? void 0 : _draft_trigger.event) != null ? _ref5 : '',
814
+ onChange: (event)=>patch((previous)=>{
815
+ var _ref;
816
+ var _previous_trigger;
817
+ return _extends({}, previous, {
818
+ trigger: event.target.value ? {
819
+ event: event.target.value,
820
+ filter: (_ref = (_previous_trigger = previous.trigger) == null ? void 0 : _previous_trigger.filter) != null ? _ref : ''
821
+ } : null
822
+ });
823
+ }),
824
+ size: "small",
825
+ select: true,
826
+ sx: {
827
+ minWidth: 180
828
+ },
829
+ children: [
830
+ /*#__PURE__*/ _jsx(MenuItem, {
831
+ value: "",
832
+ children: 'Manual only'
833
+ }),
834
+ HOST_EVENT_TYPES.map((eventType)=>/*#__PURE__*/ _jsx(MenuItem, {
835
+ value: eventType,
836
+ children: hostEventLabel(eventType)
837
+ }, eventType))
838
+ ]
839
+ }),
840
+ (draft == null ? void 0 : draft.trigger) ? /*#__PURE__*/ _jsx(TextField, {
841
+ label: "Filter (optional)",
842
+ placeholder: 'path == "/pricing"',
843
+ helperText: [
844
+ 'Runs only when this expression is truthy.',
845
+ hostEventPayloadHint(draft == null ? void 0 : (_draft_trigger1 = draft.trigger) == null ? void 0 : _draft_trigger1.event)
846
+ ].filter(Boolean).join(' '),
847
+ value: (_ref6 = draft == null ? void 0 : (_draft_trigger2 = draft.trigger) == null ? void 0 : _draft_trigger2.filter) != null ? _ref6 : '',
848
+ onChange: (event)=>patch((previous)=>_extends({}, previous, {
849
+ trigger: previous.trigger ? _extends({}, previous.trigger, {
850
+ filter: event.target.value
851
+ }) : previous.trigger
852
+ })),
853
+ size: "small",
854
+ sx: {
855
+ flex: 1
856
+ }
857
+ }) : null
858
+ ]
859
+ }),
860
+ /*#__PURE__*/ _jsx(TextField, {
861
+ label: "Return value",
862
+ helperText: "A step result name; defaults to the last step",
863
+ value: (_ref7 = draft == null ? void 0 : draft.returnValue) != null ? _ref7 : '',
864
+ onChange: (event)=>patch((previous)=>_extends({}, previous, {
865
+ returnValue: event.target.value
866
+ })),
867
+ size: "small"
868
+ }),
869
+ /*#__PURE__*/ _jsxs(Stack, {
870
+ direction: "row",
871
+ spacing: 1,
872
+ sx: {
873
+ alignItems: 'center'
874
+ },
875
+ children: [
876
+ /*#__PURE__*/ _jsx(Button, {
877
+ size: "small",
878
+ color: "primary",
879
+ onClick: handleTestRun,
880
+ children: 'Test run'
881
+ }),
882
+ /*#__PURE__*/ _jsx(Typography, {
883
+ variant: "caption",
884
+ color: "text.secondary",
885
+ children: 'Uses current variable values'
886
+ })
887
+ ]
888
+ }),
889
+ testResult ? /*#__PURE__*/ _jsx(Alert, {
890
+ severity: testResult.startsWith('Error') ? 'warning' : 'success',
891
+ children: testResult
892
+ }) : null
893
+ ]
894
+ }),
895
+ /*#__PURE__*/ _jsxs(DialogActions, {
896
+ children: [
897
+ /*#__PURE__*/ _jsx(Button, {
898
+ onClick: ()=>setDraft(null),
899
+ children: 'Cancel'
900
+ }),
901
+ /*#__PURE__*/ _jsx(Button, {
902
+ variant: "contained",
903
+ color: "primary",
904
+ disabled: !(draft == null ? void 0 : draft.name.trim()) || nameTaken,
905
+ onClick: handleSave,
906
+ children: 'Done'
907
+ })
908
+ ]
909
+ })
910
+ ]
911
+ }),
912
+ ExtensionZone ? /*#__PURE__*/ _jsx(ExtensionZone, {
913
+ slot: WORKFLOW_USAGE_ZONE.id,
914
+ hostId: hostId,
915
+ usage: usage,
916
+ onClose: ()=>setUsage(null)
917
+ }) : null,
918
+ /*#__PURE__*/ _jsxs(Dialog, {
919
+ open: Boolean(runsFor),
920
+ onClose: ()=>setRunsFor(null),
921
+ maxWidth: "sm",
922
+ fullWidth: true,
923
+ children: [
924
+ /*#__PURE__*/ _jsx(DialogTitle, {
925
+ children: `Runs — ${(_ref8 = runsFor == null ? void 0 : runsFor.name) != null ? _ref8 : ''}`
926
+ }),
927
+ /*#__PURE__*/ _jsx(DialogContent, {
928
+ children: runsFor ? /*#__PURE__*/ _jsx(HostRunHistoryCard, {
929
+ hostId: hostId,
930
+ orgId: org == null ? void 0 : org.$id,
931
+ targetId: runsFor.$id,
932
+ targetType: "workflow",
933
+ targetName: (_runsFor_name = runsFor.name) != null ? _runsFor_name : '',
934
+ header: "Recent runs"
935
+ }) : null
936
+ }),
937
+ /*#__PURE__*/ _jsx(DialogActions, {
938
+ children: /*#__PURE__*/ _jsx(Button, {
939
+ color: "inherit",
940
+ onClick: ()=>setRunsFor(null),
941
+ children: 'Close'
942
+ })
943
+ })
944
+ ]
945
+ })
946
+ ]
947
+ });
948
+ }
949
+ HostWorkflowsCard.displayName = 'HostWorkflowsCard';
950
+ export default HostWorkflowsCard;
951
+
952
+ //# sourceMappingURL=host-workflows-card.component.js.map