@revenexx/studio-integrations 1.4.1 → 2.1.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 (80) hide show
  1. package/CHANGELOG.md +297 -0
  2. package/README.md +2 -1
  3. package/dist/module.json +1 -1
  4. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +6 -6
  5. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +134 -131
  6. package/dist/runtime/components/Integrations/CredentialLogo.vue +1 -1
  7. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +1 -1
  8. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +6 -6
  9. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +2 -0
  10. package/dist/runtime/components/Integrations/ExpressionInput.vue +90 -11
  11. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +2 -0
  12. package/dist/runtime/components/Integrations/GroupFrameView.vue +121 -121
  13. package/dist/runtime/components/Integrations/NodeConfigField.vue +20 -5
  14. package/dist/runtime/components/Integrations/NodeInspector.vue +44 -41
  15. package/dist/runtime/components/Integrations/NodePalette.vue +52 -37
  16. package/dist/runtime/components/Integrations/RunActionsMenu.vue +6 -5
  17. package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +7 -0
  18. package/dist/runtime/components/Integrations/RunDetailPanel.vue +14 -2
  19. package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +7 -0
  20. package/dist/runtime/components/Integrations/RunsTable.vue +72 -70
  21. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +20 -6
  22. package/dist/runtime/components/Integrations/StickyNoteView.vue +1 -0
  23. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +10 -6
  24. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +0 -34
  25. package/dist/runtime/components/Integrations/WorkflowEditor.vue +226 -277
  26. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +0 -34
  27. package/dist/runtime/components/Integrations/WorkflowExportDialog.d.vue.ts +6 -0
  28. package/dist/runtime/components/Integrations/WorkflowExportDialog.vue +71 -0
  29. package/dist/runtime/components/Integrations/WorkflowExportDialog.vue.d.ts +6 -0
  30. package/dist/runtime/components/Integrations/WorkflowImportFileStep.d.vue.ts +8 -0
  31. package/dist/runtime/components/Integrations/WorkflowImportFileStep.vue +95 -0
  32. package/dist/runtime/components/Integrations/WorkflowImportFileStep.vue.d.ts +8 -0
  33. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +5 -4
  34. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +0 -7
  35. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +2 -2
  36. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +2 -3
  37. package/dist/runtime/components/Integrations/WorkflowsTable.vue +14 -8
  38. package/dist/runtime/composables/useIntegrationsNodes.js +4 -1
  39. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +3 -1
  40. package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -22
  41. package/dist/runtime/composables/useNow.d.ts +17 -0
  42. package/dist/runtime/composables/useNow.js +16 -0
  43. package/dist/runtime/composables/useWorkflowFile.d.ts +21 -0
  44. package/dist/runtime/composables/useWorkflowFile.js +63 -0
  45. package/dist/runtime/data/expressionEditor.d.ts +11 -1
  46. package/dist/runtime/data/expressionEditor.js +2 -1
  47. package/dist/runtime/data/nodeIcons.d.ts +14 -0
  48. package/dist/runtime/data/nodeIcons.js +57 -14
  49. package/dist/runtime/data/nodeRunData.js +1 -1
  50. package/dist/runtime/data/payloadForm.js +1 -1
  51. package/dist/runtime/i18n/messages/de.json +47 -11
  52. package/dist/runtime/i18n/messages/en.json +47 -11
  53. package/dist/runtime/pages/integrations/audit.vue +3 -3
  54. package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +2 -2
  55. package/dist/runtime/pages/integrations/credentials.vue +17 -5
  56. package/dist/runtime/pages/integrations/index.vue +5 -10
  57. package/dist/runtime/pages/integrations/schedules.vue +24 -13
  58. package/dist/runtime/pages/integrations/templates/index.vue +40 -40
  59. package/dist/runtime/pages/integrations/workflows/[id].vue +324 -324
  60. package/dist/runtime/types/integrations.d.ts +13 -0
  61. package/dist/runtime/utils/cockpitSources.d.ts +68 -1
  62. package/dist/runtime/utils/credentialState.d.ts +15 -7
  63. package/dist/runtime/utils/credentialState.js +4 -12
  64. package/dist/runtime/utils/localize.d.ts +12 -0
  65. package/dist/runtime/utils/localize.js +1 -1
  66. package/dist/runtime/utils/paletteOrder.d.ts +29 -0
  67. package/dist/runtime/utils/paletteOrder.js +46 -0
  68. package/dist/runtime/utils/relativeTime.d.ts +29 -0
  69. package/dist/runtime/utils/relativeTime.js +23 -0
  70. package/dist/runtime/utils/runFeedQuery.d.ts +12 -6
  71. package/dist/runtime/utils/runFeedQuery.js +3 -18
  72. package/dist/runtime/utils/settingValue.d.ts +33 -0
  73. package/dist/runtime/utils/settingValue.js +8 -0
  74. package/dist/runtime/utils/statusStyle.d.ts +8 -8
  75. package/dist/runtime/utils/statusStyle.js +4 -1
  76. package/dist/runtime/utils/workflowFile.d.ts +119 -0
  77. package/dist/runtime/utils/workflowFile.js +192 -0
  78. package/package.json +10 -5
  79. package/dist/runtime/utils/errorPathAdvice.d.ts +0 -60
  80. package/dist/runtime/utils/errorPathAdvice.js +0 -19
@@ -0,0 +1,192 @@
1
+ import { deepClone } from "./deepClone.js";
2
+ export const WORKFLOW_FILE_KIND = "revenexx.workflow";
3
+ export const WORKFLOW_FILE_VERSION = 1;
4
+ const BLOB_VERSION = "v0-draft";
5
+ export function withoutCredentials(blob, fieldsFor, credentialIds = /* @__PURE__ */ new Set()) {
6
+ const out = deepClone(blob);
7
+ for (const node of out.nodes ?? []) {
8
+ if (!node.config || typeof node.config !== "object") continue;
9
+ const fields = fieldsFor(node);
10
+ if (fields) stripDeclared(fields, node.config);
11
+ if (credentialIds.size) node.config = stripValues(node.config, credentialIds);
12
+ }
13
+ return out;
14
+ }
15
+ function stripDeclared(fields, values) {
16
+ for (const field of fields) {
17
+ if (!field?.key || !(field.key in values)) continue;
18
+ const value = values[field.key];
19
+ if (field.type === "credentials-ref") {
20
+ delete values[field.key];
21
+ } else if (field.type === "object" && field.properties && isRecord(value)) {
22
+ stripDeclared(field.properties, value);
23
+ } else if (field.type === "array" && field.items && Array.isArray(value)) {
24
+ const items = field.items;
25
+ if (items.type === "credentials-ref") {
26
+ values[field.key] = [];
27
+ } else if (items.type === "object" && items.properties) {
28
+ for (const row of value) if (isRecord(row)) stripDeclared(items.properties, row);
29
+ }
30
+ }
31
+ }
32
+ }
33
+ function stripValues(value, ids) {
34
+ if (Array.isArray(value)) {
35
+ return value.filter((v) => !(typeof v === "string" && ids.has(v))).map((v) => stripValues(v, ids));
36
+ }
37
+ if (isRecord(value)) {
38
+ const out = {};
39
+ for (const [k, v] of Object.entries(value)) {
40
+ if (typeof v === "string" && ids.has(v)) continue;
41
+ out[k] = stripValues(v, ids);
42
+ }
43
+ return out;
44
+ }
45
+ return value;
46
+ }
47
+ function isRecord(v) {
48
+ return !!v && typeof v === "object" && !Array.isArray(v);
49
+ }
50
+ const ENVIRONMENT_TRIGGER_KEYS = ["inbound", "secretRef"];
51
+ function withoutEnvironment(trigger) {
52
+ const config = { ...trigger.config ?? {} };
53
+ for (const key of ENVIRONMENT_TRIGGER_KEYS) delete config[key];
54
+ return { ...trigger, config };
55
+ }
56
+ export function toWorkflowFile(wf, fieldsFor, credentialIds) {
57
+ const blob = withoutCredentials(wf.blob, fieldsFor, credentialIds);
58
+ blob.triggers = (blob.triggers ?? []).map(withoutEnvironment);
59
+ return {
60
+ kind: WORKFLOW_FILE_KIND,
61
+ version: WORKFLOW_FILE_VERSION,
62
+ workflow: {
63
+ name: wf.name,
64
+ description: wf.description ?? null,
65
+ executionMode: wf.execution_mode ?? "async_only",
66
+ blobDefinitionVersion: wf.blob_definition_version ?? BLOB_VERSION,
67
+ blob
68
+ }
69
+ };
70
+ }
71
+ export function hasVariableValues(blob) {
72
+ return Object.values(blob.variables ?? {}).some((v) => v?.default !== void 0 && v.default !== null && v.default !== "");
73
+ }
74
+ export function workflowFileName(name) {
75
+ const slug = name.normalize("NFKD").replace(/\p{Mn}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
76
+ return `${slug || "workflow"}.workflow.json`;
77
+ }
78
+ export function parseWorkflowFile(text) {
79
+ let raw;
80
+ try {
81
+ raw = JSON.parse(text);
82
+ } catch {
83
+ return { ok: false, reason: "unreadable" };
84
+ }
85
+ if (!isRecord(raw)) return { ok: false, reason: "unreadable" };
86
+ if (raw.kind !== WORKFLOW_FILE_KIND) return { ok: false, reason: "kind" };
87
+ if (raw.version !== WORKFLOW_FILE_VERSION) return { ok: false, reason: "version" };
88
+ const wf = raw.workflow;
89
+ if (!isRecord(wf) || !isRecord(wf.blob)) return { ok: false, reason: "unreadable" };
90
+ if ((wf.blobDefinitionVersion ?? BLOB_VERSION) !== BLOB_VERSION) return { ok: false, reason: "definition" };
91
+ const blob = wf.blob;
92
+ const nodes = blob.nodes;
93
+ const edges = blob.edges ?? [];
94
+ const triggers = blob.triggers ?? [];
95
+ if (!Array.isArray(nodes) || !nodes.every(isNode)) return { ok: false, reason: "unreadable" };
96
+ if (!Array.isArray(edges) || !edges.every(isEdge)) return { ok: false, reason: "unreadable" };
97
+ if (!Array.isArray(triggers) || !triggers.every(isTrigger)) return { ok: false, reason: "unreadable" };
98
+ if (hasDuplicates([...nodes.map((n) => n.id), ...triggers.map((tr) => tr.id)])) return { ok: false, reason: "unreadable" };
99
+ return {
100
+ ok: true,
101
+ file: {
102
+ kind: WORKFLOW_FILE_KIND,
103
+ version: WORKFLOW_FILE_VERSION,
104
+ workflow: {
105
+ name: typeof wf.name === "string" ? wf.name : "",
106
+ description: typeof wf.description === "string" ? wf.description : null,
107
+ executionMode: isExecutionMode(wf.executionMode) ? wf.executionMode : "async_only",
108
+ blobDefinitionVersion: BLOB_VERSION,
109
+ blob: {
110
+ ...blob,
111
+ nodes: nodes.map((n) => ({ ...n, config: isRecord(n.config) ? n.config : {} })),
112
+ edges,
113
+ triggers: triggers.map((tr) => ({ ...tr, config: isRecord(tr.config) ? tr.config : {} }))
114
+ }
115
+ }
116
+ }
117
+ };
118
+ }
119
+ function hasDuplicates(ids) {
120
+ return new Set(ids).size !== ids.length;
121
+ }
122
+ function isNode(v) {
123
+ return isRecord(v) && typeof v.id === "string" && typeof v.nodeSlug === "string";
124
+ }
125
+ function isEdge(v) {
126
+ return isRecord(v) && isRecord(v.from) && typeof v.from.nodeId === "string" && isRecord(v.to) && typeof v.to.nodeId === "string";
127
+ }
128
+ function isTrigger(v) {
129
+ return isRecord(v) && typeof v.id === "string" && typeof v.type === "string";
130
+ }
131
+ function isExecutionMode(v) {
132
+ return v === "async_only" || v === "sync_only" || v === "caller_decides";
133
+ }
134
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
135
+ export function assignTriggerHandles(blob, taken, keep) {
136
+ const out = deepClone(blob);
137
+ const handleMap = /* @__PURE__ */ new Map();
138
+ const used = /* @__PURE__ */ new Set();
139
+ for (const trigger of out.triggers ?? []) {
140
+ const keepable = UUID.test(trigger.id) && !taken.has(trigger.id) && !used.has(trigger.id);
141
+ if (!keepable && !(keep?.(trigger) && !used.has(trigger.id))) {
142
+ const fresh = crypto.randomUUID();
143
+ handleMap.set(trigger.id, fresh);
144
+ trigger.id = fresh;
145
+ }
146
+ used.add(trigger.id);
147
+ }
148
+ if (!handleMap.size) return out;
149
+ for (const edge of out.edges ?? []) {
150
+ const mapped = handleMap.get(edge.from.nodeId);
151
+ if (mapped) edge.from.nodeId = mapped;
152
+ }
153
+ if (out.triggerPositions) {
154
+ const rekeyed = {};
155
+ for (const [handle, pos] of Object.entries(out.triggerPositions)) rekeyed[handleMap.get(handle) ?? handle] = pos;
156
+ out.triggerPositions = rekeyed;
157
+ }
158
+ for (const group of out.groups ?? []) {
159
+ group.nodeIds = (group.nodeIds ?? []).map((id) => handleMap.get(id) ?? id);
160
+ }
161
+ return out;
162
+ }
163
+ export function pausedTriggers(blob) {
164
+ return { ...blob, triggers: (blob.triggers ?? []).map((t) => ({ ...t, active: false })) };
165
+ }
166
+ export function blobForNewWorkflow(file, fieldsFor, tenantHandles, credentialIds = /* @__PURE__ */ new Set()) {
167
+ const clean = withoutCredentials(file.workflow.blob, fieldsFor, credentialIds);
168
+ clean.triggers = (clean.triggers ?? []).map(withoutEnvironment);
169
+ return pausedTriggers(assignTriggerHandles(clean, tenantHandles));
170
+ }
171
+ export function overwritePlan(target, file, fieldsFor, otherHandles, credentialIds = /* @__PURE__ */ new Set()) {
172
+ const clean = withoutCredentials(file.workflow.blob, fieldsFor, credentialIds);
173
+ const existing = new Map((target.triggers ?? []).map((t) => [t.id, t]));
174
+ const same = (t) => existing.get(t.id)?.type === t.type;
175
+ clean.triggers = (clean.triggers ?? []).map((t) => {
176
+ const mine = same(t) ? existing.get(t.id) : null;
177
+ const incoming = withoutEnvironment(t);
178
+ if (!mine) return { ...incoming, active: false };
179
+ const config = { ...incoming.config };
180
+ for (const key of ENVIRONMENT_TRIGGER_KEYS) {
181
+ if (mine.config?.[key] !== void 0) config[key] = mine.config[key];
182
+ }
183
+ return { ...incoming, config, active: mine.active };
184
+ });
185
+ const taken = /* @__PURE__ */ new Set([...otherHandles, ...[...existing.values()].map((t) => t.id)]);
186
+ const blob = assignTriggerHandles(clean, taken, same);
187
+ return {
188
+ blob,
189
+ replaced: { nodes: target.nodes?.length ?? 0, triggers: target.triggers?.length ?? 0 },
190
+ incoming: { nodes: blob.nodes.length, triggers: blob.triggers?.length ?? 0 }
191
+ };
192
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
- "version": "1.4.1",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "Integration Studio (core: workflows, node editor, runs, schedules, credentials, secrets, audit) as a standalone, publishable Nuxt module.",
6
6
  "repository": {
@@ -26,13 +26,14 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@nuxt/kit": "^4.0.0",
29
- "@revenexx/studio-shared": "^0.3.0",
29
+ "@revenexx/studio-shared": "^0.3.2",
30
30
  "@solar-icons/nuxt": "^1.2.0",
31
+ "@solar-icons/vue": "^1.2.0",
31
32
  "marked": "^18.0.5"
32
33
  },
33
34
  "peerDependencies": {
34
35
  "@dagrejs/dagre": "^3.0.0",
35
- "@revenexx/studio": "*",
36
+ "@revenexx/studio": "^0.2.3",
36
37
  "@tiptap/core": "^3.28.0",
37
38
  "@tiptap/extension-document": "^3.28.0",
38
39
  "@tiptap/extension-history": "^3.28.0",
@@ -52,12 +53,15 @@
52
53
  },
53
54
  "devDependencies": {
54
55
  "@changesets/cli": "^2.31.0",
56
+ "@nuxt/eslint-config": "^1.17.0",
57
+ "@nuxt/kit": "^4.0.0",
55
58
  "@nuxt/module-builder": "^1.0.2",
59
+ "@nuxt/schema": "^4.0.0",
56
60
  "@playwright/test": "^1.57.0",
57
- "@nuxt/kit": "^4.0.0",
58
61
  "@revenexx/talkback-js": "^0.3.0",
59
- "@nuxt/schema": "^4.0.0",
62
+ "eslint": "^10.11.0",
60
63
  "nuxt": "^4.2.2",
64
+ "typescript": "5.9.3",
61
65
  "vitest": "^3.2.0",
62
66
  "vue": "^3.5.26",
63
67
  "vue-tsc": "^2.2.0"
@@ -68,6 +72,7 @@
68
72
  "test": "vitest run",
69
73
  "test:e2e": "playwright test",
70
74
  "typecheck": "node scripts/typecheck.mjs",
75
+ "lint": "eslint . --max-warnings=0",
71
76
  "spec:check": "node scripts/spec-check.mjs",
72
77
  "changeset": "changeset",
73
78
  "version-packages": "changeset version"
@@ -1,60 +0,0 @@
1
- import type { WorkflowBlob } from '../types/integrations.js';
2
- /**
3
- * Nodes that route failures to an error output nothing is connected to
4
- * (specs/workflow-advice.md).
5
- *
6
- * A step that fails on a path its author drew is handled. A step that fails on a
7
- * path nobody drew is not: the engine ends the run there
8
- * (`specs/node-failure-handling.md` AC-8 in the service repository), and before
9
- * it did, the failure vanished and the run reported success. Either way the
10
- * author never chose it — they simply never saw the port.
11
- *
12
- * ## Why this is advice and not a finding
13
- *
14
- * The backend's validation findings feed the activation gate AND an
15
- * unconditional refusal to start a run. A finding here would therefore stop
16
- * every workflow with an unwired error port from running at all — turning
17
- * "delivers nothing quietly" into "does not start", for authors who never asked
18
- * for either. Computing it in the editor makes that impossible rather than
19
- * merely unlikely: there is no value for a blocking consumer to read.
20
- *
21
- * What the client cannot do is be authoritative — the catalogue may not be
22
- * loaded, and a workflow built through the API never passes here at all. For
23
- * advice that is the right trade; for a gate it would not be, which is why the
24
- * gate stays where the manifests are.
25
- *
26
- * ## Deliberately not in `validationFindings.ts`
27
- *
28
- * Everything there is a backend finding addressed by blob path, and the node
29
- * card counts those as *incomplete settings*. An unwired port is neither. Sent
30
- * through `groupFindingsByNode` it would type-check perfectly and quietly
31
- * corrupt two counts — the banner's and the card badge's.
32
- */
33
- export interface ErrorPathAdvice {
34
- /** Blob node id — not an index, so it survives a node being added or removed. */
35
- nodeId: string;
36
- /** The error output with nothing attached. */
37
- port: string;
38
- }
39
- /**
40
- * Every unwired error output in the blob, in node order.
41
- *
42
- * Runs against the blob the author is *editing*, not the saved one, so wiring a
43
- * port clears its line in the same tick — no save, and no second copy of this
44
- * rule to decide when the advice went stale.
45
- *
46
- * **Resolved against whichever manifest version the catalogue has.**
47
- * `findManifest` falls back to another version of the same slug when the pinned
48
- * one is absent, and that fallback is accepted here: a node pinned to a version
49
- * nobody serves any more is better described by a sibling version than by
50
- * silence. The cost is real though — where the versions disagree about their
51
- * ports, the advice describes the sibling's, so a port could be named that the
52
- * author's own node does not have.
53
- *
54
- * **Reads the manifest's static outputs only.** A node whose ports are named at
55
- * author time (`sourceFromConfig` / `resolveOutputs`) cannot be judged from the
56
- * declaration alone, so nothing is claimed about it — the same forbearance the
57
- * engine shows. No shipped node has a dynamic error port today; if one appears,
58
- * this is where it would silently escape notice.
59
- */
60
- export declare function unwiredErrorPorts(blob: Pick<WorkflowBlob, 'nodes' | 'edges'>): ErrorPathAdvice[];
@@ -1,19 +0,0 @@
1
- import { findManifest } from "../data/integrationNodeCatalog.js";
2
- export function unwiredErrorPorts(blob) {
3
- const advice = [];
4
- for (const node of blob.nodes) {
5
- const manifest = findManifest(node.nodeSlug, node.nodeVersion);
6
- if (!manifest) continue;
7
- const errorPorts = (manifest.outputs ?? []).filter((o) => o.kind === "error" && o.name).map((o) => o.name);
8
- if (!errorPorts.length) continue;
9
- if (!isReachable(node, blob, errorPorts)) continue;
10
- for (const port of errorPorts) {
11
- const wired = blob.edges.some((e) => e.from.nodeId === node.id && e.from.output === port);
12
- if (!wired) advice.push({ nodeId: node.id, port });
13
- }
14
- }
15
- return advice;
16
- }
17
- function isReachable(node, blob, errorPorts) {
18
- return blob.edges.some((e) => e.to.nodeId === node.id) || blob.edges.some((e) => e.from.nodeId === node.id && !errorPorts.includes(e.from.output));
19
- }