@revenexx/studio-integrations 1.1.1 → 1.3.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 +638 -0
- package/README.md +3 -1
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +6 -18
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +13 -23
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +7 -6
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +15 -4
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +45 -19
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +5 -4
- package/dist/runtime/components/Integrations/LoadingAnnouncement.d.vue.ts +38 -0
- package/dist/runtime/components/Integrations/LoadingAnnouncement.vue +16 -0
- package/dist/runtime/components/Integrations/LoadingAnnouncement.vue.d.ts +38 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +10 -2
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +5 -1
- package/dist/runtime/components/Integrations/NodeConfigField.vue +88 -1
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +5 -1
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +7 -3
- package/dist/runtime/components/Integrations/NodeInspector.vue +125 -54
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +7 -3
- 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/RunActionsMenu.vue +2 -1
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +5 -17
- package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +30 -8
- package/dist/runtime/components/Integrations/RunResponseModal.vue +92 -24
- package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +30 -8
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +61 -3
- package/dist/runtime/components/Integrations/RunsTable.vue +129 -18
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +61 -3
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +10 -1
- 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/StickyNoteView.vue +1 -1
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +18 -3
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue +70 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +2 -1
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +4 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +78 -30
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +17 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +17 -21
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +8 -11
- 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/WorkflowTriggerView.vue +6 -7
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +7 -7
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +10 -48
- package/dist/runtime/components/Integrations/canvasSides.d.ts +48 -0
- package/dist/runtime/components/Integrations/canvasSides.js +22 -0
- package/dist/runtime/composables/useCredentialTypes.js +2 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +2 -2
- package/dist/runtime/composables/useIntegrationsCredentials.js +4 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +16 -1
- package/dist/runtime/composables/useIntegrationsNodes.js +2 -2
- package/dist/runtime/composables/useIntegrationsRevisions.js +2 -6
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +4 -15
- package/dist/runtime/composables/useIntegrationsRuns.js +49 -34
- package/dist/runtime/composables/useIntegrationsSchedules.js +2 -2
- package/dist/runtime/composables/useIntegrationsSecrets.js +2 -6
- package/dist/runtime/composables/useIntegrationsTemplates.js +7 -5
- package/dist/runtime/composables/useIntegrationsTriggers.js +2 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +10 -7
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -2
- package/dist/runtime/composables/useOAuthConnect.d.ts +42 -0
- package/dist/runtime/composables/useOAuthConnect.js +33 -0
- package/dist/runtime/data/expressionEditor.d.ts +17 -5
- package/dist/runtime/data/expressionEditor.js +11 -5
- package/dist/runtime/data/handleId.d.ts +61 -1
- package/dist/runtime/data/handleId.js +8 -1
- 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/data/payloadForm.js +3 -2
- package/dist/runtime/i18n/messages/de.json +79 -12
- package/dist/runtime/i18n/messages/en.json +79 -12
- package/dist/runtime/pages/integrations/audit.vue +7 -6
- package/dist/runtime/pages/integrations/credentials.vue +9 -21
- package/dist/runtime/pages/integrations/index.vue +55 -8
- package/dist/runtime/pages/integrations/runs.vue +116 -7
- package/dist/runtime/pages/integrations/secrets.vue +2 -1
- package/dist/runtime/pages/integrations/templates/[slug].vue +2 -1
- package/dist/runtime/pages/integrations/templates/index.vue +18 -15
- package/dist/runtime/pages/integrations/workflows/[id].vue +169 -24
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -4
- package/dist/runtime/types/integrations.d.ts +49 -1
- package/dist/runtime/utils/apiError.d.ts +21 -1
- package/dist/runtime/utils/apiError.js +1 -1
- package/dist/runtime/utils/buildSettle.d.ts +68 -0
- package/dist/runtime/utils/buildSettle.js +29 -0
- package/dist/runtime/utils/clipboard.d.ts +33 -0
- package/dist/runtime/utils/clipboard.js +11 -0
- package/dist/runtime/utils/deepClone.d.ts +41 -0
- package/dist/runtime/utils/deepClone.js +3 -0
- package/dist/runtime/utils/formatDuration.d.ts +44 -0
- package/dist/runtime/utils/formatDuration.js +13 -0
- package/dist/runtime/utils/nodeDisplayName.d.ts +22 -0
- package/dist/runtime/utils/nodeDisplayName.js +6 -0
- package/dist/runtime/utils/notify.d.ts +1 -1
- package/dist/runtime/utils/runEnvelope.d.ts +31 -0
- package/dist/runtime/utils/runEnvelope.js +12 -0
- package/dist/runtime/utils/runFeedQuery.d.ts +135 -0
- package/dist/runtime/utils/runFeedQuery.js +66 -0
- package/dist/runtime/utils/runOutputs.d.ts +33 -0
- package/dist/runtime/utils/runOutputs.js +10 -0
- package/dist/runtime/utils/settingCondition.d.ts +48 -0
- package/dist/runtime/utils/settingCondition.js +84 -0
- package/dist/runtime/utils/stateNamespaceName.d.ts +23 -0
- package/dist/runtime/utils/stateNamespaceName.js +8 -0
- package/dist/runtime/utils/validationFindings.d.ts +26 -3
- package/dist/runtime/utils/validationFindings.js +18 -3
- package/package.json +1 -1
|
@@ -26,10 +26,11 @@ const props = defineProps({
|
|
|
26
26
|
siblingFields: { type: Array, required: false },
|
|
27
27
|
depth: { type: Number, required: false },
|
|
28
28
|
variables: { type: Object, required: false },
|
|
29
|
+
stateNamespaces: { type: Array, required: false },
|
|
29
30
|
upstreamOutputs: { type: Array, required: false },
|
|
30
31
|
refused: { type: String, required: false }
|
|
31
32
|
});
|
|
32
|
-
const emit = defineEmits(["set"]);
|
|
33
|
+
const emit = defineEmits(["set", "declare-state"]);
|
|
33
34
|
const t = useIntegrationsT();
|
|
34
35
|
const value = computed(() => props.config[props.field.key]);
|
|
35
36
|
function write(v) {
|
|
@@ -127,6 +128,30 @@ function onCredentialSelect(v) {
|
|
|
127
128
|
}
|
|
128
129
|
write(v);
|
|
129
130
|
}
|
|
131
|
+
const stateOptions = computed(() => {
|
|
132
|
+
const role = props.field.stateRole;
|
|
133
|
+
const declared = props.stateNamespaces ?? [];
|
|
134
|
+
const list = role ? declared.filter((n) => n.role === role) : declared;
|
|
135
|
+
const selected = value.value;
|
|
136
|
+
if (typeof selected === "string" && selected && !list.some((n) => n.namespace === selected)) {
|
|
137
|
+
return [{ namespace: selected, role: role ?? "mapping", visibility: void 0 }, ...list];
|
|
138
|
+
}
|
|
139
|
+
return list;
|
|
140
|
+
});
|
|
141
|
+
const stateTakenNames = computed(() => (props.stateNamespaces ?? []).map((n) => n.namespace));
|
|
142
|
+
const stateCreateOpen = ref(false);
|
|
143
|
+
const STATE_ACTION_NEW = "__action_new_state_namespace__";
|
|
144
|
+
function onStateSelect(v) {
|
|
145
|
+
if (v === STATE_ACTION_NEW) {
|
|
146
|
+
stateCreateOpen.value = true;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
write(v);
|
|
150
|
+
}
|
|
151
|
+
function onStateDeclared(namespace, role, visibility) {
|
|
152
|
+
emit("declare-state", namespace, role, visibility);
|
|
153
|
+
write(namespace);
|
|
154
|
+
}
|
|
130
155
|
function acceptsSftp(field) {
|
|
131
156
|
const t2 = field.credentialType;
|
|
132
157
|
return t2 === "revenexx:sftp" || Array.isArray(t2) && t2.includes("revenexx:sftp");
|
|
@@ -195,6 +220,9 @@ function removeKvRow(idx) {
|
|
|
195
220
|
kvRows.value = kvRows.value.filter((_, i) => i !== idx);
|
|
196
221
|
commitKvRows();
|
|
197
222
|
}
|
|
223
|
+
const isMultiline = computed(
|
|
224
|
+
() => Boolean(props.field.multiline) && (props.field.type === "string" || props.field.type === "expression")
|
|
225
|
+
);
|
|
198
226
|
const isQueryField = computed(
|
|
199
227
|
() => props.field.type === "array" && props.field.items?.type !== "object" && /(^|__)queries$/.test(props.field.key)
|
|
200
228
|
);
|
|
@@ -383,6 +411,7 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
383
411
|
:upstream-outputs="upstreamOutputs"
|
|
384
412
|
:invalid="invalid"
|
|
385
413
|
:label="resolveLocalized(field.label)"
|
|
414
|
+
:multiline="isMultiline"
|
|
386
415
|
@update:model-value="(v) => write(v)"
|
|
387
416
|
/>
|
|
388
417
|
|
|
@@ -409,6 +438,25 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
409
438
|
:upstream-outputs="upstreamOutputs"
|
|
410
439
|
:invalid="invalid"
|
|
411
440
|
:label="resolveLocalized(field.label)"
|
|
441
|
+
:multiline="isMultiline"
|
|
442
|
+
@update:model-value="(v) => write(v)"
|
|
443
|
+
/>
|
|
444
|
+
|
|
445
|
+
<!-- plain string the node says takes several lines. Same accessible name as
|
|
446
|
+
the one-line box below, because it is the same setting. Opens at four
|
|
447
|
+
rows, grows with what is typed and scrolls past ten — the same rule the
|
|
448
|
+
expression editor follows, so the two controls for one kind of setting
|
|
449
|
+
do not behave differently. The two heights are lines × 1.25rem plus the
|
|
450
|
+
1rem of vertical padding; `rows` alone would not set them, because the
|
|
451
|
+
shared control sizes itself to its content and ignores it. -->
|
|
452
|
+
<Textarea
|
|
453
|
+
v-else-if="field.type === 'string' && isMultiline"
|
|
454
|
+
:aria-label="resolveLocalized(field.label)"
|
|
455
|
+
:model-value="value ?? ''"
|
|
456
|
+
:placeholder="resolveLocalized(field.placeholder)"
|
|
457
|
+
:rows="4"
|
|
458
|
+
class="min-h-24 max-h-[13.5rem] resize-none rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white"
|
|
459
|
+
:class="invalid ? 'border-rose-400 focus-visible:ring-rose-400/40' : ''"
|
|
412
460
|
@update:model-value="(v) => write(v)"
|
|
413
461
|
/>
|
|
414
462
|
|
|
@@ -596,6 +644,45 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
596
644
|
/>
|
|
597
645
|
</template>
|
|
598
646
|
|
|
647
|
+
<!-- state-ref — pick one of the workflow's declared namespaces of this
|
|
648
|
+
field's role. Declaring lives INSIDE the picker, like creating a
|
|
649
|
+
credential does: a picker whose list is empty must not be a dead end
|
|
650
|
+
that sends the author to another dialog and back. -->
|
|
651
|
+
<template v-else-if="field.type === 'state-ref'">
|
|
652
|
+
<Select
|
|
653
|
+
:model-value="value ?? ''"
|
|
654
|
+
@update:model-value="(v) => onStateSelect(v)"
|
|
655
|
+
>
|
|
656
|
+
<SelectTrigger :aria-label="resolveLocalized(field.label)" size="sm" class="h-8 w-full rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
657
|
+
<SelectValue :placeholder="t('integration.nodeConfig.state.placeholder')" />
|
|
658
|
+
</SelectTrigger>
|
|
659
|
+
<SelectContent>
|
|
660
|
+
<SelectItem v-for="o in stateOptions" :key="o.namespace" :value="o.namespace">
|
|
661
|
+
<span class="flex items-center gap-1.5">
|
|
662
|
+
<span class="min-w-0 truncate font-mono">{{ o.namespace }}</span>
|
|
663
|
+
<span
|
|
664
|
+
v-if="o.visibility === 'shared'"
|
|
665
|
+
class="shrink-0 rounded-full border px-1.5 py-px text-[10px] font-medium text-muted-foreground"
|
|
666
|
+
>{{ t("integration.workflows.state.visibility.shared.label") }}</span>
|
|
667
|
+
</span>
|
|
668
|
+
</SelectItem>
|
|
669
|
+
<div v-if="!stateOptions.length" class="px-2 py-1.5 text-sm text-muted-foreground">
|
|
670
|
+
{{ t("integration.nodeConfig.state.empty") }}
|
|
671
|
+
</div>
|
|
672
|
+
<SelectSeparator />
|
|
673
|
+
<SelectItem :value="STATE_ACTION_NEW" class="font-medium text-violet-600 data-[highlighted]:text-violet-700">
|
|
674
|
+
{{ t("integration.nodeConfig.state.action.new") }}
|
|
675
|
+
</SelectItem>
|
|
676
|
+
</SelectContent>
|
|
677
|
+
</Select>
|
|
678
|
+
<IntegrationsStateNamespaceCreateDialog
|
|
679
|
+
v-model:open="stateCreateOpen"
|
|
680
|
+
:role="field.stateRole ?? 'mapping'"
|
|
681
|
+
:taken="stateTakenNames"
|
|
682
|
+
@declared="onStateDeclared"
|
|
683
|
+
/>
|
|
684
|
+
</template>
|
|
685
|
+
|
|
599
686
|
<!-- array of objects (e.g. headers / mappings) -->
|
|
600
687
|
<div v-else-if="field.type === 'array' && field.items?.type === 'object'" class="space-y-2">
|
|
601
688
|
<div
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConfigField, WorkflowVariable } from '../../types/integrations.js';
|
|
1
|
+
import type { ConfigField, WorkflowStateNamespace, WorkflowStateRole, WorkflowStateVisibility, WorkflowVariable } from '../../types/integrations.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
field: ConfigField;
|
|
4
4
|
config: Record<string, unknown>;
|
|
@@ -11,6 +11,8 @@ type __VLS_Props = {
|
|
|
11
11
|
siblingFields?: ConfigField[];
|
|
12
12
|
depth?: number;
|
|
13
13
|
variables?: Record<string, WorkflowVariable>;
|
|
14
|
+
/** The workflow's declared state namespaces — the options a `state-ref` offers. */
|
|
15
|
+
stateNamespaces?: WorkflowStateNamespace[];
|
|
14
16
|
upstreamOutputs?: Array<{
|
|
15
17
|
nodeId: string;
|
|
16
18
|
nodeName?: string;
|
|
@@ -22,7 +24,9 @@ type __VLS_Props = {
|
|
|
22
24
|
};
|
|
23
25
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
24
26
|
set: (key: string, value: unknown) => any;
|
|
27
|
+
"declare-state": (namespace: string, role: WorkflowStateRole, visibility: WorkflowStateVisibility) => any;
|
|
25
28
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
26
29
|
onSet?: ((key: string, value: unknown) => any) | undefined;
|
|
30
|
+
"onDeclare-state"?: ((namespace: string, role: WorkflowStateRole, visibility: WorkflowStateVisibility) => any) | undefined;
|
|
27
31
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
28
32
|
export default _default;
|
|
@@ -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;
|
|
@@ -29,13 +31,15 @@ type __VLS_Props = {
|
|
|
29
31
|
};
|
|
30
32
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
33
|
update: (next: WorkflowTrigger | WorkflowNode) => any;
|
|
32
|
-
close: () => any;
|
|
33
34
|
remove: () => any;
|
|
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
|
-
onClose?: (() => any) | undefined;
|
|
38
40
|
onRemove?: (() => any) | undefined;
|
|
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;
|
|
@@ -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,9 @@ 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";
|
|
44
|
+
import { deepClone } from "../../utils/deepClone";
|
|
45
|
+
import { copyToClipboard } from "../../utils/clipboard";
|
|
42
46
|
import { nodeDisplayLabel } from "../../utils/validationFindings";
|
|
43
47
|
import { authChoiceFromConfig, configPatchForAuthChoice } from "../../data/webhookAuth";
|
|
44
48
|
import {
|
|
@@ -49,11 +53,13 @@ import {
|
|
|
49
53
|
} from "../../data/nodeRunData";
|
|
50
54
|
import { resolveDisplayName, resolveLocalized } from "../../utils/localize";
|
|
51
55
|
import { describeRecurrence, parseCron, scheduleTimezone, toCron } from "../../utils/schedule";
|
|
56
|
+
import { errorReason } from "../../utils/apiError";
|
|
52
57
|
const t = useIntegrationsT();
|
|
53
58
|
const props = defineProps({
|
|
54
59
|
selection: { type: [Object, null], required: true },
|
|
55
60
|
secretKeys: { type: Array, required: true },
|
|
56
61
|
variables: { type: Object, required: false },
|
|
62
|
+
stateNamespaces: { type: Array, required: false },
|
|
57
63
|
upstreamOutputs: { type: Array, required: false },
|
|
58
64
|
autofocusName: { type: Boolean, required: false },
|
|
59
65
|
nodes: { type: Array, required: false },
|
|
@@ -63,20 +69,35 @@ const props = defineProps({
|
|
|
63
69
|
workflowId: { type: [Number, String], required: false },
|
|
64
70
|
refusedFields: { type: Object, required: false }
|
|
65
71
|
});
|
|
66
|
-
const emit = defineEmits(["update", "close", "remove", "navigate"]);
|
|
67
|
-
|
|
68
|
-
return JSON.parse(JSON.stringify(v));
|
|
69
|
-
}
|
|
70
|
-
const draft = ref(clone(props.selection));
|
|
72
|
+
const emit = defineEmits(["update", "close", "remove", "navigate", "declare-state"]);
|
|
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,8 +186,11 @@ 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
|
-
|
|
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)));
|
|
155
194
|
const requiredFields = computed(() => parameterFields.value.filter((f) => f.required));
|
|
156
195
|
const optionalFields = computed(() => parameterFields.value.filter((f) => !f.required));
|
|
157
196
|
function optionalShouldOpen() {
|
|
@@ -162,27 +201,14 @@ const optionalOpen = ref(optionalShouldOpen());
|
|
|
162
201
|
watch([() => props.selection?.id, manifest], () => {
|
|
163
202
|
optionalOpen.value = optionalShouldOpen();
|
|
164
203
|
});
|
|
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;
|
|
204
|
+
function dependentKeys(changedKey, after) {
|
|
205
|
+
return staleKeys(manifest.value?.config ?? [], changedKey, after);
|
|
180
206
|
}
|
|
181
207
|
function writeConfig(key, value) {
|
|
182
208
|
const wn = draftNode.value;
|
|
183
209
|
if (!wn) return;
|
|
184
210
|
const next = { ...wn.config, [key]: value };
|
|
185
|
-
const cleared = dependentKeys(key);
|
|
211
|
+
const cleared = dependentKeys(key, next);
|
|
186
212
|
for (const k of cleared) delete next[k];
|
|
187
213
|
const clearedDynamicSchema = [...cleared].some(
|
|
188
214
|
(k) => (manifest.value?.config ?? []).some((f) => f.key === k && f.type === "dynamic-schema")
|
|
@@ -203,12 +229,10 @@ function patchTrigger(patch) {
|
|
|
203
229
|
}
|
|
204
230
|
async function copyConfig() {
|
|
205
231
|
if (!draftNode.value) return;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
toast.error(t("integration.nodeInspector.toast.configCopyFailed"));
|
|
211
|
-
}
|
|
232
|
+
await copyToClipboard(JSON.stringify(draftNode.value.config ?? {}, null, 2), {
|
|
233
|
+
copied: t("integration.nodeInspector.toast.configCopied"),
|
|
234
|
+
failed: t("integration.nodeInspector.toast.configCopyFailed")
|
|
235
|
+
});
|
|
212
236
|
}
|
|
213
237
|
async function pasteConfig() {
|
|
214
238
|
if (!draftNode.value) return;
|
|
@@ -440,28 +464,24 @@ async function rotateEndpointSecret() {
|
|
|
440
464
|
toast.error(t("integration.nodeInspector.toast.rotateNoSecret"));
|
|
441
465
|
}
|
|
442
466
|
} catch (e) {
|
|
443
|
-
toast.error(e
|
|
467
|
+
toast.error(errorReason(e, t("integration.nodeInspector.toast.rotateFailed")));
|
|
444
468
|
} finally {
|
|
445
469
|
rotateBusy.value = false;
|
|
446
470
|
}
|
|
447
471
|
}
|
|
448
472
|
async function copySecret() {
|
|
449
473
|
if (!rotatedSecret.value) return;
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
toast.error(t("integration.nodeInspector.toast.secretCopyFailed"));
|
|
455
|
-
}
|
|
474
|
+
await copyToClipboard(rotatedSecret.value, {
|
|
475
|
+
copied: t("integration.nodeInspector.toast.secretCopied"),
|
|
476
|
+
failed: t("integration.nodeInspector.toast.secretCopyFailed")
|
|
477
|
+
});
|
|
456
478
|
}
|
|
457
479
|
async function copyWebhookUrl() {
|
|
458
480
|
if (!webhookUrl.value) return;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
toast.error(t("integration.nodeInspector.toast.webhookUrlCopyFailed"));
|
|
464
|
-
}
|
|
481
|
+
await copyToClipboard(webhookUrl.value, {
|
|
482
|
+
copied: t("integration.nodeInspector.toast.webhookUrlCopied"),
|
|
483
|
+
failed: t("integration.nodeInspector.toast.webhookUrlCopyFailed")
|
|
484
|
+
});
|
|
465
485
|
}
|
|
466
486
|
const definedEvents = typeof useDefinedEvents === "function" ? useDefinedEvents() : { emits: ref([]), load: async () => {
|
|
467
487
|
} };
|
|
@@ -526,7 +546,7 @@ async function loadRunData() {
|
|
|
526
546
|
runData.value = map;
|
|
527
547
|
runInfo.value = { id: source.id, startedAt: source.startedAt };
|
|
528
548
|
} catch (e) {
|
|
529
|
-
runError.value = e
|
|
549
|
+
runError.value = errorReason(e, t("integration.nodeInspector.input.runData.loadFailed"));
|
|
530
550
|
} finally {
|
|
531
551
|
runLoading.value = false;
|
|
532
552
|
}
|
|
@@ -640,11 +660,10 @@ function insertExpression(expression) {
|
|
|
640
660
|
el.focus();
|
|
641
661
|
return;
|
|
642
662
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
647
|
-
toast(t("integration.nodeInspector.toast.expressionCopied"));
|
|
663
|
+
void copyToClipboard(expression, {
|
|
664
|
+
copied: t("integration.nodeInspector.toast.expressionCopied"),
|
|
665
|
+
failed: t("integration.nodeInspector.toast.expressionCopyFailed")
|
|
666
|
+
});
|
|
648
667
|
}
|
|
649
668
|
const chipClass = "max-w-full cursor-grab truncate rounded-md border border-black/[0.06] bg-slate-50 px-1.5 py-0.5 font-mono text-[11px] text-slate-700 transition hover:border-black/[0.12] hover:bg-slate-100 active:cursor-grabbing";
|
|
650
669
|
const rowClass = "flex w-full min-w-0 cursor-grab items-center gap-1.5 rounded-md px-1.5 py-[3px] text-left font-mono text-[11px] text-slate-700 transition hover:bg-slate-100 active:cursor-grabbing";
|
|
@@ -767,7 +786,7 @@ const hasNext = computed(() => seqIndex.value >= 0 && seqIndex.value < sequence.
|
|
|
767
786
|
function navigate(offset) {
|
|
768
787
|
const target = sequence.value[seqIndex.value + offset];
|
|
769
788
|
if (!target) return;
|
|
770
|
-
|
|
789
|
+
commit();
|
|
771
790
|
emit("navigate", target);
|
|
772
791
|
}
|
|
773
792
|
const outputView = ref("schema");
|
|
@@ -873,7 +892,16 @@ async function runTestStep() {
|
|
|
873
892
|
try {
|
|
874
893
|
const res = await api.post(
|
|
875
894
|
`/nodes/${encodeURIComponent(wn.nodeSlug)}/${encodeURIComponent(wn.nodeVersion)}/execute:test`,
|
|
876
|
-
{
|
|
895
|
+
{
|
|
896
|
+
config: wn.config ?? {},
|
|
897
|
+
inputs,
|
|
898
|
+
// Names the workflow being edited, which is what lets the test run READ
|
|
899
|
+
// the state namespaces this workflow declares (PO-374). Writes stay
|
|
900
|
+
// refused: a correlation created by a test click would be
|
|
901
|
+
// indistinguishable from one a real run made. Omitted for an unsaved
|
|
902
|
+
// workflow, which has nothing declared yet anyway.
|
|
903
|
+
...props.workflowId ? { workflow_id: Number(props.workflowId) } : {}
|
|
904
|
+
}
|
|
877
905
|
);
|
|
878
906
|
testLogs.value = res.logs ?? [];
|
|
879
907
|
testOutputs.value = {
|
|
@@ -966,6 +994,26 @@ async function runTestStep() {
|
|
|
966
994
|
</PopoverContent>
|
|
967
995
|
</Popover>
|
|
968
996
|
</div>
|
|
997
|
+
<!-- The version, once it is something the author can change (PO-339).
|
|
998
|
+
Deliberately a line of its own and deliberately grey: where a newer
|
|
999
|
+
version merely exists, nothing is wrong, and a coloured mark for a
|
|
1000
|
+
non-problem is what teaches people to stop reading marks. It is
|
|
1001
|
+
absent altogether for a node with one runnable version — there is no
|
|
1002
|
+
choice to offer — and for a node whose pinned version cannot run,
|
|
1003
|
+
where the notice below the tabs carries the same route in the louder
|
|
1004
|
+
tone that state has earned. -->
|
|
1005
|
+
<div v-if="isNode && canMoveVersion && versionExecutable" class="flex items-center gap-1 text-xs text-muted-foreground">
|
|
1006
|
+
<span v-if="newerVersions.length" class="tabular-nums">
|
|
1007
|
+
{{ t("integration.nodeInspector.version.available", { version: newerVersions[0]?.version ?? "" }) }}
|
|
1008
|
+
</span>
|
|
1009
|
+
<button
|
|
1010
|
+
type="button"
|
|
1011
|
+
class="rounded-sm underline decoration-dotted underline-offset-2 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
|
1012
|
+
@click="versionMoveOpen = true"
|
|
1013
|
+
>
|
|
1014
|
+
{{ t("integration.nodeInspector.version.change") }}
|
|
1015
|
+
</button>
|
|
1016
|
+
</div>
|
|
969
1017
|
</div>
|
|
970
1018
|
<span class="shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wider text-slate-600">
|
|
971
1019
|
{{ groupLabel }}
|
|
@@ -1305,6 +1353,18 @@ async function runTestStep() {
|
|
|
1305
1353
|
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"
|
|
1306
1354
|
>
|
|
1307
1355
|
{{ readyVersion ? t("integration.nodeInspector.notice.versionNotReady.upgrade", { version: draftNode?.nodeVersion ?? "", ready: readyVersion }) : t("integration.nodeInspector.notice.versionNotReady.none", { version: draftNode?.nodeVersion ?? "" }) }}
|
|
1356
|
+
<!-- The sentence named a version and led nowhere (PO-339). It leads
|
|
1357
|
+
here now; where the node has no runnable version at all there is
|
|
1358
|
+
still nothing to offer, and the notice stays the statement it
|
|
1359
|
+
was. -->
|
|
1360
|
+
<button
|
|
1361
|
+
v-if="readyVersion && canMoveVersion"
|
|
1362
|
+
type="button"
|
|
1363
|
+
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"
|
|
1364
|
+
@click="versionMoveOpen = true"
|
|
1365
|
+
>
|
|
1366
|
+
{{ t("integration.nodeInspector.version.moveTo", { version: readyVersion }) }}
|
|
1367
|
+
</button>
|
|
1308
1368
|
</p>
|
|
1309
1369
|
|
|
1310
1370
|
<div
|
|
@@ -1757,6 +1817,8 @@ async function runTestStep() {
|
|
|
1757
1817
|
:sibling-fields="manifest.config ?? []"
|
|
1758
1818
|
:depth="0"
|
|
1759
1819
|
:variables="variables"
|
|
1820
|
+
:state-namespaces="offeredStateNamespaces"
|
|
1821
|
+
@declare-state="stageStateNamespace"
|
|
1760
1822
|
:upstream-outputs="upstreamOutputs"
|
|
1761
1823
|
:refused="refusedFields?.[field.key]"
|
|
1762
1824
|
@set="(k, v) => writeConfig(k, v)"
|
|
@@ -1797,6 +1859,8 @@ async function runTestStep() {
|
|
|
1797
1859
|
:sibling-fields="manifest.config ?? []"
|
|
1798
1860
|
:depth="0"
|
|
1799
1861
|
:variables="variables"
|
|
1862
|
+
:state-namespaces="offeredStateNamespaces"
|
|
1863
|
+
@declare-state="stageStateNamespace"
|
|
1800
1864
|
:upstream-outputs="upstreamOutputs"
|
|
1801
1865
|
:refused="refusedFields?.[field.key]"
|
|
1802
1866
|
@set="(k, v) => writeConfig(k, v)"
|
|
@@ -1839,6 +1903,8 @@ async function runTestStep() {
|
|
|
1839
1903
|
:sibling-fields="manifest?.config ?? []"
|
|
1840
1904
|
:depth="0"
|
|
1841
1905
|
:variables="variables"
|
|
1906
|
+
:state-namespaces="offeredStateNamespaces"
|
|
1907
|
+
@declare-state="stageStateNamespace"
|
|
1842
1908
|
:upstream-outputs="upstreamOutputs"
|
|
1843
1909
|
:refused="refusedFields?.[field.key]"
|
|
1844
1910
|
@set="(k, v) => writeConfig(k, v)"
|
|
@@ -2128,6 +2194,11 @@ async function runTestStep() {
|
|
|
2128
2194
|
</Button>
|
|
2129
2195
|
</div>
|
|
2130
2196
|
</div>
|
|
2197
|
+
|
|
2198
|
+
<!-- Asked outside the panes so it is not clipped by either, and mounted
|
|
2199
|
+
unconditionally: the two places that open it sit in different parts of
|
|
2200
|
+
the dialog and neither owns it. -->
|
|
2201
|
+
<NodeVersionMoveDialog v-model:open="versionMoveOpen" :node="draftNode" @move="applyVersionMove" />
|
|
2131
2202
|
</div>
|
|
2132
2203
|
</template>
|
|
2133
2204
|
|
|
@@ -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;
|
|
@@ -29,13 +31,15 @@ type __VLS_Props = {
|
|
|
29
31
|
};
|
|
30
32
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
33
|
update: (next: WorkflowTrigger | WorkflowNode) => any;
|
|
32
|
-
close: () => any;
|
|
33
34
|
remove: () => any;
|
|
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
|
-
onClose?: (() => any) | undefined;
|
|
38
40
|
onRemove?: (() => any) | undefined;
|
|
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;
|