@revenexx/studio-integrations 3.4.1 → 3.5.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 +15 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/NodeInspector.vue +9 -2
- package/dist/runtime/components/Integrations/NodePalette.vue +15 -7
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue +46 -6
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +7 -4
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +7 -2
- package/dist/runtime/composables/useIntegrationsNodes.js +6 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +34 -0
- package/dist/runtime/data/integrationNodeCatalog.js +24 -3
- package/dist/runtime/i18n/messages/de.json +8 -0
- package/dist/runtime/i18n/messages/en.json +8 -0
- package/dist/runtime/pages/integrations/runs.vue +1 -2
- package/dist/runtime/types/integrations.d.ts +14 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @revenexx/studio-integrations
|
|
2
2
|
|
|
3
|
+
## 3.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 06a52b1: The studio takes `@revenexx/studio-shared` 0.4.1, so the filter menu of every list counts each option over the rows the other applied filters leave — a count is what a click on it will show — and a list with no filters draws no Filter button.
|
|
8
|
+
|
|
9
|
+
Two more things change with that release:
|
|
10
|
+
|
|
11
|
+
- Every tab bar — a workflow's Editor, History and Runs, the node dialog, the create-workflow and credential dialogs, Runs and Missed runs — is now a set of real tabs: a screen reader announces them as tabs, and the arrow keys move between them.
|
|
12
|
+
- The Missed runs tab no longer shows how many firings are waiting, because the shared tab bar shows no counts on tabs. The dashboard's Needs attention panel still does.
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- fa29ad1: The studio takes `@revenexx/studio-shared` 0.4.2, so after a page loads the first Tab reaches *Skip to content* again. With 0.4.1 the sidebar scrolled to the current page in a way that, in Chrome, sent the first Tab past the skip link to the sidebar link after the current page, or into the page.
|
|
17
|
+
|
|
3
18
|
## 3.4.1
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/dist/module.json
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
ToggleGroupItem
|
|
29
29
|
} from "@revenexx/studio";
|
|
30
30
|
import EventTypeCombobox from "./EventTypeCombobox.vue";
|
|
31
|
-
import { findManifest, nodeCategoryLabel, isVersionExecutable, newestExecutableVersion, executableVersionsOf, newerExecutableVersions } from "../../data/integrationNodeCatalog";
|
|
31
|
+
import { channelManifestOf, findManifest, isChannelPin, nodeCategoryLabel, isVersionExecutable, newestExecutableVersion, executableVersionsOf, newerExecutableVersions } from "../../data/integrationNodeCatalog";
|
|
32
32
|
import NodeVersionMoveDialog from "./NodeVersionMoveDialog.vue";
|
|
33
33
|
import {
|
|
34
34
|
collectUpstreamNodes,
|
|
@@ -131,6 +131,7 @@ const newerVersions = computed(() => {
|
|
|
131
131
|
const canMoveVersion = computed(() => {
|
|
132
132
|
const wn = draftNode.value;
|
|
133
133
|
if (!wn) return false;
|
|
134
|
+
if (isChannelPin(wn.nodeVersion)) return true;
|
|
134
135
|
const runnable = executableVersionsOf(wn.nodeSlug);
|
|
135
136
|
if (!runnable.length) return false;
|
|
136
137
|
return runnable.length > 1 || runnable[0]?.version !== wn.nodeVersion;
|
|
@@ -151,6 +152,10 @@ const namePlaceholder = computed(() => {
|
|
|
151
152
|
});
|
|
152
153
|
const subtitleText = computed(() => {
|
|
153
154
|
const wn = draftNode.value;
|
|
155
|
+
if (wn && isChannelPin(wn.nodeVersion)) {
|
|
156
|
+
const current = channelManifestOf(wn.nodeSlug)?.version;
|
|
157
|
+
return wn.nodeSlug + " \xB7 " + (current ? t("integration.common.nodeVersion.channelCurrent", { version: current }) : t("integration.common.nodeVersion.channel"));
|
|
158
|
+
}
|
|
154
159
|
if (wn) return wn.nodeSlug + "@" + wn.nodeVersion;
|
|
155
160
|
return t("integration.nodeInspector.trigger.placeholder", { type: TRIGGER_LABELS.value[draftTrigger.value?.type ?? "manual"] });
|
|
156
161
|
});
|
|
@@ -1387,7 +1392,9 @@ async function runTestStep() {
|
|
|
1387
1392
|
v-if="isNode && !versionExecutable"
|
|
1388
1393
|
class="mx-4 mb-1 shrink-0 rounded-md border border-rose-200 bg-rose-50 px-2.5 py-1.5 text-sm text-rose-700"
|
|
1389
1394
|
>
|
|
1390
|
-
|
|
1395
|
+
<!-- A step on the channel has no number to name: what it lacks is a
|
|
1396
|
+
version on the channel (MAR-4075). -->
|
|
1397
|
+
{{ isChannelPin(draftNode?.nodeVersion ?? "") ? t("integration.nodeInspector.version.move.channelUnavailable") : readyVersion ? t("integration.nodeInspector.notice.versionNotReady.upgrade", { version: draftNode?.nodeVersion ?? "", ready: readyVersion }) : t("integration.nodeInspector.notice.versionNotReady.none", { version: draftNode?.nodeVersion ?? "" }) }}
|
|
1391
1398
|
<!-- The sentence named a version and led nowhere (PO-339). It leads
|
|
1392
1399
|
here now; where the node has no runnable version at all there is
|
|
1393
1400
|
still nothing to offer, and the notice stays the statement it
|
|
@@ -3,7 +3,7 @@ import { computed, ref, watch } from "vue";
|
|
|
3
3
|
import { AltArrowLeft as ArrowLeft, Bolt as Zap, Magnifier as Search } from "#solar-icons";
|
|
4
4
|
import { triggerGlyph } from "../../data/triggerGlyphs";
|
|
5
5
|
import * as Lucide from "lucide-vue-next";
|
|
6
|
-
import { INTEGRATION_NODE_CATALOG, isOffered, NODE_CATEGORIES, TRIGGER_TEMPLATES } from "../../data/integrationNodeCatalog";
|
|
6
|
+
import { CHANNEL_VERSION, channelManifestOf, INTEGRATION_NODE_CATALOG, isCandidate, isChannelPin, isOffered, NODE_CATEGORIES, offersChannel, TRIGGER_TEMPLATES } from "../../data/integrationNodeCatalog";
|
|
7
7
|
import { resolveLocalized, studioLocale } from "../../utils/localize";
|
|
8
8
|
import { orderFolderLeaves, orderFolders } from "../../utils/paletteOrder";
|
|
9
9
|
import { declaredFolderIcon } from "../../utils/folderMark";
|
|
@@ -93,6 +93,7 @@ const latestManifests = computed(() => {
|
|
|
93
93
|
const out = [];
|
|
94
94
|
for (const m of INTEGRATION_NODE_CATALOG) {
|
|
95
95
|
if (seen.has(m.slug) || !isExecutable(m)) continue;
|
|
96
|
+
if (isCandidate(m) && INTEGRATION_NODE_CATALOG.some((o) => o.slug === m.slug && isExecutable(o) && !isCandidate(o))) continue;
|
|
96
97
|
seen.add(m.slug);
|
|
97
98
|
out.push(m);
|
|
98
99
|
}
|
|
@@ -117,13 +118,18 @@ function setSelectedVersion(slug, version) {
|
|
|
117
118
|
}
|
|
118
119
|
function effVersion(m) {
|
|
119
120
|
const sel = selectedVersion.value[m.slug];
|
|
121
|
+
if (isChannelPin(sel)) return offersChannel(m.slug) ? CHANNEL_VERSION : m.version;
|
|
120
122
|
return sel && versionsOf(m.slug).some((v) => v.version === sel) ? sel : m.version;
|
|
121
123
|
}
|
|
122
124
|
function effManifest(m) {
|
|
123
125
|
const v = effVersion(m);
|
|
126
|
+
if (isChannelPin(v)) return channelManifestOf(m.slug) ?? m;
|
|
124
127
|
if (v === m.version) return m;
|
|
125
128
|
return versionsOf(m.slug).find((x) => x.version === v) ?? m;
|
|
126
129
|
}
|
|
130
|
+
function versionOptionLabel(v) {
|
|
131
|
+
return isCandidate(v) ? t("integration.common.nodeVersion.candidate", { version: v.version }) : `v${v.version}`;
|
|
132
|
+
}
|
|
127
133
|
const tree = computed(() => {
|
|
128
134
|
const apps = makeGroup("Integrations");
|
|
129
135
|
const core = makeGroup("Core");
|
|
@@ -264,7 +270,7 @@ function onVersionSelectLeave(ev) {
|
|
|
264
270
|
function onManifestDragStart(leaf, ev) {
|
|
265
271
|
if (!ev.dataTransfer) return;
|
|
266
272
|
const m = effManifest(leaf);
|
|
267
|
-
ev.dataTransfer.setData("application/integration-node", JSON.stringify({ kind: "manifest", slug: m.slug, version:
|
|
273
|
+
ev.dataTransfer.setData("application/integration-node", JSON.stringify({ kind: "manifest", slug: m.slug, version: effVersion(leaf) }));
|
|
268
274
|
ev.dataTransfer.effectAllowed = "move";
|
|
269
275
|
setCustomDragImage(ev, resolveLocalized(m.name), logoSrcOf(m), "#8b5cf6");
|
|
270
276
|
nodeDrag.start({ kind: "manifest", manifest: m });
|
|
@@ -281,7 +287,7 @@ function onDragEnd() {
|
|
|
281
287
|
}
|
|
282
288
|
function insertManifest(leaf) {
|
|
283
289
|
const m = effManifest(leaf);
|
|
284
|
-
emit("insert", { kind: "manifest", slug: m.slug, version:
|
|
290
|
+
emit("insert", { kind: "manifest", slug: m.slug, version: effVersion(leaf) });
|
|
285
291
|
}
|
|
286
292
|
function insertTrigger(tpl) {
|
|
287
293
|
emit("insert", { kind: "trigger", type: tpl.type });
|
|
@@ -408,9 +414,10 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
408
414
|
@focus="paletteDragEnabled = false"
|
|
409
415
|
@blur="paletteDragEnabled = true"
|
|
410
416
|
>
|
|
411
|
-
<option v-
|
|
417
|
+
<option v-if="offersChannel(hit.manifest.slug)" :value="CHANNEL_VERSION">{{ t("integration.common.nodeVersion.channel") }}</option>
|
|
418
|
+
<option v-for="v in versionsOf(hit.manifest.slug)" :key="v.version" :value="v.version">{{ versionOptionLabel(v) }}</option>
|
|
412
419
|
</select>
|
|
413
|
-
<span v-else class="shrink-0 text-xs tabular-nums text-muted-foreground/60">
|
|
420
|
+
<span v-else class="shrink-0 text-xs tabular-nums text-muted-foreground/60">{{ versionOptionLabel(hit.manifest) }}</span>
|
|
414
421
|
</div>
|
|
415
422
|
<div class="text-xs leading-snug text-muted-foreground">{{ resolveLocalized(hit.manifest.description) }}</div>
|
|
416
423
|
<div class="truncate text-xs text-muted-foreground/70">{{ hit.path.join(" \u203A ") }}</div>
|
|
@@ -546,9 +553,10 @@ const searching = computed(() => search.value.trim().length > 0);
|
|
|
546
553
|
@focus="paletteDragEnabled = false"
|
|
547
554
|
@blur="paletteDragEnabled = true"
|
|
548
555
|
>
|
|
549
|
-
<option v-
|
|
556
|
+
<option v-if="offersChannel(m.slug)" :value="CHANNEL_VERSION">{{ t("integration.common.nodeVersion.channel") }}</option>
|
|
557
|
+
<option v-for="v in versionsOf(m.slug)" :key="v.version" :value="v.version">{{ versionOptionLabel(v) }}</option>
|
|
550
558
|
</select>
|
|
551
|
-
<span v-else class="shrink-0 text-xs tabular-nums text-muted-foreground/60">
|
|
559
|
+
<span v-else class="shrink-0 text-xs tabular-nums text-muted-foreground/60">{{ versionOptionLabel(m) }}</span>
|
|
552
560
|
</div>
|
|
553
561
|
<div class="text-xs leading-snug text-muted-foreground">{{ resolveLocalized(m.description) }}</div>
|
|
554
562
|
</div>
|
|
@@ -9,7 +9,16 @@ import {
|
|
|
9
9
|
AlertDialogTitle,
|
|
10
10
|
Button
|
|
11
11
|
} from "@revenexx/studio";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
CHANNEL_VERSION,
|
|
14
|
+
channelManifestOf,
|
|
15
|
+
executableVersionsOf,
|
|
16
|
+
findManifest,
|
|
17
|
+
isCandidate,
|
|
18
|
+
isChannelPin,
|
|
19
|
+
newestExecutableVersion,
|
|
20
|
+
offersChannel
|
|
21
|
+
} from "../../data/integrationNodeCatalog";
|
|
13
22
|
import { planVersionMove } from "../../data/nodeVersionMove";
|
|
14
23
|
import { resolveLocalized } from "../../utils/localize";
|
|
15
24
|
const open = defineModel("open", { type: Boolean, ...{ default: false } });
|
|
@@ -22,9 +31,25 @@ const versions = computed(() => props.node ? executableVersionsOf(props.node.nod
|
|
|
22
31
|
const target = ref("");
|
|
23
32
|
watch(open, (isOpen) => {
|
|
24
33
|
if (!isOpen || !props.node) return;
|
|
34
|
+
if (isChannelPin(props.node.nodeVersion)) {
|
|
35
|
+
target.value = CHANNEL_VERSION;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
25
38
|
const newest = newestExecutableVersion(props.node.nodeSlug);
|
|
26
39
|
target.value = newest && newest !== props.node.nodeVersion ? newest : props.node.nodeVersion;
|
|
27
40
|
});
|
|
41
|
+
const channelManifest = computed(() => props.node ? channelManifestOf(props.node.nodeSlug) : null);
|
|
42
|
+
const targetManifest = computed(() => props.node ? findManifestExact(props.node.nodeSlug, target.value) : null);
|
|
43
|
+
const channelOffered = computed(() => !!props.node && (offersChannel(props.node.nodeSlug) || isChannelPin(props.node.nodeVersion)));
|
|
44
|
+
const channelUnavailable = computed(() => isChannelPin(target.value) && channelManifest.value === null);
|
|
45
|
+
function versionLabel(version) {
|
|
46
|
+
if (!isChannelPin(version)) return `v${version}`;
|
|
47
|
+
const current = channelManifest.value?.version;
|
|
48
|
+
return current ? t("integration.common.nodeVersion.channelCurrent", { version: current }) : t("integration.common.nodeVersion.channel");
|
|
49
|
+
}
|
|
50
|
+
function optionLabel(m) {
|
|
51
|
+
return isCandidate(m) ? t("integration.common.nodeVersion.candidate", { version: m.version }) : `v${m.version}`;
|
|
52
|
+
}
|
|
28
53
|
const plan = computed(() => {
|
|
29
54
|
const n = props.node;
|
|
30
55
|
if (!n) return null;
|
|
@@ -35,16 +60,18 @@ const plan = computed(() => {
|
|
|
35
60
|
);
|
|
36
61
|
});
|
|
37
62
|
function findManifestExact(slug, version) {
|
|
63
|
+
if (isChannelPin(version)) return channelManifestOf(slug);
|
|
38
64
|
const m = findManifest(slug, version);
|
|
39
65
|
return m && m.version === version ? m : null;
|
|
40
66
|
}
|
|
41
67
|
const unchanged = computed(() => !props.node || target.value === props.node.nodeVersion);
|
|
68
|
+
const blocked = computed(() => unchanged.value || channelUnavailable.value);
|
|
42
69
|
function labelOf(entry) {
|
|
43
70
|
return resolveLocalized(entry.field?.label) || entry.key;
|
|
44
71
|
}
|
|
45
72
|
function confirm() {
|
|
46
73
|
const p = plan.value;
|
|
47
|
-
if (!props.node || !p ||
|
|
74
|
+
if (!props.node || !p || blocked.value) return;
|
|
48
75
|
emit("move", { version: target.value, config: p.nextConfig });
|
|
49
76
|
open.value = false;
|
|
50
77
|
}
|
|
@@ -64,19 +91,32 @@ function confirm() {
|
|
|
64
91
|
|
|
65
92
|
<div v-if="props.node && plan" class="space-y-3 text-sm">
|
|
66
93
|
<div class="flex items-center gap-2">
|
|
67
|
-
<span class="tabular-nums text-muted-foreground">
|
|
94
|
+
<span class="tabular-nums text-muted-foreground">{{ versionLabel(props.node.nodeVersion) }}</span>
|
|
68
95
|
<span aria-hidden="true">→</span>
|
|
69
96
|
<select
|
|
70
97
|
v-model="target"
|
|
71
98
|
class="rounded border border-black/10 bg-white px-1.5 py-0.5 tabular-nums"
|
|
72
99
|
:aria-label="t('integration.nodeInspector.version.move.targetAria')"
|
|
73
100
|
>
|
|
101
|
+
<option v-if="channelOffered" :value="CHANNEL_VERSION">
|
|
102
|
+
{{ t("integration.common.nodeVersion.channel") }}
|
|
103
|
+
</option>
|
|
74
104
|
<option v-for="m in versions" :key="m.version" :value="m.version">
|
|
75
|
-
|
|
105
|
+
{{ optionLabel(m) }}
|
|
76
106
|
</option>
|
|
77
107
|
</select>
|
|
78
108
|
</div>
|
|
79
109
|
|
|
110
|
+
<p v-if="channelUnavailable" class="text-amber-600">
|
|
111
|
+
{{ t("integration.nodeInspector.version.move.channelUnavailable") }}
|
|
112
|
+
</p>
|
|
113
|
+
<p v-else-if="isChannelPin(target) && !unchanged" class="text-muted-foreground">
|
|
114
|
+
{{ t("integration.nodeInspector.version.move.channelHint") }}
|
|
115
|
+
</p>
|
|
116
|
+
<p v-else-if="!isChannelPin(target) && isCandidate(targetManifest)" class="text-amber-600">
|
|
117
|
+
{{ t("integration.nodeInspector.version.move.candidateHint") }}
|
|
118
|
+
</p>
|
|
119
|
+
|
|
80
120
|
<!-- A version the catalogue no longer carries cannot be compared against,
|
|
81
121
|
so the lists below are what arrives rather than what was checked. Said
|
|
82
122
|
once, here, instead of marking every setting as suspect. -->
|
|
@@ -88,7 +128,7 @@ function confirm() {
|
|
|
88
128
|
{{ t("integration.nodeInspector.version.move.same") }}
|
|
89
129
|
</p>
|
|
90
130
|
|
|
91
|
-
<template v-else>
|
|
131
|
+
<template v-else-if="!channelUnavailable">
|
|
92
132
|
<p v-if="plan.lossless" class="text-muted-foreground">
|
|
93
133
|
{{ t("integration.nodeInspector.version.move.lossless") }}
|
|
94
134
|
</p>
|
|
@@ -124,7 +164,7 @@ function confirm() {
|
|
|
124
164
|
<Button variant="outline" @click="open = false">
|
|
125
165
|
{{ t("integration.common.action.cancel") }}
|
|
126
166
|
</Button>
|
|
127
|
-
<Button :disabled="
|
|
167
|
+
<Button :disabled="blocked" @click="confirm">
|
|
128
168
|
{{ t("integration.nodeInspector.version.move.confirm") }}
|
|
129
169
|
</Button>
|
|
130
170
|
</AlertDialogFooter>
|
|
@@ -49,7 +49,7 @@ import { toast } from "vue-sonner";
|
|
|
49
49
|
import WorkflowEdgeView from "./WorkflowEdgeView.vue";
|
|
50
50
|
import { layoutWorkflowNodes } from "./workflowLayout";
|
|
51
51
|
import { defaultsFor } from "../../utils/configDefaults";
|
|
52
|
-
import { TRIGGER_TEMPLATES, findManifest, triggerTypeLabel } from "../../data/integrationNodeCatalog";
|
|
52
|
+
import { CHANNEL_VERSION, TRIGGER_TEMPLATES, findManifest, isChannelPin, triggerTypeLabel } from "../../data/integrationNodeCatalog";
|
|
53
53
|
import { collectUpstreamNodes, isTerminalManifest, portsForNode } from "../../data/nodeInputData";
|
|
54
54
|
import { computeLoopBodies, iteratorNodeIds, nodeLoopData } from "../../data/loopBody";
|
|
55
55
|
import { canvasShapes } from "../../data/canvasShapes";
|
|
@@ -1111,7 +1111,7 @@ function addFromPayload(payload, pos) {
|
|
|
1111
1111
|
blob.value.nodes.push({
|
|
1112
1112
|
id,
|
|
1113
1113
|
nodeSlug: manifest.slug,
|
|
1114
|
-
nodeVersion:
|
|
1114
|
+
nodeVersion: pinnedVersion(payload.version, manifest),
|
|
1115
1115
|
config: defaultsFor(manifest.config ?? []),
|
|
1116
1116
|
position: at
|
|
1117
1117
|
});
|
|
@@ -1156,7 +1156,7 @@ function onPaletteInsert(payload) {
|
|
|
1156
1156
|
blob.value.nodes.push({
|
|
1157
1157
|
id,
|
|
1158
1158
|
nodeSlug: manifest.slug,
|
|
1159
|
-
nodeVersion:
|
|
1159
|
+
nodeVersion: pinnedVersion(payload.version, manifest),
|
|
1160
1160
|
config: defaultsFor(manifest.config ?? []),
|
|
1161
1161
|
position: { x: pc.pos.x, y: pc.pos.y }
|
|
1162
1162
|
});
|
|
@@ -1194,6 +1194,9 @@ function primaryOutputName(slug, version) {
|
|
|
1194
1194
|
const m = findManifest(slug, version);
|
|
1195
1195
|
return (m?.outputs ?? []).find((o) => o.name && o.kind !== "error")?.name ?? "default";
|
|
1196
1196
|
}
|
|
1197
|
+
function pinnedVersion(chosen, manifest) {
|
|
1198
|
+
return isChannelPin(chosen) ? CHANNEL_VERSION : manifest.version;
|
|
1199
|
+
}
|
|
1197
1200
|
function spliceIntoEdge(edgeId, payload) {
|
|
1198
1201
|
const edge = blob.value.edges.find((e) => e.id === edgeId);
|
|
1199
1202
|
const manifest = findManifest(payload.slug, payload.version);
|
|
@@ -1208,7 +1211,7 @@ function spliceIntoEdge(edgeId, payload) {
|
|
|
1208
1211
|
const inserted = {
|
|
1209
1212
|
id,
|
|
1210
1213
|
nodeSlug: manifest.slug,
|
|
1211
|
-
nodeVersion:
|
|
1214
|
+
nodeVersion: pinnedVersion(payload.version, manifest),
|
|
1212
1215
|
config: defaultsFor(manifest.config ?? []),
|
|
1213
1216
|
position: { x: snap((a.x + b.x) / 2), y: snap((a.y + b.y) / 2) }
|
|
1214
1217
|
};
|
|
@@ -4,7 +4,7 @@ import { Handle, Position, useVueFlow } from "@vue-flow/core";
|
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
5
|
import * as Lucide from "lucide-vue-next";
|
|
6
6
|
import { Copy, DangerTriangle as AlertTriangle, RefreshCircle as Loader2, Repeat, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
7
|
-
import { NODE_CATEGORIES, findManifest, isVersionExecutable } from "../../data/integrationNodeCatalog";
|
|
7
|
+
import { NODE_CATEGORIES, channelManifestOf, findManifest, isChannelPin, isVersionExecutable } from "../../data/integrationNodeCatalog";
|
|
8
8
|
import { inputHandleId, outputHandleId } from "../../data/handleId";
|
|
9
9
|
import { branchLineText } from "../../data/canvasShapes";
|
|
10
10
|
import { joinRefusalText } from "../../data/refusalMessage";
|
|
@@ -23,6 +23,11 @@ defineOptions({ inheritAttrs: false });
|
|
|
23
23
|
defineEmits(["updateNodeInternals"]);
|
|
24
24
|
const t = useIntegrationsT();
|
|
25
25
|
const manifest = computed(() => findManifest(props.data.nodeSlug, props.data.nodeVersion));
|
|
26
|
+
const versionChip = computed(() => {
|
|
27
|
+
if (!isChannelPin(props.data.nodeVersion)) return `v${props.data.nodeVersion}`;
|
|
28
|
+
const channel = channelManifestOf(props.data.nodeSlug);
|
|
29
|
+
return channel ? t("integration.canvas.node.meta.channel", { version: channel.version }) : t("integration.canvas.node.meta.channelNone");
|
|
30
|
+
});
|
|
26
31
|
const category = computed(() => NODE_CATEGORIES.find((c) => c.key === manifest.value?.category));
|
|
27
32
|
const title = computed(() => nodeDisplayName(props.data));
|
|
28
33
|
const fromPackage = computed(() => manifest.value?.__package ? packageLabel(manifest.value.__package) : null);
|
|
@@ -584,7 +589,7 @@ function remove() {
|
|
|
584
589
|
<span
|
|
585
590
|
class="shrink-0 text-[10px] font-medium leading-none tabular-nums text-muted-foreground/55"
|
|
586
591
|
:title="t('integration.canvas.node.meta.versionTitle', { version: data.nodeVersion })"
|
|
587
|
-
>
|
|
592
|
+
>{{ versionChip }}</span>
|
|
588
593
|
</div>
|
|
589
594
|
</div>
|
|
590
595
|
</div>
|
|
@@ -54,7 +54,12 @@ export function useIntegrationsNodes() {
|
|
|
54
54
|
__retired: n.retired === true,
|
|
55
55
|
// The package's own order, which the palette falls back to inside a
|
|
56
56
|
// folder (PO-537). Null and absent both mean "not recorded".
|
|
57
|
-
__packagePosition: typeof n.package_position === "number" ? n.package_position : void 0
|
|
57
|
+
__packagePosition: typeof n.package_position === "number" ? n.package_position : void 0,
|
|
58
|
+
// Where the version stands in its approval, and whether the tenant's
|
|
59
|
+
// release channel runs it (MAR-4075). Missing fields = released, and
|
|
60
|
+
// the catalogue works out the channel's version on its own.
|
|
61
|
+
__releaseState: n.release_state === "candidate" || n.release_state === "approved" ? n.release_state : null,
|
|
62
|
+
__channelVersion: n.channel_version === true ? true : void 0
|
|
58
63
|
}));
|
|
59
64
|
applyNodeCatalog(manifests);
|
|
60
65
|
level.value = request === "summary" && res?.fields === "summary" ? "summary" : "full";
|
|
@@ -59,6 +59,36 @@ export declare const TRIGGER_TYPE_WORDS: readonly ["manual", "schedule", "webhoo
|
|
|
59
59
|
* key itself on screen, because a missing message resolves to its own name.
|
|
60
60
|
*/
|
|
61
61
|
export declare function triggerTypeLabel(type: TriggerType | string | null | undefined): string;
|
|
62
|
+
/**
|
|
63
|
+
* The pin a step carries to follow the tenant's release channel instead of a
|
|
64
|
+
* version number (MAR-4075). The platform resolves it per tenant when it builds
|
|
65
|
+
* the workflow; the definition keeps saying `channel`.
|
|
66
|
+
*/
|
|
67
|
+
export declare const CHANNEL_VERSION = "channel";
|
|
68
|
+
/** Whether a step's version is the channel pin rather than a number. */
|
|
69
|
+
export declare function isChannelPin(version: string | null | undefined): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Whether this version is a candidate: built, and only given to tenants on the
|
|
72
|
+
* candidate release channel while it is tested (MAR-4075). A host that names no
|
|
73
|
+
* state reads as released.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isCandidate(m: NodeManifest | null | undefined): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* The version a step pinned to `channel` runs for this tenant, or `null` when the
|
|
78
|
+
* catalogue has none.
|
|
79
|
+
*
|
|
80
|
+
* The platform marks it (`channel_version`). A host that does not falls back to
|
|
81
|
+
* the rule the platform itself falls back to: the newest offered version that is
|
|
82
|
+
* not a candidate.
|
|
83
|
+
*/
|
|
84
|
+
export declare function channelManifestOf(slug: string): NodeManifest | null;
|
|
85
|
+
/**
|
|
86
|
+
* Whether the release channel is a choice to offer for this node: the platform
|
|
87
|
+
* marked which version a `channel` step runs. A platform that marks none
|
|
88
|
+
* predates the channel pin and would refuse a save carrying it, so the studio
|
|
89
|
+
* offers it nowhere (MAR-4075).
|
|
90
|
+
*/
|
|
91
|
+
export declare function offersChannel(slug: string): boolean;
|
|
62
92
|
/**
|
|
63
93
|
* Whether this exact node version can be executed — its code was built, so
|
|
64
94
|
* `execute:test` and `config:resolve` will not be refused (PO-338).
|
|
@@ -87,6 +117,10 @@ export declare function isOffered(m: NodeManifest): boolean;
|
|
|
87
117
|
* within a slug, so the first executable entry is the newest one. `null` where the
|
|
88
118
|
* whole node is unusable — then there is nothing to point at and the surface says
|
|
89
119
|
* only what is wrong.
|
|
120
|
+
*
|
|
121
|
+
* A candidate is never what a surface suggests (MAR-4075): it is a version under
|
|
122
|
+
* test, chosen deliberately or not at all. Only where a node has nothing but
|
|
123
|
+
* candidates is the newest of them the answer.
|
|
90
124
|
*/
|
|
91
125
|
export declare function newestExecutableVersion(slug: string): string | null;
|
|
92
126
|
/**
|
|
@@ -26,7 +26,26 @@ export function triggerTypeLabel(type) {
|
|
|
26
26
|
if (!TRIGGER_TYPE_WORD_SET.has(kind)) return kind;
|
|
27
27
|
return useIntegrationsT()(`integration.common.triggerType.${kind}`);
|
|
28
28
|
}
|
|
29
|
+
export const CHANNEL_VERSION = "channel";
|
|
30
|
+
export function isChannelPin(version) {
|
|
31
|
+
return version === CHANNEL_VERSION;
|
|
32
|
+
}
|
|
33
|
+
export function isCandidate(m) {
|
|
34
|
+
return m?.__releaseState === "candidate";
|
|
35
|
+
}
|
|
36
|
+
export function channelManifestOf(slug) {
|
|
37
|
+
const versions = INTEGRATION_NODE_CATALOG.filter((m) => m.slug === slug);
|
|
38
|
+
return versions.find((m) => m.__channelVersion === true) ?? versions.find((m) => isOffered(m) && !isCandidate(m)) ?? null;
|
|
39
|
+
}
|
|
40
|
+
export function offersChannel(slug) {
|
|
41
|
+
return INTEGRATION_NODE_CATALOG.some((m) => m.slug === slug && m.__channelVersion === true && isOffered(m));
|
|
42
|
+
}
|
|
29
43
|
export function isVersionExecutable(slug, version) {
|
|
44
|
+
if (isChannelPin(version)) {
|
|
45
|
+
if (!INTEGRATION_NODE_CATALOG.some((m) => m.slug === slug)) return true;
|
|
46
|
+
const channel = channelManifestOf(slug);
|
|
47
|
+
return channel ? channel.__hasBundle !== false : false;
|
|
48
|
+
}
|
|
30
49
|
const exact = INTEGRATION_NODE_CATALOG.find((m) => m.slug === slug && m.version === version);
|
|
31
50
|
return exact ? exact.__hasBundle !== false : true;
|
|
32
51
|
}
|
|
@@ -34,20 +53,22 @@ export function isOffered(m) {
|
|
|
34
53
|
return m.__hasBundle !== false && m.__retired !== true;
|
|
35
54
|
}
|
|
36
55
|
export function newestExecutableVersion(slug) {
|
|
37
|
-
const
|
|
38
|
-
return
|
|
56
|
+
const offered = INTEGRATION_NODE_CATALOG.filter((m) => m.slug === slug && isOffered(m));
|
|
57
|
+
return (offered.find((m) => !isCandidate(m)) ?? offered[0])?.version ?? null;
|
|
39
58
|
}
|
|
40
59
|
export function executableVersionsOf(slug) {
|
|
41
60
|
return INTEGRATION_NODE_CATALOG.filter((m) => m.slug === slug && isOffered(m));
|
|
42
61
|
}
|
|
43
62
|
export function newerExecutableVersions(slug, version) {
|
|
63
|
+
if (isChannelPin(version)) return [];
|
|
44
64
|
const built = INTEGRATION_NODE_CATALOG.filter((m) => m.slug === slug && m.__hasBundle !== false);
|
|
45
65
|
const at = built.findIndex((m) => m.version === version);
|
|
46
|
-
return at < 0 ? [] : built.slice(0, at).filter(isOffered);
|
|
66
|
+
return at < 0 ? [] : built.slice(0, at).filter((m) => isOffered(m) && !isCandidate(m));
|
|
47
67
|
}
|
|
48
68
|
export function findManifest(slug, version) {
|
|
49
69
|
const matches = INTEGRATION_NODE_CATALOG.filter((m) => m.slug === slug);
|
|
50
70
|
if (!matches.length) return null;
|
|
71
|
+
if (isChannelPin(version)) return channelManifestOf(slug) ?? matches[0] ?? null;
|
|
51
72
|
if (version) return matches.find((m) => m.version === version) ?? matches[0] ?? null;
|
|
52
73
|
return matches[0] ?? null;
|
|
53
74
|
}
|
|
@@ -94,6 +94,8 @@
|
|
|
94
94
|
"integration.canvas.node.handle.virtualConverging": "Zusammenführung, nur gezeichnet: der Zweig, der ankommt",
|
|
95
95
|
"integration.canvas.node.handle.virtualMerger": "Merger, nur gezeichnet: wartet auf alle Zweige",
|
|
96
96
|
"integration.canvas.node.meta.versionTitle": "Version {version}",
|
|
97
|
+
"integration.canvas.node.meta.channel": "Kanal · v{version}",
|
|
98
|
+
"integration.canvas.node.meta.channelNone": "Kanal · keine Version",
|
|
97
99
|
"integration.canvas.node.note.heading": "Notiz",
|
|
98
100
|
"integration.canvas.node.port.outputFallback": "Ausgang",
|
|
99
101
|
"integration.canvas.node.port.resolving": "Ports",
|
|
@@ -170,6 +172,9 @@
|
|
|
170
172
|
"integration.common.triggerType.manual": "Manuell",
|
|
171
173
|
"integration.common.triggerType.schedule": "Zeitplan",
|
|
172
174
|
"integration.common.triggerType.webhook": "Webhook",
|
|
175
|
+
"integration.common.nodeVersion.channel": "Kanal (folgt dem Release-Kanal)",
|
|
176
|
+
"integration.common.nodeVersion.candidate": "v{version} (Kandidat)",
|
|
177
|
+
"integration.common.nodeVersion.channelCurrent": "Kanal · derzeit v{version}",
|
|
173
178
|
"integration.credentials.createDialog.action.changeType": "Typ ändern",
|
|
174
179
|
"integration.credentials.createDialog.description": "Erstellen Sie Zugangsdaten für diesen Node, ohne den Editor zu verlassen.",
|
|
175
180
|
"integration.credentials.createDialog.error.create": "Zugangsdaten konnten nicht erstellt werden.",
|
|
@@ -907,6 +912,9 @@
|
|
|
907
912
|
"integration.nodeInspector.version.move.targetAria": "Version, auf die dieser Schritt wechselt",
|
|
908
913
|
"integration.nodeInspector.version.move.title": "Version dieses Schritts wechseln",
|
|
909
914
|
"integration.nodeInspector.version.moveTo": "Auf Version {version} wechseln",
|
|
915
|
+
"integration.nodeInspector.version.move.candidateHint": "Diese Version ist ein Kandidat und wird noch getestet. Nur Mandanten im Kandidaten-Kanal erhalten sie. Wählen Sie sie nur, wenn Sie sie gezielt ausprobieren möchten.",
|
|
916
|
+
"integration.nodeInspector.version.move.channelHint": "Der Schritt läuft dann mit der Version, die der Release-Kanal Ihres Mandanten vorgibt, und wechselt mit, wenn sich der Kanal ändert. Die Einstellungen werden mit der Version verglichen, die der Kanal heute vorgibt.",
|
|
917
|
+
"integration.nodeInspector.version.move.channelUnavailable": "Auf Ihrem Release-Kanal ist noch keine Version dieses Nodes verfügbar, deshalb kann der Schritt ihm nicht folgen.",
|
|
910
918
|
"integration.nodes.category.action": "Aktionen",
|
|
911
919
|
"integration.nodes.category.control": "Ablaufsteuerung",
|
|
912
920
|
"integration.nodes.category.io": "I/O",
|
|
@@ -94,6 +94,8 @@
|
|
|
94
94
|
"integration.canvas.node.handle.virtualConverging": "Converging input, drawn only: whichever of these branches arrives",
|
|
95
95
|
"integration.canvas.node.handle.virtualMerger": "Merger, drawn only: waits for every branch",
|
|
96
96
|
"integration.canvas.node.meta.versionTitle": "Version {version}",
|
|
97
|
+
"integration.canvas.node.meta.channel": "channel · v{version}",
|
|
98
|
+
"integration.canvas.node.meta.channelNone": "channel · no version",
|
|
97
99
|
"integration.canvas.node.note.heading": "Note",
|
|
98
100
|
"integration.canvas.node.port.outputFallback": "out",
|
|
99
101
|
"integration.canvas.node.port.resolving": "Ports",
|
|
@@ -170,6 +172,9 @@
|
|
|
170
172
|
"integration.common.triggerType.manual": "Manual",
|
|
171
173
|
"integration.common.triggerType.schedule": "Schedule",
|
|
172
174
|
"integration.common.triggerType.webhook": "Webhook",
|
|
175
|
+
"integration.common.nodeVersion.channel": "Channel (follows release channel)",
|
|
176
|
+
"integration.common.nodeVersion.candidate": "v{version} (candidate)",
|
|
177
|
+
"integration.common.nodeVersion.channelCurrent": "Channel · currently v{version}",
|
|
173
178
|
"integration.credentials.createDialog.action.changeType": "Change type",
|
|
174
179
|
"integration.credentials.createDialog.description": "Create a credential for this node without leaving the editor.",
|
|
175
180
|
"integration.credentials.createDialog.error.create": "Could not create credential.",
|
|
@@ -907,6 +912,9 @@
|
|
|
907
912
|
"integration.nodeInspector.version.move.targetAria": "Version to change this step to",
|
|
908
913
|
"integration.nodeInspector.version.move.title": "Change this step's version",
|
|
909
914
|
"integration.nodeInspector.version.moveTo": "Change to version {version}",
|
|
915
|
+
"integration.nodeInspector.version.move.candidateHint": "This version is a candidate: it is still being tested and only reaches tenants on the candidate release channel. Pin it only to try it out.",
|
|
916
|
+
"integration.nodeInspector.version.move.channelHint": "The step will run whichever version your tenant's release channel gives it, and move along when the channel changes. The settings are compared against the version the channel runs today.",
|
|
917
|
+
"integration.nodeInspector.version.move.channelUnavailable": "No version of this node is available on your release channel yet, so the step cannot follow it.",
|
|
910
918
|
"integration.nodes.category.action": "Actions",
|
|
911
919
|
"integration.nodes.category.control": "Control flow",
|
|
912
920
|
"integration.nodes.category.io": "I/O",
|
|
@@ -14,7 +14,6 @@ const { runs, loading, error, load, query, setQuery, narrowedTotal } = useIntegr
|
|
|
14
14
|
const {
|
|
15
15
|
globalItems: missed,
|
|
16
16
|
globalLoading: missedLoading,
|
|
17
|
-
pendingCount,
|
|
18
17
|
globalPage: missedPage,
|
|
19
18
|
globalLastPage: missedLastPage,
|
|
20
19
|
goGlobalPage,
|
|
@@ -54,7 +53,7 @@ const tab = ref(route.query.tab === "missed" ? "missed" : "runs");
|
|
|
54
53
|
const tabs = computed(() => [
|
|
55
54
|
{ key: "runs", label: t("integration.runs.tabs.runs.label") },
|
|
56
55
|
// "Missed runs": trigger firings that never became a run (dead letters).
|
|
57
|
-
{ key: "missed", label: t("integration.deadLetters.tabs.missed.label")
|
|
56
|
+
{ key: "missed", label: t("integration.deadLetters.tabs.missed.label") }
|
|
58
57
|
]);
|
|
59
58
|
const POLL_MS = 3e3;
|
|
60
59
|
const hasActiveRuns = computed(() => runs.value.some((r) => r.status === "running" || r.status === "queued"));
|
|
@@ -465,6 +465,20 @@ export interface NodeManifest {
|
|
|
465
465
|
* platform refuses a save that newly uses it.
|
|
466
466
|
*/
|
|
467
467
|
__retired?: boolean;
|
|
468
|
+
/**
|
|
469
|
+
* Where this version stands in its package's approval (editor-side enrichment
|
|
470
|
+
* from `GET /nodes`; not manifest data, MAR-4075): `candidate` for a build only
|
|
471
|
+
* tenants on the candidate release channel are given, `approved` for a released
|
|
472
|
+
* one. Absent or `null` on a host that does not report it, which reads as
|
|
473
|
+
* released.
|
|
474
|
+
*/
|
|
475
|
+
__releaseState?: 'approved' | 'candidate' | null;
|
|
476
|
+
/**
|
|
477
|
+
* Whether this is the version a step pinned to `channel` runs for the tenant
|
|
478
|
+
* (editor-side enrichment from `GET /nodes`, MAR-4075). Absent on a host that
|
|
479
|
+
* does not report it; the catalogue then works the answer out itself.
|
|
480
|
+
*/
|
|
481
|
+
__channelVersion?: boolean;
|
|
468
482
|
}
|
|
469
483
|
export interface SecretRecord {
|
|
470
484
|
key: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/studio-integrations",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.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,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@nuxt/kit": "^4.0.0",
|
|
29
|
-
"@revenexx/studio-shared": "^0.
|
|
29
|
+
"@revenexx/studio-shared": "^0.4.2",
|
|
30
30
|
"@solar-icons/nuxt": "^1.2.0",
|
|
31
31
|
"@solar-icons/vue": "^1.2.0",
|
|
32
32
|
"dompurify": "^3.4.16",
|