@checkstack/automation-frontend 0.2.0 → 0.4.0
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/CHANGELOG.md +496 -0
- package/package.json +18 -13
- package/src/components/AutomationGroupCombobox.tsx +133 -0
- package/src/components/RunAsServiceAccountPicker.tsx +170 -0
- package/src/editor/ActionEditor.tsx +180 -90
- package/src/editor/ActionListEditor.tsx +27 -1
- package/src/editor/AddActionDialog.tsx +15 -45
- package/src/editor/AddConditionDialog.tsx +86 -0
- package/src/editor/AddTriggerDialog.tsx +97 -0
- package/src/editor/AutomationDefinitionEditor.tsx +41 -2
- package/src/editor/ConditionEditor.tsx +359 -70
- package/src/editor/ConditionsEditor.tsx +113 -44
- package/src/editor/ItemSheet.tsx +51 -0
- package/src/editor/RunReplayPicker.tsx +97 -0
- package/src/editor/ScriptServicesBooter.tsx +57 -0
- package/src/editor/ScriptTestRenderer.tsx +150 -0
- package/src/editor/SystemEntityPicker.test.ts +37 -0
- package/src/editor/SystemEntityPicker.tsx +109 -0
- package/src/editor/TriggersEditor.tsx +345 -137
- package/src/editor/action-helpers.test.ts +107 -0
- package/src/editor/action-helpers.ts +72 -0
- package/src/editor/action-leaf-cards.tsx +105 -1
- package/src/editor/condition-kind.test.ts +126 -0
- package/src/editor/condition-kind.ts +130 -0
- package/src/editor/item-summary.test.ts +171 -0
- package/src/editor/item-summary.ts +210 -0
- package/src/editor/picker-dialog.tsx +156 -0
- package/src/editor/registry-context.tsx +9 -2
- package/src/editor/script-actions.test.ts +184 -0
- package/src/editor/script-actions.ts +146 -0
- package/src/editor/system-entity-picker.logic.ts +23 -0
- package/src/editor/template-completion.test.ts +22 -3
- package/src/editor/template-completion.ts +16 -8
- package/src/editor/template-helpers.ts +4 -0
- package/src/editor/trigger-helpers.test.ts +28 -0
- package/src/editor/trigger-helpers.ts +17 -0
- package/src/editor/useScriptDiagnostics.ts +108 -0
- package/src/index.tsx +26 -24
- package/src/pages/AutomationEditPage.tsx +116 -48
- package/src/pages/AutomationListPage.tsx +172 -123
- package/src/pages/automation-grouping.test.ts +87 -0
- package/src/pages/automation-grouping.ts +65 -0
- package/src/script-context.test.ts +142 -1
- package/src/script-context.ts +115 -0
- package/tsconfig.json +15 -0
- package/src/components/AutomationMenuItems.tsx +0 -37
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
import {
|
|
23
23
|
assignDefaultIds,
|
|
24
24
|
collectActionIds,
|
|
25
|
+
duplicateAction,
|
|
25
26
|
makeEmptyAction,
|
|
26
27
|
makeProviderAction,
|
|
27
28
|
} from "./action-helpers";
|
|
@@ -128,6 +129,28 @@ export const ActionListEditor: React.FC<ActionListEditorProps> = ({
|
|
|
128
129
|
setIds((current) => [...current, nextId()]);
|
|
129
130
|
};
|
|
130
131
|
|
|
132
|
+
const duplicateStep = (index: number): void => {
|
|
133
|
+
const original = value[index];
|
|
134
|
+
if (!original) return;
|
|
135
|
+
// Clone with FRESH ids deduped against every id in the automation so the
|
|
136
|
+
// copy never collides with the original (or any sibling/nested step).
|
|
137
|
+
const taken = collectActionIds(definition.actions);
|
|
138
|
+
const clone = duplicateAction(original, taken);
|
|
139
|
+
// Insert directly after the original, keeping the parallel `ids` array in
|
|
140
|
+
// lockstep so each card's stable React key / drag id stays aligned.
|
|
141
|
+
const nextValue = [
|
|
142
|
+
...value.slice(0, index + 1),
|
|
143
|
+
clone,
|
|
144
|
+
...value.slice(index + 1),
|
|
145
|
+
];
|
|
146
|
+
onChange(nextValue);
|
|
147
|
+
setIds((current) => [
|
|
148
|
+
...current.slice(0, index + 1),
|
|
149
|
+
nextId(),
|
|
150
|
+
...current.slice(index + 1),
|
|
151
|
+
]);
|
|
152
|
+
};
|
|
153
|
+
|
|
131
154
|
return (
|
|
132
155
|
<div className="space-y-2">
|
|
133
156
|
<DndContext
|
|
@@ -150,6 +173,7 @@ export const ActionListEditor: React.FC<ActionListEditorProps> = ({
|
|
|
150
173
|
onChange(value.filter((_, i) => i !== index));
|
|
151
174
|
setIds((current) => current.filter((_, i) => i !== index));
|
|
152
175
|
}}
|
|
176
|
+
onDuplicate={disabled ? undefined : () => duplicateStep(index)}
|
|
153
177
|
path={childPathAt(index)}
|
|
154
178
|
definition={definition}
|
|
155
179
|
disabled={disabled}
|
|
@@ -174,10 +198,11 @@ const SortableActionItem: React.FC<{
|
|
|
174
198
|
value: ActionInput;
|
|
175
199
|
onChange: (next: ActionInput) => void;
|
|
176
200
|
onDelete: () => void;
|
|
201
|
+
onDuplicate?: () => void;
|
|
177
202
|
path: ActionPath;
|
|
178
203
|
definition: Parameters<typeof ActionEditor>[0]["definition"];
|
|
179
204
|
disabled?: boolean;
|
|
180
|
-
}> = ({ id, value, onChange, onDelete, path, definition, disabled }) => {
|
|
205
|
+
}> = ({ id, value, onChange, onDelete, onDuplicate, path, definition, disabled }) => {
|
|
181
206
|
const sortable = useSortable({ id });
|
|
182
207
|
const style: React.CSSProperties = {
|
|
183
208
|
transform: CSS.Transform.toString(sortable.transform),
|
|
@@ -189,6 +214,7 @@ const SortableActionItem: React.FC<{
|
|
|
189
214
|
value={value}
|
|
190
215
|
onChange={onChange}
|
|
191
216
|
onDelete={onDelete}
|
|
217
|
+
onDuplicate={onDuplicate}
|
|
192
218
|
path={path}
|
|
193
219
|
definition={definition}
|
|
194
220
|
stableId={id}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { Zap } from "lucide-react";
|
|
3
3
|
import {
|
|
4
|
-
Button,
|
|
5
4
|
Dialog,
|
|
6
5
|
DialogContent,
|
|
7
6
|
DialogHeader,
|
|
8
7
|
DialogTitle,
|
|
9
8
|
DynamicIcon,
|
|
10
|
-
Input,
|
|
11
9
|
TabPanel,
|
|
12
10
|
Tabs,
|
|
13
11
|
} from "@checkstack/ui";
|
|
14
12
|
import { ACTION_KIND_META, type ActionKind } from "./action-helpers";
|
|
13
|
+
import {
|
|
14
|
+
PickerAddButton,
|
|
15
|
+
PickerRow,
|
|
16
|
+
PickerSearchInput,
|
|
17
|
+
} from "./picker-dialog";
|
|
15
18
|
import { useAutomationRegistry } from "./registry-context";
|
|
16
19
|
|
|
17
20
|
/**
|
|
@@ -30,6 +33,7 @@ const BLOCK_KINDS: ActionKind[] = [
|
|
|
30
33
|
"stop",
|
|
31
34
|
"variables",
|
|
32
35
|
"wait_for_trigger",
|
|
36
|
+
"wait_until",
|
|
33
37
|
];
|
|
34
38
|
|
|
35
39
|
const TAB_ITEMS = [
|
|
@@ -37,29 +41,6 @@ const TAB_ITEMS = [
|
|
|
37
41
|
{ id: "blocks", label: "Blocks" },
|
|
38
42
|
];
|
|
39
43
|
|
|
40
|
-
/** Shared row used by both tabs — icon, name, description, and a `+` affordance. */
|
|
41
|
-
const PickerRow: React.FC<{
|
|
42
|
-
icon: React.ReactNode;
|
|
43
|
-
title: string;
|
|
44
|
-
description?: string;
|
|
45
|
-
onClick: () => void;
|
|
46
|
-
}> = ({ icon, title, description, onClick }) => (
|
|
47
|
-
<button
|
|
48
|
-
type="button"
|
|
49
|
-
onClick={onClick}
|
|
50
|
-
className="flex w-full items-start gap-3 rounded-md border border-border/60 bg-card px-3 py-2.5 text-left transition-colors hover:border-border hover:bg-accent/50"
|
|
51
|
-
>
|
|
52
|
-
<span className="mt-0.5 shrink-0 text-muted-foreground">{icon}</span>
|
|
53
|
-
<div className="min-w-0 flex-1">
|
|
54
|
-
<div className="text-sm font-medium">{title}</div>
|
|
55
|
-
{description && (
|
|
56
|
-
<div className="text-xs text-muted-foreground">{description}</div>
|
|
57
|
-
)}
|
|
58
|
-
</div>
|
|
59
|
-
<Plus className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
60
|
-
</button>
|
|
61
|
-
);
|
|
62
|
-
|
|
63
44
|
export interface AddActionDialogProps {
|
|
64
45
|
/** Insert a building-block step of the given kind. */
|
|
65
46
|
onAddKind: (kind: ActionKind) => void;
|
|
@@ -128,17 +109,11 @@ export const AddActionDialog: React.FC<AddActionDialogProps> = ({
|
|
|
128
109
|
|
|
129
110
|
return (
|
|
130
111
|
<>
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
variant="outline"
|
|
134
|
-
size="sm"
|
|
112
|
+
<PickerAddButton
|
|
113
|
+
label="Add step"
|
|
135
114
|
disabled={disabled}
|
|
136
|
-
className="h-7 text-xs"
|
|
137
115
|
onClick={() => setOpen(true)}
|
|
138
|
-
|
|
139
|
-
<Plus className="mr-1 h-3 w-3" />
|
|
140
|
-
Add step
|
|
141
|
-
</Button>
|
|
116
|
+
/>
|
|
142
117
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
143
118
|
<DialogContent size="lg">
|
|
144
119
|
<DialogHeader>
|
|
@@ -146,16 +121,11 @@ export const AddActionDialog: React.FC<AddActionDialogProps> = ({
|
|
|
146
121
|
</DialogHeader>
|
|
147
122
|
|
|
148
123
|
<div className="space-y-3">
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
onChange={(event) => setQuery(event.target.value)}
|
|
155
|
-
placeholder="Search actions and blocks…"
|
|
156
|
-
className="pl-9"
|
|
157
|
-
/>
|
|
158
|
-
</div>
|
|
124
|
+
<PickerSearchInput
|
|
125
|
+
value={query}
|
|
126
|
+
onChange={setQuery}
|
|
127
|
+
placeholder="Search actions and blocks…"
|
|
128
|
+
/>
|
|
159
129
|
|
|
160
130
|
<Tabs items={TAB_ITEMS} activeTab={tab} onTabChange={setTab} />
|
|
161
131
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DynamicIcon } from "@checkstack/ui";
|
|
3
|
+
import {
|
|
4
|
+
CONDITION_KIND_META,
|
|
5
|
+
type ConditionKind,
|
|
6
|
+
type ConditionKindGroup,
|
|
7
|
+
} from "./condition-kind";
|
|
8
|
+
import { PickerDialogShell, PickerRow } from "./picker-dialog";
|
|
9
|
+
|
|
10
|
+
export interface AddConditionDialogProps {
|
|
11
|
+
/** Append a condition of the picked kind (seeded via `defaultForKind`). */
|
|
12
|
+
onAdd: (kind: ConditionKind) => void;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Picker group order — structured first (common case), advanced last. */
|
|
17
|
+
const GROUP_ORDER: ConditionKindGroup[] = ["Structured", "Logical", "Advanced"];
|
|
18
|
+
|
|
19
|
+
const ALL_KINDS = Object.values(CONDITION_KIND_META);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Home-Assistant-style condition type picker. The operator picks the condition
|
|
23
|
+
* kind up front (grouped by `CONDITION_KIND_META.group`, searchable on label /
|
|
24
|
+
* description); on pick the consumer (`ConditionsEditor`) appends a seeded
|
|
25
|
+
* default for that kind. Mirrors the Actions "Add step" picker but is
|
|
26
|
+
* single-list.
|
|
27
|
+
*/
|
|
28
|
+
export const AddConditionDialog: React.FC<AddConditionDialogProps> = ({
|
|
29
|
+
onAdd,
|
|
30
|
+
disabled,
|
|
31
|
+
}) => (
|
|
32
|
+
<PickerDialogShell
|
|
33
|
+
addLabel="Add condition"
|
|
34
|
+
title="Add condition"
|
|
35
|
+
searchPlaceholder="Search conditions…"
|
|
36
|
+
disabled={disabled}
|
|
37
|
+
>
|
|
38
|
+
{({ query, close }) => {
|
|
39
|
+
const filtered = query
|
|
40
|
+
? ALL_KINDS.filter(
|
|
41
|
+
(meta) =>
|
|
42
|
+
meta.label.toLowerCase().includes(query) ||
|
|
43
|
+
meta.description.toLowerCase().includes(query) ||
|
|
44
|
+
meta.kind.toLowerCase().includes(query),
|
|
45
|
+
)
|
|
46
|
+
: ALL_KINDS;
|
|
47
|
+
|
|
48
|
+
const grouped = GROUP_ORDER.map((group) => ({
|
|
49
|
+
group,
|
|
50
|
+
items: filtered.filter((meta) => meta.group === group),
|
|
51
|
+
})).filter(({ items }) => items.length > 0);
|
|
52
|
+
|
|
53
|
+
if (grouped.length === 0) {
|
|
54
|
+
return (
|
|
55
|
+
<p className="px-1 py-6 text-center text-xs italic text-muted-foreground">
|
|
56
|
+
No matching conditions.
|
|
57
|
+
</p>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="space-y-3">
|
|
63
|
+
{grouped.map(({ group, items }) => (
|
|
64
|
+
<div key={group} className="space-y-1">
|
|
65
|
+
<p className="px-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
66
|
+
{group}
|
|
67
|
+
</p>
|
|
68
|
+
{items.map((meta) => (
|
|
69
|
+
<PickerRow
|
|
70
|
+
key={meta.kind}
|
|
71
|
+
icon={<DynamicIcon name={meta.icon} className="h-4 w-4" />}
|
|
72
|
+
title={meta.label}
|
|
73
|
+
description={meta.description}
|
|
74
|
+
onClick={() => {
|
|
75
|
+
onAdd(meta.kind);
|
|
76
|
+
close();
|
|
77
|
+
}}
|
|
78
|
+
/>
|
|
79
|
+
))}
|
|
80
|
+
</div>
|
|
81
|
+
))}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}}
|
|
85
|
+
</PickerDialogShell>
|
|
86
|
+
);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Zap } from "lucide-react";
|
|
3
|
+
import { useAutomationRegistry } from "./registry-context";
|
|
4
|
+
import { PickerDialogShell, PickerRow } from "./picker-dialog";
|
|
5
|
+
|
|
6
|
+
export interface AddTriggerDialogProps {
|
|
7
|
+
/** Append a trigger subscribed to the picked event's qualified id. */
|
|
8
|
+
onAdd: (event: string) => void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Home-Assistant-style trigger type picker. The operator picks a concrete
|
|
14
|
+
* trigger event (grouped by category, searchable) up front; on pick the
|
|
15
|
+
* consumer (`TriggersEditor`) creates the trigger with a unique default id and
|
|
16
|
+
* appends it. Mirrors the Actions "Add step" picker but is single-list.
|
|
17
|
+
*/
|
|
18
|
+
export const AddTriggerDialog: React.FC<AddTriggerDialogProps> = ({
|
|
19
|
+
onAdd,
|
|
20
|
+
disabled,
|
|
21
|
+
}) => {
|
|
22
|
+
const { triggers } = useAutomationRegistry();
|
|
23
|
+
|
|
24
|
+
const items = React.useMemo(
|
|
25
|
+
() =>
|
|
26
|
+
triggers.map((t) => ({
|
|
27
|
+
id: t.qualifiedId,
|
|
28
|
+
label: t.displayName,
|
|
29
|
+
description: t.description,
|
|
30
|
+
category: t.category,
|
|
31
|
+
})),
|
|
32
|
+
[triggers],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<PickerDialogShell
|
|
37
|
+
addLabel="Add trigger"
|
|
38
|
+
title="Add trigger"
|
|
39
|
+
searchPlaceholder="Search triggers…"
|
|
40
|
+
disabled={disabled}
|
|
41
|
+
>
|
|
42
|
+
{({ query, close }) => {
|
|
43
|
+
const filtered = query
|
|
44
|
+
? items.filter(
|
|
45
|
+
(item) =>
|
|
46
|
+
item.label.toLowerCase().includes(query) ||
|
|
47
|
+
item.id.toLowerCase().includes(query) ||
|
|
48
|
+
item.description?.toLowerCase().includes(query) ||
|
|
49
|
+
item.category.toLowerCase().includes(query),
|
|
50
|
+
)
|
|
51
|
+
: items;
|
|
52
|
+
|
|
53
|
+
const groups = new Map<string, typeof filtered>();
|
|
54
|
+
for (const item of filtered) {
|
|
55
|
+
const list = groups.get(item.category) ?? [];
|
|
56
|
+
list.push(item);
|
|
57
|
+
groups.set(item.category, list);
|
|
58
|
+
}
|
|
59
|
+
const grouped = [...groups.entries()].toSorted(([a], [b]) =>
|
|
60
|
+
a.localeCompare(b),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (grouped.length === 0) {
|
|
64
|
+
return (
|
|
65
|
+
<p className="px-1 py-6 text-center text-xs italic text-muted-foreground">
|
|
66
|
+
No matching triggers.
|
|
67
|
+
</p>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="space-y-3">
|
|
73
|
+
{grouped.map(([category, list]) => (
|
|
74
|
+
<div key={category} className="space-y-1">
|
|
75
|
+
<p className="px-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
76
|
+
{category}
|
|
77
|
+
</p>
|
|
78
|
+
{list.map((item) => (
|
|
79
|
+
<PickerRow
|
|
80
|
+
key={item.id}
|
|
81
|
+
icon={<Zap className="h-4 w-4" />}
|
|
82
|
+
title={item.label}
|
|
83
|
+
description={item.description}
|
|
84
|
+
onClick={() => {
|
|
85
|
+
onAdd(item.id);
|
|
86
|
+
close();
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}}
|
|
95
|
+
</PickerDialogShell>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
@@ -12,11 +12,29 @@ import {
|
|
|
12
12
|
import { TriggersEditor } from "./TriggersEditor";
|
|
13
13
|
import { ConditionsEditor } from "./ConditionsEditor";
|
|
14
14
|
import { ActionListEditor } from "./ActionListEditor";
|
|
15
|
+
import { ValidationProvider, type DefinitionIssue } from "./editor-validation";
|
|
16
|
+
import { useScriptDiagnostics } from "./useScriptDiagnostics";
|
|
17
|
+
import { collectScriptActions } from "./script-actions";
|
|
18
|
+
import { ScriptServicesBooter } from "./ScriptServicesBooter";
|
|
15
19
|
|
|
16
20
|
export interface AutomationDefinitionEditorProps {
|
|
17
21
|
value: AutomationDefinition;
|
|
18
22
|
onChange: (next: AutomationDefinition) => void;
|
|
19
23
|
disabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Id of the automation being edited, if it already exists (omitted for
|
|
26
|
+
* a brand-new, unsaved automation). Enables the script-test "Load from
|
|
27
|
+
* run" picker, which lists this automation's prior runs.
|
|
28
|
+
*/
|
|
29
|
+
automationId?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Structural validation issues (from the backend `validateDefinition`),
|
|
32
|
+
* merged here with the live, frontend-computed inline-script TYPE issues so
|
|
33
|
+
* both surface through the same per-card badges. Script type-checking lives
|
|
34
|
+
* inside this component because it needs the registry (trigger/action
|
|
35
|
+
* catalog) the `AutomationRegistryProvider` supplies.
|
|
36
|
+
*/
|
|
37
|
+
structuralIssues?: DefinitionIssue[];
|
|
20
38
|
}
|
|
21
39
|
|
|
22
40
|
/**
|
|
@@ -31,7 +49,7 @@ export interface AutomationDefinitionEditorProps {
|
|
|
31
49
|
export const AutomationDefinitionEditor: React.FC<
|
|
32
50
|
AutomationDefinitionEditorProps
|
|
33
51
|
> = (props) => (
|
|
34
|
-
<AutomationRegistryProvider>
|
|
52
|
+
<AutomationRegistryProvider automationId={props.automationId}>
|
|
35
53
|
<AutomationDefinitionProvider definition={props.value}>
|
|
36
54
|
<EditorBody {...props} />
|
|
37
55
|
</AutomationDefinitionProvider>
|
|
@@ -42,8 +60,26 @@ const EditorBody: React.FC<AutomationDefinitionEditorProps> = ({
|
|
|
42
60
|
value,
|
|
43
61
|
onChange,
|
|
44
62
|
disabled,
|
|
63
|
+
structuralIssues = [],
|
|
45
64
|
}) => {
|
|
46
|
-
const { loading } = useAutomationRegistry();
|
|
65
|
+
const { loading, actions } = useAutomationRegistry();
|
|
66
|
+
|
|
67
|
+
// Live type-check of every inline script action (collapsed cards included),
|
|
68
|
+
// merged with the backend's structural issues so both drive the same
|
|
69
|
+
// per-card badges via `ValidationProvider`.
|
|
70
|
+
const scriptIssues = useScriptDiagnostics({ definition: value });
|
|
71
|
+
const issues = React.useMemo(
|
|
72
|
+
() => [...structuralIssues, ...scriptIssues],
|
|
73
|
+
[structuralIssues, scriptIssues],
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// When the automation already contains inline scripts, boot the editor
|
|
77
|
+
// services up-front (a hidden editor) so those scripts validate the moment
|
|
78
|
+
// the automation opens - not only after a card is expanded.
|
|
79
|
+
const hasScriptActions = React.useMemo(
|
|
80
|
+
() => collectScriptActions({ definition: value, actions }).length > 0,
|
|
81
|
+
[value, actions],
|
|
82
|
+
);
|
|
47
83
|
|
|
48
84
|
// Scope at the root path = `trigger.*` only. The conditions editor
|
|
49
85
|
// uses `variableNodes` for the explicit "fx" tree and
|
|
@@ -55,6 +91,8 @@ const EditorBody: React.FC<AutomationDefinitionEditorProps> = ({
|
|
|
55
91
|
});
|
|
56
92
|
|
|
57
93
|
return (
|
|
94
|
+
<ValidationProvider issues={issues}>
|
|
95
|
+
{hasScriptActions && <ScriptServicesBooter />}
|
|
58
96
|
<div className="space-y-4">
|
|
59
97
|
{loading && (
|
|
60
98
|
<Card className="border-dashed">
|
|
@@ -95,5 +133,6 @@ const EditorBody: React.FC<AutomationDefinitionEditorProps> = ({
|
|
|
95
133
|
</CardContent>
|
|
96
134
|
</Card>
|
|
97
135
|
</div>
|
|
136
|
+
</ValidationProvider>
|
|
98
137
|
);
|
|
99
138
|
};
|