@revenexx/studio-integrations 1.2.0 → 1.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 +581 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CanvasShortcutsDialog.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/CanvasShortcutsDialog.vue +53 -0
- package/dist/runtime/components/Integrations/CanvasShortcutsDialog.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/EditorTip.vue +2 -15
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +10 -2
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +15 -1
- package/dist/runtime/components/Integrations/NodeConfigField.vue +158 -49
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +15 -1
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +5 -1
- package/dist/runtime/components/Integrations/NodeInspector.vue +136 -81
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +5 -1
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue +133 -0
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +80 -33
- package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue +111 -0
- package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +0 -4
- package/dist/runtime/components/Integrations/TemplateCard.vue +3 -6
- package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +0 -4
- package/dist/runtime/components/Integrations/TemplateInstallStep.d.vue.ts +25 -0
- package/dist/runtime/components/Integrations/TemplateInstallStep.vue +198 -0
- package/dist/runtime/components/Integrations/TemplateInstallStep.vue.d.ts +25 -0
- package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +22 -40
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +94 -65
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +36 -2
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +200 -54
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +36 -2
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +6 -2
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +4 -106
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +6 -2
- package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue +216 -0
- package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +22 -11
- package/dist/runtime/composables/useCanvasShortcuts.d.ts +34 -0
- package/dist/runtime/composables/useCanvasShortcuts.js +11 -8
- package/dist/runtime/composables/useCredentialTypes.js +3 -9
- package/dist/runtime/composables/useIntegrationsCredentials.js +6 -14
- package/dist/runtime/composables/useIntegrationsNodes.js +2 -2
- package/dist/runtime/composables/useIntegrationsRuns.js +8 -13
- package/dist/runtime/composables/useIntegrationsSecrets.js +5 -5
- package/dist/runtime/composables/useIntegrationsTemplates.js +10 -8
- package/dist/runtime/composables/useIntegrationsTriggers.js +1 -4
- package/dist/runtime/composables/useIntegrationsWorkflows.js +11 -7
- package/dist/runtime/data/canvasShortcuts.d.ts +208 -0
- package/dist/runtime/data/canvasShortcuts.js +68 -0
- package/dist/runtime/data/integrationNodeCatalog.d.ts +25 -0
- package/dist/runtime/data/integrationNodeCatalog.js +8 -0
- package/dist/runtime/data/nodeVersionMove.d.ts +72 -0
- package/dist/runtime/data/nodeVersionMove.js +65 -0
- package/dist/runtime/i18n/messages/de.json +83 -20
- package/dist/runtime/i18n/messages/en.json +83 -20
- package/dist/runtime/pages/integrations/templates/[slug].vue +22 -132
- package/dist/runtime/pages/integrations/workflows/[id].vue +158 -53
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -4
- package/dist/runtime/types/integrations.d.ts +44 -1
- package/dist/runtime/utils/apiEnvelope.d.ts +27 -0
- package/dist/runtime/utils/apiEnvelope.js +20 -0
- package/dist/runtime/utils/buildSettle.d.ts +68 -0
- package/dist/runtime/utils/buildSettle.js +29 -0
- package/dist/runtime/utils/configDefaults.d.ts +18 -0
- package/dist/runtime/utils/configDefaults.js +7 -0
- package/dist/runtime/utils/errorPathAdvice.d.ts +60 -0
- package/dist/runtime/utils/errorPathAdvice.js +19 -0
- package/dist/runtime/utils/notify.d.ts +2 -2
- package/dist/runtime/utils/runEnvelope.d.ts +12 -0
- package/dist/runtime/utils/runEnvelope.js +2 -1
- package/dist/runtime/utils/selectOptions.d.ts +48 -0
- package/dist/runtime/utils/selectOptions.js +19 -0
- package/dist/runtime/utils/settingCondition.d.ts +48 -0
- package/dist/runtime/utils/settingCondition.js +84 -0
- package/dist/runtime/utils/shortcutLabel.d.ts +40 -0
- package/dist/runtime/utils/shortcutLabel.js +48 -0
- package/dist/runtime/utils/stateNamespaceName.d.ts +23 -0
- package/dist/runtime/utils/stateNamespaceName.js +8 -0
- package/package.json +1 -1
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
ToggleGroupItem
|
|
29
29
|
} from "@revenexx/studio";
|
|
30
30
|
import EventTypeCombobox from "./EventTypeCombobox.vue";
|
|
31
|
-
import { findManifest, nodeCategoryLabel, isVersionExecutable, newestExecutableVersion } from "../../data/integrationNodeCatalog";
|
|
31
|
+
import { findManifest, nodeCategoryLabel, isVersionExecutable, newestExecutableVersion, executableVersionsOf, newerExecutableVersions } from "../../data/integrationNodeCatalog";
|
|
32
|
+
import NodeVersionMoveDialog from "./NodeVersionMoveDialog.vue";
|
|
32
33
|
import {
|
|
33
34
|
collectUpstreamNodes,
|
|
34
35
|
inputPortsForManifest,
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
} from "../../data/nodeInputData";
|
|
40
41
|
import { portDotClass } from "../../data/portTone";
|
|
41
42
|
import { computeLoopBodies, manifestIterates } from "../../data/loopBody";
|
|
43
|
+
import { applyingFields, staleKeys } from "../../utils/settingCondition";
|
|
42
44
|
import { deepClone } from "../../utils/deepClone";
|
|
43
45
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
44
46
|
import { nodeDisplayLabel } from "../../utils/validationFindings";
|
|
@@ -57,6 +59,7 @@ const props = defineProps({
|
|
|
57
59
|
selection: { type: [Object, null], required: true },
|
|
58
60
|
secretKeys: { type: Array, required: true },
|
|
59
61
|
variables: { type: Object, required: false },
|
|
62
|
+
stateNamespaces: { type: Array, required: false },
|
|
60
63
|
upstreamOutputs: { type: Array, required: false },
|
|
61
64
|
autofocusName: { type: Boolean, required: false },
|
|
62
65
|
nodes: { type: Array, required: false },
|
|
@@ -66,17 +69,35 @@ const props = defineProps({
|
|
|
66
69
|
workflowId: { type: [Number, String], required: false },
|
|
67
70
|
refusedFields: { type: Object, required: false }
|
|
68
71
|
});
|
|
69
|
-
const emit = defineEmits(["update", "close", "remove", "navigate"]);
|
|
72
|
+
const emit = defineEmits(["update", "close", "remove", "navigate", "declare-state"]);
|
|
70
73
|
const draft = ref(deepClone(props.selection));
|
|
71
74
|
watch(() => props.selection?.id, () => {
|
|
72
|
-
if (props.selection && props.selection.id !== draft.value.id)
|
|
75
|
+
if (props.selection && props.selection.id !== draft.value.id) {
|
|
76
|
+
draft.value = deepClone(props.selection);
|
|
77
|
+
pendingState.value = [];
|
|
78
|
+
}
|
|
73
79
|
});
|
|
74
80
|
const isNode = computed(() => "nodeSlug" in draft.value);
|
|
75
81
|
const draftNode = computed(() => isNode.value ? draft.value : null);
|
|
76
82
|
const draftTrigger = computed(() => isNode.value ? null : draft.value);
|
|
77
|
-
const
|
|
83
|
+
const pendingState = ref([]);
|
|
84
|
+
const offeredStateNamespaces = computed(() => [
|
|
85
|
+
...props.stateNamespaces ?? [],
|
|
86
|
+
...pendingState.value
|
|
87
|
+
]);
|
|
88
|
+
function stageStateNamespace(namespace, role, visibility) {
|
|
89
|
+
if (offeredStateNamespaces.value.some((n) => n.namespace === namespace)) return;
|
|
90
|
+
pendingState.value = [...pendingState.value, { namespace, role, visibility }];
|
|
91
|
+
}
|
|
92
|
+
const draftChanged = computed(() => JSON.stringify(draft.value) !== JSON.stringify(props.selection));
|
|
93
|
+
const dirty = computed(() => draftChanged.value || pendingState.value.length > 0);
|
|
94
|
+
function commit() {
|
|
95
|
+
for (const ns of pendingState.value) emit("declare-state", ns.namespace, ns.role, ns.visibility);
|
|
96
|
+
pendingState.value = [];
|
|
97
|
+
if (draftChanged.value) emit("update", deepClone(draft.value));
|
|
98
|
+
}
|
|
78
99
|
function save() {
|
|
79
|
-
|
|
100
|
+
commit();
|
|
80
101
|
emit("close");
|
|
81
102
|
}
|
|
82
103
|
const nameInput = ref(null);
|
|
@@ -99,6 +120,21 @@ const readyVersion = computed(() => {
|
|
|
99
120
|
const wn = draftNode.value;
|
|
100
121
|
return wn ? newestExecutableVersion(wn.nodeSlug) : null;
|
|
101
122
|
});
|
|
123
|
+
const versionMoveOpen = ref(false);
|
|
124
|
+
const newerVersions = computed(() => {
|
|
125
|
+
const wn = draftNode.value;
|
|
126
|
+
return wn ? newerExecutableVersions(wn.nodeSlug, wn.nodeVersion) : [];
|
|
127
|
+
});
|
|
128
|
+
const canMoveVersion = computed(() => {
|
|
129
|
+
const wn = draftNode.value;
|
|
130
|
+
if (!wn) return false;
|
|
131
|
+
const runnable = executableVersionsOf(wn.nodeSlug);
|
|
132
|
+
if (!runnable.length) return false;
|
|
133
|
+
return runnable.length > 1 || runnable[0]?.version !== wn.nodeVersion;
|
|
134
|
+
});
|
|
135
|
+
function applyVersionMove(payload) {
|
|
136
|
+
patchNode({ nodeVersion: payload.version, config: payload.config });
|
|
137
|
+
}
|
|
102
138
|
const TRIGGER_LABELS = computed(() => ({
|
|
103
139
|
manual: t("integration.nodeInspector.trigger.type.manual"),
|
|
104
140
|
schedule: t("integration.nodeInspector.trigger.type.schedule"),
|
|
@@ -150,39 +186,34 @@ watch(showSettingsTab, (hasThem) => {
|
|
|
150
186
|
if (!hasThem) tab.value = "parameters";
|
|
151
187
|
});
|
|
152
188
|
const SETTINGS_FIELD_KEY = /(^|_)(timeout|retry|retries|backoff)|(timeout|retry|retries|backoff)(ms|sec|seconds|attempts)?$/i;
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
189
|
+
const applyingConfig = computed(
|
|
190
|
+
() => applyingFields(manifest.value?.config ?? [], draftNode.value?.config ?? {})
|
|
191
|
+
);
|
|
192
|
+
const parameterFields = computed(() => applyingConfig.value.filter((f) => !SETTINGS_FIELD_KEY.test(f.key)));
|
|
193
|
+
const settingsFields = computed(() => applyingConfig.value.filter((f) => SETTINGS_FIELD_KEY.test(f.key)));
|
|
194
|
+
const FOLD_ABOVE = 4;
|
|
195
|
+
const ALWAYS_DRAWN = 3;
|
|
196
|
+
const foldedFields = computed(
|
|
197
|
+
() => parameterFields.value.length > FOLD_ABOVE ? parameterFields.value.slice(ALWAYS_DRAWN).filter((f) => !f.required) : []
|
|
198
|
+
);
|
|
199
|
+
const foldedKeys = computed(() => new Set(foldedFields.value.map((f) => f.key)));
|
|
200
|
+
const foldAnchorKey = computed(() => foldedFields.value[0]?.key);
|
|
157
201
|
function optionalShouldOpen() {
|
|
158
|
-
if (!
|
|
159
|
-
return
|
|
202
|
+
if (!parameterFields.value.some((f) => f.required)) return true;
|
|
203
|
+
return foldedFields.value.some((f) => props.refusedFields?.[f.key] !== void 0);
|
|
160
204
|
}
|
|
161
205
|
const optionalOpen = ref(optionalShouldOpen());
|
|
162
206
|
watch([() => props.selection?.id, manifest], () => {
|
|
163
207
|
optionalOpen.value = optionalShouldOpen();
|
|
164
208
|
});
|
|
165
|
-
function dependentKeys(changedKey) {
|
|
166
|
-
|
|
167
|
-
const out = /* @__PURE__ */ new Set();
|
|
168
|
-
const queue = [changedKey];
|
|
169
|
-
while (queue.length > 0) {
|
|
170
|
-
const dep = queue.shift();
|
|
171
|
-
for (const f of fields) {
|
|
172
|
-
if ((f.dependsOn ?? []).includes(dep) && !out.has(f.key)) {
|
|
173
|
-
out.add(f.key);
|
|
174
|
-
queue.push(f.key);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
out.delete(changedKey);
|
|
179
|
-
return out;
|
|
209
|
+
function dependentKeys(changedKey, after) {
|
|
210
|
+
return staleKeys(manifest.value?.config ?? [], changedKey, after);
|
|
180
211
|
}
|
|
181
212
|
function writeConfig(key, value) {
|
|
182
213
|
const wn = draftNode.value;
|
|
183
214
|
if (!wn) return;
|
|
184
215
|
const next = { ...wn.config, [key]: value };
|
|
185
|
-
const cleared = dependentKeys(key);
|
|
216
|
+
const cleared = dependentKeys(key, next);
|
|
186
217
|
for (const k of cleared) delete next[k];
|
|
187
218
|
const clearedDynamicSchema = [...cleared].some(
|
|
188
219
|
(k) => (manifest.value?.config ?? []).some((f) => f.key === k && f.type === "dynamic-schema")
|
|
@@ -760,7 +791,7 @@ const hasNext = computed(() => seqIndex.value >= 0 && seqIndex.value < sequence.
|
|
|
760
791
|
function navigate(offset) {
|
|
761
792
|
const target = sequence.value[seqIndex.value + offset];
|
|
762
793
|
if (!target) return;
|
|
763
|
-
|
|
794
|
+
commit();
|
|
764
795
|
emit("navigate", target);
|
|
765
796
|
}
|
|
766
797
|
const outputView = ref("schema");
|
|
@@ -866,7 +897,16 @@ async function runTestStep() {
|
|
|
866
897
|
try {
|
|
867
898
|
const res = await api.post(
|
|
868
899
|
`/nodes/${encodeURIComponent(wn.nodeSlug)}/${encodeURIComponent(wn.nodeVersion)}/execute:test`,
|
|
869
|
-
{
|
|
900
|
+
{
|
|
901
|
+
config: wn.config ?? {},
|
|
902
|
+
inputs,
|
|
903
|
+
// Names the workflow being edited, which is what lets the test run READ
|
|
904
|
+
// the state namespaces this workflow declares (PO-374). Writes stay
|
|
905
|
+
// refused: a correlation created by a test click would be
|
|
906
|
+
// indistinguishable from one a real run made. Omitted for an unsaved
|
|
907
|
+
// workflow, which has nothing declared yet anyway.
|
|
908
|
+
...props.workflowId ? { workflow_id: Number(props.workflowId) } : {}
|
|
909
|
+
}
|
|
870
910
|
);
|
|
871
911
|
testLogs.value = res.logs ?? [];
|
|
872
912
|
testOutputs.value = {
|
|
@@ -959,6 +999,26 @@ async function runTestStep() {
|
|
|
959
999
|
</PopoverContent>
|
|
960
1000
|
</Popover>
|
|
961
1001
|
</div>
|
|
1002
|
+
<!-- The version, once it is something the author can change (PO-339).
|
|
1003
|
+
Deliberately a line of its own and deliberately grey: where a newer
|
|
1004
|
+
version merely exists, nothing is wrong, and a coloured mark for a
|
|
1005
|
+
non-problem is what teaches people to stop reading marks. It is
|
|
1006
|
+
absent altogether for a node with one runnable version — there is no
|
|
1007
|
+
choice to offer — and for a node whose pinned version cannot run,
|
|
1008
|
+
where the notice below the tabs carries the same route in the louder
|
|
1009
|
+
tone that state has earned. -->
|
|
1010
|
+
<div v-if="isNode && canMoveVersion && versionExecutable" class="flex items-center gap-1 text-xs text-muted-foreground">
|
|
1011
|
+
<span v-if="newerVersions.length" class="tabular-nums">
|
|
1012
|
+
{{ t("integration.nodeInspector.version.available", { version: newerVersions[0]?.version ?? "" }) }}
|
|
1013
|
+
</span>
|
|
1014
|
+
<button
|
|
1015
|
+
type="button"
|
|
1016
|
+
class="rounded-sm underline decoration-dotted underline-offset-2 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
|
1017
|
+
@click="versionMoveOpen = true"
|
|
1018
|
+
>
|
|
1019
|
+
{{ t("integration.nodeInspector.version.change") }}
|
|
1020
|
+
</button>
|
|
1021
|
+
</div>
|
|
962
1022
|
</div>
|
|
963
1023
|
<span class="shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wider text-slate-600">
|
|
964
1024
|
{{ groupLabel }}
|
|
@@ -1298,6 +1358,18 @@ async function runTestStep() {
|
|
|
1298
1358
|
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"
|
|
1299
1359
|
>
|
|
1300
1360
|
{{ readyVersion ? t("integration.nodeInspector.notice.versionNotReady.upgrade", { version: draftNode?.nodeVersion ?? "", ready: readyVersion }) : t("integration.nodeInspector.notice.versionNotReady.none", { version: draftNode?.nodeVersion ?? "" }) }}
|
|
1361
|
+
<!-- The sentence named a version and led nowhere (PO-339). It leads
|
|
1362
|
+
here now; where the node has no runnable version at all there is
|
|
1363
|
+
still nothing to offer, and the notice stays the statement it
|
|
1364
|
+
was. -->
|
|
1365
|
+
<button
|
|
1366
|
+
v-if="readyVersion && canMoveVersion"
|
|
1367
|
+
type="button"
|
|
1368
|
+
class="ml-1 rounded-sm font-medium underline underline-offset-2 hover:text-rose-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-rose-400"
|
|
1369
|
+
@click="versionMoveOpen = true"
|
|
1370
|
+
>
|
|
1371
|
+
{{ t("integration.nodeInspector.version.moveTo", { version: readyVersion }) }}
|
|
1372
|
+
</button>
|
|
1301
1373
|
</p>
|
|
1302
1374
|
|
|
1303
1375
|
<div
|
|
@@ -1738,63 +1810,39 @@ async function runTestStep() {
|
|
|
1738
1810
|
</button>
|
|
1739
1811
|
</div>
|
|
1740
1812
|
</div>
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
:secret-keys="secretKeys"
|
|
1749
|
-
:static-keys="(manifest.config ?? []).map((f2) => f2.key)"
|
|
1750
|
-
:sibling-fields="manifest.config ?? []"
|
|
1751
|
-
:depth="0"
|
|
1752
|
-
:variables="variables"
|
|
1753
|
-
:upstream-outputs="upstreamOutputs"
|
|
1754
|
-
:refused="refusedFields?.[field.key]"
|
|
1755
|
-
@set="(k, v) => writeConfig(k, v)"
|
|
1756
|
-
/>
|
|
1757
|
-
|
|
1758
|
-
<!-- Optional settings, folded. The count is on the control on
|
|
1759
|
-
purpose: "Show 9 optional settings" is a decision the user can
|
|
1760
|
-
take without opening it, "Show more" is not. -->
|
|
1761
|
-
<template v-if="optionalFields.length">
|
|
1813
|
+
<!-- One list, in the order the node declared it. The fold's
|
|
1814
|
+
control is drawn at the first setting it hides rather than
|
|
1815
|
+
under the form, so opening it inserts the tail where it
|
|
1816
|
+
belongs instead of moving anything. The count is on the
|
|
1817
|
+
control on purpose: "Show 9 optional settings" is a decision
|
|
1818
|
+
the user can take without opening it, "Show more" is not. -->
|
|
1819
|
+
<template v-for="field in parameterFields" :key="field.key">
|
|
1762
1820
|
<button
|
|
1763
|
-
v-if="
|
|
1821
|
+
v-if="field.key === foldAnchorKey"
|
|
1764
1822
|
type="button"
|
|
1765
1823
|
class="flex items-center gap-1.5 rounded px-1 py-1 text-sm text-muted-foreground transition hover:text-foreground"
|
|
1766
|
-
@click="optionalOpen =
|
|
1824
|
+
@click="optionalOpen = !optionalOpen"
|
|
1767
1825
|
>
|
|
1768
|
-
<ChevronRight :size="13" />
|
|
1769
|
-
{{ t("integration.nodeInspector.parameters.optional.show",
|
|
1826
|
+
<component :is="optionalOpen ? ChevronDown : ChevronRight" :size="13" />
|
|
1827
|
+
{{ optionalOpen ? t("integration.nodeInspector.parameters.optional.hide", foldedFields.length) : t("integration.nodeInspector.parameters.optional.show", foldedFields.length) }}
|
|
1770
1828
|
</button>
|
|
1771
|
-
<
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
:secret-keys="secretKeys"
|
|
1789
|
-
:static-keys="(manifest.config ?? []).map((f2) => f2.key)"
|
|
1790
|
-
:sibling-fields="manifest.config ?? []"
|
|
1791
|
-
:depth="0"
|
|
1792
|
-
:variables="variables"
|
|
1793
|
-
:upstream-outputs="upstreamOutputs"
|
|
1794
|
-
:refused="refusedFields?.[field.key]"
|
|
1795
|
-
@set="(k, v) => writeConfig(k, v)"
|
|
1796
|
-
/>
|
|
1797
|
-
</template>
|
|
1829
|
+
<IntegrationsNodeConfigField
|
|
1830
|
+
v-if="optionalOpen || !foldedKeys.has(field.key)"
|
|
1831
|
+
:field="field"
|
|
1832
|
+
:config="draftNode.config ?? {}"
|
|
1833
|
+
:slug="draftNode.nodeSlug"
|
|
1834
|
+
:version="draftNode.nodeVersion"
|
|
1835
|
+
:secret-keys="secretKeys"
|
|
1836
|
+
:static-keys="(manifest.config ?? []).map((f2) => f2.key)"
|
|
1837
|
+
:sibling-fields="manifest.config ?? []"
|
|
1838
|
+
:depth="0"
|
|
1839
|
+
:variables="variables"
|
|
1840
|
+
:state-namespaces="offeredStateNamespaces"
|
|
1841
|
+
@declare-state="stageStateNamespace"
|
|
1842
|
+
:upstream-outputs="upstreamOutputs"
|
|
1843
|
+
:refused="refusedFields?.[field.key]"
|
|
1844
|
+
@set="(k, v) => writeConfig(k, v)"
|
|
1845
|
+
/>
|
|
1798
1846
|
</template>
|
|
1799
1847
|
|
|
1800
1848
|
<p v-if="!parameterFields.length" class="text-sm text-muted-foreground italic">
|
|
@@ -1832,6 +1880,8 @@ async function runTestStep() {
|
|
|
1832
1880
|
:sibling-fields="manifest?.config ?? []"
|
|
1833
1881
|
:depth="0"
|
|
1834
1882
|
:variables="variables"
|
|
1883
|
+
:state-namespaces="offeredStateNamespaces"
|
|
1884
|
+
@declare-state="stageStateNamespace"
|
|
1835
1885
|
:upstream-outputs="upstreamOutputs"
|
|
1836
1886
|
:refused="refusedFields?.[field.key]"
|
|
1837
1887
|
@set="(k, v) => writeConfig(k, v)"
|
|
@@ -2121,6 +2171,11 @@ async function runTestStep() {
|
|
|
2121
2171
|
</Button>
|
|
2122
2172
|
</div>
|
|
2123
2173
|
</div>
|
|
2174
|
+
|
|
2175
|
+
<!-- Asked outside the panes so it is not clipped by either, and mounted
|
|
2176
|
+
unconditionally: the two places that open it sit in different parts of
|
|
2177
|
+
the dialog and neither owns it. -->
|
|
2178
|
+
<NodeVersionMoveDialog v-model:open="versionMoveOpen" :node="draftNode" @move="applyVersionMove" />
|
|
2124
2179
|
</div>
|
|
2125
2180
|
</template>
|
|
2126
2181
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { NodeManifestOutput, WorkflowEdge, WorkflowNode, WorkflowTrigger, WorkflowVariable } from '../../types/integrations.js';
|
|
1
|
+
import type { NodeManifestOutput, WorkflowEdge, WorkflowNode, WorkflowStateNamespace, WorkflowStateRole, WorkflowStateVisibility, WorkflowTrigger, WorkflowVariable } from '../../types/integrations.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
/** Either a workflow node OR a trigger. Distinguished by presence of `nodeSlug`. */
|
|
4
4
|
selection: WorkflowNode | WorkflowTrigger | null;
|
|
5
5
|
secretKeys: string[];
|
|
6
6
|
variables?: Record<string, WorkflowVariable>;
|
|
7
|
+
/** The workflow's declared state namespaces — options for `state-ref` fields. */
|
|
8
|
+
stateNamespaces?: WorkflowStateNamespace[];
|
|
7
9
|
upstreamOutputs?: Array<{
|
|
8
10
|
nodeId: string;
|
|
9
11
|
nodeName?: string;
|
|
@@ -31,11 +33,13 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {
|
|
|
31
33
|
update: (next: WorkflowTrigger | WorkflowNode) => any;
|
|
32
34
|
remove: () => any;
|
|
33
35
|
close: () => any;
|
|
36
|
+
"declare-state": (namespace: string, role: WorkflowStateRole, visibility: WorkflowStateVisibility) => any;
|
|
34
37
|
navigate: (id: string) => any;
|
|
35
38
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
36
39
|
onUpdate?: ((next: WorkflowTrigger | WorkflowNode) => any) | undefined;
|
|
37
40
|
onRemove?: (() => any) | undefined;
|
|
38
41
|
onClose?: (() => any) | undefined;
|
|
42
|
+
"onDeclare-state"?: ((namespace: string, role: WorkflowStateRole, visibility: WorkflowStateVisibility) => any) | undefined;
|
|
39
43
|
onNavigate?: ((id: string) => any) | undefined;
|
|
40
44
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
41
45
|
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { WorkflowNode } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
node: WorkflowNode | null;
|
|
4
|
+
};
|
|
5
|
+
type __VLS_PublicProps = __VLS_Props & {
|
|
6
|
+
'open'?: boolean;
|
|
7
|
+
};
|
|
8
|
+
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:open": (value: boolean) => any;
|
|
10
|
+
move: (payload: {
|
|
11
|
+
version: string;
|
|
12
|
+
config: Record<string, unknown>;
|
|
13
|
+
}) => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
15
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
16
|
+
onMove?: ((payload: {
|
|
17
|
+
version: string;
|
|
18
|
+
config: Record<string, unknown>;
|
|
19
|
+
}) => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
AlertDialog,
|
|
5
|
+
AlertDialogContent,
|
|
6
|
+
AlertDialogDescription,
|
|
7
|
+
AlertDialogFooter,
|
|
8
|
+
AlertDialogHeader,
|
|
9
|
+
AlertDialogTitle,
|
|
10
|
+
Button
|
|
11
|
+
} from "@revenexx/studio";
|
|
12
|
+
import { executableVersionsOf, findManifest, newestExecutableVersion } from "../../data/integrationNodeCatalog";
|
|
13
|
+
import { planVersionMove } from "../../data/nodeVersionMove";
|
|
14
|
+
import { resolveLocalized } from "../../utils/localize";
|
|
15
|
+
const open = defineModel("open", { type: Boolean, ...{ default: false } });
|
|
16
|
+
const props = defineProps({
|
|
17
|
+
node: { type: [Object, null], required: true }
|
|
18
|
+
});
|
|
19
|
+
const emit = defineEmits(["move"]);
|
|
20
|
+
const t = useIntegrationsT();
|
|
21
|
+
const versions = computed(() => props.node ? executableVersionsOf(props.node.nodeSlug) : []);
|
|
22
|
+
const target = ref("");
|
|
23
|
+
watch(open, (isOpen) => {
|
|
24
|
+
if (!isOpen || !props.node) return;
|
|
25
|
+
const newest = newestExecutableVersion(props.node.nodeSlug);
|
|
26
|
+
target.value = newest && newest !== props.node.nodeVersion ? newest : props.node.nodeVersion;
|
|
27
|
+
});
|
|
28
|
+
const plan = computed(() => {
|
|
29
|
+
const n = props.node;
|
|
30
|
+
if (!n) return null;
|
|
31
|
+
return planVersionMove(
|
|
32
|
+
findManifestExact(n.nodeSlug, n.nodeVersion),
|
|
33
|
+
findManifestExact(n.nodeSlug, target.value),
|
|
34
|
+
n.config
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
function findManifestExact(slug, version) {
|
|
38
|
+
const m = findManifest(slug, version);
|
|
39
|
+
return m && m.version === version ? m : null;
|
|
40
|
+
}
|
|
41
|
+
const unchanged = computed(() => !props.node || target.value === props.node.nodeVersion);
|
|
42
|
+
function labelOf(entry) {
|
|
43
|
+
return resolveLocalized(entry.field?.label) || entry.key;
|
|
44
|
+
}
|
|
45
|
+
function confirm() {
|
|
46
|
+
const p = plan.value;
|
|
47
|
+
if (!props.node || !p || unchanged.value) return;
|
|
48
|
+
emit("move", { version: target.value, config: p.nextConfig });
|
|
49
|
+
open.value = false;
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<template>
|
|
54
|
+
<AlertDialog :open="open" @update:open="(v) => {
|
|
55
|
+
if (!v) open = false;
|
|
56
|
+
}">
|
|
57
|
+
<AlertDialogContent>
|
|
58
|
+
<AlertDialogHeader>
|
|
59
|
+
<AlertDialogTitle>{{ t("integration.nodeInspector.version.move.title") }}</AlertDialogTitle>
|
|
60
|
+
<AlertDialogDescription>
|
|
61
|
+
{{ t("integration.nodeInspector.version.move.description") }}
|
|
62
|
+
</AlertDialogDescription>
|
|
63
|
+
</AlertDialogHeader>
|
|
64
|
+
|
|
65
|
+
<div v-if="props.node && plan" class="space-y-3 text-sm">
|
|
66
|
+
<div class="flex items-center gap-2">
|
|
67
|
+
<span class="tabular-nums text-muted-foreground">v{{ props.node.nodeVersion }}</span>
|
|
68
|
+
<span aria-hidden="true">→</span>
|
|
69
|
+
<select
|
|
70
|
+
v-model="target"
|
|
71
|
+
class="rounded border border-black/10 bg-white px-1.5 py-0.5 tabular-nums"
|
|
72
|
+
:aria-label="t('integration.nodeInspector.version.move.targetAria')"
|
|
73
|
+
>
|
|
74
|
+
<option v-for="m in versions" :key="m.version" :value="m.version">
|
|
75
|
+
v{{ m.version }}
|
|
76
|
+
</option>
|
|
77
|
+
</select>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- A version the catalogue no longer carries cannot be compared against,
|
|
81
|
+
so the lists below are what arrives rather than what was checked. Said
|
|
82
|
+
once, here, instead of marking every setting as suspect. -->
|
|
83
|
+
<p v-if="!plan.comparable" class="text-amber-600">
|
|
84
|
+
{{ t("integration.nodeInspector.version.move.notComparable", { version: props.node.nodeVersion }) }}
|
|
85
|
+
</p>
|
|
86
|
+
|
|
87
|
+
<p v-if="unchanged" class="text-muted-foreground">
|
|
88
|
+
{{ t("integration.nodeInspector.version.move.same") }}
|
|
89
|
+
</p>
|
|
90
|
+
|
|
91
|
+
<template v-else>
|
|
92
|
+
<p v-if="plan.lossless" class="text-muted-foreground">
|
|
93
|
+
{{ t("integration.nodeInspector.version.move.lossless") }}
|
|
94
|
+
</p>
|
|
95
|
+
|
|
96
|
+
<div v-if="plan.dropped.length">
|
|
97
|
+
<p class="font-medium">{{ t("integration.nodeInspector.version.move.dropped") }}</p>
|
|
98
|
+
<ul class="mt-0.5 list-disc pl-5 text-muted-foreground">
|
|
99
|
+
<li v-for="e in plan.dropped" :key="e.key">
|
|
100
|
+
{{ labelOf(e) }}
|
|
101
|
+
<!-- The key, where it is not already the whole label. A setting no
|
|
102
|
+
version declares any more has nothing but its key to go by, and
|
|
103
|
+
printing that twice reads as a bug rather than as precision. -->
|
|
104
|
+
<span v-if="labelOf(e) !== e.key" class="text-xs">({{ e.key }})</span>
|
|
105
|
+
</li>
|
|
106
|
+
</ul>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div v-if="plan.review.length">
|
|
110
|
+
<p class="font-medium">{{ t("integration.nodeInspector.version.move.review") }}</p>
|
|
111
|
+
<ul class="mt-0.5 list-disc pl-5 text-muted-foreground">
|
|
112
|
+
<li v-for="e in plan.review" :key="e.key">
|
|
113
|
+
{{ labelOf(e) }}
|
|
114
|
+
<span class="text-xs">
|
|
115
|
+
({{ e.reason === "nowRequired" ? t("integration.nodeInspector.version.move.reason.nowRequired") : t("integration.nodeInspector.version.move.reason.changed") }})
|
|
116
|
+
</span>
|
|
117
|
+
</li>
|
|
118
|
+
</ul>
|
|
119
|
+
</div>
|
|
120
|
+
</template>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<AlertDialogFooter>
|
|
124
|
+
<Button variant="outline" @click="open = false">
|
|
125
|
+
{{ t("integration.common.action.cancel") }}
|
|
126
|
+
</Button>
|
|
127
|
+
<Button :disabled="unchanged" @click="confirm">
|
|
128
|
+
{{ t("integration.nodeInspector.version.move.confirm") }}
|
|
129
|
+
</Button>
|
|
130
|
+
</AlertDialogFooter>
|
|
131
|
+
</AlertDialogContent>
|
|
132
|
+
</AlertDialog>
|
|
133
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { WorkflowNode } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
node: WorkflowNode | null;
|
|
4
|
+
};
|
|
5
|
+
type __VLS_PublicProps = __VLS_Props & {
|
|
6
|
+
'open'?: boolean;
|
|
7
|
+
};
|
|
8
|
+
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:open": (value: boolean) => any;
|
|
10
|
+
move: (payload: {
|
|
11
|
+
version: string;
|
|
12
|
+
config: Record<string, unknown>;
|
|
13
|
+
}) => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
15
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
16
|
+
onMove?: ((payload: {
|
|
17
|
+
version: string;
|
|
18
|
+
config: Record<string, unknown>;
|
|
19
|
+
}) => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
export default _default;
|