@revenexx/studio-integrations 2.0.0 → 2.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 (73) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/README.md +1 -0
  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 +28 -9
  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 +7 -26
  21. package/dist/runtime/components/Integrations/StickyNoteView.vue +1 -0
  22. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +10 -6
  23. package/dist/runtime/components/Integrations/WorkflowEditor.vue +224 -225
  24. package/dist/runtime/components/Integrations/WorkflowExportDialog.d.vue.ts +6 -0
  25. package/dist/runtime/components/Integrations/WorkflowExportDialog.vue +71 -0
  26. package/dist/runtime/components/Integrations/WorkflowExportDialog.vue.d.ts +6 -0
  27. package/dist/runtime/components/Integrations/WorkflowImportFileStep.d.vue.ts +8 -0
  28. package/dist/runtime/components/Integrations/WorkflowImportFileStep.vue +95 -0
  29. package/dist/runtime/components/Integrations/WorkflowImportFileStep.vue.d.ts +8 -0
  30. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +5 -4
  31. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +0 -7
  32. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +2 -2
  33. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +2 -3
  34. package/dist/runtime/components/Integrations/WorkflowsTable.vue +14 -8
  35. package/dist/runtime/composables/useIntegrationsNodes.js +4 -1
  36. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +3 -1
  37. package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -22
  38. package/dist/runtime/composables/useNow.d.ts +17 -0
  39. package/dist/runtime/composables/useNow.js +16 -0
  40. package/dist/runtime/composables/useWorkflowFile.d.ts +21 -0
  41. package/dist/runtime/composables/useWorkflowFile.js +63 -0
  42. package/dist/runtime/data/expressionEditor.d.ts +11 -1
  43. package/dist/runtime/data/expressionEditor.js +2 -1
  44. package/dist/runtime/data/nodeIcons.d.ts +14 -0
  45. package/dist/runtime/data/nodeIcons.js +57 -14
  46. package/dist/runtime/data/nodeRunData.js +1 -1
  47. package/dist/runtime/data/payloadForm.js +1 -1
  48. package/dist/runtime/i18n/messages/de.json +42 -0
  49. package/dist/runtime/i18n/messages/en.json +42 -0
  50. package/dist/runtime/pages/integrations/audit.vue +3 -3
  51. package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +2 -2
  52. package/dist/runtime/pages/integrations/index.vue +5 -10
  53. package/dist/runtime/pages/integrations/schedules.vue +13 -1
  54. package/dist/runtime/pages/integrations/templates/index.vue +40 -40
  55. package/dist/runtime/pages/integrations/workflows/[id].vue +324 -244
  56. package/dist/runtime/types/integrations.d.ts +13 -0
  57. package/dist/runtime/utils/credentialState.d.ts +0 -7
  58. package/dist/runtime/utils/credentialState.js +0 -12
  59. package/dist/runtime/utils/localize.d.ts +12 -0
  60. package/dist/runtime/utils/localize.js +1 -1
  61. package/dist/runtime/utils/paletteOrder.d.ts +29 -0
  62. package/dist/runtime/utils/paletteOrder.js +46 -0
  63. package/dist/runtime/utils/relativeTime.d.ts +29 -0
  64. package/dist/runtime/utils/relativeTime.js +23 -0
  65. package/dist/runtime/utils/runFeedQuery.d.ts +12 -6
  66. package/dist/runtime/utils/runFeedQuery.js +3 -18
  67. package/dist/runtime/utils/settingValue.d.ts +33 -0
  68. package/dist/runtime/utils/settingValue.js +8 -0
  69. package/dist/runtime/utils/statusStyle.d.ts +8 -8
  70. package/dist/runtime/utils/statusStyle.js +4 -1
  71. package/dist/runtime/utils/workflowFile.d.ts +119 -0
  72. package/dist/runtime/utils/workflowFile.js +192 -0
  73. package/package.json +8 -3
@@ -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": "2.0.0",
3
+ "version": "2.2.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": {
@@ -28,6 +28,7 @@
28
28
  "@nuxt/kit": "^4.0.0",
29
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": {
@@ -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"