@checkstack/automation-frontend 0.2.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +664 -0
  2. package/package.json +38 -0
  3. package/src/components/AutomationMenuItems.tsx +37 -0
  4. package/src/editor/ActionEditor.tsx +367 -0
  5. package/src/editor/ActionListEditor.tsx +203 -0
  6. package/src/editor/AddActionDialog.tsx +225 -0
  7. package/src/editor/AutomationDefinitionContext.tsx +37 -0
  8. package/src/editor/AutomationDefinitionEditor.tsx +99 -0
  9. package/src/editor/ConditionEditor.tsx +218 -0
  10. package/src/editor/ConditionsEditor.tsx +89 -0
  11. package/src/editor/ItemPicker.tsx +147 -0
  12. package/src/editor/TriggersEditor.tsx +269 -0
  13. package/src/editor/action-composite-cards.tsx +390 -0
  14. package/src/editor/action-helpers.ts +365 -0
  15. package/src/editor/action-leaf-cards.tsx +426 -0
  16. package/src/editor/editor-validation.test.ts +95 -0
  17. package/src/editor/editor-validation.tsx +200 -0
  18. package/src/editor/registry-context.tsx +192 -0
  19. package/src/editor/template-completion.test.ts +412 -0
  20. package/src/editor/template-completion.ts +664 -0
  21. package/src/editor/template-helpers.test.ts +145 -0
  22. package/src/editor/template-helpers.ts +95 -0
  23. package/src/editor/trigger-helpers.test.ts +58 -0
  24. package/src/editor/trigger-helpers.ts +67 -0
  25. package/src/editor/useConnectionOptionResolvers.ts +80 -0
  26. package/src/editor/yaml-markers.ts +116 -0
  27. package/src/index.tsx +95 -0
  28. package/src/pages/AutomationEditPage.tsx +567 -0
  29. package/src/pages/AutomationListPage.tsx +304 -0
  30. package/src/pages/RunDetailPage.tsx +333 -0
  31. package/src/pages/RunsPage.tsx +233 -0
  32. package/src/pages/TemplatePlaygroundPage.tsx +224 -0
  33. package/src/script-context.test.ts +247 -0
  34. package/src/script-context.ts +218 -0
  35. package/tsconfig.json +29 -0
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Generate the `declare const context: …` TypeScript declaration that
3
+ * Monaco's `addExtraLib` consumes to drive IntelliSense inside an
4
+ * inline-script action editor.
5
+ *
6
+ * Built on top of `@checkstack/automation-common`'s `resolveVariableScope`:
7
+ *
8
+ * 1. Resolver returns `VariableEntry[]` rooted at `trigger`, `var`,
9
+ * `artifact`, `repeat`.
10
+ * 2. This module walks those entries (plus the unfiltered registry
11
+ * info) and emits a TypeScript declaration whose top-level shape is
12
+ *
13
+ * declare const context: {
14
+ * trigger:
15
+ * | { event: "incident.created"; payload: { incidentId: string; … } }
16
+ * | { event: "incident.resolved"; payload: { incidentId: string; … } };
17
+ * artifacts: { open_jira: { issue: { key: string; url?: string } }; … };
18
+ * var: { foo: string; count: number; … };
19
+ * repeat: { index: number; item?: unknown };
20
+ * };
21
+ *
22
+ * so Monaco narrows `context.trigger.payload` to the right variant
23
+ * inside `if (context.trigger.event === "incident.created") { … }`.
24
+ *
25
+ * Reuses `jsonSchemaToTypeScript` from `@checkstack/ui` for the per-field
26
+ * type conversion — no duplicated logic; we wrap it with the
27
+ * trigger-union envelope.
28
+ */
29
+ import type {
30
+ ActionPath,
31
+ ArtifactTypeInfo,
32
+ AutomationDefinition,
33
+ TriggerInfo,
34
+ VariableScope,
35
+ } from "@checkstack/automation-common";
36
+ import { deriveTriggerId, resolveVariableScope } from "@checkstack/automation-common";
37
+ // Deep-import the standalone helper rather than going through the
38
+ // `@checkstack/ui` barrel — the barrel pulls in MonacoEditor which
39
+ // side-effect imports Vite-only `?worker` modules, which break bun's
40
+ // test runner. The helper itself has no DOM/Vite dependencies.
41
+ import { jsonSchemaToTypeScript } from "@checkstack/ui/src/components/CodeEditor/generateTypeDefinitions";
42
+
43
+ export interface GenerateAutomationContextTypesInput {
44
+ /** The automation definition being edited. */
45
+ definition: AutomationDefinition;
46
+ /** All triggers registered across the platform (full registry list). */
47
+ triggers: TriggerInfo[];
48
+ /** Optional artifact-type registry — used to type `context.artifacts.*`. */
49
+ artifactTypes?: ArtifactTypeInfo[];
50
+ /**
51
+ * Optional action registry — needed to expose `context.artifacts.*` typed
52
+ * from each upstream action's `produces` field. When omitted, the
53
+ * generator still emits `trigger.*` and `var.*` correctly but leaves
54
+ * `artifacts` as `Record<string, unknown>`.
55
+ */
56
+ actions?: { qualifiedId: string; produces?: string }[];
57
+ /** Path to the action being edited (drives the scope). */
58
+ path: ActionPath;
59
+ }
60
+
61
+ export interface GenerateAutomationContextTypesResult {
62
+ /** The TS declaration string ready to feed to Monaco's `addExtraLib`. */
63
+ typeDefinitions: string;
64
+ /** The resolved scope — handy for the editor to also drive the picker. */
65
+ scope: VariableScope;
66
+ }
67
+
68
+ /**
69
+ * Emit the TS declarations for an inline-script action editor.
70
+ *
71
+ * The output starts with the trigger discriminated union and ends with a
72
+ * single `declare const context: { … }`. The shape matches what
73
+ * `integration-script.run_script` (and any future inline-script action)
74
+ * exposes on `globalThis.context` at runtime — so what the user
75
+ * autocompletes is what they get.
76
+ */
77
+ export function generateAutomationContextTypes(
78
+ input: GenerateAutomationContextTypesInput,
79
+ ): GenerateAutomationContextTypesResult {
80
+ const { definition, triggers, artifactTypes, actions, path } = input;
81
+
82
+ const scope = resolveVariableScope({
83
+ definition,
84
+ triggers,
85
+ actions: actions?.map((a) => ({
86
+ qualifiedId: a.qualifiedId,
87
+ displayName: a.qualifiedId,
88
+ category: "Uncategorized",
89
+ // Derive the owning plugin id from the qualified action id (the part
90
+ // before the first dot) so the resolver can strip it off `produces`
91
+ // to recover the local artifact name (e.g. `integration-jira.issue`
92
+ // -> `issue`). An unqualified id yields an empty owner, leaving the
93
+ // produces id intact.
94
+ ownerPluginId: a.qualifiedId.includes(".")
95
+ ? a.qualifiedId.slice(0, a.qualifiedId.indexOf("."))
96
+ : "",
97
+ configSchema: {},
98
+ produces: a.produces,
99
+ consumes: [],
100
+ })) ?? [],
101
+ artifactTypes: artifactTypes ?? [],
102
+ path,
103
+ });
104
+
105
+ // ─── Trigger union ─────────────────────────────────────────────────────
106
+ // Each variant carries the trigger's `id` (explicit or derived) and `event`,
107
+ // both literal types, so a script can discriminate on either - including two
108
+ // triggers that share the same event but have distinct ids.
109
+ const triggerUnion =
110
+ definition.triggers.length === 0
111
+ ? "{ readonly id: string; readonly event: string; readonly payload: Record<string, unknown> }"
112
+ : definition.triggers
113
+ .map((t) => {
114
+ const info = triggers.find((r) => r.qualifiedId === t.event);
115
+ const id = t.id ?? deriveTriggerId(t.event);
116
+ const payloadType = info
117
+ ? jsonSchemaToTypeScript(
118
+ info.payloadSchema as Parameters<
119
+ typeof jsonSchemaToTypeScript
120
+ >[0],
121
+ )
122
+ : "Record<string, unknown>";
123
+ return `{ readonly id: ${JSON.stringify(id)}; readonly event: ${JSON.stringify(t.event)}; readonly payload: ${payloadType} }`;
124
+ })
125
+ .join("\n | ");
126
+
127
+ // ─── Artifacts map ─────────────────────────────────────────────────────
128
+ // The resolver nests artifacts as `artifact.<actionId>.<localName>`, mirroring
129
+ // the runtime `artifacts.<actionId>.<localName>.<field>` shape. Each top-level
130
+ // child is an action-id node; its own children are the produced artifact(s)
131
+ // keyed by local name, each carrying the artifact's JSON Schema.
132
+ const artifactIdNodes = scope.entries.find((e) => e.path === "artifact")
133
+ ?.children;
134
+ const artifactsType =
135
+ !artifactIdNodes || artifactIdNodes.length === 0
136
+ ? "Record<string, unknown>"
137
+ : `{\n${artifactIdNodes
138
+ .map((idNode) => {
139
+ const actionId = idNode.path.split(".").pop() ?? idNode.path;
140
+ const localNodes = idNode.children ?? [];
141
+ const inner =
142
+ localNodes.length === 0
143
+ ? "Record<string, unknown>"
144
+ : `{\n${localNodes
145
+ .map((localNode) => {
146
+ const localName =
147
+ localNode.path.split(".").pop() ?? localNode.path;
148
+ const dataType = localNode.jsonSchema
149
+ ? jsonSchemaToTypeScript(
150
+ localNode.jsonSchema as Parameters<
151
+ typeof jsonSchemaToTypeScript
152
+ >[0],
153
+ )
154
+ : "unknown";
155
+ return ` readonly ${JSON.stringify(localName)}: ${dataType};`;
156
+ })
157
+ .join("\n")}\n }`;
158
+ return ` readonly ${JSON.stringify(actionId)}: ${inner};`;
159
+ })
160
+ .join("\n")}\n}`;
161
+
162
+ // ─── var bag ──────────────────────────────────────────────────────────
163
+ const varEntries =
164
+ scope.entries.find((e) => e.path === "var")?.children ?? [];
165
+ const varType =
166
+ varEntries.length === 0
167
+ ? "Record<string, unknown>"
168
+ : `{\n${varEntries
169
+ .map((entry) => {
170
+ const name = entry.path.slice("var.".length);
171
+ return ` readonly ${JSON.stringify(name)}?: ${entry.type === "string | template" ? "string" : entry.type === "number" ? "number" : entry.type === "boolean" ? "boolean" : "unknown"};`;
172
+ })
173
+ .join("\n")}\n}`;
174
+
175
+ // ─── repeat scope ──────────────────────────────────────────────────────
176
+ const repeatEntries =
177
+ scope.entries.find((e) => e.path === "repeat")?.children ?? [];
178
+ const hasRepeat = repeatEntries.length > 0;
179
+ const hasForEach = repeatEntries.some((e) => e.path === "repeat.item");
180
+ const repeatLine = hasRepeat
181
+ ? ` readonly repeat: { readonly index: number;${hasForEach ? " readonly item: unknown;" : ""} };`
182
+ : null;
183
+
184
+ // ─── Compose ───────────────────────────────────────────────────────────
185
+ const lines: Array<string | null> = [
186
+ "/**",
187
+ " * Auto-generated automation runtime context. Reflects the triggers",
188
+ " * subscribed by this automation and the artifacts / variables in",
189
+ " * scope at the action position being edited.",
190
+ " */",
191
+ "",
192
+ "/** Who or what caused the event (present on every trigger). */",
193
+ 'type AutomationActor = { readonly type: "system" | "user" | "application" | "service"; readonly id: string; readonly name?: string };',
194
+ "",
195
+ "/** Trigger that fired this run. */",
196
+ `type AutomationTrigger = (\n | ${triggerUnion}\n) & { readonly actor: AutomationActor };`,
197
+ "",
198
+ "declare const context: {",
199
+ " readonly trigger: AutomationTrigger;",
200
+ ` readonly artifacts: ${indent(artifactsType, " ")};`,
201
+ ` readonly var: ${indent(varType, " ")};`,
202
+ repeatLine,
203
+ " readonly automation: { readonly runId: string; readonly automationId: string; readonly contextKey: string | null };",
204
+ "};",
205
+ ];
206
+
207
+ return {
208
+ typeDefinitions: lines.filter((l): l is string => l !== null).join("\n"),
209
+ scope,
210
+ };
211
+ }
212
+
213
+ function indent(input: string, pad: string): string {
214
+ return input
215
+ .split("\n")
216
+ .map((line, i) => (i === 0 ? line : `${pad}${line}`))
217
+ .join("\n");
218
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "extends": "@checkstack/tsconfig/frontend.json",
3
+ "include": [
4
+ "src"
5
+ ],
6
+ "references": [
7
+ {
8
+ "path": "../automation-common"
9
+ },
10
+ {
11
+ "path": "../common"
12
+ },
13
+ {
14
+ "path": "../frontend-api"
15
+ },
16
+ {
17
+ "path": "../integration-common"
18
+ },
19
+ {
20
+ "path": "../signal-frontend"
21
+ },
22
+ {
23
+ "path": "../template-engine"
24
+ },
25
+ {
26
+ "path": "../ui"
27
+ }
28
+ ]
29
+ }