@revenexx/studio-integrations 0.7.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +633 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +4 -1
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +8 -2
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +2 -6
- package/dist/runtime/components/Integrations/ExpressionInput.vue +5 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +16 -3
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +1 -0
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +135 -10
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +2 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +34 -8
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +2 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +12 -8
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +9 -6
- package/dist/runtime/components/Integrations/RunsTable.vue +2 -2
- package/dist/runtime/components/Integrations/StickyNoteView.vue +45 -11
- package/dist/runtime/components/Integrations/SurfaceCard.d.vue.ts +50 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue +55 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue.d.ts +50 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +3 -3
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +12 -3
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +5 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +79 -12
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +13 -3
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +95 -29
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +11 -10
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +37 -26
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +4 -5
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +53 -14
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +9 -4
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -0
- package/dist/runtime/composables/useIntegrationsCredentials.js +2 -2
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +9 -2
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +27 -18
- package/dist/runtime/composables/useIntegrationsNodes.js +11 -1
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +30 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +70 -29
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +7 -14
- package/dist/runtime/composables/useIntegrationsSchedules.js +80 -35
- package/dist/runtime/composables/useIntegrationsTriggers.d.ts +30 -3
- package/dist/runtime/composables/useIntegrationsTriggers.js +18 -7
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +10 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.js +25 -4
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +22 -1
- package/dist/runtime/data/integrationNodeCatalog.js +8 -4
- package/dist/runtime/data/loopBody.d.ts +129 -0
- package/dist/runtime/data/loopBody.js +76 -0
- package/dist/runtime/data/nodeInputData.d.ts +58 -1
- package/dist/runtime/data/nodeInputData.js +30 -5
- package/dist/runtime/data/triggerGlyphs.d.ts +47 -0
- package/dist/runtime/data/triggerGlyphs.js +11 -0
- package/dist/runtime/i18n/messages/de.json +62 -33
- package/dist/runtime/i18n/messages/en.json +32 -3
- package/dist/runtime/pages/integrations/credentials.vue +4 -1
- package/dist/runtime/pages/integrations/index.vue +32 -34
- package/dist/runtime/pages/integrations/runs.vue +22 -10
- package/dist/runtime/pages/integrations/schedules.vue +64 -7
- package/dist/runtime/pages/integrations/templates/[slug].vue +13 -5
- package/dist/runtime/pages/integrations/workflows/[id].vue +193 -104
- package/dist/runtime/types/integrations.d.ts +64 -1
- package/dist/runtime/utils/cockpitSources.d.ts +22 -3
- package/dist/runtime/utils/credentialState.d.ts +6 -1
- package/dist/runtime/utils/credentialState.js +1 -1
- package/dist/runtime/utils/localize.d.ts +34 -0
- package/dist/runtime/utils/localize.js +22 -7
- package/dist/runtime/utils/pollBudget.d.ts +1 -1
- package/dist/runtime/utils/servedPaths.d.ts +22 -0
- package/dist/runtime/utils/servedPaths.js +3 -0
- package/dist/runtime/utils/statusStyle.d.ts +52 -0
- package/dist/runtime/utils/statusStyle.js +26 -0
- package/package.json +1 -1
|
@@ -259,7 +259,10 @@ async function submit() {
|
|
|
259
259
|
class="rounded-md px-3 py-2 text-sm"
|
|
260
260
|
:class="testResult.ok ? 'bg-emerald-500/10 text-emerald-700' : 'bg-rose-500/10 text-rose-700'"
|
|
261
261
|
>
|
|
262
|
-
|
|
262
|
+
<!-- The type's own sentence, either way. This is the surface where it is
|
|
263
|
+
worth the most: nothing is stored yet, so the operator is deciding
|
|
264
|
+
whether these access data reach what they meant (specs/credential-test.md). -->
|
|
265
|
+
{{ testResult.ok ? testResult.message ?? t("integration.credentials.shared.test.ok") : testResult.error ?? testResult.message ?? t("integration.credentials.shared.test.failed") }}
|
|
263
266
|
</div>
|
|
264
267
|
</div>
|
|
265
268
|
|
|
@@ -214,7 +214,13 @@ async function connect() {
|
|
|
214
214
|
{{ t("integration.credentials.detail.action.reconnect") }}
|
|
215
215
|
</Button>
|
|
216
216
|
</div>
|
|
217
|
-
|
|
217
|
+
<!-- The stored sentence, and only while nothing fresher is on screen. The
|
|
218
|
+
live box below shows the result of a test just run, and the reload that
|
|
219
|
+
follows it puts the very same sentence here — so both would print it
|
|
220
|
+
twice, and past the length the platform truncates at, twice differently.
|
|
221
|
+
The freshly returned one wins, being whole (specs/credential-test.md
|
|
222
|
+
AC-1). -->
|
|
223
|
+
<p v-if="state.message && !liveTest" class="mt-1 text-sm break-words opacity-90">
|
|
218
224
|
{{ state.message }}
|
|
219
225
|
</p>
|
|
220
226
|
<p v-else-if="state.key === 'untested'" class="mt-1 text-sm opacity-80">
|
|
@@ -314,7 +320,7 @@ async function connect() {
|
|
|
314
320
|
class="rounded-md px-3 py-2 text-sm"
|
|
315
321
|
:class="liveTest.ok ? 'bg-emerald-500/10 text-emerald-700' : 'bg-rose-500/10 text-rose-700'"
|
|
316
322
|
>
|
|
317
|
-
{{ liveTest.ok ? t("integration.credentials.shared.test.ok") : liveTest.error ?? t("integration.credentials.shared.test.failed") }}
|
|
323
|
+
{{ liveTest.ok ? liveTest.message ?? t("integration.credentials.shared.test.ok") : liveTest.error ?? liveTest.message ?? t("integration.credentials.shared.test.failed") }}
|
|
318
324
|
</div>
|
|
319
325
|
|
|
320
326
|
<!-- The save was refused. Same rule as the create dialog: stated here, and at
|
|
@@ -3,6 +3,7 @@ import { onMounted, ref, watch } from "vue";
|
|
|
3
3
|
import { RefreshCircle as Loader2, Refresh as ReplayIcon, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
4
4
|
import { Badge, Button } from "@revenexx/studio";
|
|
5
5
|
import { toast } from "vue-sonner";
|
|
6
|
+
import { deadLetterStatusClass, deadLetterStatusLabel } from "../../utils/statusStyle";
|
|
6
7
|
const props = defineProps({
|
|
7
8
|
workflowId: { type: Number, required: true }
|
|
8
9
|
});
|
|
@@ -62,11 +63,6 @@ async function onDiscard(dl) {
|
|
|
62
63
|
busyId.value = null;
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
|
-
function statusClass(status) {
|
|
66
|
-
if (status === "pending") return "bg-amber-500/15 text-amber-700 border-amber-500/25";
|
|
67
|
-
if (status === "replayed") return "bg-emerald-500/15 text-emerald-700 border-emerald-500/25";
|
|
68
|
-
return "bg-slate-500/15 text-slate-600 border-slate-500/20";
|
|
69
|
-
}
|
|
70
66
|
function fmt(iso) {
|
|
71
67
|
if (!iso) return "\u2014";
|
|
72
68
|
try {
|
|
@@ -117,7 +113,7 @@ function fmt(iso) {
|
|
|
117
113
|
:key="dl.id"
|
|
118
114
|
class="flex items-start gap-3 px-3 py-2.5"
|
|
119
115
|
>
|
|
120
|
-
<Badge variant="outline" :class="['border text-xs font-medium uppercase shrink-0 mt-0.5',
|
|
116
|
+
<Badge variant="outline" :class="['border text-xs font-medium uppercase shrink-0 mt-0.5', deadLetterStatusClass(dl.status)]">{{ deadLetterStatusLabel(dl.status) }}</Badge>
|
|
121
117
|
<div class="min-w-0 flex-1">
|
|
122
118
|
<div class="text-sm break-words">{{ dl.reason || t("integration.deadLetters.reason.fallback") }}</div>
|
|
123
119
|
<div class="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-muted-foreground font-mono">
|
|
@@ -220,8 +220,13 @@ function insert(expr) {
|
|
|
220
220
|
<span class="min-w-0 truncate font-mono" :class="preview.misses ? 'text-amber-600' : 'text-emerald-700'">{{ preview.resolved }}</span>
|
|
221
221
|
</p>
|
|
222
222
|
|
|
223
|
+
<!-- Named, because the same expressions are offered twice on this surface —
|
|
224
|
+
here and in the panel beside the form — and a popup nothing can address
|
|
225
|
+
is a popup nothing can test either. -->
|
|
223
226
|
<div
|
|
224
227
|
v-if="open && hasSuggestions"
|
|
228
|
+
role="group"
|
|
229
|
+
:aria-label="t('integration.nodeConfig.expression.picker.aria')"
|
|
225
230
|
class="absolute right-0 top-full z-50 mt-1 flex max-h-72 w-80 flex-col rounded-md border border-black/[0.08] bg-white text-sm shadow-lg"
|
|
226
231
|
>
|
|
227
232
|
<div class="shrink-0 border-b border-black/[0.06] p-1">
|
|
@@ -5,6 +5,9 @@ type __VLS_Props = {
|
|
|
5
5
|
field: ConfigField;
|
|
6
6
|
config: Record<string, unknown>;
|
|
7
7
|
modelValue: unknown;
|
|
8
|
+
/** The settings this one sits among, so a dependency can be named as the
|
|
9
|
+
* operator sees it rather than by its config key. */
|
|
10
|
+
siblingFields?: ConfigField[];
|
|
8
11
|
};
|
|
9
12
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
13
|
"update:modelValue": (value: unknown) => any;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref } from "vue";
|
|
3
|
+
import { isVersionExecutable } from "../../data/integrationNodeCatalog";
|
|
3
4
|
import { ChevronDown } from "lucide-vue-next";
|
|
4
5
|
import { Input } from "@revenexx/studio";
|
|
5
6
|
import { resolveLocalized } from "../../utils/localize";
|
|
@@ -8,12 +9,14 @@ const props = defineProps({
|
|
|
8
9
|
version: { type: String, required: true },
|
|
9
10
|
field: { type: Object, required: true },
|
|
10
11
|
config: { type: Object, required: true },
|
|
11
|
-
modelValue: { type: null, required: true }
|
|
12
|
+
modelValue: { type: null, required: true },
|
|
13
|
+
siblingFields: { type: Array, required: false }
|
|
12
14
|
});
|
|
13
15
|
const emit = defineEmits(["update:modelValue"]);
|
|
14
16
|
const t = useIntegrationsT();
|
|
15
17
|
const search = ref("");
|
|
16
18
|
const focused = ref(false);
|
|
19
|
+
const versionNotReady = computed(() => !isVersionExecutable(props.slug, props.version));
|
|
17
20
|
const q = useNodeConfigResolve({
|
|
18
21
|
slug: () => props.slug,
|
|
19
22
|
version: () => props.version,
|
|
@@ -22,6 +25,10 @@ const q = useNodeConfigResolve({
|
|
|
22
25
|
dependsOn: () => props.field.dependsOn,
|
|
23
26
|
search: () => focused.value ? search.value : ""
|
|
24
27
|
});
|
|
28
|
+
const unsatisfiedLabels = computed(() => q.unsatisfied.value.map((key) => {
|
|
29
|
+
const sibling = props.siblingFields?.find((f) => f.key === key);
|
|
30
|
+
return sibling && resolveLocalized(sibling.label) || key;
|
|
31
|
+
}));
|
|
25
32
|
const options = computed(() => q.data.value?.options ?? []);
|
|
26
33
|
const isMulti = computed(() => props.field.type === "multiselect");
|
|
27
34
|
const disabledControlClass = "flex h-8 items-center rounded-md border border-black/[0.08] bg-white px-2 text-sm text-muted-foreground opacity-50";
|
|
@@ -76,10 +83,16 @@ function toggleMulti(optValue) {
|
|
|
76
83
|
|
|
77
84
|
<template>
|
|
78
85
|
<div>
|
|
86
|
+
<!-- the pinned version cannot be run, so there is nothing to resolve -->
|
|
87
|
+
<template v-if="versionNotReady">
|
|
88
|
+
<div :class="disabledControlClass">—</div>
|
|
89
|
+
<p class="mt-1 text-sm text-amber-600">{{ t("integration.nodeConfig.dynamicField.versionNotReady") }}</p>
|
|
90
|
+
</template>
|
|
91
|
+
|
|
79
92
|
<!-- gated: required dependencies still unset -->
|
|
80
|
-
<template v-if="q.gated.value">
|
|
93
|
+
<template v-else-if="q.gated.value">
|
|
81
94
|
<div :class="disabledControlClass">—</div>
|
|
82
|
-
<p class="mt-1 text-sm text-amber-600">{{ t("integration.nodeConfig.dynamicField.gated", { keys:
|
|
95
|
+
<p class="mt-1 text-sm text-amber-600">{{ t("integration.nodeConfig.dynamicField.gated", { keys: unsatisfiedLabels.join(", ") }) }}</p>
|
|
83
96
|
</template>
|
|
84
97
|
|
|
85
98
|
<!-- first load: no data yet -->
|
|
@@ -5,6 +5,9 @@ type __VLS_Props = {
|
|
|
5
5
|
field: ConfigField;
|
|
6
6
|
config: Record<string, unknown>;
|
|
7
7
|
modelValue: unknown;
|
|
8
|
+
/** The settings this one sits among, so a dependency can be named as the
|
|
9
|
+
* operator sees it rather than by its config key. */
|
|
10
|
+
siblingFields?: ConfigField[];
|
|
8
11
|
};
|
|
9
12
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
13
|
"update:modelValue": (value: unknown) => any;
|
|
@@ -6,6 +6,9 @@ type __VLS_Props = {
|
|
|
6
6
|
version: string;
|
|
7
7
|
secretKeys: string[];
|
|
8
8
|
staticKeys?: string[];
|
|
9
|
+
/** The whole set of settings this one belongs to — a `dynamic` field names the
|
|
10
|
+
* ones it waits for out of it (NodeConfigDynamicField). */
|
|
11
|
+
siblingFields?: ConfigField[];
|
|
9
12
|
depth?: number;
|
|
10
13
|
variables?: Record<string, WorkflowVariable>;
|
|
11
14
|
upstreamOutputs?: Array<{
|
|
@@ -23,6 +23,7 @@ const props = defineProps({
|
|
|
23
23
|
version: { type: String, required: true },
|
|
24
24
|
secretKeys: { type: Array, required: true },
|
|
25
25
|
staticKeys: { type: Array, required: false },
|
|
26
|
+
siblingFields: { type: Array, required: false },
|
|
26
27
|
depth: { type: Number, required: false },
|
|
27
28
|
variables: { type: Object, required: false },
|
|
28
29
|
upstreamOutputs: { type: Array, required: false },
|
|
@@ -395,6 +396,7 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
395
396
|
:field="field"
|
|
396
397
|
:config="config"
|
|
397
398
|
:model-value="value"
|
|
399
|
+
:sibling-fields="siblingFields"
|
|
398
400
|
@update:model-value="write"
|
|
399
401
|
/>
|
|
400
402
|
|
|
@@ -6,6 +6,9 @@ type __VLS_Props = {
|
|
|
6
6
|
version: string;
|
|
7
7
|
secretKeys: string[];
|
|
8
8
|
staticKeys?: string[];
|
|
9
|
+
/** The whole set of settings this one belongs to — a `dynamic` field names the
|
|
10
|
+
* ones it waits for out of it (NodeConfigDynamicField). */
|
|
11
|
+
siblingFields?: ConfigField[];
|
|
9
12
|
depth?: number;
|
|
10
13
|
variables?: Record<string, WorkflowVariable>;
|
|
11
14
|
upstreamOutputs?: Array<{
|
|
@@ -28,13 +28,13 @@ type __VLS_Props = {
|
|
|
28
28
|
refusedFields?: Record<string, string>;
|
|
29
29
|
};
|
|
30
30
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
|
-
close: () => any;
|
|
32
31
|
update: (next: WorkflowTrigger | WorkflowNode) => any;
|
|
32
|
+
close: () => any;
|
|
33
33
|
remove: () => any;
|
|
34
34
|
navigate: (id: string) => any;
|
|
35
35
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
36
|
-
onClose?: (() => any) | undefined;
|
|
37
36
|
onUpdate?: ((next: WorkflowTrigger | WorkflowNode) => any) | undefined;
|
|
37
|
+
onClose?: (() => any) | undefined;
|
|
38
38
|
onRemove?: (() => any) | undefined;
|
|
39
39
|
onNavigate?: ((id: string) => any) | undefined;
|
|
40
40
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, nextTick, onMounted, provide, ref, watch } from "vue";
|
|
3
|
-
import { AddCircle as Plus, TrashBinMinimalistic as TrashBin
|
|
4
|
-
import {
|
|
3
|
+
import { AddCircle as Plus, Copy, Magnifier as Search, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
4
|
+
import { triggerGlyph as glyphForTrigger } from "../../data/triggerGlyphs";
|
|
5
|
+
import { ChevronDown, ChevronLeft, ChevronRight, Database, MessageSquareText, X } from "lucide-vue-next";
|
|
5
6
|
import { toast } from "vue-sonner";
|
|
6
7
|
import {
|
|
7
8
|
Button,
|
|
@@ -27,15 +28,18 @@ import {
|
|
|
27
28
|
ToggleGroupItem
|
|
28
29
|
} from "@revenexx/studio";
|
|
29
30
|
import EventTypeCombobox from "./EventTypeCombobox.vue";
|
|
30
|
-
import { findManifest, nodeCategoryLabel } from "../../data/integrationNodeCatalog";
|
|
31
|
+
import { findManifest, nodeCategoryLabel, isVersionExecutable, newestExecutableVersion } from "../../data/integrationNodeCatalog";
|
|
31
32
|
import {
|
|
32
33
|
collectUpstreamNodes,
|
|
34
|
+
inputPortsForManifest,
|
|
33
35
|
nodeOutputExpr,
|
|
34
36
|
portsForNode,
|
|
35
37
|
triggerPayloadExpr,
|
|
36
38
|
variableExpr
|
|
37
39
|
} from "../../data/nodeInputData";
|
|
38
40
|
import { portDotClass } from "../../data/portTone";
|
|
41
|
+
import { computeLoopBodies, manifestIterates } from "../../data/loopBody";
|
|
42
|
+
import { nodeDisplayLabel } from "../../utils/validationFindings";
|
|
39
43
|
import { authChoiceFromConfig, configPatchForAuthChoice } from "../../data/webhookAuth";
|
|
40
44
|
import {
|
|
41
45
|
dataTypeOf,
|
|
@@ -87,14 +91,21 @@ const manifest = computed(() => {
|
|
|
87
91
|
const wn = draftNode.value;
|
|
88
92
|
return wn ? findManifest(wn.nodeSlug, wn.nodeVersion) : null;
|
|
89
93
|
});
|
|
90
|
-
const
|
|
94
|
+
const versionExecutable = computed(() => {
|
|
95
|
+
const wn = draftNode.value;
|
|
96
|
+
return wn ? isVersionExecutable(wn.nodeSlug, wn.nodeVersion) : true;
|
|
97
|
+
});
|
|
98
|
+
const readyVersion = computed(() => {
|
|
99
|
+
const wn = draftNode.value;
|
|
100
|
+
return wn ? newestExecutableVersion(wn.nodeSlug) : null;
|
|
101
|
+
});
|
|
91
102
|
const TRIGGER_LABELS = computed(() => ({
|
|
92
103
|
manual: t("integration.nodeInspector.trigger.type.manual"),
|
|
93
104
|
schedule: t("integration.nodeInspector.trigger.type.schedule"),
|
|
94
105
|
webhook: t("integration.nodeInspector.trigger.type.webhook"),
|
|
95
106
|
event: t("integration.nodeInspector.trigger.type.event")
|
|
96
107
|
}));
|
|
97
|
-
const triggerGlyph = computed(() =>
|
|
108
|
+
const triggerGlyph = computed(() => glyphForTrigger(draftTrigger.value?.type));
|
|
98
109
|
const namePlaceholder = computed(() => {
|
|
99
110
|
if (draftNode.value) return resolveLocalized(manifest.value?.name) || draftNode.value.nodeSlug;
|
|
100
111
|
return t("integration.nodeInspector.trigger.placeholder", { type: TRIGGER_LABELS.value[draftTrigger.value?.type ?? "manual"] });
|
|
@@ -115,6 +126,7 @@ const nameValue = computed(() => {
|
|
|
115
126
|
const name = draft.value.name;
|
|
116
127
|
return resolveDisplayName(name);
|
|
117
128
|
});
|
|
129
|
+
const dialogTitle = computed(() => t("integration.nodeInspector.dialogTitle", { name: nameValue.value || namePlaceholder.value }));
|
|
118
130
|
function writeName(name) {
|
|
119
131
|
draft.value = { ...draft.value, name };
|
|
120
132
|
}
|
|
@@ -759,8 +771,25 @@ function navigate(offset) {
|
|
|
759
771
|
emit("navigate", target);
|
|
760
772
|
}
|
|
761
773
|
const outputView = ref("schema");
|
|
774
|
+
const selfInputs = computed(() => inputPortsForManifest(manifest.value));
|
|
762
775
|
const selfSample = computed(() => draftNode.value ? sampleFor(draftNode.value.id) : void 0);
|
|
763
776
|
const selfPorts = computed(() => draftNode.value ? withSampledFields(portsForNode(draftNode.value, manifest.value, props.dynamicOutputs?.[draftNode.value.id]), selfSample.value) : []);
|
|
777
|
+
const loopBody = computed(() => {
|
|
778
|
+
if (!draftNode.value || !manifestIterates(manifest.value)) return null;
|
|
779
|
+
const nodes = props.nodes ?? [];
|
|
780
|
+
const [body] = computeLoopBodies([draftNode.value.id], props.edges ?? []);
|
|
781
|
+
if (!body) return null;
|
|
782
|
+
const labelOf = (id) => {
|
|
783
|
+
const n = nodes.find((x) => x.id === id);
|
|
784
|
+
return n ? nodeDisplayLabel(n) : id;
|
|
785
|
+
};
|
|
786
|
+
const inBody = (ids) => nodes.filter((n) => ids.has(n.id)).map((n) => labelOf(n.id));
|
|
787
|
+
return {
|
|
788
|
+
size: body.nodeIds.size,
|
|
789
|
+
names: inBody(body.nodeIds),
|
|
790
|
+
joined: inBody(body.joinedWithDone)
|
|
791
|
+
};
|
|
792
|
+
});
|
|
764
793
|
const selfSampleJson = computed(() => selfSample.value ? JSON.stringify(selfSample.value, null, 2) : "");
|
|
765
794
|
const selfPinned = computed(() => !!draftNode.value && !!pinnedSamples.value[draftNode.value.id]);
|
|
766
795
|
watch(selfSample, (s) => {
|
|
@@ -867,6 +896,11 @@ async function runTestStep() {
|
|
|
867
896
|
<!-- Fills the editor's near-fullscreen config lightbox; the Dialog owns
|
|
868
897
|
width/height, border and the close control. -->
|
|
869
898
|
<div class="flex min-h-0 flex-1 flex-col bg-white">
|
|
899
|
+
<!-- The dialog's accessible name. Not drawn: the header below says the same
|
|
900
|
+
thing in the name field and the line under it, and says it better. -->
|
|
901
|
+
<DialogTitle class="sr-only">
|
|
902
|
+
{{ dialogTitle }}
|
|
903
|
+
</DialogTitle>
|
|
870
904
|
<!-- Header -->
|
|
871
905
|
<div class="flex shrink-0 items-center gap-3 border-b border-black/[0.06] px-4 py-3">
|
|
872
906
|
<div class="flex size-9 shrink-0 items-center justify-center rounded-md border border-black/[0.06] bg-slate-50 text-slate-600">
|
|
@@ -1032,6 +1066,36 @@ async function runTestStep() {
|
|
|
1032
1066
|
<p class="mt-0.5 text-xs leading-snug text-muted-foreground">
|
|
1033
1067
|
{{ t("integration.nodeInspector.input.description") }}
|
|
1034
1068
|
</p>
|
|
1069
|
+
<!-- What this node accepts, as opposed to the list below, which is what
|
|
1070
|
+
other nodes offer it. It sits in the header because somebody
|
|
1071
|
+
choosing a value is already reading here, and because the port
|
|
1072
|
+
belongs to the node being configured rather than to any row in the
|
|
1073
|
+
list (AC-20). -->
|
|
1074
|
+
<div v-if="isNode && selfInputs.length" class="mt-2 space-y-1 border-t border-black/[0.06] pt-2">
|
|
1075
|
+
<div v-for="p in selfInputs" :key="p.name" class="space-y-0.5">
|
|
1076
|
+
<div class="flex items-baseline gap-1.5">
|
|
1077
|
+
<!-- Not the uppercase treatment the section labels below use, and
|
|
1078
|
+
that is the point: NODES / TRIGGER / VARIABLES each head a list
|
|
1079
|
+
of values a click inserts, so they are a series of *sources*.
|
|
1080
|
+
This heads nothing and is not a source — it is a statement about
|
|
1081
|
+
the node being configured. Dressed as a fourth member of that
|
|
1082
|
+
series it told the reader there was a fourth place to take data
|
|
1083
|
+
from. -->
|
|
1084
|
+
<span
|
|
1085
|
+
class="text-xs font-medium text-muted-foreground"
|
|
1086
|
+
:class="selfInputs.length > 1 ? 'font-mono' : ''"
|
|
1087
|
+
>
|
|
1088
|
+
{{ selfInputs.length > 1 ? `${p.name}:` : t("integration.nodeInspector.input.expects.heading") }}
|
|
1089
|
+
</span>
|
|
1090
|
+
<span class="font-mono text-[10px] text-slate-500">
|
|
1091
|
+
{{ p.dataType }}<template v-if="p.dataType"> · </template>{{ p.required ? t("integration.nodeInspector.input.expects.required") : t("integration.nodeInspector.input.expects.optional") }}
|
|
1092
|
+
</span>
|
|
1093
|
+
</div>
|
|
1094
|
+
<p v-if="resolveLocalized(p.description)" class="text-xs leading-snug text-muted-foreground">
|
|
1095
|
+
{{ resolveLocalized(p.description) }}
|
|
1096
|
+
</p>
|
|
1097
|
+
</div>
|
|
1098
|
+
</div>
|
|
1035
1099
|
<div class="relative mt-2">
|
|
1036
1100
|
<Search :size="12" class="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
|
1037
1101
|
<input
|
|
@@ -1098,7 +1162,30 @@ async function runTestStep() {
|
|
|
1098
1162
|
</span>
|
|
1099
1163
|
</button>
|
|
1100
1164
|
<div v-show="!sectionClosed(s.id)" class="space-y-2 border-t border-black/[0.06] px-2 py-2">
|
|
1101
|
-
|
|
1165
|
+
<!-- These ports belong to an UPSTREAM node, so the sentence is that
|
|
1166
|
+
node's. On a 300px column it is a tooltip rather than text: the
|
|
1167
|
+
values below are what this panel is for, and a sentence per port
|
|
1168
|
+
pushes them out of view. The readable copy is the Output tab
|
|
1169
|
+
(AC-19).
|
|
1170
|
+
|
|
1171
|
+
The title hangs on the port's whole block, not on its name row:
|
|
1172
|
+
that row is only drawn for a node with several ports or a
|
|
1173
|
+
branch/error one, so a single `default` port — which is most
|
|
1174
|
+
nodes, No Operation among them — would have had nowhere to carry
|
|
1175
|
+
it and the tooltip would have been dead in the commonest case.
|
|
1176
|
+
|
|
1177
|
+
What that buys is uneven, and it is the reason this is a
|
|
1178
|
+
convenience rather than a promise: each field row carries its own
|
|
1179
|
+
title (the expression to copy), and a child title wins, so the
|
|
1180
|
+
sentence is read by resting on the port's name or the space around
|
|
1181
|
+
its rows rather than on a row itself. Where a reader is owed the
|
|
1182
|
+
text outright, that is the Output tab. -->
|
|
1183
|
+
<div
|
|
1184
|
+
v-for="p in s.ports"
|
|
1185
|
+
:key="p.name"
|
|
1186
|
+
class="space-y-1 pl-0.5"
|
|
1187
|
+
:title="resolveLocalized(p.description) || void 0"
|
|
1188
|
+
>
|
|
1102
1189
|
<div v-if="s.ports.length > 1 || p.kind !== 'default'" class="flex items-center gap-1.5">
|
|
1103
1190
|
<span class="size-1.5 rounded-full" :class="portDotClass(p.kind, p.name)" />
|
|
1104
1191
|
<span class="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">{{ p.name }}</span>
|
|
@@ -1207,6 +1294,19 @@ async function runTestStep() {
|
|
|
1207
1294
|
<!-- Same TabBar as the workflow page — one tab look everywhere. -->
|
|
1208
1295
|
<TabBar v-model="tab" :tabs="inspectorTabs" class="shrink-0 px-4" />
|
|
1209
1296
|
|
|
1297
|
+
<!-- The pinned version has no built code, so nothing here can be tested
|
|
1298
|
+
or resolved. Above the tabs' content rather than inside one of them:
|
|
1299
|
+
the state belongs to the node, not to a tab, and a reader who opens
|
|
1300
|
+
this node on Parameters would otherwise meet the card's mark outside
|
|
1301
|
+
and no word about it inside. Outside the scrolling area so it cannot
|
|
1302
|
+
be scrolled away from. -->
|
|
1303
|
+
<p
|
|
1304
|
+
v-if="isNode && !versionExecutable"
|
|
1305
|
+
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
|
+
>
|
|
1307
|
+
{{ readyVersion ? t("integration.nodeInspector.notice.versionNotReady.upgrade", { version: draftNode?.nodeVersion ?? "", ready: readyVersion }) : t("integration.nodeInspector.notice.versionNotReady.none", { version: draftNode?.nodeVersion ?? "" }) }}
|
|
1308
|
+
</p>
|
|
1309
|
+
|
|
1210
1310
|
<div
|
|
1211
1311
|
class="min-h-0 flex-1 overflow-y-auto p-4"
|
|
1212
1312
|
:class="dragActive ? 'chip-drag-live' : ''"
|
|
@@ -1654,6 +1754,7 @@ async function runTestStep() {
|
|
|
1654
1754
|
:version="draftNode.nodeVersion"
|
|
1655
1755
|
:secret-keys="secretKeys"
|
|
1656
1756
|
:static-keys="(manifest.config ?? []).map((f2) => f2.key)"
|
|
1757
|
+
:sibling-fields="manifest.config ?? []"
|
|
1657
1758
|
:depth="0"
|
|
1658
1759
|
:variables="variables"
|
|
1659
1760
|
:upstream-outputs="upstreamOutputs"
|
|
@@ -1693,6 +1794,7 @@ async function runTestStep() {
|
|
|
1693
1794
|
:version="draftNode.nodeVersion"
|
|
1694
1795
|
:secret-keys="secretKeys"
|
|
1695
1796
|
:static-keys="(manifest.config ?? []).map((f2) => f2.key)"
|
|
1797
|
+
:sibling-fields="manifest.config ?? []"
|
|
1696
1798
|
:depth="0"
|
|
1697
1799
|
:variables="variables"
|
|
1698
1800
|
:upstream-outputs="upstreamOutputs"
|
|
@@ -1734,6 +1836,7 @@ async function runTestStep() {
|
|
|
1734
1836
|
:version="draftNode.nodeVersion"
|
|
1735
1837
|
:secret-keys="secretKeys"
|
|
1736
1838
|
:static-keys="(manifest?.config ?? []).map((f2) => f2.key)"
|
|
1839
|
+
:sibling-fields="manifest?.config ?? []"
|
|
1737
1840
|
:depth="0"
|
|
1738
1841
|
:variables="variables"
|
|
1739
1842
|
:upstream-outputs="upstreamOutputs"
|
|
@@ -1852,6 +1955,28 @@ async function runTestStep() {
|
|
|
1852
1955
|
rather than a permanent column — it is the last thing done, if it
|
|
1853
1956
|
is done at all, and it needs the width when it is. -->
|
|
1854
1957
|
<div v-else-if="tab === 'output' && draftNode" class="mx-auto w-full max-w-5xl space-y-5">
|
|
1958
|
+
<!-- An iterator's own tab leads with what it repeats (PO-277): the
|
|
1959
|
+
ports below say what leaves Item and Done, and this says how much
|
|
1960
|
+
of the workflow hangs off the first of them. Above the port
|
|
1961
|
+
schema on purpose — the count is the thing a reader came here to
|
|
1962
|
+
check, and it is the one statement the canvas cannot make. -->
|
|
1963
|
+
<section v-if="loopBody" class="space-y-1.5 rounded-md border border-black/[0.06] bg-white p-3">
|
|
1964
|
+
<span class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.output.loopBody.heading") }}</span>
|
|
1965
|
+
<p class="text-sm text-foreground/80">
|
|
1966
|
+
{{ t("integration.nodeInspector.output.loopBody.size", loopBody.size) }}
|
|
1967
|
+
</p>
|
|
1968
|
+
<p v-if="loopBody.names.length" class="text-xs text-muted-foreground">
|
|
1969
|
+
{{ t("integration.nodeInspector.output.loopBody.names", { names: loopBody.names.join(", ") }) }}
|
|
1970
|
+
</p>
|
|
1971
|
+
<!-- Amber, not rose, and not a validation finding: a body wired
|
|
1972
|
+
into the Done side may be exactly what the author wanted, so
|
|
1973
|
+
this states what the engine will do and leaves the judgement
|
|
1974
|
+
to them. Routing it through the findings would refuse an
|
|
1975
|
+
activation that is legitimate. -->
|
|
1976
|
+
<p v-if="loopBody.joined.length" class="text-xs text-amber-700">
|
|
1977
|
+
{{ t("integration.nodeInspector.output.loopBody.joined", loopBody.joined.length, { names: loopBody.joined.join(", ") }) }}
|
|
1978
|
+
</p>
|
|
1979
|
+
</section>
|
|
1855
1980
|
<section class="space-y-2">
|
|
1856
1981
|
<div class="flex items-center justify-between gap-2">
|
|
1857
1982
|
<p class="text-xs text-muted-foreground">
|
|
@@ -1878,8 +2003,8 @@ async function runTestStep() {
|
|
|
1878
2003
|
<Button
|
|
1879
2004
|
size="sm"
|
|
1880
2005
|
class="h-8"
|
|
1881
|
-
:disabled="testRunning"
|
|
1882
|
-
:title="t('integration.nodeInspector.output.test.runTitle')"
|
|
2006
|
+
:disabled="testRunning || !versionExecutable"
|
|
2007
|
+
:title="versionExecutable ? t('integration.nodeInspector.output.test.runTitle') : void 0"
|
|
1883
2008
|
@click="runTestStep"
|
|
1884
2009
|
>
|
|
1885
2010
|
{{ testRunning ? t("integration.nodeInspector.output.test.running") : t("integration.nodeInspector.output.test.action") }}
|
|
@@ -1892,7 +2017,7 @@ async function runTestStep() {
|
|
|
1892
2017
|
{{ testInputOpen ? t("integration.nodeInspector.output.test.hideInput") : t("integration.nodeInspector.output.test.editInput") }}
|
|
1893
2018
|
</button>
|
|
1894
2019
|
<span class="text-xs text-muted-foreground">
|
|
1895
|
-
{{ t("integration.nodeInspector.output.test.runHint") }}
|
|
2020
|
+
{{ versionExecutable ? t("integration.nodeInspector.output.test.runHint") : "" }}
|
|
1896
2021
|
</span>
|
|
1897
2022
|
</div>
|
|
1898
2023
|
<Textarea
|
|
@@ -1999,7 +2124,7 @@ async function runTestStep() {
|
|
|
1999
2124
|
{{ t("integration.common.action.cancel") }}
|
|
2000
2125
|
</Button>
|
|
2001
2126
|
<Button size="sm" class="h-8" @click="save">
|
|
2002
|
-
{{ t("integration.
|
|
2127
|
+
{{ t("integration.nodeInspector.footer.done") }}
|
|
2003
2128
|
</Button>
|
|
2004
2129
|
</div>
|
|
2005
2130
|
</div>
|
|
@@ -28,13 +28,13 @@ type __VLS_Props = {
|
|
|
28
28
|
refusedFields?: Record<string, string>;
|
|
29
29
|
};
|
|
30
30
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
|
-
close: () => any;
|
|
32
31
|
update: (next: WorkflowTrigger | WorkflowNode) => any;
|
|
32
|
+
close: () => any;
|
|
33
33
|
remove: () => any;
|
|
34
34
|
navigate: (id: string) => any;
|
|
35
35
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
36
|
-
onClose?: (() => any) | undefined;
|
|
37
36
|
onUpdate?: ((next: WorkflowTrigger | WorkflowNode) => any) | undefined;
|
|
37
|
+
onClose?: (() => any) | undefined;
|
|
38
38
|
onRemove?: (() => any) | undefined;
|
|
39
39
|
onNavigate?: ((id: string) => any) | undefined;
|
|
40
40
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -3,7 +3,8 @@ import type { PortEntry } from '../../data/nodeInputData.js';
|
|
|
3
3
|
* The ports a node hands downstream, with their declared fields and whatever
|
|
4
4
|
* sample value is known for each.
|
|
5
5
|
*
|
|
6
|
-
* Rendered from one place — the Output tab (specs/node-configuration.md AC-4
|
|
6
|
+
* Rendered from one place — the Output tab (specs/node-configuration.md AC-4,
|
|
7
|
+
* and AC-19 for the sentences the publisher wrote about a port and its fields). It
|
|
7
8
|
* keeps its own file for readability rather than for reuse: the schema list is
|
|
8
9
|
* a self-contained tree with its own type glyphs and sample formatting, and
|
|
9
10
|
* inlining it would bury the tab's own markup.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { portDotClass } from "../../data/portTone";
|
|
3
|
-
|
|
3
|
+
import { resolveLocalized } from "../../utils/localize";
|
|
4
|
+
const props = defineProps({
|
|
4
5
|
ports: { type: Array, required: true },
|
|
5
6
|
valueAt: { type: Function, required: true }
|
|
6
7
|
});
|
|
@@ -16,6 +17,15 @@ const TYPE_GLYPHS = {
|
|
|
16
17
|
function typeGlyph(dataType) {
|
|
17
18
|
return dataType ? TYPE_GLYPHS[dataType] ?? null : null;
|
|
18
19
|
}
|
|
20
|
+
function portText(p) {
|
|
21
|
+
return resolveLocalized(p.description);
|
|
22
|
+
}
|
|
23
|
+
function fieldText(f) {
|
|
24
|
+
return resolveLocalized(f.description);
|
|
25
|
+
}
|
|
26
|
+
function saysNoFields(p) {
|
|
27
|
+
return !portText(p) || props.valueAt(p.name) !== null;
|
|
28
|
+
}
|
|
19
29
|
</script>
|
|
20
30
|
|
|
21
31
|
<template>
|
|
@@ -25,14 +35,30 @@ function typeGlyph(dataType) {
|
|
|
25
35
|
<span class="size-1.5 rounded-full" :class="portDotClass(p.kind, p.name)" />
|
|
26
36
|
<span class="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">{{ p.name }}</span>
|
|
27
37
|
</div>
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
<p v-if="portText(p)" class="text-[11px] leading-snug text-muted-foreground">
|
|
39
|
+
{{ portText(p) }}
|
|
40
|
+
</p>
|
|
41
|
+
<!-- Two columns, sized to the widest field rather than split down the
|
|
42
|
+
middle: `max-content` puts every sentence one gap after the longest
|
|
43
|
+
name, so short names like `count` do not leave the reader crossing the
|
|
44
|
+
dialog to find what they mean, and the names still line up with each
|
|
45
|
+
other. The sample value is capped for the same reason — left free it
|
|
46
|
+
would widen the column and push the sentences back out. Both cells are
|
|
47
|
+
always emitted, or a field with nothing to say would slide the next
|
|
48
|
+
row's name into the sentence column. -->
|
|
49
|
+
<div v-if="p.fields.length" class="grid grid-cols-[max-content_minmax(0,1fr)] items-baseline gap-x-6 gap-y-0.5">
|
|
50
|
+
<template v-for="f in p.fields" :key="f.name">
|
|
51
|
+
<div class="flex min-w-0 items-baseline gap-1 font-mono text-[11px]">
|
|
52
|
+
<span v-if="typeGlyph(f.dataType)" class="shrink-0 font-semibold" :class="typeGlyph(f.dataType).cls">{{ typeGlyph(f.dataType).char }}</span>
|
|
53
|
+
<span class="shrink-0 text-slate-700">{{ f.name }}</span>
|
|
54
|
+
<span v-if="valueAt(p.name, f.name) !== null" class="max-w-56 truncate text-slate-400">: {{ valueAt(p.name, f.name) }}</span>
|
|
55
|
+
</div>
|
|
56
|
+
<p class="min-w-0 text-[11px] leading-snug text-muted-foreground">
|
|
57
|
+
{{ fieldText(f) }}
|
|
58
|
+
</p>
|
|
59
|
+
</template>
|
|
34
60
|
</div>
|
|
35
|
-
<p v-else class="text-[11px] leading-snug text-muted-foreground">
|
|
61
|
+
<p v-else-if="saysNoFields(p)" class="text-[11px] leading-snug text-muted-foreground">
|
|
36
62
|
{{ t("integration.nodeInspector.output.schema.noFields") }}<template v-if="valueAt(p.name) !== null">{{ t("integration.nodeInspector.output.schema.sampledSuffix") }} <span class="font-mono">{{ valueAt(p.name) }}</span></template>.
|
|
37
63
|
</p>
|
|
38
64
|
</div>
|
|
@@ -3,7 +3,8 @@ import type { PortEntry } from '../../data/nodeInputData.js';
|
|
|
3
3
|
* The ports a node hands downstream, with their declared fields and whatever
|
|
4
4
|
* sample value is known for each.
|
|
5
5
|
*
|
|
6
|
-
* Rendered from one place — the Output tab (specs/node-configuration.md AC-4
|
|
6
|
+
* Rendered from one place — the Output tab (specs/node-configuration.md AC-4,
|
|
7
|
+
* and AC-19 for the sentences the publisher wrote about a port and its fields). It
|
|
7
8
|
* keeps its own file for readability rather than for reuse: the schema list is
|
|
8
9
|
* a self-contained tree with its own type glyphs and sample formatting, and
|
|
9
10
|
* inlining it would bury the tab's own markup.
|