@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.
Files changed (119) hide show
  1. package/CHANGELOG.md +638 -0
  2. package/README.md +3 -1
  3. package/dist/module.json +1 -1
  4. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +6 -18
  5. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +13 -23
  6. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +7 -6
  7. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +15 -4
  8. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
  9. package/dist/runtime/components/Integrations/ExpressionInput.vue +45 -19
  10. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
  11. package/dist/runtime/components/Integrations/GroupFrameView.vue +5 -4
  12. package/dist/runtime/components/Integrations/LoadingAnnouncement.d.vue.ts +38 -0
  13. package/dist/runtime/components/Integrations/LoadingAnnouncement.vue +16 -0
  14. package/dist/runtime/components/Integrations/LoadingAnnouncement.vue.d.ts +38 -0
  15. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +10 -2
  16. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +5 -1
  17. package/dist/runtime/components/Integrations/NodeConfigField.vue +88 -1
  18. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +5 -1
  19. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +7 -3
  20. package/dist/runtime/components/Integrations/NodeInspector.vue +125 -54
  21. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +7 -3
  22. package/dist/runtime/components/Integrations/NodeVersionMoveDialog.d.vue.ts +21 -0
  23. package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue +133 -0
  24. package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue.d.ts +21 -0
  25. package/dist/runtime/components/Integrations/RunActionsMenu.vue +2 -1
  26. package/dist/runtime/components/Integrations/RunDetailPanel.vue +5 -17
  27. package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +30 -8
  28. package/dist/runtime/components/Integrations/RunResponseModal.vue +92 -24
  29. package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +30 -8
  30. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +61 -3
  31. package/dist/runtime/components/Integrations/RunsTable.vue +129 -18
  32. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +61 -3
  33. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +10 -1
  34. package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.d.vue.ts +16 -0
  35. package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue +111 -0
  36. package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue.d.ts +16 -0
  37. package/dist/runtime/components/Integrations/StickyNoteView.vue +1 -1
  38. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +18 -3
  39. package/dist/runtime/components/Integrations/WorkflowDeleteDialog.d.vue.ts +18 -0
  40. package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue +70 -0
  41. package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue.d.ts +18 -0
  42. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +2 -1
  43. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +4 -4
  44. package/dist/runtime/components/Integrations/WorkflowEditor.vue +78 -30
  45. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +17 -6
  46. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +17 -21
  47. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +8 -11
  48. package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.d.vue.ts +13 -0
  49. package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue +216 -0
  50. package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue.d.ts +13 -0
  51. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +6 -7
  52. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +7 -7
  53. package/dist/runtime/components/Integrations/WorkflowsTable.vue +10 -48
  54. package/dist/runtime/components/Integrations/canvasSides.d.ts +48 -0
  55. package/dist/runtime/components/Integrations/canvasSides.js +22 -0
  56. package/dist/runtime/composables/useCredentialTypes.js +2 -6
  57. package/dist/runtime/composables/useIntegrationsAudit.js +2 -2
  58. package/dist/runtime/composables/useIntegrationsCredentials.js +4 -8
  59. package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +4 -0
  60. package/dist/runtime/composables/useIntegrationsDeadLetters.js +16 -1
  61. package/dist/runtime/composables/useIntegrationsNodes.js +2 -2
  62. package/dist/runtime/composables/useIntegrationsRevisions.js +2 -6
  63. package/dist/runtime/composables/useIntegrationsRuns.d.ts +4 -15
  64. package/dist/runtime/composables/useIntegrationsRuns.js +49 -34
  65. package/dist/runtime/composables/useIntegrationsSchedules.js +2 -2
  66. package/dist/runtime/composables/useIntegrationsSecrets.js +2 -6
  67. package/dist/runtime/composables/useIntegrationsTemplates.js +7 -5
  68. package/dist/runtime/composables/useIntegrationsTriggers.js +2 -2
  69. package/dist/runtime/composables/useIntegrationsWorkflows.js +10 -7
  70. package/dist/runtime/composables/useNodeConfigResolve.js +2 -2
  71. package/dist/runtime/composables/useOAuthConnect.d.ts +42 -0
  72. package/dist/runtime/composables/useOAuthConnect.js +33 -0
  73. package/dist/runtime/data/expressionEditor.d.ts +17 -5
  74. package/dist/runtime/data/expressionEditor.js +11 -5
  75. package/dist/runtime/data/handleId.d.ts +61 -1
  76. package/dist/runtime/data/handleId.js +8 -1
  77. package/dist/runtime/data/integrationNodeCatalog.d.ts +25 -0
  78. package/dist/runtime/data/integrationNodeCatalog.js +8 -0
  79. package/dist/runtime/data/nodeVersionMove.d.ts +72 -0
  80. package/dist/runtime/data/nodeVersionMove.js +65 -0
  81. package/dist/runtime/data/payloadForm.js +3 -2
  82. package/dist/runtime/i18n/messages/de.json +79 -12
  83. package/dist/runtime/i18n/messages/en.json +79 -12
  84. package/dist/runtime/pages/integrations/audit.vue +7 -6
  85. package/dist/runtime/pages/integrations/credentials.vue +9 -21
  86. package/dist/runtime/pages/integrations/index.vue +55 -8
  87. package/dist/runtime/pages/integrations/runs.vue +116 -7
  88. package/dist/runtime/pages/integrations/secrets.vue +2 -1
  89. package/dist/runtime/pages/integrations/templates/[slug].vue +2 -1
  90. package/dist/runtime/pages/integrations/templates/index.vue +18 -15
  91. package/dist/runtime/pages/integrations/workflows/[id].vue +169 -24
  92. package/dist/runtime/pages/integrations/workflows/index.vue +23 -4
  93. package/dist/runtime/types/integrations.d.ts +49 -1
  94. package/dist/runtime/utils/apiError.d.ts +21 -1
  95. package/dist/runtime/utils/apiError.js +1 -1
  96. package/dist/runtime/utils/buildSettle.d.ts +68 -0
  97. package/dist/runtime/utils/buildSettle.js +29 -0
  98. package/dist/runtime/utils/clipboard.d.ts +33 -0
  99. package/dist/runtime/utils/clipboard.js +11 -0
  100. package/dist/runtime/utils/deepClone.d.ts +41 -0
  101. package/dist/runtime/utils/deepClone.js +3 -0
  102. package/dist/runtime/utils/formatDuration.d.ts +44 -0
  103. package/dist/runtime/utils/formatDuration.js +13 -0
  104. package/dist/runtime/utils/nodeDisplayName.d.ts +22 -0
  105. package/dist/runtime/utils/nodeDisplayName.js +6 -0
  106. package/dist/runtime/utils/notify.d.ts +1 -1
  107. package/dist/runtime/utils/runEnvelope.d.ts +31 -0
  108. package/dist/runtime/utils/runEnvelope.js +12 -0
  109. package/dist/runtime/utils/runFeedQuery.d.ts +135 -0
  110. package/dist/runtime/utils/runFeedQuery.js +66 -0
  111. package/dist/runtime/utils/runOutputs.d.ts +33 -0
  112. package/dist/runtime/utils/runOutputs.js +10 -0
  113. package/dist/runtime/utils/settingCondition.d.ts +48 -0
  114. package/dist/runtime/utils/settingCondition.js +84 -0
  115. package/dist/runtime/utils/stateNamespaceName.d.ts +23 -0
  116. package/dist/runtime/utils/stateNamespaceName.js +8 -0
  117. package/dist/runtime/utils/validationFindings.d.ts +26 -3
  118. package/dist/runtime/utils/validationFindings.js +18 -3
  119. package/package.json +1 -1
@@ -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;
@@ -10,6 +10,7 @@ import {
10
10
  DropdownMenuTrigger
11
11
  } from "@revenexx/studio";
12
12
  import { toast } from "vue-sonner";
13
+ import { errorReason } from "../../utils/apiError";
13
14
  const props = defineProps({
14
15
  run: { type: Object, required: true }
15
16
  });
@@ -78,7 +79,7 @@ async function runAction(a) {
78
79
  );
79
80
  emit("changed");
80
81
  } catch (e) {
81
- toast.error(e?.message ?? actionErrorFallback(a));
82
+ toast.error(errorReason(e, actionErrorFallback(a)));
82
83
  } finally {
83
84
  busy.value = false;
84
85
  }
@@ -3,8 +3,10 @@ import { computed, onUnmounted, ref, watch } from "vue";
3
3
  import { Download as DownloadIcon, RefreshCircle as RefreshIcon } from "#solar-icons";
4
4
  import { Badge, Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@revenexx/studio";
5
5
  import { runStatusClass, runStatusLabel } from "../../utils/statusStyle";
6
- import { IntegrationsApiError } from "../../utils/apiError";
6
+ import { IntegrationsApiError, errorReason } from "../../utils/apiError";
7
7
  import { createPollBudget, SAFETY_POLL_MS } from "../../utils/pollBudget";
8
+ import { formatDuration } from "../../utils/formatDuration";
9
+ import { unwrapRun } from "../../utils/runEnvelope";
8
10
  const props = defineProps({
9
11
  run: { type: Object, required: true },
10
12
  view: { type: String, required: false, default: "details" }
@@ -59,14 +61,6 @@ function pickStr(obj, keys) {
59
61
  const v = pick(obj, keys);
60
62
  return v == null ? null : String(v);
61
63
  }
62
- function unwrapRun(res) {
63
- if (!res || typeof res !== "object") return null;
64
- const o = res;
65
- if ("data" in o && o.data && typeof o.data === "object" && !("status" in o) && !("temporal_run_id" in o)) {
66
- return o.data;
67
- }
68
- return o;
69
- }
70
64
  function isNonEmptyObject(v) {
71
65
  return !!v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length > 0;
72
66
  }
@@ -141,8 +135,7 @@ function statusOf(e) {
141
135
  return e instanceof IntegrationsApiError ? e.status : null;
142
136
  }
143
137
  function genericMsg(e, fallback) {
144
- if (e instanceof IntegrationsApiError) return e.message || fallback;
145
- return e?.message ?? fallback;
138
+ return errorReason(e, fallback);
146
139
  }
147
140
  function detailsErrMsg(e) {
148
141
  const s = statusOf(e);
@@ -170,12 +163,7 @@ function stepsErrMsg(e) {
170
163
  if (s === 503) return t("integration.runs.detail.error.details.unavailable");
171
164
  return genericMsg(e, t("integration.runs.detail.error.steps.generic"));
172
165
  }
173
- function fmtMs(ms) {
174
- if (ms == null) return "\u2014";
175
- if (ms < 1e3) return `${ms}ms`;
176
- if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
177
- return `${(ms / 6e4).toFixed(1)}m`;
178
- }
166
+ const fmtMs = formatDuration;
179
167
  function activityName(a) {
180
168
  const at = a ? a.activity_type ?? a.activityType : void 0;
181
169
  if (typeof at === "string") return at;
@@ -1,18 +1,40 @@
1
+ import type { RunOutput } from '../../utils/runOutputs.js';
1
2
  /**
2
- * Result surface for a manual run, sync and async alike. The parent opens it
3
- * in a loading state, awaits the trigger, then feeds through the outcome:
3
+ * Result surface for a manual run, sync and async alike.
4
4
  *
5
- * - sync: the run's response body (JSON) or a mapped error (timeout /
6
- * failed run). A failed 422 ships the run object, so `runId` lets
7
- * the operator jump straight into the run detail.
8
- * - async: a started confirmation with the fresh run's number — the visible
9
- * counterpart to the fire-and-forget POST.
5
+ * BOTH MODES END HERE, and that is the point of the component (PO-380). The
6
+ * parent opens it on the press, awaits the trigger, and then feeds through
7
+ * whatever it learns about the run — the body a sync request waited for, or the
8
+ * successive states of an async run the parent follows on the author's behalf.
9
+ * What differs between the two is only who does the waiting; the async branch
10
+ * used to stop at "run #99 started", which said that the press did something and
11
+ * nothing about what it did.
12
+ *
13
+ * WHAT IT LEADS WITH is the other half. A run's record carries its Temporal ids,
14
+ * its two re-run references and four timestamps, and the author pressed Run to
15
+ * see none of them: `outputs` is the returned value split per node and named the
16
+ * way the canvas names it, `result` is that value whole for a shape that cannot
17
+ * be split, and the record itself is a click away rather than printed here.
10
18
  */
19
+ interface RunSummary {
20
+ id?: unknown;
21
+ status?: unknown;
22
+ started_at?: unknown;
23
+ completed_at?: unknown;
24
+ }
11
25
  type __VLS_Props = {
12
26
  open: boolean;
27
+ /** The trigger request is still out. */
13
28
  loading?: boolean;
14
29
  mode?: 'sync' | 'async';
15
- response?: unknown;
30
+ /** The run as the platform last reported it — the identity line above the result. */
31
+ run?: RunSummary | null;
32
+ /** What the run returned, per node. `null` where the value has no such shape. */
33
+ outputs?: RunOutput[] | null;
34
+ /** …and the value whole, for when it has not. */
35
+ result?: unknown;
36
+ /** The studio is still following a run that has not ended. */
37
+ following?: boolean;
16
38
  error?: string | null;
17
39
  /** The run behind this result (sync failure / async start) for deep-linking. */
18
40
  runId?: string | null;
@@ -1,4 +1,5 @@
1
1
  <script setup>
2
+ import { computed } from "vue";
2
3
  import { CheckCircle as CheckCircle2, RefreshCircle as Loader2 } from "#solar-icons";
3
4
  import {
4
5
  Button,
@@ -8,22 +9,50 @@ import {
8
9
  DialogHeader,
9
10
  DialogTitle
10
11
  } from "@revenexx/studio";
11
- defineProps({
12
+ import { formatDuration } from "../../utils/formatDuration";
13
+ import { runStatusClass, runStatusLabel } from "../../utils/statusStyle";
14
+ import { isTerminalRun } from "../../utils/runEnvelope";
15
+ const props = defineProps({
12
16
  open: { type: Boolean, required: true },
13
17
  loading: { type: Boolean, required: false },
14
18
  mode: { type: String, required: false },
15
- response: { type: null, required: false },
19
+ run: { type: [Object, null], required: false },
20
+ outputs: { type: [Array, null], required: false },
21
+ result: { type: null, required: false },
22
+ following: { type: Boolean, required: false },
16
23
  error: { type: [String, null], required: false },
17
24
  runId: { type: [String, null], required: false }
18
25
  });
19
26
  const emit = defineEmits(["update:open", "view-run"]);
20
27
  const t = useIntegrationsT();
28
+ const runNumber = computed(() => props.run?.id != null ? String(props.run.id) : props.runId);
29
+ const status = computed(() => props.run?.status != null ? String(props.run.status) : "");
30
+ const duration = computed(() => {
31
+ const from = props.run?.started_at;
32
+ const to = props.run?.completed_at;
33
+ if (typeof from !== "string" || typeof to !== "string") return null;
34
+ const ms = Date.parse(to) - Date.parse(from);
35
+ return Number.isFinite(ms) && ms >= 0 ? ms : null;
36
+ });
37
+ const ended = computed(() => {
38
+ if (props.following) return false;
39
+ if (props.mode !== "async") return true;
40
+ return isTerminalRun(props.run);
41
+ });
42
+ const returnedNothing = computed(() => props.result == null || typeof props.result === "object" && !Array.isArray(props.result) && !Object.keys(props.result).length);
43
+ function pretty(value) {
44
+ return typeof value === "string" ? value : JSON.stringify(value, null, 2);
45
+ }
21
46
  </script>
22
47
 
23
48
  <template>
24
49
  <Dialog :open="open" @update:open="(v) => emit('update:open', v)">
25
50
  <!-- Wide result surface — but hard-capped: min-w-0 down the tree keeps the
26
- JSON <pre> scrolling INSIDE instead of blowing the dialog open. -->
51
+ JSON <pre> scrolling INSIDE instead of blowing the dialog open. The same
52
+ had to be said about the height, and was not: the dialog grew as tall as
53
+ whatever came back, `overflow-hidden` clipped what stuck out, and nothing
54
+ scrolled — so a result of two node outputs ran off the top and the bottom
55
+ of the screen at once and took the buttons below it out of reach (PO-402). -->
27
56
  <DialogContent class="max-w-[94vw] overflow-hidden sm:max-w-4xl">
28
57
  <DialogHeader>
29
58
  <DialogTitle>{{ mode === "async" ? t("integration.runs.response.title.async") : t("integration.runs.response.title.sync") }}</DialogTitle>
@@ -32,7 +61,10 @@ const t = useIntegrationsT();
32
61
  </DialogDescription>
33
62
  </DialogHeader>
34
63
 
35
- <div class="min-w-0 max-w-full text-sm">
64
+ <!-- The body is what scrolls, which is how the create-workflow, node-config
65
+ and SFTP-import dialogs solve the same thing. Capped below the viewport so
66
+ the dialog's own header stays put above it. -->
67
+ <div class="min-w-0 max-h-[70vh] max-w-full overflow-y-auto text-sm">
36
68
  <div v-if="loading" class="flex items-center gap-2 py-6 text-muted-foreground">
37
69
  <Loader2 :size="16" class="animate-spin" />
38
70
  {{ mode === "async" ? t("integration.runs.response.loading.async") : t("integration.runs.response.loading.sync") }}
@@ -49,36 +81,72 @@ const t = useIntegrationsT();
49
81
  </div>
50
82
  </template>
51
83
 
52
- <template v-else-if="mode === 'async'">
53
- <div class="flex items-center gap-2 rounded-md border border-emerald-500/20 bg-emerald-500/5 px-3 py-2 text-emerald-700">
84
+ <template v-else>
85
+ <!-- The confirmation an async press earns: the run exists and is named,
86
+ whatever the studio manages to read back about it afterwards. -->
87
+ <div
88
+ v-if="mode === 'async'"
89
+ class="flex items-center gap-2 rounded-md border border-emerald-500/20 bg-emerald-500/5 px-3 py-2 text-emerald-700"
90
+ >
54
91
  <CheckCircle2 :size="15" class="shrink-0" />
55
- <span v-if="runId">{{ t("integration.runs.response.async.startedWithId", { id: runId }) }}</span>
92
+ <span v-if="runNumber">{{ t("integration.runs.response.async.startedWithId", { id: runNumber }) }}</span>
56
93
  <span v-else>{{ t("integration.runs.response.async.started") }}</span>
57
94
  </div>
58
- <div class="mt-3 flex justify-end gap-2">
95
+
96
+ <!-- Where the run has got to: three facts, and the rest of its record
97
+ stays behind the button below. -->
98
+ <div v-if="runNumber || status" class="mt-3 flex flex-wrap items-center gap-x-2 gap-y-1 text-muted-foreground">
99
+ <span v-if="runNumber" class="font-mono">{{ t("integration.runs.response.runNumber", { id: runNumber }) }}</span>
100
+ <span
101
+ v-if="status"
102
+ class="rounded px-1.5 py-0.5 text-xs"
103
+ :class="runStatusClass(status)"
104
+ >{{ runStatusLabel(status) }}</span>
105
+ <span v-if="duration != null" class="tabular-nums text-xs">{{ formatDuration(duration) }}</span>
106
+ </div>
107
+
108
+ <div v-if="following" class="mt-3 flex items-center gap-2 py-3 text-muted-foreground">
109
+ <Loader2 :size="16" class="animate-spin" />
110
+ {{ t("integration.runs.response.following") }}
111
+ </div>
112
+
113
+ <!-- What it returned, one block per node that produced something. -->
114
+ <div v-else-if="outputs?.length" class="mt-3 space-y-3">
115
+ <div v-for="out in outputs" :key="out.nodeId" class="min-w-0">
116
+ <p class="mb-1 font-medium text-foreground">
117
+ {{ out.name }}
118
+ </p>
119
+ <pre
120
+ class="max-h-[45vh] w-full min-w-0 max-w-full overflow-auto rounded-md border border-black/[0.06] bg-slate-50 p-3 text-xs"
121
+ >{{ pretty(out.value) }}</pre>
122
+ </div>
123
+ </div>
124
+
125
+ <!-- Nothing at all where the run has not ended: the studio has stopped
126
+ looking, which is not the same as the run having finished, and the
127
+ confirmation and the status line above already say where it stands. -->
128
+ <template v-else-if="ended">
129
+ <p v-if="returnedNothing" class="mt-3 py-3 text-muted-foreground">
130
+ {{ t("integration.runs.response.nothingReturned") }}
131
+ </p>
132
+
133
+ <!-- A returned value this workflow's nodes cannot account for: shown
134
+ whole rather than filed under a name it may not belong to. -->
135
+ <pre
136
+ v-else
137
+ class="mt-3 max-h-[60vh] w-full min-w-0 max-w-full overflow-auto rounded-md border border-black/[0.06] bg-slate-50 p-3 text-xs"
138
+ >{{ pretty(result) }}</pre>
139
+ </template>
140
+
141
+ <div class="mt-4 flex justify-end gap-2">
59
142
  <Button variant="outline" class="h-8 text-sm" @click="emit('update:open', false)">
60
143
  {{ t("integration.common.action.close") }}
61
144
  </Button>
62
- <Button v-if="runId" class="h-8 text-sm" @click="emit('view-run', runId)">
145
+ <Button v-if="runNumber" class="h-8 text-sm" @click="emit('view-run', String(runNumber))">
63
146
  {{ t("integration.runs.response.action.viewRun") }}
64
147
  </Button>
65
148
  </div>
66
149
  </template>
67
-
68
- <template v-else-if="response !== void 0 && response !== null">
69
- <pre
70
- class="max-h-[66vh] w-full min-w-0 max-w-full overflow-auto rounded-md border border-black/[0.06] bg-slate-50 p-3 text-xs"
71
- >{{ JSON.stringify(response, null, 2) }}</pre>
72
- <div v-if="runId" class="mt-3 flex justify-end">
73
- <Button variant="outline" class="h-8 text-sm" @click="emit('view-run', runId)">
74
- {{ t("integration.runs.response.action.viewRun") }}
75
- </Button>
76
- </div>
77
- </template>
78
-
79
- <p v-else class="py-6 text-muted-foreground">
80
- {{ t("integration.runs.response.empty") }}
81
- </p>
82
150
  </div>
83
151
  </DialogContent>
84
152
  </Dialog>
@@ -1,18 +1,40 @@
1
+ import type { RunOutput } from '../../utils/runOutputs.js';
1
2
  /**
2
- * Result surface for a manual run, sync and async alike. The parent opens it
3
- * in a loading state, awaits the trigger, then feeds through the outcome:
3
+ * Result surface for a manual run, sync and async alike.
4
4
  *
5
- * - sync: the run's response body (JSON) or a mapped error (timeout /
6
- * failed run). A failed 422 ships the run object, so `runId` lets
7
- * the operator jump straight into the run detail.
8
- * - async: a started confirmation with the fresh run's number — the visible
9
- * counterpart to the fire-and-forget POST.
5
+ * BOTH MODES END HERE, and that is the point of the component (PO-380). The
6
+ * parent opens it on the press, awaits the trigger, and then feeds through
7
+ * whatever it learns about the run — the body a sync request waited for, or the
8
+ * successive states of an async run the parent follows on the author's behalf.
9
+ * What differs between the two is only who does the waiting; the async branch
10
+ * used to stop at "run #99 started", which said that the press did something and
11
+ * nothing about what it did.
12
+ *
13
+ * WHAT IT LEADS WITH is the other half. A run's record carries its Temporal ids,
14
+ * its two re-run references and four timestamps, and the author pressed Run to
15
+ * see none of them: `outputs` is the returned value split per node and named the
16
+ * way the canvas names it, `result` is that value whole for a shape that cannot
17
+ * be split, and the record itself is a click away rather than printed here.
10
18
  */
19
+ interface RunSummary {
20
+ id?: unknown;
21
+ status?: unknown;
22
+ started_at?: unknown;
23
+ completed_at?: unknown;
24
+ }
11
25
  type __VLS_Props = {
12
26
  open: boolean;
27
+ /** The trigger request is still out. */
13
28
  loading?: boolean;
14
29
  mode?: 'sync' | 'async';
15
- response?: unknown;
30
+ /** The run as the platform last reported it — the identity line above the result. */
31
+ run?: RunSummary | null;
32
+ /** What the run returned, per node. `null` where the value has no such shape. */
33
+ outputs?: RunOutput[] | null;
34
+ /** …and the value whole, for when it has not. */
35
+ result?: unknown;
36
+ /** The studio is still following a run that has not ended. */
37
+ following?: boolean;
16
38
  error?: string | null;
17
39
  /** The run behind this result (sync failure / async start) for deep-linking. */
18
40
  runId?: string | null;
@@ -1,4 +1,5 @@
1
- import type { WorkflowRun } from '../../types/integrations.js';
1
+ import type { RunStatus, TriggerType, WorkflowRun } from '../../types/integrations.js';
2
+ import type { RunSort, RunSortDirection } from '../../utils/runFeedQuery.js';
2
3
  type __VLS_Props = {
3
4
  runs: WorkflowRun[];
4
5
  loading?: boolean;
@@ -7,14 +8,71 @@ type __VLS_Props = {
7
8
  pagination?: boolean;
8
9
  /** false inside a workflow — the context makes the column redundant. */
9
10
  showWorkflow?: boolean;
11
+ /**
12
+ * The owning list reads its page and its narrowing from the platform.
13
+ *
14
+ * The table then renders the rows it is handed and reports what the operator
15
+ * asked for, instead of narrowing and slicing an array. That is what lets the
16
+ * list reach past the newest hundred runs at all (PO-369).
17
+ */
18
+ serverMode?: boolean;
19
+ /** How many runs match the narrowing. The pager's "of 7"; server mode only. */
20
+ total?: number;
21
+ /**
22
+ * How many rows a page holds — the SAME number the page asks the platform for.
23
+ *
24
+ * The shared table works its footer's "showing 1–25 of 140" out from its own
25
+ * page size, so a table left on its default while the request asks for another
26
+ * number states a range that is simply wrong: it counted twenty where
27
+ * twenty-five had arrived.
28
+ */
29
+ perPage?: number;
30
+ /** The narrowing in force. Owned by the page, drawn here. */
31
+ status?: RunStatus[];
32
+ triggerType?: TriggerType | '';
33
+ /** The order the platform is reading the list in. Server mode only. */
34
+ sort?: RunSort;
35
+ direction?: RunSortDirection;
10
36
  };
11
- declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
37
+ type __VLS_PublicProps = __VLS_Props & {
38
+ /** 1-based, as the platform pages. The shared table counts from zero. */
39
+ 'page'?: number;
40
+ };
41
+ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
42
+ search: (text: string) => any;
12
43
  changed: () => any;
13
- }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
44
+ narrow: (patch: {
45
+ status?: RunStatus[];
46
+ triggerType?: TriggerType | "";
47
+ }) => any;
48
+ "page-size": (size: number) => any;
49
+ reorder: (order: {
50
+ sort: RunSort;
51
+ direction: RunSortDirection;
52
+ }) => any;
53
+ "update:page": (value: number) => any;
54
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
55
+ onSearch?: ((text: string) => any) | undefined;
14
56
  onChanged?: (() => any) | undefined;
57
+ onNarrow?: ((patch: {
58
+ status?: RunStatus[];
59
+ triggerType?: TriggerType | "";
60
+ }) => any) | undefined;
61
+ "onPage-size"?: ((size: number) => any) | undefined;
62
+ onReorder?: ((order: {
63
+ sort: RunSort;
64
+ direction: RunSortDirection;
65
+ }) => any) | undefined;
66
+ "onUpdate:page"?: ((value: number) => any) | undefined;
15
67
  }>, {
68
+ sort: RunSort;
16
69
  loading: boolean;
70
+ total: number;
71
+ direction: RunSortDirection;
72
+ triggerType: TriggerType | "";
73
+ perPage: number;
17
74
  pagination: boolean;
18
75
  showWorkflow: boolean;
76
+ serverMode: boolean;
19
77
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
78
  export default _default;