@burdenoff/fluidgrids-fe-sdk 2026.1023.3 → 2026.1023.5

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.
@@ -11,14 +11,25 @@ import { Tabs, TabsList, TabsTrigger, TabsContent, Input, Switch, Select, Select
11
11
  import { toast } from 'sonner';
12
12
  import { jsxs, jsx } from 'react/jsx-runtime';
13
13
 
14
- // src/modules/settings/utils/max-parallelism.ts
15
- function parseMaxParallelism(value) {
14
+ // src/modules/settings/utils/error-trigger.ts
15
+ var ERROR_TRIGGER_NODE_TYPE = "internal:error-trigger";
16
+ function asRecord(value) {
17
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
18
+ }
19
+ function hasErrorTriggerNode(definitionJson) {
20
+ const definition = asRecord(definitionJson);
21
+ const dataNodes = asRecord(definition?.data)?.nodes;
22
+ const nodes = Array.isArray(dataNodes) ? dataNodes : definition?.nodes;
23
+ return Array.isArray(nodes) && nodes.some((node) => asRecord(node)?.type === ERROR_TRIGGER_NODE_TYPE);
24
+ }
25
+
26
+ // src/modules/settings/utils/optional-positive-int.ts
27
+ function parseOptionalPositiveInt(value) {
16
28
  if (value.trim() === "") return void 0;
17
29
  const parsed = Number.parseInt(value, 10);
18
30
  return Number.isFinite(parsed) ? Math.max(1, parsed) : void 0;
19
31
  }
20
32
  var DEFAULT_EXECUTION_SETTINGS = {
21
- timeout: 300,
22
33
  onError: "stop"
23
34
  };
24
35
  var DEFAULT_SETTINGS = {
@@ -188,6 +199,15 @@ function ExecutionTab({ workflowKey }) {
188
199
  }, [workflow?.definitionJson]);
189
200
  const [form, setForm] = useState(persistedConfig);
190
201
  useEffect(() => setForm(persistedConfig), [persistedConfig]);
202
+ const selectedErrorWorkflowKey = form.errorWorkflowKey;
203
+ const { data: errorWorkflowData } = useWorkflowDefinitionQuery({
204
+ variables: { workflowKey: selectedErrorWorkflowKey ?? "" },
205
+ skip: !selectedErrorWorkflowKey
206
+ });
207
+ const selectedErrorWorkflow = errorWorkflowData?.workflowDefinition;
208
+ const errorWorkflowLacksTrigger = Boolean(
209
+ selectedErrorWorkflowKey && selectedErrorWorkflow && !hasErrorTriggerNode(selectedErrorWorkflow.definitionJson)
210
+ );
191
211
  const handleSave = useCallback(async () => {
192
212
  if (!workflow?.id) return;
193
213
  try {
@@ -272,7 +292,7 @@ function ExecutionTab({ workflowKey }) {
272
292
  ),
273
293
  description: tr(
274
294
  "fluidgrids.settings.execution.defaultTimeoutDescription",
275
- "Maximum time this workflow's execution can run (in seconds)"
295
+ "Stop a run that keeps running longer than this. Time a run spends paused (a Wait over a minute, an approval, a person's answer) does not count. Leave empty for no limit."
276
296
  ),
277
297
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
278
298
  /* @__PURE__ */ jsx(
@@ -280,10 +300,18 @@ function ExecutionTab({ workflowKey }) {
280
300
  {
281
301
  type: "number",
282
302
  min: 1,
283
- value: form.timeout ?? DEFAULT_EXECUTION_SETTINGS.timeout,
303
+ value: form.timeout ?? "",
304
+ placeholder: tr(
305
+ "fluidgrids.settings.execution.timeoutPlaceholder",
306
+ "No timeout"
307
+ ),
308
+ "aria-label": tr(
309
+ "fluidgrids.settings.execution.defaultTimeoutLabel",
310
+ "Timeout"
311
+ ),
284
312
  onChange: (e) => setForm((f) => ({
285
313
  ...f,
286
- timeout: Math.max(1, parseInt(e.target.value, 10) || 1)
314
+ timeout: parseOptionalPositiveInt(e.target.value)
287
315
  })),
288
316
  disabled: loading,
289
317
  className: "w-[100px] text-right"
@@ -323,7 +351,7 @@ function ExecutionTab({ workflowKey }) {
323
351
  ),
324
352
  onChange: (e) => setForm((f) => ({
325
353
  ...f,
326
- maxParallelism: parseMaxParallelism(e.target.value)
354
+ maxParallelism: parseOptionalPositiveInt(e.target.value)
327
355
  })),
328
356
  disabled: loading,
329
357
  className: "w-[100px] text-right"
@@ -370,34 +398,50 @@ function ExecutionTab({ workflowKey }) {
370
398
  ),
371
399
  description: tr(
372
400
  "fluidgrids.settings.execution.errorWorkflowDescription",
373
- "Workflow key to run when this workflow's execution fails (optional)"
401
+ "Runs when a production run of this workflow fails, starting at that workflow's Error Trigger (optional)"
374
402
  ),
375
- children: /* @__PURE__ */ jsxs(
376
- Select,
377
- {
378
- value: form.errorWorkflowKey ?? "__none__",
379
- onValueChange: (val) => setForm((f) => ({
380
- ...f,
381
- errorWorkflowKey: val === "__none__" ? void 0 : val
382
- })),
383
- disabled: loading || otherWorkflowsLoading,
384
- children: [
385
- /* @__PURE__ */ jsx(SelectTrigger, { className: "w-[220px] bg-background-secondary border-border-secondary", children: /* @__PURE__ */ jsx(
386
- SelectValue,
387
- {
388
- placeholder: tr(
389
- "fluidgrids.settings.execution.errorWorkflowPlaceholder",
403
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end gap-1", children: [
404
+ /* @__PURE__ */ jsxs(
405
+ Select,
406
+ {
407
+ value: form.errorWorkflowKey ?? "__none__",
408
+ onValueChange: (val) => setForm((f) => ({
409
+ ...f,
410
+ errorWorkflowKey: val === "__none__" ? void 0 : val
411
+ })),
412
+ disabled: loading || otherWorkflowsLoading,
413
+ children: [
414
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "w-[220px] bg-background-secondary border-border-secondary", children: /* @__PURE__ */ jsx(
415
+ SelectValue,
416
+ {
417
+ placeholder: tr(
418
+ "fluidgrids.settings.execution.errorWorkflowPlaceholder",
419
+ "None"
420
+ )
421
+ }
422
+ ) }),
423
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
424
+ /* @__PURE__ */ jsx(SelectItem, { value: "__none__", children: tr(
425
+ "fluidgrids.settings.execution.errorWorkflowNone",
390
426
  "None"
391
- )
392
- }
393
- ) }),
394
- /* @__PURE__ */ jsxs(SelectContent, { children: [
395
- /* @__PURE__ */ jsx(SelectItem, { value: "__none__", children: tr("fluidgrids.settings.execution.errorWorkflowNone", "None") }),
396
- errorWorkflowOptions.map((w) => /* @__PURE__ */ jsx(SelectItem, { value: w.workflowKey, children: w.name }, w.workflowKey))
397
- ] })
398
- ]
399
- }
400
- )
427
+ ) }),
428
+ errorWorkflowOptions.map((w) => /* @__PURE__ */ jsx(SelectItem, { value: w.workflowKey, children: w.name }, w.workflowKey))
429
+ ] })
430
+ ]
431
+ }
432
+ ),
433
+ errorWorkflowLacksTrigger && /* @__PURE__ */ jsx(
434
+ "p",
435
+ {
436
+ className: "max-w-[260px] text-right text-xs text-status-warning-text",
437
+ "data-testid": "error-workflow-no-trigger",
438
+ children: tr(
439
+ "fluidgrids.settings.execution.errorWorkflowNoTrigger",
440
+ "This workflow has no Error Trigger, so it will not start. Add an Error Trigger node to it."
441
+ )
442
+ }
443
+ )
444
+ ] })
401
445
  }
402
446
  ),
403
447
  /* @__PURE__ */ jsx(
@@ -1,4 +1,4 @@
1
- import { Card, FluidGridsMark } from './chunk-DUIUT2TB.js';
1
+ import { Card, FluidGridsMark } from './chunk-JG7UFL5M.js';
2
2
  import { Input } from './chunk-IEVSFCFS.js';
3
3
  import { Button } from './chunk-BQXVT32O.js';
4
4
  import { useWorkflowDefinition } from './chunk-J3XDX5HG.js';
@@ -2,7 +2,7 @@ import { deepClone, applyTemplateInputs } from './chunk-GEMNVFPK.js';
2
2
  import { Input as Input$1 } from './chunk-IEVSFCFS.js';
3
3
  import { Dialog as Dialog$1, DialogContent as DialogContent$1, DialogHeader as DialogHeader$1, DialogTitle as DialogTitle$1, DialogDescription as DialogDescription$1, DialogFooter } from './chunk-YXMFRQYV.js';
4
4
  import { Badge as Badge$1, Button } from './chunk-BQXVT32O.js';
5
- import { QuotaExhaustedDialog } from './chunk-OD2XBMS6.js';
5
+ import { QuotaExhaustedDialog } from './chunk-3VF67N3X.js';
6
6
  import { EntityTagPicker } from './chunk-IULOX3NQ.js';
7
7
  import { useWorkflowVersions, useRestoreWorkflowVersion, useActivateWorkflow, useDeleteWorkflow, useLazyWorkflowDefinition, useDeactivateWorkflow } from './chunk-J3XDX5HG.js';
8
8
  import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
@@ -1,5 +1,5 @@
1
- import { useImportWorkflowTemplate, FlowBuilderPage, useWorkflowTemplates, TemplateCard } from './chunk-DUIUT2TB.js';
2
- import { QuotaExhaustedDialog } from './chunk-OD2XBMS6.js';
1
+ import { useImportWorkflowTemplate, FlowBuilderPage, useWorkflowTemplates, TemplateCard } from './chunk-JG7UFL5M.js';
2
+ import { QuotaExhaustedDialog } from './chunk-3VF67N3X.js';
3
3
  import { useWorkflowDefinitions, useDuplicateWorkflow } from './chunk-J3XDX5HG.js';
4
4
  import { templatesControls } from './chunk-D3UWK7GY.js';
5
5
  import { useFluidGridsPageControls } from './chunk-PJAWNFJV.js';
@@ -1,8 +1,7 @@
1
- import { isGuidedTourIntent, pickGuidedTour, withoutGuidedTourIntent, isCreateIntent, decideMissingWorkflow } from './chunk-YGBDZ424.js';
2
- import { useInstalledNodes, buildNodeCheck, FlowBuilderPage, buildRequiredNodeTypes } from './chunk-DUIUT2TB.js';
3
- import { QuotaExhaustedDialog } from './chunk-OD2XBMS6.js';
1
+ import { isGuidedTourIntent, pickGuidedTour, withoutGuidedTourIntent, isCreateIntent, CreateWorkflowRefusalDialog, decideMissingWorkflow, blankWorkflowCreateInput, classifyCreateRefusal } from './chunk-7XBCGNUG.js';
2
+ import { useInstalledNodes, buildNodeCheck, FlowBuilderPage, buildRequiredNodeTypes } from './chunk-JG7UFL5M.js';
4
3
  import { useLazyWorkflowDefinition, useCreateWorkflow } from './chunk-J3XDX5HG.js';
5
- import { useFlowStore, refetchStoreNodes, BILLING_PLANS_PATH, mergeStickyNotes, isQuotaExhaustedFailure } from './chunk-MR4OTF7D.js';
4
+ import { useFlowStore, refetchStoreNodes, BILLING_PLANS_PATH, mergeStickyNotes } from './chunk-MR4OTF7D.js';
6
5
  import { useTr } from './chunk-UULVIT25.js';
7
6
  import { useLocation, useNavigate, useParams } from 'react-router';
8
7
  import { toast } from 'sonner';
@@ -82,7 +81,7 @@ function WorkflowItem({
82
81
  const [workflow, setWorkflow] = useState(null);
83
82
  const [isLoading, setIsLoading] = useState(true);
84
83
  const [loadError, setLoadError] = useState(null);
85
- const [createQuotaExhausted, setCreateQuotaExhausted] = useState(false);
84
+ const [createRefusal, setCreateRefusal] = useState(null);
86
85
  const upgradingRef = useRef(false);
87
86
  const resetWorkflow = useFlowStore((s) => s.actions.resetWorkflow);
88
87
  const { getWorkflow } = useLazyWorkflowDefinition();
@@ -326,60 +325,34 @@ function WorkflowItem({
326
325
  }
327
326
  if (!creatingRef.current) {
328
327
  creatingRef.current = true;
329
- const initialDefinitionJson = {
330
- key: id,
331
- name: newWorkflow.name,
332
- description: newWorkflow.name,
333
- workflow_type: "query-conditional",
334
- data: {
335
- ...newWorkflow
336
- },
337
- meta: {
338
- creator: "api_create",
339
- version
340
- },
341
- tags: []
342
- };
328
+ let reported = null;
329
+ let created = null;
343
330
  try {
344
- const refusal = { quotaExhausted: false };
345
- const created = await createWorkflow(
346
- {
347
- workflowKey: id,
348
- version,
349
- name: newWorkflow.name,
350
- description: newWorkflow.name,
351
- definitionJson: initialDefinitionJson
352
- },
331
+ created = await createWorkflow(
332
+ blankWorkflowCreateInput(id, version),
353
333
  {
354
334
  onError: (createError) => {
355
- refusal.quotaExhausted = isQuotaExhaustedFailure(createError);
335
+ reported = createError;
356
336
  }
357
337
  }
358
338
  );
359
- if (!created && refusal.quotaExhausted) {
360
- if (mounted) setCreateQuotaExhausted(true);
361
- return;
362
- }
363
- if (created && mounted) {
364
- setWorkflow({
365
- ...newWorkflow,
366
- id: created.workflowKey,
367
- workflowKey: created.workflowKey,
368
- version: created.version,
369
- dbId: created.id,
370
- status: created.status ?? "DRAFT"
371
- });
372
- } else if (mounted) {
373
- setWorkflow(newWorkflow);
374
- }
375
339
  } catch (createError) {
376
- console.error(
377
- "[WorkflowItem] Error creating workflow:",
378
- createError
379
- );
380
- if (mounted) {
381
- setWorkflow(fetchedWorkflowRef.current ?? newWorkflow);
382
- }
340
+ reported = createError;
341
+ created = null;
342
+ }
343
+ if (!created) {
344
+ if (mounted) setCreateRefusal(classifyCreateRefusal(reported));
345
+ return;
346
+ }
347
+ if (mounted) {
348
+ setWorkflow({
349
+ ...newWorkflow,
350
+ id: created.workflowKey,
351
+ workflowKey: created.workflowKey,
352
+ version: created.version,
353
+ dbId: created.id,
354
+ status: created.status ?? "DRAFT"
355
+ });
383
356
  }
384
357
  }
385
358
  }
@@ -455,6 +428,50 @@ function WorkflowItem({
455
428
  /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground/60", children: "Loading workflow..." })
456
429
  ] }) })
457
430
  );
431
+ if (createRefusal?.kind === "failed")
432
+ return /* @__PURE__ */ jsxs(
433
+ "div",
434
+ {
435
+ role: "alert",
436
+ className: "flex h-full min-h-[60vh] w-full flex-col items-center justify-center gap-3 p-6 text-center",
437
+ children: [
438
+ /* @__PURE__ */ jsx("p", { className: "text-base font-medium text-foreground", children: tr(
439
+ "fluidgrids.builder.workflowNotCreated.title",
440
+ "Workflow not created"
441
+ ) }),
442
+ /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm text-foreground/60", children: tr(
443
+ "fluidgrids.builder.workflowNotCreated.description",
444
+ "The new workflow could not be created, so there is nothing to edit yet."
445
+ ) }),
446
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
447
+ /* @__PURE__ */ jsx(
448
+ "button",
449
+ {
450
+ type: "button",
451
+ className: "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground",
452
+ onClick: () => {
453
+ setCreateRefusal(null);
454
+ setRetryNonce((n) => n + 1);
455
+ },
456
+ children: tr("fluidgrids.builder.workflowNotFound.retry", "Try again")
457
+ }
458
+ ),
459
+ /* @__PURE__ */ jsx(
460
+ "button",
461
+ {
462
+ type: "button",
463
+ className: "rounded-md border px-4 py-2 text-sm font-medium text-foreground",
464
+ onClick: () => navigate("../list", { replace: true }),
465
+ children: tr(
466
+ "fluidgrids.builder.workflowNotFound.back",
467
+ "Back to Workflows"
468
+ )
469
+ }
470
+ )
471
+ ] })
472
+ ]
473
+ }
474
+ );
458
475
  if (notFound)
459
476
  return /* @__PURE__ */ jsxs(
460
477
  "div",
@@ -516,19 +533,17 @@ function WorkflowItem({
516
533
  }
517
534
  ) }),
518
535
  /* @__PURE__ */ jsx(
519
- QuotaExhaustedDialog,
536
+ CreateWorkflowRefusalDialog,
520
537
  {
521
538
  surface: "builder-create",
522
- open: createQuotaExhausted,
523
- onOpenChange: (open) => {
524
- if (open) return;
525
- setCreateQuotaExhausted(false);
539
+ refusal: createRefusal,
540
+ onClose: () => {
541
+ setCreateRefusal(null);
526
542
  if (upgradingRef.current) return;
527
543
  navigate("../list", { replace: true });
528
544
  },
529
545
  onUpgrade: () => {
530
546
  upgradingRef.current = true;
531
- setCreateQuotaExhausted(false);
532
547
  navigate(BILLING_PLANS_PATH);
533
548
  }
534
549
  }